1 | /* Tests for posix_spawn signal handling. |
2 | Copyright (C) 2021-2022 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 | |
37 | /* Nonzero if the program gets called via `exec'. */ |
38 | static int restart; |
39 | |
40 | /* Hold the four initial argument used to respawn the process, plus |
41 | the extra '--direct' and '--restart', and a final NULL. */ |
42 | static char *initial_argv[7]; |
43 | static int initial_argv_count; |
44 | |
45 | #define CMDLINE_OPTIONS \ |
46 | { "restart", no_argument, &restart, 1 }, |
47 | |
48 | #define NFDS 100 |
49 | |
50 | static int |
51 | parse_fd (const char *str) |
52 | { |
53 | char *endptr; |
54 | long unsigned int fd = strtoul (str, &endptr, 10); |
55 | if (*endptr != '\0' || fd > INT_MAX) |
56 | FAIL_EXIT1 ("invalid file descriptor value: %s" , str); |
57 | return fd; |
58 | } |
59 | |
60 | /* Called on process re-execution. The arguments are the expected opened |
61 | file descriptors. */ |
62 | _Noreturn static void |
63 | handle_restart (int argc, char *argv[]) |
64 | { |
65 | TEST_VERIFY (argc > 0); |
66 | int lowfd = parse_fd (str: argv[0]); |
67 | |
68 | size_t nfds = argc > 1 ? argc - 1 : 0; |
69 | struct fd_t |
70 | { |
71 | int fd; |
72 | _Bool found; |
73 | } *fds = xmalloc (n: sizeof (struct fd_t) * nfds); |
74 | for (int i = 0; i < nfds; i++) |
75 | { |
76 | fds[i].fd = parse_fd (str: argv[i + 1]); |
77 | fds[i].found = false; |
78 | } |
79 | |
80 | DIR *dirp = opendir (FD_TO_FILENAME_PREFIX); |
81 | if (dirp == NULL) |
82 | FAIL_EXIT1 ("opendir (\"" FD_TO_FILENAME_PREFIX "\"): %m" ); |
83 | |
84 | while (true) |
85 | { |
86 | errno = 0; |
87 | struct dirent64 *e = readdir64 (dirp: dirp); |
88 | if (e == NULL) |
89 | { |
90 | if (errno != 0) |
91 | FAIL_EXIT1 ("readdir: %m" ); |
92 | break; |
93 | } |
94 | |
95 | if (e->d_name[0] == '.') |
96 | continue; |
97 | |
98 | char *endptr; |
99 | long int fd = strtol (e->d_name, &endptr, 10); |
100 | if (*endptr != '\0' || fd < 0 || fd > INT_MAX) |
101 | FAIL_EXIT1 ("readdir: invalid file descriptor name: /proc/self/fd/%s" , |
102 | e->d_name); |
103 | |
104 | /* Ignore the descriptors not in the range of the opened files. */ |
105 | if (fd < lowfd || fd == dirfd (dirp)) |
106 | continue; |
107 | |
108 | bool found = false; |
109 | for (int i = 0; i < nfds; i++) |
110 | if (fds[i].fd == fd) |
111 | fds[i].found = found = true; |
112 | |
113 | if (!found) |
114 | { |
115 | char *path = xasprintf (format: "/proc/self/fd/%s" , e->d_name); |
116 | char *resolved = xreadlink (path); |
117 | FAIL_EXIT1 ("unexpected open file descriptor %ld: %s" , fd, resolved); |
118 | } |
119 | } |
120 | closedir (dirp: dirp); |
121 | |
122 | for (int i = 0; i < nfds; i++) |
123 | if (!fds[i].found) |
124 | FAIL_EXIT1 ("file descriptor %d not opened" , fds[i].fd); |
125 | |
126 | free (ptr: fds); |
127 | |
128 | exit (EXIT_SUCCESS); |
129 | } |
130 | |
131 | static void |
132 | spawn_closefrom_test (posix_spawn_file_actions_t *fa, int lowfd, int highfd, |
133 | int *, size_t ) |
134 | { |
135 | /* 3 or 7 elements from initial_argv: |
136 | + path to ld.so optional |
137 | + --library-path optional |
138 | + the library path optional |
139 | + application name |
140 | + --direct |
141 | + --restart |
142 | + lowest opened file descriptor |
143 | + up to 2 * maximum_fd arguments (the expected open file descriptors), |
144 | plus NULL. */ |
145 | |
146 | int argv_size = initial_argv_count + 2 * NFDS + 1; |
147 | char *args[argv_size]; |
148 | int argc = 0; |
149 | |
150 | for (char **arg = initial_argv; *arg != NULL; arg++) |
151 | args[argc++] = *arg; |
152 | |
153 | args[argc++] = xasprintf (format: "%d" , lowfd); |
154 | |
155 | for (int i = lowfd; i < highfd; i++) |
156 | args[argc++] = xasprintf (format: "%d" , i); |
157 | |
158 | for (int i = 0; i < nextrafds; i++) |
159 | args[argc++] = xasprintf (format: "%d" , extrafds[i]); |
160 | |
161 | args[argc] = NULL; |
162 | TEST_VERIFY (argc < argv_size); |
163 | |
164 | pid_t pid; |
165 | int status; |
166 | |
167 | TEST_COMPARE (posix_spawn (&pid, args[0], fa, NULL, args, environ), 0); |
168 | TEST_COMPARE (xwaitpid (pid, &status, 0), pid); |
169 | TEST_VERIFY (WIFEXITED (status)); |
170 | TEST_VERIFY (!WIFSIGNALED (status)); |
171 | TEST_COMPARE (WEXITSTATUS (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 descritptor 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 | |