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