Home | History | Annotate | Download | only in cgroup
      1 /* SPDX-License-Identifier: GPL-2.0 */
      2 
      3 #include <linux/limits.h>
      4 #include <sys/types.h>
      5 #include <unistd.h>
      6 #include <stdio.h>
      7 #include <errno.h>
      8 
      9 #include "../kselftest.h"
     10 #include "cgroup_util.h"
     11 
     12 /*
     13  * A(0) - B(0) - C(1)
     14  *        \ D(0)
     15  *
     16  * A, B and C's "populated" fields would be 1 while D's 0.
     17  * test that after the one process in C is moved to root,
     18  * A,B and C's "populated" fields would flip to "0" and file
     19  * modified events will be generated on the
     20  * "cgroup.events" files of both cgroups.
     21  */
     22 static int test_cgcore_populated(const char *root)
     23 {
     24 	int ret = KSFT_FAIL;
     25 	char *cg_test_a = NULL, *cg_test_b = NULL;
     26 	char *cg_test_c = NULL, *cg_test_d = NULL;
     27 
     28 	cg_test_a = cg_name(root, "cg_test_a");
     29 	cg_test_b = cg_name(root, "cg_test_a/cg_test_b");
     30 	cg_test_c = cg_name(root, "cg_test_a/cg_test_b/cg_test_c");
     31 	cg_test_d = cg_name(root, "cg_test_a/cg_test_b/cg_test_d");
     32 
     33 	if (!cg_test_a || !cg_test_b || !cg_test_c || !cg_test_d)
     34 		goto cleanup;
     35 
     36 	if (cg_create(cg_test_a))
     37 		goto cleanup;
     38 
     39 	if (cg_create(cg_test_b))
     40 		goto cleanup;
     41 
     42 	if (cg_create(cg_test_c))
     43 		goto cleanup;
     44 
     45 	if (cg_create(cg_test_d))
     46 		goto cleanup;
     47 
     48 	if (cg_enter_current(cg_test_c))
     49 		goto cleanup;
     50 
     51 	if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 1\n"))
     52 		goto cleanup;
     53 
     54 	if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 1\n"))
     55 		goto cleanup;
     56 
     57 	if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 1\n"))
     58 		goto cleanup;
     59 
     60 	if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
     61 		goto cleanup;
     62 
     63 	if (cg_enter_current(root))
     64 		goto cleanup;
     65 
     66 	if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 0\n"))
     67 		goto cleanup;
     68 
     69 	if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 0\n"))
     70 		goto cleanup;
     71 
     72 	if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 0\n"))
     73 		goto cleanup;
     74 
     75 	if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
     76 		goto cleanup;
     77 
     78 	ret = KSFT_PASS;
     79 
     80 cleanup:
     81 	if (cg_test_d)
     82 		cg_destroy(cg_test_d);
     83 	if (cg_test_c)
     84 		cg_destroy(cg_test_c);
     85 	if (cg_test_b)
     86 		cg_destroy(cg_test_b);
     87 	if (cg_test_a)
     88 		cg_destroy(cg_test_a);
     89 	free(cg_test_d);
     90 	free(cg_test_c);
     91 	free(cg_test_b);
     92 	free(cg_test_a);
     93 	return ret;
     94 }
     95 
     96 /*
     97  * A (domain threaded) - B (threaded) - C (domain)
     98  *
     99  * test that C can't be used until it is turned into a
    100  * threaded cgroup.  "cgroup.type" file will report "domain (invalid)" in
    101  * these cases. Operations which fail due to invalid topology use
    102  * EOPNOTSUPP as the errno.
    103  */
    104 static int test_cgcore_invalid_domain(const char *root)
    105 {
    106 	int ret = KSFT_FAIL;
    107 	char *grandparent = NULL, *parent = NULL, *child = NULL;
    108 
    109 	grandparent = cg_name(root, "cg_test_grandparent");
    110 	parent = cg_name(root, "cg_test_grandparent/cg_test_parent");
    111 	child = cg_name(root, "cg_test_grandparent/cg_test_parent/cg_test_child");
    112 	if (!parent || !child || !grandparent)
    113 		goto cleanup;
    114 
    115 	if (cg_create(grandparent))
    116 		goto cleanup;
    117 
    118 	if (cg_create(parent))
    119 		goto cleanup;
    120 
    121 	if (cg_create(child))
    122 		goto cleanup;
    123 
    124 	if (cg_write(parent, "cgroup.type", "threaded"))
    125 		goto cleanup;
    126 
    127 	if (cg_read_strcmp(child, "cgroup.type", "domain invalid\n"))
    128 		goto cleanup;
    129 
    130 	if (!cg_enter_current(child))
    131 		goto cleanup;
    132 
    133 	if (errno != EOPNOTSUPP)
    134 		goto cleanup;
    135 
    136 	ret = KSFT_PASS;
    137 
    138 cleanup:
    139 	cg_enter_current(root);
    140 	if (child)
    141 		cg_destroy(child);
    142 	if (parent)
    143 		cg_destroy(parent);
    144 	if (grandparent)
    145 		cg_destroy(grandparent);
    146 	free(child);
    147 	free(parent);
    148 	free(grandparent);
    149 	return ret;
    150 }
    151 
    152 /*
    153  * Test that when a child becomes threaded
    154  * the parent type becomes domain threaded.
    155  */
    156 static int test_cgcore_parent_becomes_threaded(const char *root)
    157 {
    158 	int ret = KSFT_FAIL;
    159 	char *parent = NULL, *child = NULL;
    160 
    161 	parent = cg_name(root, "cg_test_parent");
    162 	child = cg_name(root, "cg_test_parent/cg_test_child");
    163 	if (!parent || !child)
    164 		goto cleanup;
    165 
    166 	if (cg_create(parent))
    167 		goto cleanup;
    168 
    169 	if (cg_create(child))
    170 		goto cleanup;
    171 
    172 	if (cg_write(child, "cgroup.type", "threaded"))
    173 		goto cleanup;
    174 
    175 	if (cg_read_strcmp(parent, "cgroup.type", "domain threaded\n"))
    176 		goto cleanup;
    177 
    178 	ret = KSFT_PASS;
    179 
    180 cleanup:
    181 	if (child)
    182 		cg_destroy(child);
    183 	if (parent)
    184 		cg_destroy(parent);
    185 	free(child);
    186 	free(parent);
    187 	return ret;
    188 
    189 }
    190 
    191 /*
    192  * Test that there's no internal process constrain on threaded cgroups.
    193  * You can add threads/processes on a parent with a controller enabled.
    194  */
    195 static int test_cgcore_no_internal_process_constraint_on_threads(const char *root)
    196 {
    197 	int ret = KSFT_FAIL;
    198 	char *parent = NULL, *child = NULL;
    199 
    200 	if (cg_read_strstr(root, "cgroup.controllers", "cpu") ||
    201 	    cg_read_strstr(root, "cgroup.subtree_control", "cpu")) {
    202 		ret = KSFT_SKIP;
    203 		goto cleanup;
    204 	}
    205 
    206 	parent = cg_name(root, "cg_test_parent");
    207 	child = cg_name(root, "cg_test_parent/cg_test_child");
    208 	if (!parent || !child)
    209 		goto cleanup;
    210 
    211 	if (cg_create(parent))
    212 		goto cleanup;
    213 
    214 	if (cg_create(child))
    215 		goto cleanup;
    216 
    217 	if (cg_write(parent, "cgroup.type", "threaded"))
    218 		goto cleanup;
    219 
    220 	if (cg_write(child, "cgroup.type", "threaded"))
    221 		goto cleanup;
    222 
    223 	if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
    224 		goto cleanup;
    225 
    226 	if (cg_enter_current(parent))
    227 		goto cleanup;
    228 
    229 	ret = KSFT_PASS;
    230 
    231 cleanup:
    232 	cg_enter_current(root);
    233 	cg_enter_current(root);
    234 	if (child)
    235 		cg_destroy(child);
    236 	if (parent)
    237 		cg_destroy(parent);
    238 	free(child);
    239 	free(parent);
    240 	return ret;
    241 }
    242 
    243 /*
    244  * Test that you can't enable a controller on a child if it's not enabled
    245  * on the parent.
    246  */
    247 static int test_cgcore_top_down_constraint_enable(const char *root)
    248 {
    249 	int ret = KSFT_FAIL;
    250 	char *parent = NULL, *child = NULL;
    251 
    252 	parent = cg_name(root, "cg_test_parent");
    253 	child = cg_name(root, "cg_test_parent/cg_test_child");
    254 	if (!parent || !child)
    255 		goto cleanup;
    256 
    257 	if (cg_create(parent))
    258 		goto cleanup;
    259 
    260 	if (cg_create(child))
    261 		goto cleanup;
    262 
    263 	if (!cg_write(child, "cgroup.subtree_control", "+memory"))
    264 		goto cleanup;
    265 
    266 	ret = KSFT_PASS;
    267 
    268 cleanup:
    269 	if (child)
    270 		cg_destroy(child);
    271 	if (parent)
    272 		cg_destroy(parent);
    273 	free(child);
    274 	free(parent);
    275 	return ret;
    276 }
    277 
    278 /*
    279  * Test that you can't disable a controller on a parent
    280  * if it's enabled in a child.
    281  */
    282 static int test_cgcore_top_down_constraint_disable(const char *root)
    283 {
    284 	int ret = KSFT_FAIL;
    285 	char *parent = NULL, *child = NULL;
    286 
    287 	parent = cg_name(root, "cg_test_parent");
    288 	child = cg_name(root, "cg_test_parent/cg_test_child");
    289 	if (!parent || !child)
    290 		goto cleanup;
    291 
    292 	if (cg_create(parent))
    293 		goto cleanup;
    294 
    295 	if (cg_create(child))
    296 		goto cleanup;
    297 
    298 	if (cg_write(parent, "cgroup.subtree_control", "+memory"))
    299 		goto cleanup;
    300 
    301 	if (cg_write(child, "cgroup.subtree_control", "+memory"))
    302 		goto cleanup;
    303 
    304 	if (!cg_write(parent, "cgroup.subtree_control", "-memory"))
    305 		goto cleanup;
    306 
    307 	ret = KSFT_PASS;
    308 
    309 cleanup:
    310 	if (child)
    311 		cg_destroy(child);
    312 	if (parent)
    313 		cg_destroy(parent);
    314 	free(child);
    315 	free(parent);
    316 	return ret;
    317 }
    318 
    319 /*
    320  * Test internal process constraint.
    321  * You can't add a pid to a domain parent if a controller is enabled.
    322  */
    323 static int test_cgcore_internal_process_constraint(const char *root)
    324 {
    325 	int ret = KSFT_FAIL;
    326 	char *parent = NULL, *child = NULL;
    327 
    328 	parent = cg_name(root, "cg_test_parent");
    329 	child = cg_name(root, "cg_test_parent/cg_test_child");
    330 	if (!parent || !child)
    331 		goto cleanup;
    332 
    333 	if (cg_create(parent))
    334 		goto cleanup;
    335 
    336 	if (cg_create(child))
    337 		goto cleanup;
    338 
    339 	if (cg_write(parent, "cgroup.subtree_control", "+memory"))
    340 		goto cleanup;
    341 
    342 	if (!cg_enter_current(parent))
    343 		goto cleanup;
    344 
    345 	ret = KSFT_PASS;
    346 
    347 cleanup:
    348 	if (child)
    349 		cg_destroy(child);
    350 	if (parent)
    351 		cg_destroy(parent);
    352 	free(child);
    353 	free(parent);
    354 	return ret;
    355 }
    356 
    357 #define T(x) { x, #x }
    358 struct corecg_test {
    359 	int (*fn)(const char *root);
    360 	const char *name;
    361 } tests[] = {
    362 	T(test_cgcore_internal_process_constraint),
    363 	T(test_cgcore_top_down_constraint_enable),
    364 	T(test_cgcore_top_down_constraint_disable),
    365 	T(test_cgcore_no_internal_process_constraint_on_threads),
    366 	T(test_cgcore_parent_becomes_threaded),
    367 	T(test_cgcore_invalid_domain),
    368 	T(test_cgcore_populated),
    369 };
    370 #undef T
    371 
    372 int main(int argc, char *argv[])
    373 {
    374 	char root[PATH_MAX];
    375 	int i, ret = EXIT_SUCCESS;
    376 
    377 	if (cg_find_unified_root(root, sizeof(root)))
    378 		ksft_exit_skip("cgroup v2 isn't mounted\n");
    379 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
    380 		switch (tests[i].fn(root)) {
    381 		case KSFT_PASS:
    382 			ksft_test_result_pass("%s\n", tests[i].name);
    383 			break;
    384 		case KSFT_SKIP:
    385 			ksft_test_result_skip("%s\n", tests[i].name);
    386 			break;
    387 		default:
    388 			ret = EXIT_FAILURE;
    389 			ksft_test_result_fail("%s\n", tests[i].name);
    390 			break;
    391 		}
    392 	}
    393 
    394 	return ret;
    395 }
    396