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 #include <signal.h> 9 #include <string.h> 10 #include <pthread.h> 11 12 #include "../kselftest.h" 13 #include "cgroup_util.h" 14 15 /* 16 * A(0) - B(0) - C(1) 17 * \ D(0) 18 * 19 * A, B and C's "populated" fields would be 1 while D's 0. 20 * test that after the one process in C is moved to root, 21 * A,B and C's "populated" fields would flip to "0" and file 22 * modified events will be generated on the 23 * "cgroup.events" files of both cgroups. 24 */ 25 static int test_cgcore_populated(const char *root) 26 { 27 int ret = KSFT_FAIL; 28 char *cg_test_a = NULL, *cg_test_b = NULL; 29 char *cg_test_c = NULL, *cg_test_d = NULL; 30 31 cg_test_a = cg_name(root, "cg_test_a"); 32 cg_test_b = cg_name(root, "cg_test_a/cg_test_b"); 33 cg_test_c = cg_name(root, "cg_test_a/cg_test_b/cg_test_c"); 34 cg_test_d = cg_name(root, "cg_test_a/cg_test_b/cg_test_d"); 35 36 if (!cg_test_a || !cg_test_b || !cg_test_c || !cg_test_d) 37 goto cleanup; 38 39 if (cg_create(cg_test_a)) 40 goto cleanup; 41 42 if (cg_create(cg_test_b)) 43 goto cleanup; 44 45 if (cg_create(cg_test_c)) 46 goto cleanup; 47 48 if (cg_create(cg_test_d)) 49 goto cleanup; 50 51 if (cg_enter_current(cg_test_c)) 52 goto cleanup; 53 54 if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 1\n")) 55 goto cleanup; 56 57 if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 1\n")) 58 goto cleanup; 59 60 if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 1\n")) 61 goto cleanup; 62 63 if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n")) 64 goto cleanup; 65 66 if (cg_enter_current(root)) 67 goto cleanup; 68 69 if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 0\n")) 70 goto cleanup; 71 72 if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 0\n")) 73 goto cleanup; 74 75 if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 0\n")) 76 goto cleanup; 77 78 if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n")) 79 goto cleanup; 80 81 ret = KSFT_PASS; 82 83 cleanup: 84 if (cg_test_d) 85 cg_destroy(cg_test_d); 86 if (cg_test_c) 87 cg_destroy(cg_test_c); 88 if (cg_test_b) 89 cg_destroy(cg_test_b); 90 if (cg_test_a) 91 cg_destroy(cg_test_a); 92 free(cg_test_d); 93 free(cg_test_c); 94 free(cg_test_b); 95 free(cg_test_a); 96 return ret; 97 } 98 99 /* 100 * A (domain threaded) - B (threaded) - C (domain) 101 * 102 * test that C can't be used until it is turned into a 103 * threaded cgroup. "cgroup.type" file will report "domain (invalid)" in 104 * these cases. Operations which fail due to invalid topology use 105 * EOPNOTSUPP as the errno. 106 */ 107 static int test_cgcore_invalid_domain(const char *root) 108 { 109 int ret = KSFT_FAIL; 110 char *grandparent = NULL, *parent = NULL, *child = NULL; 111 112 grandparent = cg_name(root, "cg_test_grandparent"); 113 parent = cg_name(root, "cg_test_grandparent/cg_test_parent"); 114 child = cg_name(root, "cg_test_grandparent/cg_test_parent/cg_test_child"); 115 if (!parent || !child || !grandparent) 116 goto cleanup; 117 118 if (cg_create(grandparent)) 119 goto cleanup; 120 121 if (cg_create(parent)) 122 goto cleanup; 123 124 if (cg_create(child)) 125 goto cleanup; 126 127 if (cg_write(parent, "cgroup.type", "threaded")) 128 goto cleanup; 129 130 if (cg_read_strcmp(child, "cgroup.type", "domain invalid\n")) 131 goto cleanup; 132 133 if (!cg_enter_current(child)) 134 goto cleanup; 135 136 if (errno != EOPNOTSUPP) 137 goto cleanup; 138 139 ret = KSFT_PASS; 140 141 cleanup: 142 cg_enter_current(root); 143 if (child) 144 cg_destroy(child); 145 if (parent) 146 cg_destroy(parent); 147 if (grandparent) 148 cg_destroy(grandparent); 149 free(child); 150 free(parent); 151 free(grandparent); 152 return ret; 153 } 154 155 /* 156 * Test that when a child becomes threaded 157 * the parent type becomes domain threaded. 158 */ 159 static int test_cgcore_parent_becomes_threaded(const char *root) 160 { 161 int ret = KSFT_FAIL; 162 char *parent = NULL, *child = NULL; 163 164 parent = cg_name(root, "cg_test_parent"); 165 child = cg_name(root, "cg_test_parent/cg_test_child"); 166 if (!parent || !child) 167 goto cleanup; 168 169 if (cg_create(parent)) 170 goto cleanup; 171 172 if (cg_create(child)) 173 goto cleanup; 174 175 if (cg_write(child, "cgroup.type", "threaded")) 176 goto cleanup; 177 178 if (cg_read_strcmp(parent, "cgroup.type", "domain threaded\n")) 179 goto cleanup; 180 181 ret = KSFT_PASS; 182 183 cleanup: 184 if (child) 185 cg_destroy(child); 186 if (parent) 187 cg_destroy(parent); 188 free(child); 189 free(parent); 190 return ret; 191 192 } 193 194 /* 195 * Test that there's no internal process constrain on threaded cgroups. 196 * You can add threads/processes on a parent with a controller enabled. 197 */ 198 static int test_cgcore_no_internal_process_constraint_on_threads(const char *root) 199 { 200 int ret = KSFT_FAIL; 201 char *parent = NULL, *child = NULL; 202 203 if (cg_read_strstr(root, "cgroup.controllers", "cpu") || 204 cg_write(root, "cgroup.subtree_control", "+cpu")) { 205 ret = KSFT_SKIP; 206 goto cleanup; 207 } 208 209 parent = cg_name(root, "cg_test_parent"); 210 child = cg_name(root, "cg_test_parent/cg_test_child"); 211 if (!parent || !child) 212 goto cleanup; 213 214 if (cg_create(parent)) 215 goto cleanup; 216 217 if (cg_create(child)) 218 goto cleanup; 219 220 if (cg_write(parent, "cgroup.type", "threaded")) 221 goto cleanup; 222 223 if (cg_write(child, "cgroup.type", "threaded")) 224 goto cleanup; 225 226 if (cg_write(parent, "cgroup.subtree_control", "+cpu")) 227 goto cleanup; 228 229 if (cg_enter_current(parent)) 230 goto cleanup; 231 232 ret = KSFT_PASS; 233 234 cleanup: 235 cg_enter_current(root); 236 cg_enter_current(root); 237 if (child) 238 cg_destroy(child); 239 if (parent) 240 cg_destroy(parent); 241 free(child); 242 free(parent); 243 return ret; 244 } 245 246 /* 247 * Test that you can't enable a controller on a child if it's not enabled 248 * on the parent. 249 */ 250 static int test_cgcore_top_down_constraint_enable(const char *root) 251 { 252 int ret = KSFT_FAIL; 253 char *parent = NULL, *child = NULL; 254 255 parent = cg_name(root, "cg_test_parent"); 256 child = cg_name(root, "cg_test_parent/cg_test_child"); 257 if (!parent || !child) 258 goto cleanup; 259 260 if (cg_create(parent)) 261 goto cleanup; 262 263 if (cg_create(child)) 264 goto cleanup; 265 266 if (!cg_write(child, "cgroup.subtree_control", "+memory")) 267 goto cleanup; 268 269 ret = KSFT_PASS; 270 271 cleanup: 272 if (child) 273 cg_destroy(child); 274 if (parent) 275 cg_destroy(parent); 276 free(child); 277 free(parent); 278 return ret; 279 } 280 281 /* 282 * Test that you can't disable a controller on a parent 283 * if it's enabled in a child. 284 */ 285 static int test_cgcore_top_down_constraint_disable(const char *root) 286 { 287 int ret = KSFT_FAIL; 288 char *parent = NULL, *child = NULL; 289 290 parent = cg_name(root, "cg_test_parent"); 291 child = cg_name(root, "cg_test_parent/cg_test_child"); 292 if (!parent || !child) 293 goto cleanup; 294 295 if (cg_create(parent)) 296 goto cleanup; 297 298 if (cg_create(child)) 299 goto cleanup; 300 301 if (cg_write(parent, "cgroup.subtree_control", "+memory")) 302 goto cleanup; 303 304 if (cg_write(child, "cgroup.subtree_control", "+memory")) 305 goto cleanup; 306 307 if (!cg_write(parent, "cgroup.subtree_control", "-memory")) 308 goto cleanup; 309 310 ret = KSFT_PASS; 311 312 cleanup: 313 if (child) 314 cg_destroy(child); 315 if (parent) 316 cg_destroy(parent); 317 free(child); 318 free(parent); 319 return ret; 320 } 321 322 /* 323 * Test internal process constraint. 324 * You can't add a pid to a domain parent if a controller is enabled. 325 */ 326 static int test_cgcore_internal_process_constraint(const char *root) 327 { 328 int ret = KSFT_FAIL; 329 char *parent = NULL, *child = NULL; 330 331 parent = cg_name(root, "cg_test_parent"); 332 child = cg_name(root, "cg_test_parent/cg_test_child"); 333 if (!parent || !child) 334 goto cleanup; 335 336 if (cg_create(parent)) 337 goto cleanup; 338 339 if (cg_create(child)) 340 goto cleanup; 341 342 if (cg_write(parent, "cgroup.subtree_control", "+memory")) 343 goto cleanup; 344 345 if (!cg_enter_current(parent)) 346 goto cleanup; 347 348 ret = KSFT_PASS; 349 350 cleanup: 351 if (child) 352 cg_destroy(child); 353 if (parent) 354 cg_destroy(parent); 355 free(child); 356 free(parent); 357 return ret; 358 } 359 360 static void *dummy_thread_fn(void *arg) 361 { 362 return (void *)(size_t)pause(); 363 } 364 365 /* 366 * Test threadgroup migration. 367 * All threads of a process are migrated together. 368 */ 369 static int test_cgcore_proc_migration(const char *root) 370 { 371 int ret = KSFT_FAIL; 372 int t, c_threads, n_threads = 13; 373 char *src = NULL, *dst = NULL; 374 pthread_t threads[n_threads]; 375 376 src = cg_name(root, "cg_src"); 377 dst = cg_name(root, "cg_dst"); 378 if (!src || !dst) 379 goto cleanup; 380 381 if (cg_create(src)) 382 goto cleanup; 383 if (cg_create(dst)) 384 goto cleanup; 385 386 if (cg_enter_current(src)) 387 goto cleanup; 388 389 for (c_threads = 0; c_threads < n_threads; ++c_threads) { 390 if (pthread_create(&threads[c_threads], NULL, dummy_thread_fn, NULL)) 391 goto cleanup; 392 } 393 394 cg_enter_current(dst); 395 if (cg_read_lc(dst, "cgroup.threads") != n_threads + 1) 396 goto cleanup; 397 398 ret = KSFT_PASS; 399 400 cleanup: 401 for (t = 0; t < c_threads; ++t) { 402 pthread_cancel(threads[t]); 403 } 404 405 for (t = 0; t < c_threads; ++t) { 406 pthread_join(threads[t], NULL); 407 } 408 409 cg_enter_current(root); 410 411 if (dst) 412 cg_destroy(dst); 413 if (src) 414 cg_destroy(src); 415 free(dst); 416 free(src); 417 return ret; 418 } 419 420 static void *migrating_thread_fn(void *arg) 421 { 422 int g, i, n_iterations = 1000; 423 char **grps = arg; 424 char lines[3][PATH_MAX]; 425 426 for (g = 1; g < 3; ++g) 427 snprintf(lines[g], sizeof(lines[g]), "0::%s", grps[g] + strlen(grps[0])); 428 429 for (i = 0; i < n_iterations; ++i) { 430 cg_enter_current_thread(grps[(i % 2) + 1]); 431 432 if (proc_read_strstr(0, 1, "cgroup", lines[(i % 2) + 1])) 433 return (void *)-1; 434 } 435 return NULL; 436 } 437 438 /* 439 * Test single thread migration. 440 * Threaded cgroups allow successful migration of a thread. 441 */ 442 static int test_cgcore_thread_migration(const char *root) 443 { 444 int ret = KSFT_FAIL; 445 char *dom = NULL; 446 char line[PATH_MAX]; 447 char *grps[3] = { (char *)root, NULL, NULL }; 448 pthread_t thr; 449 void *retval; 450 451 dom = cg_name(root, "cg_dom"); 452 grps[1] = cg_name(root, "cg_dom/cg_src"); 453 grps[2] = cg_name(root, "cg_dom/cg_dst"); 454 if (!grps[1] || !grps[2] || !dom) 455 goto cleanup; 456 457 if (cg_create(dom)) 458 goto cleanup; 459 if (cg_create(grps[1])) 460 goto cleanup; 461 if (cg_create(grps[2])) 462 goto cleanup; 463 464 if (cg_write(grps[1], "cgroup.type", "threaded")) 465 goto cleanup; 466 if (cg_write(grps[2], "cgroup.type", "threaded")) 467 goto cleanup; 468 469 if (cg_enter_current(grps[1])) 470 goto cleanup; 471 472 if (pthread_create(&thr, NULL, migrating_thread_fn, grps)) 473 goto cleanup; 474 475 if (pthread_join(thr, &retval)) 476 goto cleanup; 477 478 if (retval) 479 goto cleanup; 480 481 snprintf(line, sizeof(line), "0::%s", grps[1] + strlen(grps[0])); 482 if (proc_read_strstr(0, 1, "cgroup", line)) 483 goto cleanup; 484 485 ret = KSFT_PASS; 486 487 cleanup: 488 cg_enter_current(root); 489 if (grps[2]) 490 cg_destroy(grps[2]); 491 if (grps[1]) 492 cg_destroy(grps[1]); 493 if (dom) 494 cg_destroy(dom); 495 free(grps[2]); 496 free(grps[1]); 497 free(dom); 498 return ret; 499 } 500 501 #define T(x) { x, #x } 502 struct corecg_test { 503 int (*fn)(const char *root); 504 const char *name; 505 } tests[] = { 506 T(test_cgcore_internal_process_constraint), 507 T(test_cgcore_top_down_constraint_enable), 508 T(test_cgcore_top_down_constraint_disable), 509 T(test_cgcore_no_internal_process_constraint_on_threads), 510 T(test_cgcore_parent_becomes_threaded), 511 T(test_cgcore_invalid_domain), 512 T(test_cgcore_populated), 513 T(test_cgcore_proc_migration), 514 T(test_cgcore_thread_migration), 515 }; 516 #undef T 517 518 int main(int argc, char *argv[]) 519 { 520 char root[PATH_MAX]; 521 int i, ret = EXIT_SUCCESS; 522 523 if (cg_find_unified_root(root, sizeof(root))) 524 ksft_exit_skip("cgroup v2 isn't mounted\n"); 525 526 if (cg_read_strstr(root, "cgroup.subtree_control", "memory")) 527 if (cg_write(root, "cgroup.subtree_control", "+memory")) 528 ksft_exit_skip("Failed to set memory controller\n"); 529 530 for (i = 0; i < ARRAY_SIZE(tests); i++) { 531 switch (tests[i].fn(root)) { 532 case KSFT_PASS: 533 ksft_test_result_pass("%s\n", tests[i].name); 534 break; 535 case KSFT_SKIP: 536 ksft_test_result_skip("%s\n", tests[i].name); 537 break; 538 default: 539 ret = EXIT_FAILURE; 540 ksft_test_result_fail("%s\n", tests[i].name); 541 break; 542 } 543 } 544 545 return ret; 546 } 547