exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / execute.c
blob8bbba2be62417bd5b9ca88cbd7018cac015e011b
1 /* Creation of autonomous subprocesses.
2 Copyright (C) 2001-2004, 2006-2024 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
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.
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.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
19 #include <config.h>
21 /* Specification. */
22 #include "execute.h"
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <stdlib.h>
27 #include <signal.h>
28 #include <unistd.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
33 #include "canonicalize.h"
34 #include <error.h>
35 #include "fatal-signal.h"
36 #include "filename.h"
37 #include "findprog.h"
38 #include "wait-process.h"
39 #include "xalloc.h"
40 #include "gettext.h"
42 #define _(str) gettext (str)
45 /* Choice of implementation for native Windows.
46 - Define to 0 to use the posix_spawn facility (modules 'posix_spawn' and
47 'posix_spawnp'), that is based on the module 'windows-spawn'.
48 - Define to 1 to use the older code, that uses the module 'windows-spawn'
49 directly.
50 You can set this macro from a Makefile or at configure time, from the
51 CPPFLAGS. */
52 #ifndef EXECUTE_IMPL_AVOID_POSIX_SPAWN
53 # define EXECUTE_IMPL_AVOID_POSIX_SPAWN 0
54 #endif
57 #if (defined _WIN32 && !defined __CYGWIN__) && EXECUTE_IMPL_AVOID_POSIX_SPAWN
59 /* Native Windows API. */
60 # if GNULIB_MSVC_NOTHROW
61 # include "msvc-nothrow.h"
62 # else
63 # include <io.h>
64 # endif
65 # include <process.h>
66 # include "windows-spawn.h"
68 #else
70 /* Unix API. */
71 # include <spawn.h>
73 #endif
76 #if defined EINTR && (defined _WIN32 && !defined __CYGWIN__) && EXECUTE_IMPL_AVOID_POSIX_SPAWN
78 /* EINTR handling for close(), open().
79 These functions can return -1/EINTR even though we don't have any
80 signal handlers set up, namely when we get interrupted via SIGSTOP. */
82 static int
83 nonintr_close (int fd)
85 int retval;
88 retval = close (fd);
89 while (retval < 0 && errno == EINTR);
91 return retval;
93 #undef close /* avoid warning related to gnulib module unistd */
94 #define close nonintr_close
96 static int
97 nonintr_open (const char *pathname, int oflag, mode_t mode)
99 int retval;
102 retval = open (pathname, oflag, mode);
103 while (retval < 0 && errno == EINTR);
105 return retval;
107 #undef open /* avoid warning on VMS */
108 #define open nonintr_open
110 #endif
114 execute (const char *progname,
115 const char *prog_path, const char * const *prog_argv,
116 const char *directory,
117 bool ignore_sigpipe,
118 bool null_stdin, bool null_stdout, bool null_stderr,
119 bool slave_process, bool exit_on_error,
120 int *termsigp)
122 int saved_errno;
123 char *prog_path_to_free = NULL;
125 if (directory != NULL)
127 /* If a change of directory is requested, make sure PROG_PATH is absolute
128 before we do so. This is needed because
129 - posix_spawn and posix_spawnp are required to resolve a relative
130 PROG_PATH *after* changing the directory. See
131 <https://www.austingroupbugs.net/view.php?id=1208>:
132 "if this pathname does not start with a <slash> it shall be
133 interpreted relative to the working directory of the child
134 process _after_ all file_actions have been performed."
135 But this would be a surprising application behaviour, possibly
136 even security relevant.
137 - For the Windows CreateProcess() function, it is unspecified whether
138 a relative file name is interpreted to the parent's current
139 directory or to the specified directory. See
140 <https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa> */
141 if (! IS_ABSOLUTE_FILE_NAME (prog_path))
143 const char *resolved_prog =
144 find_in_given_path (prog_path, getenv ("PATH"), NULL, false);
145 if (resolved_prog == NULL)
146 goto fail_with_errno;
147 if (resolved_prog != prog_path)
148 prog_path_to_free = (char *) resolved_prog;
149 prog_path = resolved_prog;
151 if (! IS_ABSOLUTE_FILE_NAME (prog_path))
153 char *absolute_prog =
154 canonicalize_filename_mode (prog_path,
155 CAN_MISSING | CAN_NOLINKS);
156 if (absolute_prog == NULL)
158 free (prog_path_to_free);
159 goto fail_with_errno;
161 free (prog_path_to_free);
162 prog_path_to_free = absolute_prog;
163 prog_path = absolute_prog;
165 if (! IS_ABSOLUTE_FILE_NAME (prog_path))
166 abort ();
171 #if (defined _WIN32 && !defined __CYGWIN__) && EXECUTE_IMPL_AVOID_POSIX_SPAWN
173 /* Native Windows API. */
175 char *argv_mem_to_free;
177 const char **argv = prepare_spawn (prog_argv, &argv_mem_to_free);
178 if (argv == NULL)
179 xalloc_die ();
181 int exitcode = -1;
183 /* Create standard file handles of child process. */
184 int nullinfd = -1;
185 int nulloutfd = -1;
186 if ((!null_stdin
187 || (nullinfd = open ("NUL", O_RDONLY, 0)) >= 0)
188 && (!(null_stdout || null_stderr)
189 || (nulloutfd = open ("NUL", O_RDWR, 0)) >= 0))
190 /* Pass the environment explicitly. This is needed if the program has
191 modified the environment using putenv() or [un]setenv(). On Windows,
192 processes have two environments, one in the "environment block" of the
193 process and managed through SetEnvironmentVariable(), and one inside the
194 process, in the location retrieved by the 'environ' macro. If we were
195 to pass NULL, the child process would inherit a copy of the environment
196 block - ignoring the effects of putenv() and [un]setenv(). */
198 HANDLE stdin_handle =
199 (HANDLE) _get_osfhandle (null_stdin ? nullinfd : STDIN_FILENO);
200 HANDLE stdout_handle =
201 (HANDLE) _get_osfhandle (null_stdout ? nulloutfd : STDOUT_FILENO);
202 HANDLE stderr_handle =
203 (HANDLE) _get_osfhandle (null_stderr ? nulloutfd : STDERR_FILENO);
205 exitcode = spawnpvech (P_WAIT, prog_path, argv + 1,
206 (const char * const *) environ, directory,
207 stdin_handle, stdout_handle, stderr_handle);
208 # if 0 /* Executing arbitrary files as shell scripts is unsecure. */
209 if (exitcode == -1 && errno == ENOEXEC)
211 /* prog is not a native executable. Try to execute it as a
212 shell script. Note that prepare_spawn() has already prepended
213 a hidden element "sh.exe" to argv. */
214 argv[1] = prog_path;
215 exitcode = spawnpvech (P_WAIT, argv[0], argv,
216 (const char * const *) environ, directory,
217 stdin_handle, stdout_handle, stderr_handle);
219 # endif
221 if (exitcode == -1)
222 saved_errno = errno;
223 if (nulloutfd >= 0)
224 close (nulloutfd);
225 if (nullinfd >= 0)
226 close (nullinfd);
227 free (argv);
228 free (argv_mem_to_free);
229 free (prog_path_to_free);
231 /* Treat failure and signalled child processes like wait_subprocess()
232 does. */
233 if (termsigp != NULL)
234 *termsigp = 0;
236 if (exitcode == -1)
237 goto fail_with_saved_errno;
239 if (WIFSIGNALED (exitcode))
241 if (termsigp != NULL)
242 *termsigp = WTERMSIG (exitcode);
243 saved_errno = 0;
244 goto fail_with_saved_errno;
247 return exitcode;
249 #else
251 /* Unix API. */
252 /* Note about 127: Some errors during posix_spawnp() cause the function
253 posix_spawnp() to return an error code; some other errors cause the
254 subprocess to exit with return code 127. It is implementation
255 dependent which error is reported which way. We treat both cases as
256 equivalent. */
257 sigset_t blocked_signals;
258 posix_spawn_file_actions_t actions;
259 bool actions_allocated;
260 posix_spawnattr_t attrs;
261 bool attrs_allocated;
262 int err;
263 pid_t child;
265 if (slave_process)
267 sigprocmask (SIG_SETMASK, NULL, &blocked_signals);
268 block_fatal_signals ();
270 actions_allocated = false;
271 attrs_allocated = false;
272 if ((err = posix_spawn_file_actions_init (&actions)) != 0
273 || (actions_allocated = true,
274 (null_stdin
275 && (err = posix_spawn_file_actions_addopen (&actions,
276 STDIN_FILENO,
277 "/dev/null", O_RDONLY,
279 != 0)
280 || (null_stdout
281 && (err = posix_spawn_file_actions_addopen (&actions,
282 STDOUT_FILENO,
283 "/dev/null", O_RDWR,
285 != 0)
286 || (null_stderr
287 && (err = posix_spawn_file_actions_addopen (&actions,
288 STDERR_FILENO,
289 "/dev/null", O_RDWR,
291 != 0)
292 || (directory != NULL
293 && (err = posix_spawn_file_actions_addchdir (&actions,
294 directory)))
295 # if !(defined _WIN32 && !defined __CYGWIN__)
296 || (slave_process
297 && ((err = posix_spawnattr_init (&attrs)) != 0
298 || (attrs_allocated = true,
299 (err = posix_spawnattr_setsigmask (&attrs,
300 &blocked_signals))
301 != 0
302 || (err = posix_spawnattr_setflags (&attrs,
303 POSIX_SPAWN_SETSIGMASK))
304 != 0)))
305 # endif
306 || (err = (directory != NULL
307 ? posix_spawn (&child, prog_path, &actions,
308 attrs_allocated ? &attrs : NULL,
309 (char * const *) prog_argv, environ)
310 : posix_spawnp (&child, prog_path, &actions,
311 attrs_allocated ? &attrs : NULL,
312 (char * const *) prog_argv, environ)))
313 != 0))
315 if (actions_allocated)
316 posix_spawn_file_actions_destroy (&actions);
317 if (attrs_allocated)
318 posix_spawnattr_destroy (&attrs);
319 if (slave_process)
320 unblock_fatal_signals ();
321 free (prog_path_to_free);
322 if (termsigp != NULL)
323 *termsigp = 0;
324 saved_errno = err;
325 goto fail_with_saved_errno;
327 posix_spawn_file_actions_destroy (&actions);
328 if (attrs_allocated)
329 posix_spawnattr_destroy (&attrs);
330 if (slave_process)
332 register_slave_subprocess (child);
333 unblock_fatal_signals ();
335 free (prog_path_to_free);
337 return wait_subprocess (child, progname, ignore_sigpipe, null_stderr,
338 slave_process, exit_on_error, termsigp);
340 #endif
342 fail_with_errno:
343 saved_errno = errno;
344 fail_with_saved_errno:
345 if (exit_on_error || !null_stderr)
346 error (exit_on_error ? EXIT_FAILURE : 0, saved_errno,
347 _("%s subprocess failed"), progname);
348 return 127;