Fix a serious gcc warning.
[gnulib.git] / lib / spawni.c
1 /* Guts of POSIX spawn interface.  Generic POSIX.1 version.
2    Copyright (C) 2000-2005, 2006, 2008 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program 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
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 #include <config.h>
19
20 /* Specification.  */
21 #include <spawn.h>
22 #include "spawn_int.h"
23
24 #include <alloca.h>
25 #include <errno.h>
26
27 #include <fcntl.h>
28 #ifndef O_LARGEFILE
29 # define O_LARGEFILE 0
30 #endif
31
32 #if _LIBC || HAVE_PATHS_H
33 # include <paths.h>
34 #else
35 # define _PATH_BSHELL "/bin/sh"
36 #endif
37
38 #include <signal.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 #if _LIBC
44 # include <not-cancel.h>
45 #else
46 # define close_not_cancel close
47 # define open_not_cancel open
48 #endif
49
50 #if _LIBC
51 # include <local-setxid.h>
52 #else
53 # if !HAVE_SETEUID
54 #  define seteuid(id) setresuid (-1, id, -1)
55 # endif
56 # if !HAVE_SETEGID
57 #  define setegid(id) setresgid (-1, id, -1)
58 # endif
59 # define local_seteuid(id) seteuid (id)
60 # define local_setegid(id) setegid (id)
61 #endif
62
63 #if _LIBC
64 # define alloca __alloca
65 # define execve __execve
66 # define dup2 __dup2
67 # define fork __fork
68 # define getgid __getgid
69 # define getuid __getuid
70 # define sched_setparam __sched_setparam
71 # define sched_setscheduler __sched_setscheduler
72 # define setpgid __setpgid
73 # define sigaction __sigaction
74 # define sigismember __sigismember
75 # define sigprocmask __sigprocmask
76 # define strchrnul __strchrnul
77 # define vfork __vfork
78 #else
79 # undef internal_function
80 # define internal_function /* empty */
81 #endif
82
83
84 /* The Unix standard contains a long explanation of the way to signal
85    an error after the fork() was successful.  Since no new wait status
86    was wanted there is no way to signal an error using one of the
87    available methods.  The committee chose to signal an error by a
88    normal program exit with the exit code 127.  */
89 #define SPAWN_ERROR     127
90
91
92 /* The file is accessible but it is not an executable file.  Invoke
93    the shell to interpret it as a script.  */
94 static void
95 internal_function
96 script_execute (const char *file, char *const argv[], char *const envp[])
97 {
98   /* Count the arguments.  */
99   int argc = 0;
100   while (argv[argc++])
101     ;
102
103   /* Construct an argument list for the shell.  */
104   {
105     char **new_argv = (char **) alloca ((argc + 1) * sizeof (char *));
106     new_argv[0] = (char *) _PATH_BSHELL;
107     new_argv[1] = (char *) file;
108     while (argc > 1)
109       {
110         new_argv[argc] = argv[argc - 1];
111         --argc;
112       }
113
114     /* Execute the shell.  */
115     execve (new_argv[0], new_argv, envp);
116   }
117 }
118
119
120 /* Spawn a new process executing PATH with the attributes describes in *ATTRP.
121    Before running the process perform the actions described in FILE-ACTIONS. */
122 int
123 __spawni (pid_t *pid, const char *file,
124           const posix_spawn_file_actions_t *file_actions,
125           const posix_spawnattr_t *attrp, char *const argv[],
126           char *const envp[], int use_path)
127 {
128   pid_t new_pid;
129   char *path, *p, *name;
130   size_t len;
131   size_t pathlen;
132
133   /* Do this once.  */
134   short int flags = attrp == NULL ? 0 : attrp->_flags;
135
136   /* Avoid gcc warning
137        "variable 'flags' might be clobbered by 'longjmp' or 'vfork'"  */
138   (void) &flags;
139
140   /* Generate the new process.  */
141 #if HAVE_VFORK
142   if ((flags & POSIX_SPAWN_USEVFORK) != 0
143       /* If no major work is done, allow using vfork.  Note that we
144          might perform the path searching.  But this would be done by
145          a call to execvp(), too, and such a call must be OK according
146          to POSIX.  */
147       || ((flags & (POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF
148                     | POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER
149                     | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_RESETIDS)) == 0
150           && file_actions == NULL))
151     new_pid = vfork ();
152   else
153 #endif
154     new_pid = fork ();
155
156   if (new_pid != 0)
157     {
158       if (new_pid < 0)
159         return errno;
160
161       /* The call was successful.  Store the PID if necessary.  */
162       if (pid != NULL)
163         *pid = new_pid;
164
165       return 0;
166     }
167
168   /* Set signal mask.  */
169   if ((flags & POSIX_SPAWN_SETSIGMASK) != 0
170       && sigprocmask (SIG_SETMASK, &attrp->_ss, NULL) != 0)
171     _exit (SPAWN_ERROR);
172
173   /* Set signal default action.  */
174   if ((flags & POSIX_SPAWN_SETSIGDEF) != 0)
175     {
176       /* We have to iterate over all signals.  This could possibly be
177          done better but it requires system specific solutions since
178          the sigset_t data type can be very different on different
179          architectures.  */
180       int sig;
181       struct sigaction sa;
182
183       memset (&sa, '\0', sizeof (sa));
184       sa.sa_handler = SIG_DFL;
185
186       for (sig = 1; sig <= NSIG; ++sig)
187         if (sigismember (&attrp->_sd, sig) != 0
188             && sigaction (sig, &sa, NULL) != 0)
189           _exit (SPAWN_ERROR);
190
191     }
192
193 #if (_LIBC ? defined _POSIX_PRIORITY_SCHEDULING : HAVE_SCHED_SETPARAM && HAVE_SCHED_SETSCHEDULER)
194   /* Set the scheduling algorithm and parameters.  */
195   if ((flags & (POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER))
196       == POSIX_SPAWN_SETSCHEDPARAM)
197     {
198       if (sched_setparam (0, &attrp->_sp) == -1)
199         _exit (SPAWN_ERROR);
200     }
201   else if ((flags & POSIX_SPAWN_SETSCHEDULER) != 0)
202     {
203       if (sched_setscheduler (0, attrp->_policy,
204                               (flags & POSIX_SPAWN_SETSCHEDPARAM) != 0
205                               ? &attrp->_sp : NULL) == -1)
206         _exit (SPAWN_ERROR);
207     }
208 #endif
209
210   /* Set the process group ID.  */
211   if ((flags & POSIX_SPAWN_SETPGROUP) != 0
212       && setpgid (0, attrp->_pgrp) != 0)
213     _exit (SPAWN_ERROR);
214
215   /* Set the effective user and group IDs.  */
216   if ((flags & POSIX_SPAWN_RESETIDS) != 0
217       && (local_seteuid (getuid ()) != 0
218           || local_setegid (getgid ()) != 0))
219     _exit (SPAWN_ERROR);
220
221   /* Execute the file actions.  */
222   if (file_actions != NULL)
223     {
224       int cnt;
225
226       for (cnt = 0; cnt < file_actions->_used; ++cnt)
227         {
228           struct __spawn_action *action = &file_actions->_actions[cnt];
229
230           switch (action->tag)
231             {
232             case spawn_do_close:
233               if (close_not_cancel (action->action.close_action.fd) != 0)
234                 /* Signal the error.  */
235                 _exit (SPAWN_ERROR);
236               break;
237
238             case spawn_do_open:
239               {
240                 int new_fd = open_not_cancel (action->action.open_action.path,
241                                               action->action.open_action.oflag
242                                               | O_LARGEFILE,
243                                               action->action.open_action.mode);
244
245                 if (new_fd == -1)
246                   /* The `open' call failed.  */
247                   _exit (SPAWN_ERROR);
248
249                 /* Make sure the desired file descriptor is used.  */
250                 if (new_fd != action->action.open_action.fd)
251                   {
252                     if (dup2 (new_fd, action->action.open_action.fd)
253                         != action->action.open_action.fd)
254                       /* The `dup2' call failed.  */
255                       _exit (SPAWN_ERROR);
256
257                     if (close_not_cancel (new_fd) != 0)
258                       /* The `close' call failed.  */
259                       _exit (SPAWN_ERROR);
260                   }
261               }
262               break;
263
264             case spawn_do_dup2:
265               if (dup2 (action->action.dup2_action.fd,
266                         action->action.dup2_action.newfd)
267                   != action->action.dup2_action.newfd)
268                 /* The `dup2' call failed.  */
269                 _exit (SPAWN_ERROR);
270               break;
271             }
272         }
273     }
274
275   if (! use_path || strchr (file, '/') != NULL)
276     {
277       /* The FILE parameter is actually a path.  */
278       execve (file, argv, envp);
279
280       if (errno == ENOEXEC)
281         script_execute (file, argv, envp);
282
283       /* Oh, oh.  `execve' returns.  This is bad.  */
284       _exit (SPAWN_ERROR);
285     }
286
287   /* We have to search for FILE on the path.  */
288   path = getenv ("PATH");
289   if (path == NULL)
290     {
291 #if HAVE_CONFSTR
292       /* There is no `PATH' in the environment.
293          The default search path is the current directory
294          followed by the path `confstr' returns for `_CS_PATH'.  */
295       len = confstr (_CS_PATH, (char *) NULL, 0);
296       path = (char *) alloca (1 + len);
297       path[0] = ':';
298       (void) confstr (_CS_PATH, path + 1, len);
299 #else
300       /* Pretend that the PATH contains only the current directory.  */
301       path = "";
302 #endif
303     }
304
305   len = strlen (file) + 1;
306   pathlen = strlen (path);
307   name = alloca (pathlen + len + 1);
308   /* Copy the file name at the top.  */
309   name = (char *) memcpy (name + pathlen + 1, file, len);
310   /* And add the slash.  */
311   *--name = '/';
312
313   p = path;
314   do
315     {
316       char *startp;
317
318       path = p;
319       p = strchrnul (path, ':');
320
321       if (p == path)
322         /* Two adjacent colons, or a colon at the beginning or the end
323            of `PATH' means to search the current directory.  */
324         startp = name + 1;
325       else
326         startp = (char *) memcpy (name - (p - path), path, p - path);
327
328       /* Try to execute this name.  If it works, execv will not return.  */
329       execve (startp, argv, envp);
330
331       if (errno == ENOEXEC)
332         script_execute (startp, argv, envp);
333
334       switch (errno)
335         {
336         case EACCES:
337         case ENOENT:
338         case ESTALE:
339         case ENOTDIR:
340           /* Those errors indicate the file is missing or not executable
341              by us, in which case we want to just try the next path
342              directory.  */
343           break;
344
345         default:
346           /* Some other error means we found an executable file, but
347              something went wrong executing it; return the error to our
348              caller.  */
349           _exit (SPAWN_ERROR);
350         }
351     }
352   while (*p++ != '\0');
353
354   /* Return with an error.  */
355   _exit (SPAWN_ERROR);
356 }