1/* Tests for posix_spawn signal handling.
2 Copyright (C) 2021-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 <http://www.gnu.org/licenses/>. */
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <getopt.h>
22#include <spawn.h>
23#include <fcntl.h>
24#include <sys/wait.h>
25#include <dirent.h>
26#include <stdbool.h>
27#include <errno.h>
28#include <limits.h>
29
30#include <support/check.h>
31#include <support/xunistd.h>
32#include <support/support.h>
33
34#include <arch-fd_to_filename.h>
35#include <array_length.h>
36#include <tst-spawn.h>
37
38/* Nonzero if the program gets called via `exec'. */
39static int restart;
40
41/* Hold the four initial argument used to respawn the process, plus
42 the extra '--direct' and '--restart', and a final NULL. */
43static char *initial_argv[7];
44static int initial_argv_count;
45
46#define CMDLINE_OPTIONS \
47 { "restart", no_argument, &restart, 1 },
48
49#define NFDS 100
50
51static int
52parse_fd (const char *str)
53{
54 char *endptr;
55 long unsigned int fd = strtoul (str, &endptr, 10);
56 if (*endptr != '\0' || fd > INT_MAX)
57 FAIL_EXIT1 ("invalid file descriptor value: %s", str);
58 return fd;
59}
60
61/* Called on process re-execution. The arguments are the expected opened
62 file descriptors. */
63_Noreturn static void
64handle_restart (int argc, char *argv[])
65{
66 TEST_VERIFY (argc > 0);
67 int lowfd = parse_fd (str: argv[0]);
68
69 size_t nfds = argc > 1 ? argc - 1 : 0;
70 struct fd_t
71 {
72 int fd;
73 _Bool found;
74 } *fds = xmalloc (n: sizeof (struct fd_t) * nfds);
75 for (int i = 0; i < nfds; i++)
76 {
77 fds[i].fd = parse_fd (str: argv[i + 1]);
78 fds[i].found = false;
79 }
80
81 DIR *dirp = opendir (FD_TO_FILENAME_PREFIX);
82 if (dirp == NULL)
83 FAIL_EXIT1 ("opendir (\"" FD_TO_FILENAME_PREFIX "\"): %m");
84
85 while (true)
86 {
87 errno = 0;
88 struct dirent64 *e = readdir64 (dirp: dirp);
89 if (e == NULL)
90 {
91 if (errno != 0)
92 FAIL_EXIT1 ("readdir: %m");
93 break;
94 }
95
96 if (e->d_name[0] == '.')
97 continue;
98
99 char *endptr;
100 long int fd = strtol (e->d_name, &endptr, 10);
101 if (*endptr != '\0' || fd < 0 || fd > INT_MAX)
102 FAIL_EXIT1 ("readdir: invalid file descriptor name: /proc/self/fd/%s",
103 e->d_name);
104
105 /* Ignore the descriptors not in the range of the opened files. */
106 if (fd < lowfd || fd == dirfd (dirp))
107 continue;
108
109 bool found = false;
110 for (int i = 0; i < nfds; i++)
111 if (fds[i].fd == fd)
112 fds[i].found = found = true;
113
114 if (!found)
115 {
116 char *path = xasprintf (format: "/proc/self/fd/%s", e->d_name);
117 char *resolved = xreadlink (path);
118 FAIL_EXIT1 ("unexpected open file descriptor %ld: %s", fd, resolved);
119 }
120 }
121 closedir (dirp: dirp);
122
123 for (int i = 0; i < nfds; i++)
124 if (!fds[i].found)
125 FAIL_EXIT1 ("file descriptor %d not opened", fds[i].fd);
126
127 free (ptr: fds);
128
129 exit (EXIT_SUCCESS);
130}
131
132static void
133spawn_closefrom_test (posix_spawn_file_actions_t *fa, int lowfd, int highfd,
134 int *extrafds, size_t nextrafds)
135{
136 /* 3 or 7 elements from initial_argv:
137 + path to ld.so optional
138 + --library-path optional
139 + the library path optional
140 + application name
141 + --direct
142 + --restart
143 + lowest opened file descriptor
144 + up to 2 * maximum_fd arguments (the expected open file descriptors),
145 plus NULL. */
146
147 int argv_size = initial_argv_count + 2 * NFDS + 1;
148 char *args[argv_size];
149 int argc = 0;
150
151 for (char **arg = initial_argv; *arg != NULL; arg++)
152 args[argc++] = *arg;
153
154 args[argc++] = xasprintf (format: "%d", lowfd);
155
156 for (int i = lowfd; i < highfd; i++)
157 args[argc++] = xasprintf (format: "%d", i);
158
159 for (int i = 0; i < nextrafds; i++)
160 args[argc++] = xasprintf (format: "%d", extrafds[i]);
161
162 args[argc] = NULL;
163 TEST_VERIFY (argc < argv_size);
164
165 PID_T_TYPE pid;
166 siginfo_t sinfo;
167
168 TEST_COMPARE (POSIX_SPAWN (&pid, args[0], fa, NULL, args, environ), 0);
169 TEST_COMPARE (WAITID (P_PID, pid, &sinfo, WEXITED), 0);
170 TEST_COMPARE (sinfo.si_code, CLD_EXITED);
171 TEST_COMPARE (sinfo.si_status, 0);
172}
173
174static void
175do_test_closefrom (void)
176{
177 int lowfd = support_open_dev_null_range (NFDS, O_RDONLY, mode: 0600);
178 const int half_fd = lowfd + NFDS / 2;
179
180 /* Close half of the descriptors and check result. */
181 {
182 posix_spawn_file_actions_t fa;
183 TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
184
185 int ret = posix_spawn_file_actions_addclosefrom_np (&fa, from: half_fd);
186 if (ret == EINVAL)
187 /* Hurd currently does not support closefrom fileaction. */
188 FAIL_UNSUPPORTED ("posix_spawn_file_actions_addclosefrom_np unsupported");
189 TEST_COMPARE (ret, 0);
190
191 spawn_closefrom_test (fa: &fa, lowfd, highfd: half_fd, NULL, nextrafds: 0);
192
193 TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
194 }
195
196 /* Create some gaps, close up to a threshold, and check result. */
197 xclose (lowfd + 57);
198 xclose (lowfd + 78);
199 xclose (lowfd + 81);
200 xclose (lowfd + 82);
201 xclose (lowfd + 84);
202 xclose (lowfd + 90);
203
204 {
205 posix_spawn_file_actions_t fa;
206 TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
207
208 TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, half_fd), 0);
209
210 spawn_closefrom_test (fa: &fa, lowfd, highfd: half_fd, NULL, nextrafds: 0);
211
212 TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
213 }
214
215 /* Close the remaining but the last one. */
216 {
217 posix_spawn_file_actions_t fa;
218 TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
219
220 TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, lowfd + 1), 0);
221
222 spawn_closefrom_test (fa: &fa, lowfd, highfd: lowfd + 1, NULL, nextrafds: 0);
223
224 TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
225 }
226
227 /* Close everything. */
228 {
229 posix_spawn_file_actions_t fa;
230 TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
231
232 TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, lowfd), 0);
233
234 spawn_closefrom_test (fa: &fa, lowfd, highfd: lowfd, NULL, nextrafds: 0);
235
236 TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
237 }
238
239 /* Close a range and add some file actions. */
240 {
241 posix_spawn_file_actions_t fa;
242 TEST_COMPARE (posix_spawn_file_actions_init (&fa), 0);
243
244 TEST_COMPARE (posix_spawn_file_actions_addclosefrom_np (&fa, lowfd + 1), 0);
245 TEST_COMPARE (posix_spawn_file_actions_addopen (&fa, lowfd, "/dev/null",
246 0666, O_RDONLY), 0);
247 TEST_COMPARE (posix_spawn_file_actions_adddup2 (&fa, lowfd, lowfd + 1), 0);
248 TEST_COMPARE (posix_spawn_file_actions_addopen (&fa, lowfd, "/dev/null",
249 0666, O_RDONLY), 0);
250
251 spawn_closefrom_test (fa: &fa, lowfd, highfd: lowfd, extrafds: (int[]){lowfd, lowfd + 1}, nextrafds: 2);
252
253 TEST_COMPARE (posix_spawn_file_actions_destroy (&fa), 0);
254 }
255}
256
257static int
258do_test (int argc, char *argv[])
259{
260 /* We must have either:
261
262 - one or four parameters if called initially:
263 + argv[1]: path for ld.so optional
264 + argv[2]: "--library-path" optional
265 + argv[3]: the library path optional
266 + argv[4]: the application name
267
268 - six parameters left if called through re-execution:
269 + argv[1]: the application name
270 + argv[2]: the lowest file descriptor expected
271 + argv[3]: first expected open file descriptor optional
272 + argv[n]: last expected open file descriptor optional
273
274 * When built with --enable-hardcoded-path-in-tests or issued without
275 using the loader directly. */
276
277 if (restart)
278 /* Ignore the application name. */
279 handle_restart (argc: argc - 1, argv: &argv[1]);
280
281 TEST_VERIFY_EXIT (argc == 2 || argc == 5);
282
283 int i;
284
285 for (i = 0; i < argc - 1; i++)
286 initial_argv[i] = argv[i + 1];
287 initial_argv[i++] = (char *) "--direct";
288 initial_argv[i++] = (char *) "--restart";
289
290 initial_argv_count = i;
291
292 do_test_closefrom ();
293
294 return 0;
295}
296
297#define TEST_FUNCTION_ARGV do_test
298#include <support/test-driver.c>
299

source code of glibc/posix/tst-spawn5.c