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 },
47static 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. */
52static char *spargs[8];
53
54static inline char *
55startswith (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
63static char *
64get_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. */
101static void
102handle_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
112static int
113do_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
127static int
128create_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
160static int
161do_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

source code of glibc/sysdeps/unix/sysv/linux/tst-spawn-cgroup.c