1 | /* Tests for posix_spawn cgroup extension. |
2 | Copyright (C) 2023-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <assert.h> |
20 | #include <errno.h> |
21 | #include <fcntl.h> |
22 | #include <getopt.h> |
23 | #include <spawn.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | #include <support/check.h> |
27 | #include <support/support.h> |
28 | #include <support/xstdio.h> |
29 | #include <support/xunistd.h> |
30 | #include <support/temp_file.h> |
31 | #include <sys/vfs.h> |
32 | #include <sys/wait.h> |
33 | #include <unistd.h> |
34 | |
35 | #define CGROUPFS "/sys/fs/cgroup/" |
36 | #ifndef CGROUP2_SUPER_MAGIC |
37 | # define CGROUP2_SUPER_MAGIC 0x63677270 |
38 | #endif |
39 | |
40 | #define F_TYPE_EQUAL(a, b) (a == (typeof (a)) b) |
41 | |
42 | #define CGROUP_TEST "test-spawn-cgroup" |
43 | |
44 | /* Nonzero if the program gets called via `exec'. */ |
45 | #define CMDLINE_OPTIONS \ |
46 | { "restart", no_argument, &restart, 1 }, |
47 | static int restart; |
48 | |
49 | /* Hold the four initial argument used to respawn the process, plus the extra |
50 | '--direct', '--restart', the check type ('SIG_IGN' or 'SIG_DFL'), and a |
51 | final NULL. */ |
52 | static char *spargs[8]; |
53 | |
54 | static inline char * |
55 | startswith (const char *s, const char *prefix) |
56 | { |
57 | size_t l = strlen (prefix); |
58 | if (strncmp (s, prefix, l) == 0) |
59 | return (char *) s + l; |
60 | return NULL; |
61 | } |
62 | |
63 | static char * |
64 | get_cgroup (void) |
65 | { |
66 | FILE *f = fopen ("/proc/self/cgroup" , "re" ); |
67 | if (f == NULL) |
68 | FAIL_UNSUPPORTED ("no cgroup defined for the process: %m" ); |
69 | |
70 | char *cgroup = NULL; |
71 | |
72 | char *line = NULL; |
73 | size_t linesiz = 0; |
74 | while (xgetline (lineptr: &line, n: &linesiz, stream: f) > 0) |
75 | { |
76 | char *entry = startswith (s: line, prefix: "0:" ); |
77 | if (entry == NULL) |
78 | continue; |
79 | |
80 | entry = strchr (entry, ':'); |
81 | if (entry == NULL) |
82 | continue; |
83 | |
84 | cgroup = entry + 1; |
85 | size_t l = strlen (cgroup); |
86 | if (cgroup[l - 1] == '\n') |
87 | cgroup[l - 1] = '\0'; |
88 | |
89 | cgroup = xstrdup (entry + 1); |
90 | break; |
91 | } |
92 | |
93 | xfclose (f); |
94 | free (ptr: line); |
95 | |
96 | return cgroup; |
97 | } |
98 | |
99 | |
100 | /* Called on process re-execution. */ |
101 | static void |
102 | handle_restart (int argc, char *argv[]) |
103 | { |
104 | assert (argc == 1); |
105 | char *newcgroup = argv[0]; |
106 | |
107 | char *current_cgroup = get_cgroup (); |
108 | TEST_VERIFY_EXIT (current_cgroup != NULL); |
109 | TEST_COMPARE_STRING (newcgroup, current_cgroup); |
110 | } |
111 | |
112 | static int |
113 | do_test_cgroup_failure (pid_t *pid, int cgroup) |
114 | { |
115 | posix_spawnattr_t attr; |
116 | TEST_COMPARE (posix_spawnattr_init (&attr), 0); |
117 | TEST_COMPARE (posix_spawnattr_setflags (&attr, POSIX_SPAWN_SETCGROUP), 0); |
118 | TEST_COMPARE (posix_spawnattr_setcgroup_np (&attr, cgroup), 0); |
119 | |
120 | int cgetgroup; |
121 | TEST_COMPARE (posix_spawnattr_getcgroup_np (&attr, &cgetgroup), 0); |
122 | TEST_COMPARE (cgroup, cgetgroup); |
123 | |
124 | return posix_spawn (pid: pid, path: spargs[0], NULL, attrp: &attr, argv: spargs, envp: environ); |
125 | } |
126 | |
127 | static int |
128 | create_new_cgroup (char **newcgroup) |
129 | { |
130 | struct statfs fs; |
131 | if (statfs (CGROUPFS, buf: &fs) < 0) |
132 | { |
133 | if (errno == ENOENT) |
134 | FAIL_UNSUPPORTED ("no cgroupv2 mount found" ); |
135 | FAIL_EXIT1 ("statfs (%s): %m\n" , CGROUPFS); |
136 | } |
137 | |
138 | if (!F_TYPE_EQUAL (fs.f_type, CGROUP2_SUPER_MAGIC)) |
139 | FAIL_UNSUPPORTED ("%s is not a cgroupv2 (expected %#jx, got %#jx)" , |
140 | CGROUPFS, (intmax_t) CGROUP2_SUPER_MAGIC, |
141 | (intmax_t) fs.f_type); |
142 | |
143 | char *cgroup = get_cgroup (); |
144 | TEST_VERIFY_EXIT (cgroup != NULL); |
145 | *newcgroup = xasprintf (format: "%s/%s" , cgroup, CGROUP_TEST); |
146 | char *cgpath = xasprintf (format: "%s%s/%s" , CGROUPFS, cgroup, CGROUP_TEST); |
147 | free (ptr: cgroup); |
148 | |
149 | if (mkdir (path: cgpath, mode: 0755) == -1 && errno != EEXIST) |
150 | { |
151 | if (errno == EACCES || errno == EPERM || errno == EROFS) |
152 | FAIL_UNSUPPORTED ("can not create a new cgroupv2 group" ); |
153 | FAIL_EXIT1 ("mkdir (%s): %m" , cgpath); |
154 | } |
155 | add_temp_file (name: cgpath); |
156 | |
157 | return xopen (path: cgpath, O_DIRECTORY | O_RDONLY | O_CLOEXEC, 0666); |
158 | } |
159 | |
160 | static int |
161 | do_test (int argc, char *argv[]) |
162 | { |
163 | /* We must have either: |
164 | |
165 | - one or four parameters if called initially: |
166 | + argv[1]: path for ld.so optional |
167 | + argv[2]: "--library-path" optional |
168 | + argv[3]: the library path optional |
169 | + argv[4]: the application name |
170 | |
171 | - six parameters left if called through re-execution: |
172 | + argv[4/1]: the application name |
173 | + argv[5/2]: the created cgroup |
174 | |
175 | * When built with --enable-hardcoded-path-in-tests or issued without |
176 | using the loader directly. */ |
177 | |
178 | if (restart) |
179 | { |
180 | handle_restart (argc: argc - 1, argv: &argv[1]); |
181 | return 0; |
182 | } |
183 | |
184 | TEST_VERIFY_EXIT (argc == 2 || argc == 5); |
185 | |
186 | char *newcgroup; |
187 | int cgroup = create_new_cgroup (newcgroup: &newcgroup); |
188 | |
189 | int i; |
190 | for (i = 0; i < argc - 1; i++) |
191 | spargs[i] = argv[i + 1]; |
192 | spargs[i++] = (char *) "--direct" ; |
193 | spargs[i++] = (char *) "--restart" ; |
194 | spargs[i++] = (char *) newcgroup; |
195 | spargs[i] = NULL; |
196 | |
197 | /* Check if invalid cgroups returns an error. */ |
198 | { |
199 | int r = do_test_cgroup_failure (NULL, cgroup: -1); |
200 | if (r == EOPNOTSUPP) |
201 | FAIL_UNSUPPORTED ("posix_spawn POSIX_SPAWN_SETCGROUP is not supported" ); |
202 | TEST_COMPARE (r, EINVAL); |
203 | } |
204 | |
205 | { |
206 | pid_t pid; |
207 | TEST_COMPARE (do_test_cgroup_failure (&pid, cgroup), 0); |
208 | |
209 | siginfo_t sinfo; |
210 | TEST_COMPARE (waitid (P_PID, pid, &sinfo, WEXITED), 0); |
211 | TEST_COMPARE (sinfo.si_signo, SIGCHLD); |
212 | TEST_COMPARE (sinfo.si_code, CLD_EXITED); |
213 | TEST_COMPARE (sinfo.si_status, 0); |
214 | } |
215 | |
216 | xclose (cgroup); |
217 | free (ptr: newcgroup); |
218 | |
219 | return 0; |
220 | } |
221 | |
222 | #define TEST_FUNCTION_ARGV do_test |
223 | #include <support/test-driver.c> |
224 | |