1 /* SPDX-License-Identifier: GPL-2.0 */
2
3 #define _GNU_SOURCE
4 #include <linux/limits.h>
5 #include <linux/sched.h>
6 #include <sys/types.h>
7 #include <sys/mman.h>
8 #include <sys/mount.h>
9 #include <sys/stat.h>
10 #include <sys/wait.h>
11 #include <unistd.h>
12 #include <fcntl.h>
13 #include <sched.h>
14 #include <stdio.h>
15 #include <errno.h>
16 #include <signal.h>
17 #include <string.h>
18 #include <pthread.h>
19
20 #include "kselftest.h"
21 #include "cgroup_util.h"
22
23 static bool nsdelegate;
24 #ifndef CLONE_NEWCGROUP
25 #define CLONE_NEWCGROUP 0
26 #endif
27
touch_anon(char * buf,size_t size)28 static int touch_anon(char *buf, size_t size)
29 {
30 int fd;
31 char *pos = buf;
32
33 fd = open("/dev/urandom", O_RDONLY);
34 if (fd < 0)
35 return -1;
36
37 while (size > 0) {
38 ssize_t ret = read(fd, pos, size);
39
40 if (ret < 0) {
41 if (errno != EINTR) {
42 close(fd);
43 return -1;
44 }
45 } else {
46 pos += ret;
47 size -= ret;
48 }
49 }
50 close(fd);
51
52 return 0;
53 }
54
alloc_and_touch_anon_noexit(const char * cgroup,void * arg)55 static int alloc_and_touch_anon_noexit(const char *cgroup, void *arg)
56 {
57 int ppid = getppid();
58 size_t size = (size_t)arg;
59 void *buf;
60
61 buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,
62 0, 0);
63 if (buf == MAP_FAILED)
64 return -1;
65
66 if (touch_anon((char *)buf, size)) {
67 munmap(buf, size);
68 return -1;
69 }
70
71 while (getppid() == ppid)
72 sleep(1);
73
74 munmap(buf, size);
75 return 0;
76 }
77
78 /*
79 * Create a child process that allocates and touches 100MB, then waits to be
80 * killed. Wait until the child is attached to the cgroup, kill all processes
81 * in that cgroup and wait until "cgroup.procs" is empty. At this point try to
82 * destroy the empty cgroup. The test helps detect race conditions between
83 * dying processes leaving the cgroup and cgroup destruction path.
84 */
test_cgcore_destroy(const char * root)85 static int test_cgcore_destroy(const char *root)
86 {
87 int ret = KSFT_FAIL;
88 char *cg_test = NULL;
89 int child_pid;
90 char buf[BUF_SIZE];
91
92 cg_test = cg_name(root, "cg_test");
93
94 if (!cg_test)
95 goto cleanup;
96
97 for (int i = 0; i < 10; i++) {
98 if (cg_create(cg_test))
99 goto cleanup;
100
101 child_pid = cg_run_nowait(cg_test, alloc_and_touch_anon_noexit,
102 (void *) MB(100));
103
104 if (child_pid < 0)
105 goto cleanup;
106
107 /* wait for the child to enter cgroup */
108 if (cg_wait_for_proc_count(cg_test, 1))
109 goto cleanup;
110
111 if (cg_killall(cg_test))
112 goto cleanup;
113
114 /* wait for cgroup to be empty */
115 while (1) {
116 if (cg_read(cg_test, "cgroup.procs", buf, sizeof(buf)))
117 goto cleanup;
118 if (buf[0] == '\0')
119 break;
120 usleep(1000);
121 }
122
123 if (rmdir(cg_test))
124 goto cleanup;
125
126 if (waitpid(child_pid, NULL, 0) < 0)
127 goto cleanup;
128 }
129 ret = KSFT_PASS;
130 cleanup:
131 if (cg_test)
132 cg_destroy(cg_test);
133 free(cg_test);
134 return ret;
135 }
136
137 /*
138 * A(0) - B(0) - C(1)
139 * \ D(0)
140 *
141 * A, B and C's "populated" fields would be 1 while D's 0.
142 * test that after the one process in C is moved to root,
143 * A,B and C's "populated" fields would flip to "0" and file
144 * modified events will be generated on the
145 * "cgroup.events" files of both cgroups.
146 */
test_cgcore_populated(const char * root)147 static int test_cgcore_populated(const char *root)
148 {
149 int ret = KSFT_FAIL;
150 int err;
151 char *cg_test_a = NULL, *cg_test_b = NULL;
152 char *cg_test_c = NULL, *cg_test_d = NULL;
153 int cgroup_fd = -EBADF;
154 pid_t pid;
155
156 if (cg_test_v1_named)
157 return KSFT_SKIP;
158
159 cg_test_a = cg_name(root, "cg_test_a");
160 cg_test_b = cg_name(root, "cg_test_a/cg_test_b");
161 cg_test_c = cg_name(root, "cg_test_a/cg_test_b/cg_test_c");
162 cg_test_d = cg_name(root, "cg_test_a/cg_test_b/cg_test_d");
163
164 if (!cg_test_a || !cg_test_b || !cg_test_c || !cg_test_d)
165 goto cleanup;
166
167 if (cg_create(cg_test_a))
168 goto cleanup;
169
170 if (cg_create(cg_test_b))
171 goto cleanup;
172
173 if (cg_create(cg_test_c))
174 goto cleanup;
175
176 if (cg_create(cg_test_d))
177 goto cleanup;
178
179 if (cg_enter_current(cg_test_c))
180 goto cleanup;
181
182 if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 1\n"))
183 goto cleanup;
184
185 if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 1\n"))
186 goto cleanup;
187
188 if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 1\n"))
189 goto cleanup;
190
191 if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
192 goto cleanup;
193
194 if (cg_enter_current(root))
195 goto cleanup;
196
197 if (cg_read_strcmp(cg_test_a, "cgroup.events", "populated 0\n"))
198 goto cleanup;
199
200 if (cg_read_strcmp(cg_test_b, "cgroup.events", "populated 0\n"))
201 goto cleanup;
202
203 if (cg_read_strcmp(cg_test_c, "cgroup.events", "populated 0\n"))
204 goto cleanup;
205
206 if (cg_read_strcmp(cg_test_d, "cgroup.events", "populated 0\n"))
207 goto cleanup;
208
209 /* Test that we can directly clone into a new cgroup. */
210 cgroup_fd = dirfd_open_opath(cg_test_d);
211 if (cgroup_fd < 0)
212 goto cleanup;
213
214 pid = clone_into_cgroup(cgroup_fd);
215 if (pid < 0) {
216 if (errno == ENOSYS)
217 goto cleanup_pass;
218 goto cleanup;
219 }
220
221 if (pid == 0) {
222 if (raise(SIGSTOP))
223 exit(EXIT_FAILURE);
224 exit(EXIT_SUCCESS);
225 }
226
227 err = cg_read_strcmp(cg_test_d, "cgroup.events", "populated 1\n");
228
229 (void)clone_reap(pid, WSTOPPED);
230 (void)kill(pid, SIGCONT);
231 (void)clone_reap(pid, WEXITED);
232
233 if (err)
234 goto cleanup;
235
236 if (cg_read_strcmp_wait(cg_test_d, "cgroup.events",
237 "populated 0\n"))
238 goto cleanup;
239
240 /* Remove cgroup. */
241 if (cg_test_d) {
242 cg_destroy(cg_test_d);
243 free(cg_test_d);
244 cg_test_d = NULL;
245 }
246
247 pid = clone_into_cgroup(cgroup_fd);
248 if (pid < 0)
249 goto cleanup_pass;
250 if (pid == 0)
251 exit(EXIT_SUCCESS);
252 (void)clone_reap(pid, WEXITED);
253 goto cleanup;
254
255 cleanup_pass:
256 ret = KSFT_PASS;
257
258 cleanup:
259 if (cg_test_d)
260 cg_destroy(cg_test_d);
261 if (cg_test_c)
262 cg_destroy(cg_test_c);
263 if (cg_test_b)
264 cg_destroy(cg_test_b);
265 if (cg_test_a)
266 cg_destroy(cg_test_a);
267 free(cg_test_d);
268 free(cg_test_c);
269 free(cg_test_b);
270 free(cg_test_a);
271 if (cgroup_fd >= 0)
272 close(cgroup_fd);
273 return ret;
274 }
275
276 /*
277 * A (domain threaded) - B (threaded) - C (domain)
278 *
279 * test that C can't be used until it is turned into a
280 * threaded cgroup. "cgroup.type" file will report "domain (invalid)" in
281 * these cases. Operations which fail due to invalid topology use
282 * EOPNOTSUPP as the errno.
283 */
test_cgcore_invalid_domain(const char * root)284 static int test_cgcore_invalid_domain(const char *root)
285 {
286 int ret = KSFT_FAIL;
287 char *grandparent = NULL, *parent = NULL, *child = NULL;
288
289 if (cg_test_v1_named)
290 return KSFT_SKIP;
291
292 grandparent = cg_name(root, "cg_test_grandparent");
293 parent = cg_name(root, "cg_test_grandparent/cg_test_parent");
294 child = cg_name(root, "cg_test_grandparent/cg_test_parent/cg_test_child");
295 if (!parent || !child || !grandparent)
296 goto cleanup;
297
298 if (cg_create(grandparent))
299 goto cleanup;
300
301 if (cg_create(parent))
302 goto cleanup;
303
304 if (cg_create(child))
305 goto cleanup;
306
307 if (cg_write(parent, "cgroup.type", "threaded"))
308 goto cleanup;
309
310 if (cg_read_strcmp(child, "cgroup.type", "domain invalid\n"))
311 goto cleanup;
312
313 if (!cg_enter_current(child))
314 goto cleanup;
315
316 if (errno != EOPNOTSUPP)
317 goto cleanup;
318
319 if (!clone_into_cgroup_run_wait(child))
320 goto cleanup;
321
322 if (errno == ENOSYS)
323 goto cleanup_pass;
324
325 if (errno != EOPNOTSUPP)
326 goto cleanup;
327
328 cleanup_pass:
329 ret = KSFT_PASS;
330
331 cleanup:
332 cg_enter_current(root);
333 if (child)
334 cg_destroy(child);
335 if (parent)
336 cg_destroy(parent);
337 if (grandparent)
338 cg_destroy(grandparent);
339 free(child);
340 free(parent);
341 free(grandparent);
342 return ret;
343 }
344
345 /*
346 * Test that when a child becomes threaded
347 * the parent type becomes domain threaded.
348 */
test_cgcore_parent_becomes_threaded(const char * root)349 static int test_cgcore_parent_becomes_threaded(const char *root)
350 {
351 int ret = KSFT_FAIL;
352 char *parent = NULL, *child = NULL;
353
354 if (cg_test_v1_named)
355 return KSFT_SKIP;
356
357 parent = cg_name(root, "cg_test_parent");
358 child = cg_name(root, "cg_test_parent/cg_test_child");
359 if (!parent || !child)
360 goto cleanup;
361
362 if (cg_create(parent))
363 goto cleanup;
364
365 if (cg_create(child))
366 goto cleanup;
367
368 if (cg_write(child, "cgroup.type", "threaded"))
369 goto cleanup;
370
371 if (cg_read_strcmp(parent, "cgroup.type", "domain threaded\n"))
372 goto cleanup;
373
374 ret = KSFT_PASS;
375
376 cleanup:
377 if (child)
378 cg_destroy(child);
379 if (parent)
380 cg_destroy(parent);
381 free(child);
382 free(parent);
383 return ret;
384
385 }
386
387 /*
388 * Test that there's no internal process constrain on threaded cgroups.
389 * You can add threads/processes on a parent with a controller enabled.
390 */
test_cgcore_no_internal_process_constraint_on_threads(const char * root)391 static int test_cgcore_no_internal_process_constraint_on_threads(const char *root)
392 {
393 int ret = KSFT_FAIL;
394 char *parent = NULL, *child = NULL;
395
396 if (cg_test_v1_named ||
397 cg_read_strstr(root, "cgroup.controllers", "cpu") ||
398 cg_write(root, "cgroup.subtree_control", "+cpu")) {
399 ret = KSFT_SKIP;
400 goto cleanup;
401 }
402
403 parent = cg_name(root, "cg_test_parent");
404 child = cg_name(root, "cg_test_parent/cg_test_child");
405 if (!parent || !child)
406 goto cleanup;
407
408 if (cg_create(parent))
409 goto cleanup;
410
411 if (cg_create(child))
412 goto cleanup;
413
414 if (cg_write(parent, "cgroup.type", "threaded"))
415 goto cleanup;
416
417 if (cg_write(child, "cgroup.type", "threaded"))
418 goto cleanup;
419
420 if (cg_write(parent, "cgroup.subtree_control", "+cpu"))
421 goto cleanup;
422
423 if (cg_enter_current(parent))
424 goto cleanup;
425
426 ret = KSFT_PASS;
427
428 cleanup:
429 cg_enter_current(root);
430 if (child)
431 cg_destroy(child);
432 if (parent)
433 cg_destroy(parent);
434 free(child);
435 free(parent);
436 return ret;
437 }
438
439 /*
440 * Test that you can't enable a controller on a child if it's not enabled
441 * on the parent.
442 */
test_cgcore_top_down_constraint_enable(const char * root)443 static int test_cgcore_top_down_constraint_enable(const char *root)
444 {
445 int ret = KSFT_FAIL;
446 char *parent = NULL, *child = NULL;
447
448 if (cg_test_v1_named)
449 return KSFT_SKIP;
450
451 parent = cg_name(root, "cg_test_parent");
452 child = cg_name(root, "cg_test_parent/cg_test_child");
453 if (!parent || !child)
454 goto cleanup;
455
456 if (cg_create(parent))
457 goto cleanup;
458
459 if (cg_create(child))
460 goto cleanup;
461
462 if (!cg_write(child, "cgroup.subtree_control", "+memory"))
463 goto cleanup;
464
465 ret = KSFT_PASS;
466
467 cleanup:
468 if (child)
469 cg_destroy(child);
470 if (parent)
471 cg_destroy(parent);
472 free(child);
473 free(parent);
474 return ret;
475 }
476
477 /*
478 * Test that you can't disable a controller on a parent
479 * if it's enabled in a child.
480 */
test_cgcore_top_down_constraint_disable(const char * root)481 static int test_cgcore_top_down_constraint_disable(const char *root)
482 {
483 int ret = KSFT_FAIL;
484 char *parent = NULL, *child = NULL;
485
486 if (cg_test_v1_named)
487 return KSFT_SKIP;
488
489 parent = cg_name(root, "cg_test_parent");
490 child = cg_name(root, "cg_test_parent/cg_test_child");
491 if (!parent || !child)
492 goto cleanup;
493
494 if (cg_create(parent))
495 goto cleanup;
496
497 if (cg_create(child))
498 goto cleanup;
499
500 if (cg_write(parent, "cgroup.subtree_control", "+memory"))
501 goto cleanup;
502
503 if (cg_write(child, "cgroup.subtree_control", "+memory"))
504 goto cleanup;
505
506 if (!cg_write(parent, "cgroup.subtree_control", "-memory"))
507 goto cleanup;
508
509 ret = KSFT_PASS;
510
511 cleanup:
512 if (child)
513 cg_destroy(child);
514 if (parent)
515 cg_destroy(parent);
516 free(child);
517 free(parent);
518 return ret;
519 }
520
521 /*
522 * Test internal process constraint.
523 * You can't add a pid to a domain parent if a controller is enabled.
524 */
test_cgcore_internal_process_constraint(const char * root)525 static int test_cgcore_internal_process_constraint(const char *root)
526 {
527 int ret = KSFT_FAIL;
528 char *parent = NULL, *child = NULL;
529
530 if (cg_test_v1_named)
531 return KSFT_SKIP;
532
533 parent = cg_name(root, "cg_test_parent");
534 child = cg_name(root, "cg_test_parent/cg_test_child");
535 if (!parent || !child)
536 goto cleanup;
537
538 if (cg_create(parent))
539 goto cleanup;
540
541 if (cg_create(child))
542 goto cleanup;
543
544 if (cg_write(parent, "cgroup.subtree_control", "+memory"))
545 goto cleanup;
546
547 if (!cg_enter_current(parent))
548 goto cleanup;
549
550 if (!clone_into_cgroup_run_wait(parent))
551 goto cleanup;
552
553 ret = KSFT_PASS;
554
555 cleanup:
556 if (child)
557 cg_destroy(child);
558 if (parent)
559 cg_destroy(parent);
560 free(child);
561 free(parent);
562 return ret;
563 }
564
dummy_thread_fn(void * arg)565 static void *dummy_thread_fn(void *arg)
566 {
567 return (void *)(size_t)pause();
568 }
569
570 /*
571 * Test threadgroup migration.
572 * All threads of a process are migrated together.
573 */
test_cgcore_proc_migration(const char * root)574 static int test_cgcore_proc_migration(const char *root)
575 {
576 int ret = KSFT_FAIL;
577 int t, c_threads = 0, n_threads = 13;
578 char *src = NULL, *dst = NULL;
579 pthread_t threads[n_threads];
580
581 src = cg_name(root, "cg_src");
582 dst = cg_name(root, "cg_dst");
583 if (!src || !dst)
584 goto cleanup;
585
586 if (cg_create(src))
587 goto cleanup;
588 if (cg_create(dst))
589 goto cleanup;
590
591 if (cg_enter_current(src))
592 goto cleanup;
593
594 for (c_threads = 0; c_threads < n_threads; ++c_threads) {
595 if (pthread_create(&threads[c_threads], NULL, dummy_thread_fn, NULL))
596 goto cleanup;
597 }
598
599 cg_enter_current(dst);
600 if (cg_read_lc(dst, CG_THREADS_FILE) != n_threads + 1)
601 goto cleanup;
602
603 ret = KSFT_PASS;
604
605 cleanup:
606 for (t = 0; t < c_threads; ++t) {
607 pthread_cancel(threads[t]);
608 }
609
610 for (t = 0; t < c_threads; ++t) {
611 pthread_join(threads[t], NULL);
612 }
613
614 cg_enter_current(root);
615
616 if (dst)
617 cg_destroy(dst);
618 if (src)
619 cg_destroy(src);
620 free(dst);
621 free(src);
622 return ret;
623 }
624
migrating_thread_fn(void * arg)625 static void *migrating_thread_fn(void *arg)
626 {
627 int g, i, n_iterations = 1000;
628 char **grps = arg;
629 char lines[3][PATH_MAX];
630
631 for (g = 1; g < 3; ++g)
632 snprintf(lines[g], sizeof(lines[g]), CG_PATH_FORMAT, grps[g] + strlen(grps[0]));
633
634 for (i = 0; i < n_iterations; ++i) {
635 cg_enter_current_thread(grps[(i % 2) + 1]);
636
637 if (proc_read_strstr(0, 1, "cgroup", lines[(i % 2) + 1]))
638 return (void *)-1;
639 }
640 return NULL;
641 }
642
643 /*
644 * Test single thread migration.
645 * Threaded cgroups allow successful migration of a thread.
646 */
test_cgcore_thread_migration(const char * root)647 static int test_cgcore_thread_migration(const char *root)
648 {
649 int ret = KSFT_FAIL;
650 char *dom = NULL;
651 char line[PATH_MAX];
652 char *grps[3] = { (char *)root, NULL, NULL };
653 pthread_t thr;
654 void *retval;
655
656 dom = cg_name(root, "cg_dom");
657 grps[1] = cg_name(root, "cg_dom/cg_src");
658 grps[2] = cg_name(root, "cg_dom/cg_dst");
659 if (!grps[1] || !grps[2] || !dom)
660 goto cleanup;
661
662 if (cg_create(dom))
663 goto cleanup;
664 if (cg_create(grps[1]))
665 goto cleanup;
666 if (cg_create(grps[2]))
667 goto cleanup;
668
669 if (!cg_test_v1_named) {
670 if (cg_write(grps[1], "cgroup.type", "threaded"))
671 goto cleanup;
672 if (cg_write(grps[2], "cgroup.type", "threaded"))
673 goto cleanup;
674 }
675
676 if (cg_enter_current(grps[1]))
677 goto cleanup;
678
679 if (pthread_create(&thr, NULL, migrating_thread_fn, grps))
680 goto cleanup;
681
682 if (pthread_join(thr, &retval))
683 goto cleanup;
684
685 if (retval)
686 goto cleanup;
687
688 snprintf(line, sizeof(line), CG_PATH_FORMAT, grps[1] + strlen(grps[0]));
689 if (proc_read_strstr(0, 1, "cgroup", line))
690 goto cleanup;
691
692 ret = KSFT_PASS;
693
694 cleanup:
695 cg_enter_current(root);
696 if (grps[2])
697 cg_destroy(grps[2]);
698 if (grps[1])
699 cg_destroy(grps[1]);
700 if (dom)
701 cg_destroy(dom);
702 free(grps[2]);
703 free(grps[1]);
704 free(dom);
705 return ret;
706 }
707
708 /*
709 * cgroup migration permission check should be performed based on the
710 * credentials at the time of open instead of write.
711 */
test_cgcore_lesser_euid_open(const char * root)712 static int test_cgcore_lesser_euid_open(const char *root)
713 {
714 const uid_t test_euid = TEST_UID;
715 int ret = KSFT_FAIL;
716 char *cg_test_a = NULL, *cg_test_b = NULL;
717 char *cg_test_a_procs = NULL, *cg_test_b_procs = NULL;
718 int cg_test_b_procs_fd = -1;
719 uid_t saved_uid;
720
721 cg_test_a = cg_name(root, "cg_test_a");
722 cg_test_b = cg_name(root, "cg_test_b");
723
724 if (!cg_test_a || !cg_test_b)
725 goto cleanup;
726
727 cg_test_a_procs = cg_name(cg_test_a, "cgroup.procs");
728 cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
729
730 if (!cg_test_a_procs || !cg_test_b_procs)
731 goto cleanup;
732
733 if (cg_create(cg_test_a) || cg_create(cg_test_b))
734 goto cleanup;
735
736 if (cg_enter_current(cg_test_a))
737 goto cleanup;
738
739 if (chown(cg_test_a_procs, test_euid, -1) ||
740 chown(cg_test_b_procs, test_euid, -1))
741 goto cleanup;
742
743 saved_uid = geteuid();
744 if (seteuid(test_euid))
745 goto cleanup;
746
747 cg_test_b_procs_fd = open(cg_test_b_procs, O_RDWR);
748
749 if (seteuid(saved_uid))
750 goto cleanup;
751
752 if (cg_test_b_procs_fd < 0)
753 goto cleanup;
754
755 if (write(cg_test_b_procs_fd, "0", 1) >= 0 || errno != EACCES)
756 goto cleanup;
757
758 ret = KSFT_PASS;
759
760 cleanup:
761 cg_enter_current(root);
762 if (cg_test_b_procs_fd >= 0)
763 close(cg_test_b_procs_fd);
764 if (cg_test_b)
765 cg_destroy(cg_test_b);
766 if (cg_test_a)
767 cg_destroy(cg_test_a);
768 free(cg_test_b_procs);
769 free(cg_test_a_procs);
770 free(cg_test_b);
771 free(cg_test_a);
772 return ret;
773 }
774
775 struct lesser_ns_open_thread_arg {
776 const char *path;
777 int fd;
778 int err;
779 };
780
lesser_ns_open_thread_fn(void * arg)781 static int lesser_ns_open_thread_fn(void *arg)
782 {
783 struct lesser_ns_open_thread_arg *targ = arg;
784
785 targ->fd = open(targ->path, O_RDWR);
786 targ->err = errno;
787 return 0;
788 }
789
790 /*
791 * cgroup migration permission check should be performed based on the cgroup
792 * namespace at the time of open instead of write.
793 */
test_cgcore_lesser_ns_open(const char * root)794 static int test_cgcore_lesser_ns_open(const char *root)
795 {
796 static char stack[65536];
797 int ret = KSFT_FAIL;
798 char *cg_test_a = NULL, *cg_test_b = NULL;
799 char *cg_test_b_procs = NULL;
800 int cg_test_b_procs_fd = -1;
801 struct lesser_ns_open_thread_arg targ = { .fd = -1 };
802 pid_t pid;
803 int status;
804
805 if (!nsdelegate)
806 return KSFT_SKIP;
807
808 cg_test_a = cg_name(root, "cg_test_a");
809 cg_test_b = cg_name(root, "cg_test_b");
810
811 if (!cg_test_a || !cg_test_b)
812 goto cleanup;
813
814 cg_test_b_procs = cg_name(cg_test_b, "cgroup.procs");
815
816 if (!cg_test_b_procs)
817 goto cleanup;
818
819 if (cg_create(cg_test_a) || cg_create(cg_test_b))
820 goto cleanup;
821
822 if (cg_enter_current(cg_test_b))
823 goto cleanup;
824
825 targ.path = cg_test_b_procs;
826 pid = clone(lesser_ns_open_thread_fn, stack + sizeof(stack),
827 CLONE_NEWCGROUP | CLONE_FILES | CLONE_VM | SIGCHLD,
828 &targ);
829 if (pid < 0)
830 goto cleanup;
831
832 if (waitpid(pid, &status, 0) < 0)
833 goto cleanup;
834
835 if (!WIFEXITED(status))
836 goto cleanup;
837
838 cg_test_b_procs_fd = targ.fd;
839 if (cg_test_b_procs_fd < 0)
840 goto cleanup;
841
842 if (cg_enter_current(cg_test_a))
843 goto cleanup;
844
845 if ((status = write(cg_test_b_procs_fd, "0", 1)) >= 0 || errno != ENOENT)
846 goto cleanup;
847
848 ret = KSFT_PASS;
849
850 cleanup:
851 cg_enter_current(root);
852 if (cg_test_b_procs_fd >= 0)
853 close(cg_test_b_procs_fd);
854 if (cg_test_b)
855 cg_destroy(cg_test_b);
856 if (cg_test_a)
857 cg_destroy(cg_test_a);
858 free(cg_test_b_procs);
859 free(cg_test_b);
860 free(cg_test_a);
861 return ret;
862 }
863
setup_named_v1_root(char * root,size_t len,const char * name)864 static int setup_named_v1_root(char *root, size_t len, const char *name)
865 {
866 char options[PATH_MAX];
867 int r;
868
869 r = snprintf(root, len, "/mnt/cg_selftest");
870 if (r < 0)
871 return r;
872
873 r = snprintf(options, sizeof(options), "none,name=%s", name);
874 if (r < 0)
875 return r;
876
877 r = mkdir(root, 0755);
878 if (r < 0 && errno != EEXIST)
879 return r;
880
881 r = mount("none", root, "cgroup", 0, options);
882 if (r < 0)
883 return r;
884
885 return 0;
886 }
887
cleanup_named_v1_root(char * root)888 static void cleanup_named_v1_root(char *root)
889 {
890 if (!cg_test_v1_named)
891 return;
892 umount(root);
893 rmdir(root);
894 }
895
896 #define T(x) { x, #x }
897 struct corecg_test {
898 int (*fn)(const char *root);
899 const char *name;
900 } tests[] = {
901 T(test_cgcore_internal_process_constraint),
902 T(test_cgcore_top_down_constraint_enable),
903 T(test_cgcore_top_down_constraint_disable),
904 T(test_cgcore_no_internal_process_constraint_on_threads),
905 T(test_cgcore_parent_becomes_threaded),
906 T(test_cgcore_invalid_domain),
907 T(test_cgcore_populated),
908 T(test_cgcore_proc_migration),
909 T(test_cgcore_thread_migration),
910 T(test_cgcore_destroy),
911 T(test_cgcore_lesser_euid_open),
912 T(test_cgcore_lesser_ns_open),
913 };
914 #undef T
915
main(int argc,char * argv[])916 int main(int argc, char *argv[])
917 {
918 char root[PATH_MAX];
919 int i;
920
921 ksft_print_header();
922 ksft_set_plan(ARRAY_SIZE(tests));
923 if (cg_find_unified_root(root, sizeof(root), &nsdelegate)) {
924 if (setup_named_v1_root(root, sizeof(root), CG_NAMED_NAME))
925 ksft_exit_skip("cgroup v2 isn't mounted and could not setup named v1 hierarchy\n");
926 cg_test_v1_named = true;
927 goto post_v2_setup;
928 }
929
930 if (cg_read_strstr(root, "cgroup.subtree_control", "memory"))
931 if (cg_write(root, "cgroup.subtree_control", "+memory"))
932 ksft_exit_skip("Failed to set memory controller\n");
933
934 post_v2_setup:
935 for (i = 0; i < ARRAY_SIZE(tests); i++) {
936 switch (tests[i].fn(root)) {
937 case KSFT_PASS:
938 ksft_test_result_pass("%s\n", tests[i].name);
939 break;
940 case KSFT_SKIP:
941 ksft_test_result_skip("%s\n", tests[i].name);
942 break;
943 default:
944 ksft_test_result_fail("%s\n", tests[i].name);
945 break;
946 }
947 }
948
949 cleanup_named_v1_root(root);
950 ksft_finished();
951 }
952