1 | /* spawn a new process running an executable. Hurd version. |
2 | Copyright (C) 2001-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 License as |
7 | published by the Free Software Foundation; either version 2.1 of the |
8 | 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; see the file COPYING.LIB. If |
17 | not, see <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <errno.h> |
20 | #include <fcntl.h> |
21 | #include <paths.h> |
22 | #include <spawn.h> |
23 | #include <stdlib.h> |
24 | #include <string.h> |
25 | #include <stdio.h> |
26 | #include <unistd.h> |
27 | #include <hurd.h> |
28 | #include <hurd/signal.h> |
29 | #include <hurd/fd.h> |
30 | #include <hurd/id.h> |
31 | #include <hurd/lookup.h> |
32 | #include <hurd/resource.h> |
33 | #include <assert.h> |
34 | #include <argz.h> |
35 | #include "spawn_int.h" |
36 | |
37 | /* Spawn a new process executing PATH with the attributes describes in *ATTRP. |
38 | Before running the process perform the actions described in FILE-ACTIONS. */ |
39 | int |
40 | __spawni (pid_t *pid, const char *file, |
41 | const posix_spawn_file_actions_t *file_actions, |
42 | const posix_spawnattr_t *attrp, |
43 | char *const argv[], char *const envp[], |
44 | int xflags) |
45 | { |
46 | pid_t new_pid; |
47 | char *path, *p, *name; |
48 | char *concat_name = NULL; |
49 | const char *relpath, *abspath; |
50 | int res; |
51 | size_t len; |
52 | size_t pathlen; |
53 | short int flags; |
54 | |
55 | /* The generic POSIX.1 implementation of posix_spawn uses fork and exec. |
56 | In traditional POSIX systems (Unix, Linux, etc), the only way to |
57 | create a new process is by fork, which also copies all the things from |
58 | the parent process that will be immediately wiped and replaced by the |
59 | exec. |
60 | |
61 | This Hurd implementation works by doing an exec on a fresh task, |
62 | without ever doing all the work of fork. The only work done by fork |
63 | that remains visible after an exec is registration with the proc |
64 | server, and the inheritance of various values and ports. All those |
65 | inherited values and ports are what get collected up and passed in the |
66 | file_exec_paths RPC by an exec call. So we do the proc server |
67 | registration here, following the model of fork (see fork.c). We then |
68 | collect up the inherited values and ports from this (parent) process |
69 | following the model of exec (see hurd/hurdexec.c), modify or replace each |
70 | value that fork would (plus the specific changes demanded by ATTRP and |
71 | FILE_ACTIONS), and make the file_exec_paths RPC on the requested |
72 | executable file with the child process's task port rather than our own. |
73 | This should be indistinguishable from the fork + exec implementation, |
74 | except that all errors will be detected here (in the parent process) |
75 | and return proper errno codes rather than the child dying with 127. |
76 | |
77 | XXX The one exception to this supposed indistinguishableness is that |
78 | when posix_spawn_file_actions_addopen has been used, the parent |
79 | process can do various filesystem RPCs on the child's behalf, rather |
80 | than the child process doing it. If these block due to a broken or |
81 | malicious filesystem server or just a blocked network fs or a serial |
82 | port waiting for carrier detect (!!), the parent's posix_spawn call |
83 | can block arbitrarily rather than just the child blocking. Possible |
84 | solutions include: |
85 | * punt to plain fork + exec implementation if addopen was used |
86 | ** easy to do |
87 | ** gives up all benefits of this implementation in that case |
88 | * if addopen was used, don't do any file actions at all here; |
89 | instead, exec an installed helper program e.g.: |
90 | /libexec/spawn-helper close 3 dup2 1 2 open 0 /file 0x123 0666 exec /bin/foo foo a1 a2 |
91 | ** extra exec might be more or less overhead than fork |
92 | * could do some weird half-fork thing where the child would inherit |
93 | our vm and run some code here, but not do the full work of fork |
94 | |
95 | XXX Actually, the parent opens the executable file on behalf of |
96 | the child, and that has all the same issues. |
97 | |
98 | I am favoring the half-fork solution. That is, we do task_create with |
99 | vm inheritance, and we setjmp/longjmp the child like fork does. But |
100 | rather than all the fork hair, the parent just packs up init/dtable |
101 | ports and does a single IPC to a receive right inserted in the child. */ |
102 | |
103 | error_t err; |
104 | task_t task; |
105 | file_t execfile; |
106 | process_t proc; |
107 | auth_t auth; |
108 | int ints[INIT_INT_MAX]; |
109 | file_t *dtable; |
110 | unsigned int dtablesize, orig_dtablesize, i; |
111 | struct hurd_port **dtable_cells; |
112 | char *dtable_cloexec; |
113 | struct hurd_userlink *ulink_dtable = NULL; |
114 | struct hurd_sigstate *ss; |
115 | |
116 | /* Child current working dir */ |
117 | file_t ccwdir = MACH_PORT_NULL; |
118 | |
119 | /* For POSIX_SPAWN_RESETIDS, this reauthenticates our root/current |
120 | directory ports with the new AUTH port. */ |
121 | file_t rcrdir = MACH_PORT_NULL, rcwdir = MACH_PORT_NULL; |
122 | error_t reauthenticate (int which, file_t *result) |
123 | { |
124 | error_t err; |
125 | mach_port_t ref; |
126 | if (*result != MACH_PORT_NULL) |
127 | return 0; |
128 | ref = __mach_reply_port (); |
129 | if (which == INIT_PORT_CWDIR && ccwdir != MACH_PORT_NULL) |
130 | { |
131 | err = __io_reauthenticate (ccwdir, ref, MACH_MSG_TYPE_MAKE_SEND); |
132 | if (!err) |
133 | err = __auth_user_authenticate (auth, |
134 | ref, MACH_MSG_TYPE_MAKE_SEND, |
135 | result); |
136 | } |
137 | else |
138 | err = HURD_PORT_USE |
139 | (&_hurd_ports[which], |
140 | ({ |
141 | err = __io_reauthenticate (port, ref, MACH_MSG_TYPE_MAKE_SEND); |
142 | if (!err) |
143 | err = __auth_user_authenticate (auth, |
144 | ref, MACH_MSG_TYPE_MAKE_SEND, |
145 | result); |
146 | err; |
147 | })); |
148 | __mach_port_destroy (__mach_task_self (), ref); |
149 | return err; |
150 | } |
151 | |
152 | /* Reauthenticate one of our file descriptors for the child. A null |
153 | element of DTABLE_CELLS indicates a descriptor that was already |
154 | reauthenticated, or was newly opened on behalf of the child. */ |
155 | error_t reauthenticate_fd (int fd) |
156 | { |
157 | if (dtable_cells[fd] != NULL && dtable[fd] != MACH_PORT_NULL) |
158 | { |
159 | file_t newfile; |
160 | mach_port_t ref = __mach_reply_port (); |
161 | error_t err = __io_reauthenticate (dtable[fd], |
162 | ref, MACH_MSG_TYPE_MAKE_SEND); |
163 | if (!err) |
164 | err = __auth_user_authenticate (auth, |
165 | ref, MACH_MSG_TYPE_MAKE_SEND, |
166 | &newfile); |
167 | __mach_port_destroy (__mach_task_self (), ref); |
168 | if (err) |
169 | return err; |
170 | _hurd_port_free (dtable_cells[fd], &ulink_dtable[fd], dtable[fd]); |
171 | dtable_cells[fd] = NULL; |
172 | dtable[fd] = newfile; |
173 | } |
174 | return 0; |
175 | } |
176 | |
177 | /* These callbacks are for looking up file names on behalf of the child. */ |
178 | error_t child_init_port (int which, error_t (*operate) (mach_port_t)) |
179 | { |
180 | if (flags & POSIX_SPAWN_RESETIDS) |
181 | switch (which) |
182 | { |
183 | case INIT_PORT_AUTH: |
184 | return (*operate) (auth); |
185 | case INIT_PORT_CRDIR: |
186 | return (reauthenticate (INIT_PORT_CRDIR, &rcrdir) |
187 | ?: (*operate) (rcrdir)); |
188 | case INIT_PORT_CWDIR: |
189 | return (reauthenticate (INIT_PORT_CWDIR, &rcwdir) |
190 | ?: (*operate) (rcwdir)); |
191 | } |
192 | else |
193 | switch (which) |
194 | { |
195 | case INIT_PORT_CWDIR: |
196 | if (ccwdir != MACH_PORT_NULL) |
197 | return (*operate) (ccwdir); |
198 | break; |
199 | } |
200 | assert (which != INIT_PORT_PROC); |
201 | return _hurd_ports_use (which, operate); |
202 | } |
203 | file_t child_fd (int fd) |
204 | { |
205 | if ((unsigned int) fd < dtablesize && dtable[fd] != MACH_PORT_NULL) |
206 | { |
207 | if (flags & POSIX_SPAWN_RESETIDS) |
208 | { |
209 | /* Reauthenticate this descriptor right now, |
210 | since it is going to be used on behalf of the child. */ |
211 | errno = reauthenticate_fd (fd); |
212 | if (errno) |
213 | return MACH_PORT_NULL; |
214 | } |
215 | __mach_port_mod_refs (__mach_task_self (), dtable[fd], |
216 | MACH_PORT_RIGHT_SEND, +1); |
217 | return dtable[fd]; |
218 | } |
219 | return __hurd_fail (EBADF), MACH_PORT_NULL; |
220 | } |
221 | inline error_t child_lookup (const char *file, int oflag, mode_t mode, |
222 | file_t *result) |
223 | { |
224 | return __hurd_file_name_lookup (&child_init_port, &child_fd, 0, |
225 | file, oflag, mode, result); |
226 | } |
227 | auto error_t child_chdir (const char *name) |
228 | { |
229 | file_t new_ccwdir; |
230 | |
231 | /* Append trailing "/." to directory name to force ENOTDIR if |
232 | it's not a directory and EACCES if we don't have search |
233 | permission. */ |
234 | len = strlen (name); |
235 | const char *lookup = name; |
236 | if (len >= 2 && name[len - 2] == '/' && name[len - 1] == '.') |
237 | lookup = name; |
238 | else if (len == 0) |
239 | /* Special-case empty file name according to POSIX. */ |
240 | return __hurd_fail (ENOENT); |
241 | else |
242 | { |
243 | char *n = alloca (len + 3); |
244 | memcpy (n, name, len); |
245 | n[len] = '/'; |
246 | n[len + 1] = '.'; |
247 | n[len + 2] = '\0'; |
248 | lookup = n; |
249 | } |
250 | |
251 | error_t err = child_lookup (lookup, 0, 0, &new_ccwdir); |
252 | if (!err) |
253 | { |
254 | if (ccwdir != MACH_PORT_NULL) |
255 | __mach_port_deallocate (__mach_task_self (), ccwdir); |
256 | ccwdir = new_ccwdir; |
257 | } |
258 | |
259 | return err; |
260 | } |
261 | inline error_t child_lookup_under (file_t startdir, const char *file, |
262 | int oflag, mode_t mode, file_t *result) |
263 | { |
264 | error_t use_init_port (int which, error_t (*operate) (mach_port_t)) |
265 | { |
266 | return (which == INIT_PORT_CWDIR ? (*operate) (startdir) |
267 | : child_init_port (which, operate)); |
268 | } |
269 | |
270 | return __hurd_file_name_lookup (&use_init_port, &child_fd, 0, |
271 | file, oflag, mode, result); |
272 | } |
273 | auto error_t child_fchdir (int fd) |
274 | { |
275 | file_t new_ccwdir; |
276 | error_t err; |
277 | |
278 | if ((unsigned int)fd >= dtablesize |
279 | || dtable[fd] == MACH_PORT_NULL) |
280 | return EBADF; |
281 | |
282 | /* We look up "." to force ENOTDIR if it's not a directory and EACCES if |
283 | we don't have search permission. */ |
284 | if (dtable_cells[fd] != NULL) |
285 | err = HURD_PORT_USE (dtable_cells[fd], |
286 | ({ |
287 | child_lookup_under (port, "." , O_NOTRANS, 0, &new_ccwdir); |
288 | })); |
289 | else |
290 | err = child_lookup_under (dtable[fd], "." , O_NOTRANS, 0, &new_ccwdir); |
291 | |
292 | if (!err) |
293 | { |
294 | if (ccwdir != MACH_PORT_NULL) |
295 | __mach_port_deallocate (__mach_task_self (), ccwdir); |
296 | ccwdir = new_ccwdir; |
297 | } |
298 | |
299 | return err; |
300 | } |
301 | |
302 | |
303 | /* Do this once. */ |
304 | flags = attrp == NULL ? 0 : attrp->__flags; |
305 | |
306 | /* Generate the new process. We create a task that does not inherit our |
307 | memory, and then register it as our child like fork does. See fork.c |
308 | for comments about the sequencing of these proc operations. */ |
309 | |
310 | err = __task_create (__mach_task_self (), |
311 | #ifdef KERN_INVALID_LEDGER |
312 | NULL, 0, /* OSF Mach */ |
313 | #endif |
314 | 0, &task); |
315 | if (err) |
316 | return __hurd_fail (err); |
317 | // From here down we must deallocate TASK and PROC before returning. |
318 | proc = MACH_PORT_NULL; |
319 | auth = MACH_PORT_NULL; |
320 | err = __USEPORT (PROC, __proc_task2pid (port, task, &new_pid)); |
321 | if (!err) |
322 | err = __USEPORT (PROC, __proc_task2proc (port, task, &proc)); |
323 | if (!err) |
324 | err = __USEPORT (PROC, __proc_child (port, task)); |
325 | if (err) |
326 | goto out; |
327 | |
328 | /* Load up the ints to give the new program. */ |
329 | memset (ints, 0, sizeof ints); |
330 | ints[INIT_UMASK] = _hurd_umask; |
331 | ints[INIT_TRACEMASK] = _hurdsig_traced; |
332 | |
333 | ss = _hurd_self_sigstate (); |
334 | |
335 | retry: |
336 | assert (! __spin_lock_locked (&ss->critical_section_lock)); |
337 | __spin_lock (&ss->critical_section_lock); |
338 | |
339 | _hurd_sigstate_lock (ss); |
340 | ints[INIT_SIGMASK] = ss->blocked; |
341 | ints[INIT_SIGPENDING] = 0; |
342 | ints[INIT_SIGIGN] = 0; |
343 | /* Unless we were asked to reset all handlers to SIG_DFL, |
344 | pass down the set of signals that were set to SIG_IGN. */ |
345 | { |
346 | struct sigaction *actions = _hurd_sigstate_actions (ss); |
347 | if ((flags & POSIX_SPAWN_SETSIGDEF) == 0) |
348 | for (i = 1; i < NSIG; ++i) |
349 | if (actions[i].sa_handler == SIG_IGN) |
350 | ints[INIT_SIGIGN] |= __sigmask (i); |
351 | } |
352 | |
353 | /* We hold the critical section lock until the exec has failed so that no |
354 | signal can arrive between when we pack the blocked and ignored signals, |
355 | and when the exec actually happens. A signal handler could change what |
356 | signals are blocked and ignored. Either the change will be reflected |
357 | in the exec, or the signal will never be delivered. Setting the |
358 | critical section flag avoids anything we call trying to acquire the |
359 | sigstate lock. */ |
360 | |
361 | _hurd_sigstate_unlock (ss); |
362 | |
363 | /* Set signal mask. */ |
364 | if ((flags & POSIX_SPAWN_SETSIGMASK) != 0) |
365 | ints[INIT_SIGMASK] = attrp->__ss; |
366 | |
367 | #ifdef _POSIX_PRIORITY_SCHEDULING |
368 | /* Set the scheduling algorithm and parameters. */ |
369 | # error implement me |
370 | if ((flags & (POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER)) |
371 | == POSIX_SPAWN_SETSCHEDPARAM) |
372 | { |
373 | if (__sched_setparam (0, &attrp->__sp) == -1) |
374 | _exit (SPAWN_ERROR); |
375 | } |
376 | else if ((flags & POSIX_SPAWN_SETSCHEDULER) != 0) |
377 | { |
378 | if (__sched_setscheduler (0, attrp->__policy, |
379 | (flags & POSIX_SPAWN_SETSCHEDPARAM) != 0 |
380 | ? &attrp->__sp : NULL) == -1) |
381 | _exit (SPAWN_ERROR); |
382 | } |
383 | #endif |
384 | |
385 | if (!err && (flags & POSIX_SPAWN_SETSID) != 0) |
386 | err = __proc_setsid (proc); |
387 | |
388 | /* Set the process group ID. */ |
389 | if (!err && (flags & POSIX_SPAWN_SETPGROUP) != 0) |
390 | err = __proc_setpgrp (proc, new_pid, attrp->__pgrp); |
391 | |
392 | /* Set the effective user and group IDs. */ |
393 | if (!err && (flags & POSIX_SPAWN_RESETIDS) != 0) |
394 | { |
395 | /* We need a different auth port for the child. */ |
396 | |
397 | __mutex_lock (&_hurd_id.lock); |
398 | err = _hurd_check_ids (); /* Get _hurd_id up to date. */ |
399 | if (!err && _hurd_id.rid_auth == MACH_PORT_NULL) |
400 | { |
401 | /* Set up _hurd_id.rid_auth. This is a special auth server port |
402 | which uses the real uid and gid (the first aux uid and gid) as |
403 | the only effective uid and gid. */ |
404 | |
405 | if (_hurd_id.aux.nuids < 1 || _hurd_id.aux.ngids < 1) |
406 | /* We do not have a real UID and GID. Lose, lose, lose! */ |
407 | err = EGRATUITOUS; |
408 | |
409 | /* Create a new auth port using our real UID and GID (the first |
410 | auxiliary UID and GID) as the only effective IDs. */ |
411 | if (!err) |
412 | err = __USEPORT (AUTH, |
413 | __auth_makeauth (port, |
414 | NULL, MACH_MSG_TYPE_COPY_SEND, 0, |
415 | _hurd_id.aux.uids, 1, |
416 | _hurd_id.aux.uids, |
417 | _hurd_id.aux.nuids, |
418 | _hurd_id.aux.gids, 1, |
419 | _hurd_id.aux.gids, |
420 | _hurd_id.aux.ngids, |
421 | &_hurd_id.rid_auth)); |
422 | } |
423 | if (!err) |
424 | { |
425 | /* Use the real-ID auth port in place of the normal one. */ |
426 | assert (_hurd_id.rid_auth != MACH_PORT_NULL); |
427 | auth = _hurd_id.rid_auth; |
428 | __mach_port_mod_refs (__mach_task_self (), auth, |
429 | MACH_PORT_RIGHT_SEND, +1); |
430 | } |
431 | __mutex_unlock (&_hurd_id.lock); |
432 | } |
433 | else |
434 | /* Copy our existing auth port. */ |
435 | err = __USEPORT (AUTH, __mach_port_mod_refs (__mach_task_self (), |
436 | (auth = port), |
437 | MACH_PORT_RIGHT_SEND, +1)); |
438 | |
439 | if (err) |
440 | { |
441 | _hurd_critical_section_unlock (ss); |
442 | |
443 | if (err == EINTR) |
444 | { |
445 | /* Got a signal while inside an RPC of the critical section, retry again */ |
446 | __mach_port_deallocate (__mach_task_self (), auth); |
447 | auth = MACH_PORT_NULL; |
448 | goto retry; |
449 | } |
450 | |
451 | goto out; |
452 | } |
453 | |
454 | /* Pack up the descriptor table to give the new program. |
455 | These descriptors will need to be reauthenticated below |
456 | if POSIX_SPAWN_RESETIDS is set. */ |
457 | __mutex_lock (&_hurd_dtable_lock); |
458 | dtablesize = _hurd_dtablesize; |
459 | orig_dtablesize = _hurd_dtablesize; |
460 | dtable = __alloca (dtablesize * sizeof (dtable[0])); |
461 | ulink_dtable = __alloca (dtablesize * sizeof (ulink_dtable[0])); |
462 | dtable_cells = __alloca (dtablesize * sizeof (dtable_cells[0])); |
463 | dtable_cloexec = __alloca (orig_dtablesize); |
464 | for (i = 0; i < dtablesize; ++i) |
465 | { |
466 | struct hurd_fd *const d = _hurd_dtable[i]; |
467 | if (d == NULL) |
468 | { |
469 | dtable[i] = MACH_PORT_NULL; |
470 | dtable_cells[i] = NULL; |
471 | continue; |
472 | } |
473 | /* Note that this might return MACH_PORT_NULL. */ |
474 | dtable[i] = _hurd_port_get (&d->port, &ulink_dtable[i]); |
475 | dtable_cells[i] = &d->port; |
476 | dtable_cloexec[i] = (d->flags & FD_CLOEXEC) != 0; |
477 | } |
478 | __mutex_unlock (&_hurd_dtable_lock); |
479 | |
480 | /* Safe to let signals happen now. */ |
481 | _hurd_critical_section_unlock (ss); |
482 | |
483 | /* Execute the file actions. */ |
484 | if (file_actions != NULL) |
485 | for (i = 0; i < file_actions->__used; ++i) |
486 | { |
487 | /* Close a file descriptor in the child. */ |
488 | error_t do_close (int fd) |
489 | { |
490 | if ((unsigned int)fd < dtablesize |
491 | && dtable[fd] != MACH_PORT_NULL) |
492 | { |
493 | if (dtable_cells[fd] == NULL) |
494 | __mach_port_deallocate (__mach_task_self (), dtable[fd]); |
495 | else |
496 | { |
497 | _hurd_port_free (dtable_cells[fd], |
498 | &ulink_dtable[fd], dtable[fd]); |
499 | } |
500 | dtable_cells[fd] = NULL; |
501 | dtable[fd] = MACH_PORT_NULL; |
502 | return 0; |
503 | } |
504 | return EBADF; |
505 | } |
506 | |
507 | /* Close file descriptors in the child. */ |
508 | error_t do_closefrom (int lowfd) |
509 | { |
510 | while ((unsigned int) lowfd < dtablesize) |
511 | { |
512 | error_t err = do_close (lowfd); |
513 | if (err != 0 && err != EBADF) |
514 | return err; |
515 | lowfd++; |
516 | } |
517 | return 0; |
518 | } |
519 | |
520 | /* Make sure the dtable can hold NEWFD. */ |
521 | #define EXPAND_DTABLE(newfd) \ |
522 | ({ \ |
523 | if ((unsigned int)newfd >= dtablesize \ |
524 | && newfd < _hurd_rlimits[RLIMIT_OFILE].rlim_cur) \ |
525 | { \ |
526 | /* We need to expand the dtable for the child. */ \ |
527 | NEW_TABLE (dtable, newfd); \ |
528 | NEW_ULINK_TABLE (ulink_dtable, newfd); \ |
529 | NEW_TABLE (dtable_cells, newfd); \ |
530 | dtablesize = newfd + 1; \ |
531 | } \ |
532 | ((unsigned int)newfd < dtablesize ? 0 : EMFILE); \ |
533 | }) |
534 | #define NEW_TABLE(x, newfd) \ |
535 | do { __typeof (x) new_##x = __alloca ((newfd + 1) * sizeof (x[0])); \ |
536 | memcpy (new_##x, x, dtablesize * sizeof (x[0])); \ |
537 | memset (&new_##x[dtablesize], 0, (newfd + 1 - dtablesize) * sizeof (x[0])); \ |
538 | x = new_##x; } while (0) |
539 | #define NEW_ULINK_TABLE(x, newfd) \ |
540 | do { __typeof (x) new_##x = __alloca ((newfd + 1) * sizeof (x[0])); \ |
541 | unsigned i; \ |
542 | for (i = 0; i < dtablesize; i++) \ |
543 | if (dtable_cells[i] != NULL) \ |
544 | _hurd_port_move (dtable_cells[i], &new_##x[i], &x[i]); \ |
545 | else \ |
546 | memset (&new_##x[i], 0, sizeof (new_##x[i])); \ |
547 | memset (&new_##x[dtablesize], 0, (newfd + 1 - dtablesize) * sizeof (x[0])); \ |
548 | x = new_##x; } while (0) |
549 | |
550 | struct __spawn_action *action = &file_actions->__actions[i]; |
551 | |
552 | switch (action->tag) |
553 | { |
554 | case spawn_do_close: |
555 | err = do_close (action->action.close_action.fd); |
556 | break; |
557 | |
558 | case spawn_do_dup2: |
559 | if ((unsigned int)action->action.dup2_action.fd < dtablesize |
560 | && dtable[action->action.dup2_action.fd] != MACH_PORT_NULL) |
561 | { |
562 | const int fd = action->action.dup2_action.fd; |
563 | const int newfd = action->action.dup2_action.newfd; |
564 | // dup2 always clears any old FD_CLOEXEC flag on the new fd. |
565 | if (newfd < orig_dtablesize) |
566 | dtable_cloexec[newfd] = 0; |
567 | if (fd == newfd) |
568 | // Same is same as same was. |
569 | break; |
570 | err = EXPAND_DTABLE (newfd); |
571 | if (!err) |
572 | { |
573 | /* Close the old NEWFD and replace it with FD's |
574 | contents, which can be either an original |
575 | descriptor (DTABLE_CELLS[FD] != 0) or a new |
576 | right that we acquired in this function. */ |
577 | do_close (newfd); |
578 | dtable_cells[newfd] = dtable_cells[fd]; |
579 | if (dtable_cells[newfd] != NULL) |
580 | dtable[newfd] = _hurd_port_get (dtable_cells[newfd], |
581 | &ulink_dtable[newfd]); |
582 | else |
583 | { |
584 | dtable[newfd] = dtable[fd]; |
585 | err = __mach_port_mod_refs (__mach_task_self (), |
586 | dtable[fd], |
587 | MACH_PORT_RIGHT_SEND, +1); |
588 | } |
589 | } |
590 | } |
591 | else |
592 | // The old FD specified was bogus. |
593 | err = EBADF; |
594 | break; |
595 | |
596 | case spawn_do_open: |
597 | /* Open a file on behalf of the child. |
598 | |
599 | XXX note that this can subject the parent to arbitrary |
600 | delays waiting for the files to open. I don't know what the |
601 | spec says about this. If it's not permissible, then this |
602 | whole forkless implementation is probably untenable. */ |
603 | { |
604 | const int fd = action->action.open_action.fd; |
605 | |
606 | do_close (fd); |
607 | if (fd < orig_dtablesize) |
608 | dtable_cloexec[fd] = 0; |
609 | err = EXPAND_DTABLE (fd); |
610 | if (err) |
611 | break; |
612 | |
613 | err = child_lookup (action->action.open_action.path, |
614 | action->action.open_action.oflag, |
615 | action->action.open_action.mode, |
616 | &dtable[fd]); |
617 | dtable_cells[fd] = NULL; |
618 | break; |
619 | } |
620 | |
621 | case spawn_do_chdir: |
622 | err = child_chdir (action->action.chdir_action.path); |
623 | break; |
624 | |
625 | case spawn_do_fchdir: |
626 | err = child_fchdir (action->action.fchdir_action.fd); |
627 | break; |
628 | |
629 | case spawn_do_closefrom: |
630 | err = do_closefrom (action->action.closefrom_action.from); |
631 | break; |
632 | |
633 | case spawn_do_tcsetpgrp: |
634 | { |
635 | pid_t pgrp; |
636 | /* Check if it is possible to avoid an extra syscall. */ |
637 | if ((attrp->__flags & POSIX_SPAWN_SETPGROUP) |
638 | != 0 && attrp->__pgrp != 0) |
639 | pgrp = attrp->__pgrp; |
640 | else |
641 | err = __proc_getpgrp (proc, new_pid, &pgrp); |
642 | if (!err) |
643 | err = __tcsetpgrp (action->action.setpgrp_action.fd, pgrp); |
644 | } |
645 | } |
646 | |
647 | if (err) |
648 | goto out; |
649 | } |
650 | |
651 | /* Only now can we perform FD_CLOEXEC. We had to leave the descriptors |
652 | unmolested for the file actions to use. Note that the DTABLE_CLOEXEC |
653 | array is never expanded by file actions, so it might now have fewer |
654 | than DTABLESIZE elements. */ |
655 | for (i = 0; i < orig_dtablesize; ++i) |
656 | if (dtable[i] != MACH_PORT_NULL && dtable_cloexec[i]) |
657 | { |
658 | assert (dtable_cells[i] != NULL); |
659 | _hurd_port_free (dtable_cells[i], &ulink_dtable[i], dtable[i]); |
660 | dtable[i] = MACH_PORT_NULL; |
661 | } |
662 | |
663 | /* Prune trailing null ports from the descriptor table. */ |
664 | while (dtablesize > 0 && dtable[dtablesize - 1] == MACH_PORT_NULL) |
665 | --dtablesize; |
666 | |
667 | if (flags & POSIX_SPAWN_RESETIDS) |
668 | { |
669 | /* Reauthenticate all the child's ports with its new auth handle. */ |
670 | |
671 | mach_port_t ref; |
672 | process_t newproc; |
673 | |
674 | /* Reauthenticate with the proc server. */ |
675 | ref = __mach_reply_port (); |
676 | err = __proc_reauthenticate (proc, ref, MACH_MSG_TYPE_MAKE_SEND); |
677 | if (!err) |
678 | err = __auth_user_authenticate (auth, |
679 | ref, MACH_MSG_TYPE_MAKE_SEND, |
680 | &newproc); |
681 | __mach_port_destroy (__mach_task_self (), ref); |
682 | if (!err) |
683 | { |
684 | __mach_port_deallocate (__mach_task_self (), proc); |
685 | proc = newproc; |
686 | } |
687 | |
688 | if (!err) |
689 | err = reauthenticate (INIT_PORT_CRDIR, &rcrdir); |
690 | if (!err) |
691 | err = reauthenticate (INIT_PORT_CWDIR, &rcwdir); |
692 | |
693 | /* We must reauthenticate all the fds except those that came from |
694 | `spawn_do_open' file actions, which were opened using the child's |
695 | auth port to begin with. */ |
696 | for (i = 0; !err && i < dtablesize; ++i) |
697 | err = reauthenticate_fd (i); |
698 | } |
699 | if (err) |
700 | goto out; |
701 | |
702 | /* Now we are ready to open the executable file using the child's ports. |
703 | We do this after performing all the file actions so the order of |
704 | events is the same as for a fork, exec sequence. This affects things |
705 | like the meaning of a /dev/fd file name, as well as which error |
706 | conditions are diagnosed first and what side effects (file creation, |
707 | etc) can be observed before what errors. */ |
708 | |
709 | if ((xflags & SPAWN_XFLAGS_USE_PATH) == 0 || strchr (file, '/') != NULL) |
710 | /* The FILE parameter is actually a path. */ |
711 | err = child_lookup (relpath = file, O_EXEC, 0, &execfile); |
712 | else |
713 | { |
714 | /* We have to search for FILE on the path. */ |
715 | path = getenv ("PATH" ); |
716 | if (path == NULL) |
717 | { |
718 | /* There is no `PATH' in the environment. |
719 | The default search path is the current directory |
720 | followed by the path `confstr' returns for `_CS_PATH'. */ |
721 | len = __confstr (_CS_PATH, (char *) NULL, 0); |
722 | path = (char *) __alloca (1 + len); |
723 | path[0] = ':'; |
724 | (void) __confstr (_CS_PATH, path + 1, len); |
725 | } |
726 | |
727 | len = strlen (file) + 1; |
728 | pathlen = strlen (path); |
729 | name = __alloca (pathlen + len + 1); |
730 | /* Copy the file name at the top. */ |
731 | name = (char *) memcpy (name + pathlen + 1, file, len); |
732 | /* And add the slash. */ |
733 | *--name = '/'; |
734 | |
735 | p = path; |
736 | do |
737 | { |
738 | char *startp; |
739 | |
740 | path = p; |
741 | p = __strchrnul (path, ':'); |
742 | |
743 | if (p == path) |
744 | /* Two adjacent colons, or a colon at the beginning or the end |
745 | of `PATH' means to search the current directory. */ |
746 | startp = name + 1; |
747 | else |
748 | startp = (char *) memcpy (name - (p - path), path, p - path); |
749 | |
750 | /* Try to open this file name. */ |
751 | err = child_lookup (startp, O_EXEC, 0, &execfile); |
752 | switch (err) |
753 | { |
754 | case EACCES: |
755 | case ENOENT: |
756 | case ESTALE: |
757 | case ENOTDIR: |
758 | /* Those errors indicate the file is missing or not executable |
759 | by us, in which case we want to just try the next path |
760 | directory. */ |
761 | continue; |
762 | |
763 | case 0: /* Success! */ |
764 | default: |
765 | /* Some other error means we found an executable file, but |
766 | something went wrong executing it; return the error to our |
767 | caller. */ |
768 | break; |
769 | } |
770 | |
771 | // We only get here when we are done looking for the file. |
772 | relpath = startp; |
773 | break; |
774 | } |
775 | while (*p++ != '\0'); |
776 | } |
777 | if (err) |
778 | goto out; |
779 | |
780 | if (relpath[0] == '/') |
781 | { |
782 | /* Already an absolute path */ |
783 | abspath = relpath; |
784 | } |
785 | else |
786 | { |
787 | /* Relative path */ |
788 | char *cwd = __getcwd (NULL, 0); |
789 | if (cwd == NULL) |
790 | { |
791 | err = errno; |
792 | goto out; |
793 | } |
794 | |
795 | res = __asprintf (&concat_name, "%s/%s" , cwd, relpath); |
796 | free (ptr: cwd); |
797 | if (res == -1) |
798 | { |
799 | err = errno; |
800 | goto out; |
801 | } |
802 | |
803 | abspath = concat_name; |
804 | } |
805 | |
806 | /* Almost there! */ |
807 | { |
808 | mach_port_t ports[_hurd_nports]; |
809 | struct hurd_userlink ulink_ports[_hurd_nports]; |
810 | char *args = NULL, *env = NULL; |
811 | size_t argslen = 0, envlen = 0; |
812 | |
813 | inline error_t exec (file_t file) |
814 | { |
815 | sigset_t old, new; |
816 | |
817 | /* Avoid getting interrupted while exec(), notably not after the exec |
818 | server has committed to the exec and started thrashing the task. |
819 | |
820 | Various issues otherwise show up when building e.g. ghc. |
821 | |
822 | TODO Rather add proper interrupt support to the exec server, that |
823 | avoids interrupts in that period. */ |
824 | __sigfillset(&new); |
825 | __sigprocmask (SIG_SETMASK, &new, &old); |
826 | |
827 | error_t err = __file_exec_paths |
828 | (file, task, |
829 | __sigismember (&_hurdsig_traced, SIGKILL) ? EXEC_SIGTRAP : 0, |
830 | relpath, abspath, args, argslen, env, envlen, |
831 | dtable, MACH_MSG_TYPE_COPY_SEND, dtablesize, |
832 | ports, MACH_MSG_TYPE_COPY_SEND, _hurd_nports, |
833 | ints, INIT_INT_MAX, |
834 | NULL, 0, NULL, 0); |
835 | |
836 | /* Fallback for backwards compatibility. This can just be removed |
837 | when __file_exec goes away. */ |
838 | if (err == MIG_BAD_ID) |
839 | err = __file_exec (file, task, |
840 | (__sigismember (&_hurdsig_traced, SIGKILL) |
841 | ? EXEC_SIGTRAP : 0), |
842 | args, argslen, env, envlen, |
843 | dtable, MACH_MSG_TYPE_COPY_SEND, dtablesize, |
844 | ports, MACH_MSG_TYPE_COPY_SEND, _hurd_nports, |
845 | ints, INIT_INT_MAX, |
846 | NULL, 0, NULL, 0); |
847 | |
848 | __sigprocmask (SIG_SETMASK, &old, NULL); |
849 | |
850 | return err; |
851 | } |
852 | |
853 | /* Now we are out of things that can fail before the file_exec RPC, |
854 | for which everything else must be prepared. The only thing left |
855 | to do is packing up the argument and environment strings, |
856 | and the array of init ports. */ |
857 | |
858 | if (argv != NULL) |
859 | err = __argz_create (argv: argv, argz: &args, len: &argslen); |
860 | if (!err && envp != NULL) |
861 | err = __argz_create (argv: envp, argz: &env, len: &envlen); |
862 | |
863 | /* Load up the ports to give to the new program. |
864 | Note the loop/switch below must parallel exactly to release refs. */ |
865 | for (i = 0; i < _hurd_nports; ++i) |
866 | { |
867 | switch (i) |
868 | { |
869 | case INIT_PORT_AUTH: |
870 | ports[i] = auth; |
871 | continue; |
872 | case INIT_PORT_PROC: |
873 | ports[i] = proc; |
874 | continue; |
875 | case INIT_PORT_CRDIR: |
876 | if (flags & POSIX_SPAWN_RESETIDS) |
877 | { |
878 | ports[i] = rcrdir; |
879 | continue; |
880 | } |
881 | break; |
882 | case INIT_PORT_CWDIR: |
883 | if (flags & POSIX_SPAWN_RESETIDS) |
884 | { |
885 | ports[i] = rcwdir; |
886 | continue; |
887 | } |
888 | if (ccwdir != MACH_PORT_NULL) |
889 | { |
890 | ports[i] = ccwdir; |
891 | continue; |
892 | } |
893 | break; |
894 | } |
895 | ports[i] = _hurd_port_get (&_hurd_ports[i], &ulink_ports[i]); |
896 | } |
897 | |
898 | /* Finally, try executing the file we opened. */ |
899 | if (!err) |
900 | err = exec (execfile); |
901 | __mach_port_deallocate (__mach_task_self (), execfile); |
902 | |
903 | if ((err == ENOEXEC) && (xflags & SPAWN_XFLAGS_TRY_SHELL) != 0) |
904 | { |
905 | /* The file is accessible but it is not an executable file. |
906 | Invoke the shell to interpret it as a script. */ |
907 | err = 0; |
908 | if (!argslen) |
909 | err = __argz_insert (argz: &args, argz_len: &argslen, before: args, entry: relpath); |
910 | if (!err) |
911 | err = __argz_insert (argz: &args, argz_len: &argslen, before: args, _PATH_BSHELL); |
912 | if (!err) |
913 | err = child_lookup (_PATH_BSHELL, O_EXEC, 0, &execfile); |
914 | if (!err) |
915 | { |
916 | err = exec (execfile); |
917 | __mach_port_deallocate (__mach_task_self (), execfile); |
918 | } |
919 | } |
920 | |
921 | /* Release the references just packed up in PORTS. |
922 | This switch must always parallel the one above that fills PORTS. */ |
923 | for (i = 0; i < _hurd_nports; ++i) |
924 | { |
925 | switch (i) |
926 | { |
927 | case INIT_PORT_AUTH: |
928 | case INIT_PORT_PROC: |
929 | continue; |
930 | case INIT_PORT_CRDIR: |
931 | if (flags & POSIX_SPAWN_RESETIDS) |
932 | continue; |
933 | break; |
934 | case INIT_PORT_CWDIR: |
935 | if (flags & POSIX_SPAWN_RESETIDS) |
936 | continue; |
937 | if (ccwdir != MACH_PORT_NULL) |
938 | continue; |
939 | break; |
940 | } |
941 | _hurd_port_free (&_hurd_ports[i], &ulink_ports[i], ports[i]); |
942 | } |
943 | |
944 | free (ptr: args); |
945 | free (ptr: env); |
946 | } |
947 | |
948 | /* We did it! We have a child! */ |
949 | if (pid != NULL) |
950 | *pid = new_pid; |
951 | |
952 | out: |
953 | /* Clean up all the references we are now holding. */ |
954 | |
955 | if (task != MACH_PORT_NULL) |
956 | { |
957 | if (err) |
958 | /* We failed after creating the task, so kill it. */ |
959 | __task_terminate (task); |
960 | __mach_port_deallocate (__mach_task_self (), task); |
961 | } |
962 | __mach_port_deallocate (__mach_task_self (), auth); |
963 | __mach_port_deallocate (__mach_task_self (), proc); |
964 | if (ccwdir != MACH_PORT_NULL) |
965 | __mach_port_deallocate (__mach_task_self (), ccwdir); |
966 | if (rcrdir != MACH_PORT_NULL) |
967 | __mach_port_deallocate (__mach_task_self (), rcrdir); |
968 | if (rcwdir != MACH_PORT_NULL) |
969 | __mach_port_deallocate (__mach_task_self (), rcwdir); |
970 | |
971 | if (ulink_dtable) |
972 | /* Release references to the file descriptor ports. */ |
973 | for (i = 0; i < dtablesize; ++i) |
974 | if (dtable[i] != MACH_PORT_NULL) |
975 | { |
976 | if (dtable_cells[i] == NULL) |
977 | __mach_port_deallocate (__mach_task_self (), dtable[i]); |
978 | else |
979 | _hurd_port_free (dtable_cells[i], &ulink_dtable[i], dtable[i]); |
980 | } |
981 | |
982 | free (ptr: concat_name); |
983 | |
984 | if (err) |
985 | /* This hack canonicalizes the error code that we return. */ |
986 | err = (__hurd_fail (err), errno); |
987 | |
988 | return err; |
989 | } |
990 | |