Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/cgroup: add tests for cloning into cgroups

Expand the cgroup test-suite to include tests for CLONE_INTO_CGROUP.
This adds the following tests:
- CLONE_INTO_CGROUP manages to clone a process directly into a correctly
delegated cgroup
- CLONE_INTO_CGROUP fails to clone a process into a cgroup that has been
removed after we've opened an fd to it
- CLONE_INTO_CGROUP fails to clone a process into an invalid domain
cgroup
- CLONE_INTO_CGROUP adheres to the no internal process constraint
- CLONE_INTO_CGROUP works with the freezer feature

Cc: Tejun Heo <tj@kernel.org>
Cc: Shuah Khan <shuah@kernel.org>
Cc: cgroups@vger.kernel.org
Cc: linux-kselftest@vger.kernel.org
Acked-by: Roman Gushchin <guro@fb.com>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
Signed-off-by: Tejun Heo <tj@kernel.org>

authored by

Christian Brauner and committed by
Tejun Heo
9bd5910d ef2c41cf

+214 -5
+3 -3
tools/testing/selftests/cgroup/Makefile
··· 11 11 12 12 include ../lib.mk 13 13 14 - $(OUTPUT)/test_memcontrol: cgroup_util.c 15 - $(OUTPUT)/test_core: cgroup_util.c 16 - $(OUTPUT)/test_freezer: cgroup_util.c 14 + $(OUTPUT)/test_memcontrol: cgroup_util.c ../clone3/clone3_selftests.h 15 + $(OUTPUT)/test_core: cgroup_util.c ../clone3/clone3_selftests.h 16 + $(OUTPUT)/test_freezer: cgroup_util.c ../clone3/clone3_selftests.h
+126
tools/testing/selftests/cgroup/cgroup_util.c
··· 15 15 #include <unistd.h> 16 16 17 17 #include "cgroup_util.h" 18 + #include "../clone3/clone3_selftests.h" 18 19 19 20 static ssize_t read_text(const char *path, char *buf, size_t max_len) 20 21 { ··· 332 331 } 333 332 } 334 333 334 + pid_t clone_into_cgroup(int cgroup_fd) 335 + { 336 + #ifdef CLONE_ARGS_SIZE_VER2 337 + pid_t pid; 338 + 339 + struct clone_args args = { 340 + .flags = CLONE_INTO_CGROUP, 341 + .exit_signal = SIGCHLD, 342 + .cgroup = cgroup_fd, 343 + }; 344 + 345 + pid = sys_clone3(&args, sizeof(struct clone_args)); 346 + /* 347 + * Verify that this is a genuine test failure: 348 + * ENOSYS -> clone3() not available 349 + * E2BIG -> CLONE_INTO_CGROUP not available 350 + */ 351 + if (pid < 0 && (errno == ENOSYS || errno == E2BIG)) 352 + goto pretend_enosys; 353 + 354 + return pid; 355 + 356 + pretend_enosys: 357 + #endif 358 + errno = ENOSYS; 359 + return -ENOSYS; 360 + } 361 + 362 + int clone_reap(pid_t pid, int options) 363 + { 364 + int ret; 365 + siginfo_t info = { 366 + .si_signo = 0, 367 + }; 368 + 369 + again: 370 + ret = waitid(P_PID, pid, &info, options | __WALL | __WNOTHREAD); 371 + if (ret < 0) { 372 + if (errno == EINTR) 373 + goto again; 374 + return -1; 375 + } 376 + 377 + if (options & WEXITED) { 378 + if (WIFEXITED(info.si_status)) 379 + return WEXITSTATUS(info.si_status); 380 + } 381 + 382 + if (options & WSTOPPED) { 383 + if (WIFSTOPPED(info.si_status)) 384 + return WSTOPSIG(info.si_status); 385 + } 386 + 387 + if (options & WCONTINUED) { 388 + if (WIFCONTINUED(info.si_status)) 389 + return 0; 390 + } 391 + 392 + return -1; 393 + } 394 + 395 + int dirfd_open_opath(const char *dir) 396 + { 397 + return open(dir, O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW | O_PATH); 398 + } 399 + 400 + #define close_prot_errno(fd) \ 401 + if (fd >= 0) { \ 402 + int _e_ = errno; \ 403 + close(fd); \ 404 + errno = _e_; \ 405 + } 406 + 407 + static int clone_into_cgroup_run_nowait(const char *cgroup, 408 + int (*fn)(const char *cgroup, void *arg), 409 + void *arg) 410 + { 411 + int cgroup_fd; 412 + pid_t pid; 413 + 414 + cgroup_fd = dirfd_open_opath(cgroup); 415 + if (cgroup_fd < 0) 416 + return -1; 417 + 418 + pid = clone_into_cgroup(cgroup_fd); 419 + close_prot_errno(cgroup_fd); 420 + if (pid == 0) 421 + exit(fn(cgroup, arg)); 422 + 423 + return pid; 424 + } 425 + 335 426 int cg_run_nowait(const char *cgroup, 336 427 int (*fn)(const char *cgroup, void *arg), 337 428 void *arg) 338 429 { 339 430 int pid; 431 + 432 + pid = clone_into_cgroup_run_nowait(cgroup, fn, arg); 433 + if (pid > 0) 434 + return pid; 435 + 436 + /* Genuine test failure. */ 437 + if (pid < 0 && errno != ENOSYS) 438 + return -1; 340 439 341 440 pid = fork(); 342 441 if (pid == 0) { ··· 550 449 return -1; 551 450 552 451 return strstr(buf, needle) ? 0 : -1; 452 + } 453 + 454 + int clone_into_cgroup_run_wait(const char *cgroup) 455 + { 456 + int cgroup_fd; 457 + pid_t pid; 458 + 459 + cgroup_fd = dirfd_open_opath(cgroup); 460 + if (cgroup_fd < 0) 461 + return -1; 462 + 463 + pid = clone_into_cgroup(cgroup_fd); 464 + close_prot_errno(cgroup_fd); 465 + if (pid < 0) 466 + return -1; 467 + 468 + if (pid == 0) 469 + exit(EXIT_SUCCESS); 470 + 471 + /* 472 + * We don't care whether this fails. We only care whether the initial 473 + * clone succeeded. 474 + */ 475 + (void)clone_reap(pid, WEXITED); 476 + return 0; 553 477 }
+4
tools/testing/selftests/cgroup/cgroup_util.h
··· 50 50 extern int cg_killall(const char *cgroup); 51 51 extern ssize_t proc_read_text(int pid, bool thread, const char *item, char *buf, size_t size); 52 52 extern int proc_read_strstr(int pid, bool thread, const char *item, const char *needle); 53 + extern pid_t clone_into_cgroup(int cgroup_fd); 54 + extern int clone_reap(pid_t pid, int options); 55 + extern int clone_into_cgroup_run_wait(const char *cgroup); 56 + extern int dirfd_open_opath(const char *dir);
+64
tools/testing/selftests/cgroup/test_core.c
··· 137 137 static int test_cgcore_populated(const char *root) 138 138 { 139 139 int ret = KSFT_FAIL; 140 + int err; 140 141 char *cg_test_a = NULL, *cg_test_b = NULL; 141 142 char *cg_test_c = NULL, *cg_test_d = NULL; 143 + int cgroup_fd = -EBADF; 144 + pid_t pid; 142 145 143 146 cg_test_a = cg_name(root, "cg_test_a"); 144 147 cg_test_b = cg_name(root, "cg_test_a/cg_test_b"); ··· 193 190 if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n")) 194 191 goto cleanup; 195 192 193 + /* Test that we can directly clone into a new cgroup. */ 194 + cgroup_fd = dirfd_open_opath(cg_test_d); 195 + if (cgroup_fd < 0) 196 + goto cleanup; 197 + 198 + pid = clone_into_cgroup(cgroup_fd); 199 + if (pid < 0) { 200 + if (errno == ENOSYS) 201 + goto cleanup_pass; 202 + goto cleanup; 203 + } 204 + 205 + if (pid == 0) { 206 + if (raise(SIGSTOP)) 207 + exit(EXIT_FAILURE); 208 + exit(EXIT_SUCCESS); 209 + } 210 + 211 + err = cg_read_strcmp(cg_test_d, "cgroup.events", "populated 1\n"); 212 + 213 + (void)clone_reap(pid, WSTOPPED); 214 + (void)kill(pid, SIGCONT); 215 + (void)clone_reap(pid, WEXITED); 216 + 217 + if (err) 218 + goto cleanup; 219 + 220 + if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n")) 221 + goto cleanup; 222 + 223 + /* Remove cgroup. */ 224 + if (cg_test_d) { 225 + cg_destroy(cg_test_d); 226 + free(cg_test_d); 227 + cg_test_d = NULL; 228 + } 229 + 230 + pid = clone_into_cgroup(cgroup_fd); 231 + if (pid < 0) 232 + goto cleanup_pass; 233 + if (pid == 0) 234 + exit(EXIT_SUCCESS); 235 + (void)clone_reap(pid, WEXITED); 236 + goto cleanup; 237 + 238 + cleanup_pass: 196 239 ret = KSFT_PASS; 197 240 198 241 cleanup: ··· 254 205 free(cg_test_c); 255 206 free(cg_test_b); 256 207 free(cg_test_a); 208 + if (cgroup_fd >= 0) 209 + close(cgroup_fd); 257 210 return ret; 258 211 } 259 212 ··· 299 248 if (errno != EOPNOTSUPP) 300 249 goto cleanup; 301 250 251 + if (!clone_into_cgroup_run_wait(child)) 252 + goto cleanup; 253 + 254 + if (errno == ENOSYS) 255 + goto cleanup_pass; 256 + 257 + if (errno != EOPNOTSUPP) 258 + goto cleanup; 259 + 260 + cleanup_pass: 302 261 ret = KSFT_PASS; 303 262 304 263 cleanup: ··· 516 455 goto cleanup; 517 456 518 457 if (!cg_enter_current(parent)) 458 + goto cleanup; 459 + 460 + if (!clone_into_cgroup_run_wait(parent)) 519 461 goto cleanup; 520 462 521 463 ret = KSFT_PASS;
+17 -2
tools/testing/selftests/clone3/clone3_selftests.h
··· 5 5 6 6 #define _GNU_SOURCE 7 7 #include <sched.h> 8 + #include <linux/sched.h> 9 + #include <linux/types.h> 8 10 #include <stdint.h> 9 11 #include <syscall.h> 10 - #include <linux/types.h> 12 + #include <sys/wait.h> 13 + 14 + #include "../kselftest.h" 11 15 12 16 #define ptr_to_u64(ptr) ((__u64)((uintptr_t)(ptr))) 17 + 18 + #ifndef CLONE_INTO_CGROUP 19 + #define CLONE_INTO_CGROUP 0x200000000ULL /* Clone into a specific cgroup given the right permissions. */ 20 + #endif 21 + 22 + #ifndef CLONE_ARGS_SIZE_VER0 23 + #define CLONE_ARGS_SIZE_VER0 64 24 + #endif 13 25 14 26 #ifndef __NR_clone3 15 27 #define __NR_clone3 -1 ··· 34 22 __aligned_u64 stack; 35 23 __aligned_u64 stack_size; 36 24 __aligned_u64 tls; 25 + #define CLONE_ARGS_SIZE_VER1 80 37 26 __aligned_u64 set_tid; 38 27 __aligned_u64 set_tid_size; 28 + #define CLONE_ARGS_SIZE_VER2 88 29 + __aligned_u64 cgroup; 39 30 }; 40 - #endif 31 + #endif /* __NR_clone3 */ 41 32 42 33 static pid_t sys_clone3(struct clone_args *args, size_t size) 43 34 {