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'. */ |
39 | static 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. */ |
43 | static char *initial_argv[7]; |
44 | static int initial_argv_count; |
45 | |
46 | #define CMDLINE_OPTIONS \ |
47 | { "restart", no_argument, &restart, 1 }, |
48 | |
49 | #define NFDS 100 |
50 | |
51 | static int |
52 | parse_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 |
64 | handle_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 | |
132 | static void |
133 | spawn_closefrom_test (posix_spawn_file_actions_t *fa, int lowfd, int highfd, |
134 | int *, size_t ) |
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 | |
174 | static void |
175 | do_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 | |
257 | static int |
258 | do_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 | |