From 0e169691290a6d2187a4ff41495fc5678cbfdcdc Mon Sep 17 00:00:00 2001 From: Adhemerval Zanella Date: Fri, 12 Apr 2019 17:39:53 -0300 Subject: support: Add support_capture_subprogram Its API is similar to support_capture_subprocess, but rather creates a new process based on the input path and arguments. Under the hoods it uses posix_spawn to create the new process. It also allows the use of other support_capture_* functions to check for expected results and free the resources. Checked on x86_64-linux-gnu. * support/Makefile (libsupport-routines): Add support_subprocess, xposix_spawn, xposix_spawn_file_actions_addclose, and xposix_spawn_file_actions_adddup2. (tst-support_capture_subprocess-ARGS): New rule. * support/capture_subprocess.h (support_capture_subprogram): New prototype. * support/support_capture_subprocess.c (support_capture_subprocess): Refactor to use support_subprocess and support_capture_poll. (support_capture_subprogram): New function. * support/tst-support_capture_subprocess.c (write_mode_to_str, str_to_write_mode, test_common, parse_int, handle_restart, do_subprocess, do_subprogram, do_multiple_tests): New functions. (do_test): Add support_capture_subprogram tests. * support/subprocess.h: New file. * support/support_subprocess.c: Likewise. * support/xposix_spawn.c: Likewise. * support/xposix_spawn_file_actions_addclose.c: Likewise. * support/xposix_spawn_file_actions_adddup2.c: Likewise. * support/xspawn.h: Likewise. Reviewed-by: Carlos O'Donell --- support/Makefile | 6 + support/capture_subprocess.h | 6 + support/subprocess.h | 49 +++++++ support/support_capture_subprocess.c | 80 ++++++------ support/support_subprocess.c | 152 ++++++++++++++++++++++ support/tst-support_capture_subprocess.c | 183 +++++++++++++++++++++++++-- support/xposix_spawn.c | 32 +++++ support/xposix_spawn_file_actions_addclose.c | 29 +++++ support/xposix_spawn_file_actions_adddup2.c | 30 +++++ support/xspawn.h | 34 +++++ 10 files changed, 551 insertions(+), 50 deletions(-) create mode 100644 support/subprocess.h create mode 100644 support/support_subprocess.c create mode 100644 support/xposix_spawn.c create mode 100644 support/xposix_spawn_file_actions_addclose.c create mode 100644 support/xposix_spawn_file_actions_adddup2.c create mode 100644 support/xspawn.h (limited to 'support') diff --git a/support/Makefile b/support/Makefile index f173565202..574e34cf1b 100644 --- a/support/Makefile +++ b/support/Makefile @@ -63,6 +63,7 @@ libsupport-routines = \ support_record_failure \ support_run_diff \ support_shared_allocate \ + support_subprocess \ support_test_compare_blob \ support_test_compare_failure \ support_test_compare_string \ @@ -151,6 +152,9 @@ libsupport-routines = \ xsignal \ xsigstack \ xsocket \ + xposix_spawn \ + xposix_spawn_file_actions_addclose \ + xposix_spawn_file_actions_adddup2 \ xstrdup \ xstrndup \ xsymlink \ @@ -226,4 +230,6 @@ endif $(objpfx)tst-support_format_dns_packet: $(common-objpfx)resolv/libresolv.so +tst-support_capture_subprocess-ARGS = -- $(host-test-program-cmd) + include ../Rules diff --git a/support/capture_subprocess.h b/support/capture_subprocess.h index 2dce42e3a3..2832cfc635 100644 --- a/support/capture_subprocess.h +++ b/support/capture_subprocess.h @@ -35,6 +35,12 @@ struct support_capture_subprocess struct support_capture_subprocess support_capture_subprocess (void (*callback) (void *), void *closure); +/* Issue FILE with ARGV arguments by using posix_spawn and capture standard + output, standard error, and the exit status. The out.buffer and err.buffer + are handle as support_capture_subprocess. */ +struct support_capture_subprocess support_capture_subprogram + (const char *file, char *const argv[]); + /* Deallocate the subprocess data captured by support_capture_subprocess. */ void support_capture_subprocess_free (struct support_capture_subprocess *); diff --git a/support/subprocess.h b/support/subprocess.h new file mode 100644 index 0000000000..c031878d94 --- /dev/null +++ b/support/subprocess.h @@ -0,0 +1,49 @@ +/* Create a subprocess. + Copyright (C) 2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef SUPPORT_SUBPROCESS_H +#define SUPPORT_SUBPROCESS_H + +#include + +struct support_subprocess +{ + int stdout_pipe[2]; + int stderr_pipe[2]; + pid_t pid; +}; + +/* Invoke CALLBACK (CLOSURE) in a subprocess created with fork and return + its PID, a pipe redirected to STDOUT, and a pipe redirected to STDERR. */ +struct support_subprocess support_subprocess + (void (*callback) (void *), void *closure); + +/* Issue FILE with ARGV arguments by using posix_spawn and return is PID, a + pipe redirected to STDOUT, and a pipe redirected to STDERR. */ +struct support_subprocess support_subprogram + (const char *file, char *const argv[]); + +/* Wait for the subprocess indicated by PROC::PID. Return the status + indicate by waitpid call. */ +int support_process_wait (struct support_subprocess *proc); + +/* Terminate the subprocess indicated by PROC::PID, first with a SIGTERM and + then with a SIGKILL. Return the status as for waitpid call. */ +int support_process_terminate (struct support_subprocess *proc); + +#endif diff --git a/support/support_capture_subprocess.c b/support/support_capture_subprocess.c index 167514faf1..948ce5a0c6 100644 --- a/support/support_capture_subprocess.c +++ b/support/support_capture_subprocess.c @@ -16,6 +16,7 @@ License along with the GNU C Library; if not, see . */ +#include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include static void transfer (const char *what, struct pollfd *pfd, struct xmemstream *stream) @@ -50,59 +52,53 @@ transfer (const char *what, struct pollfd *pfd, struct xmemstream *stream) } } -struct support_capture_subprocess -support_capture_subprocess (void (*callback) (void *), void *closure) +static void +support_capture_poll (struct support_capture_subprocess *result, + struct support_subprocess *proc) { - struct support_capture_subprocess result; - xopen_memstream (&result.out); - xopen_memstream (&result.err); - - int stdout_pipe[2]; - xpipe (stdout_pipe); - TEST_VERIFY (stdout_pipe[0] > STDERR_FILENO); - TEST_VERIFY (stdout_pipe[1] > STDERR_FILENO); - int stderr_pipe[2]; - xpipe (stderr_pipe); - TEST_VERIFY (stderr_pipe[0] > STDERR_FILENO); - TEST_VERIFY (stderr_pipe[1] > STDERR_FILENO); - - TEST_VERIFY (fflush (stdout) == 0); - TEST_VERIFY (fflush (stderr) == 0); - - pid_t pid = xfork (); - if (pid == 0) - { - xclose (stdout_pipe[0]); - xclose (stderr_pipe[0]); - xdup2 (stdout_pipe[1], STDOUT_FILENO); - xdup2 (stderr_pipe[1], STDERR_FILENO); - xclose (stdout_pipe[1]); - xclose (stderr_pipe[1]); - callback (closure); - _exit (0); - } - xclose (stdout_pipe[1]); - xclose (stderr_pipe[1]); - struct pollfd fds[2] = { - { .fd = stdout_pipe[0], .events = POLLIN }, - { .fd = stderr_pipe[0], .events = POLLIN }, + { .fd = proc->stdout_pipe[0], .events = POLLIN }, + { .fd = proc->stderr_pipe[0], .events = POLLIN }, }; do { xpoll (fds, 2, -1); - transfer ("stdout", &fds[0], &result.out); - transfer ("stderr", &fds[1], &result.err); + transfer ("stdout", &fds[0], &result->out); + transfer ("stderr", &fds[1], &result->err); } while (fds[0].events != 0 || fds[1].events != 0); - xclose (stdout_pipe[0]); - xclose (stderr_pipe[0]); - xfclose_memstream (&result.out); - xfclose_memstream (&result.err); - xwaitpid (pid, &result.status, 0); + xfclose_memstream (&result->out); + xfclose_memstream (&result->err); + + result->status = support_process_wait (proc); +} + +struct support_capture_subprocess +support_capture_subprocess (void (*callback) (void *), void *closure) +{ + struct support_capture_subprocess result; + xopen_memstream (&result.out); + xopen_memstream (&result.err); + + struct support_subprocess proc = support_subprocess (callback, closure); + + support_capture_poll (&result, &proc); + return result; +} + +struct support_capture_subprocess +support_capture_subprogram (const char *file, char *const argv[]) +{ + struct support_capture_subprocess result; + xopen_memstream (&result.out); + xopen_memstream (&result.err); + + struct support_subprocess proc = support_subprogram (file, argv); + + support_capture_poll (&result, &proc); return result; } diff --git a/support/support_subprocess.c b/support/support_subprocess.c new file mode 100644 index 0000000000..0c8cc6af30 --- /dev/null +++ b/support/support_subprocess.c @@ -0,0 +1,152 @@ +/* Create subprocess. + Copyright (C) 2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct support_subprocess +support_suprocess_init (void) +{ + struct support_subprocess result; + + xpipe (result.stdout_pipe); + TEST_VERIFY (result.stdout_pipe[0] > STDERR_FILENO); + TEST_VERIFY (result.stdout_pipe[1] > STDERR_FILENO); + + xpipe (result.stderr_pipe); + TEST_VERIFY (result.stderr_pipe[0] > STDERR_FILENO); + TEST_VERIFY (result.stderr_pipe[1] > STDERR_FILENO); + + TEST_VERIFY (fflush (stdout) == 0); + TEST_VERIFY (fflush (stderr) == 0); + + return result; +} + +struct support_subprocess +support_subprocess (void (*callback) (void *), void *closure) +{ + struct support_subprocess result = support_suprocess_init (); + + result.pid = xfork (); + if (result.pid == 0) + { + xclose (result.stdout_pipe[0]); + xclose (result.stderr_pipe[0]); + xdup2 (result.stdout_pipe[1], STDOUT_FILENO); + xdup2 (result.stderr_pipe[1], STDERR_FILENO); + xclose (result.stdout_pipe[1]); + xclose (result.stderr_pipe[1]); + callback (closure); + _exit (0); + } + xclose (result.stdout_pipe[1]); + xclose (result.stderr_pipe[1]); + + return result; +} + +struct support_subprocess +support_subprogram (const char *file, char *const argv[]) +{ + struct support_subprocess result = support_suprocess_init (); + + posix_spawn_file_actions_t fa; + /* posix_spawn_file_actions_init does not fail. */ + posix_spawn_file_actions_init (&fa); + + xposix_spawn_file_actions_addclose (&fa, result.stdout_pipe[0]); + xposix_spawn_file_actions_addclose (&fa, result.stderr_pipe[0]); + xposix_spawn_file_actions_adddup2 (&fa, result.stdout_pipe[1], STDOUT_FILENO); + xposix_spawn_file_actions_adddup2 (&fa, result.stderr_pipe[1], STDERR_FILENO); + xposix_spawn_file_actions_addclose (&fa, result.stdout_pipe[1]); + xposix_spawn_file_actions_addclose (&fa, result.stderr_pipe[1]); + + result.pid = xposix_spawn (file, &fa, NULL, argv, NULL); + + xclose (result.stdout_pipe[1]); + xclose (result.stderr_pipe[1]); + + return result; +} + +int +support_process_wait (struct support_subprocess *proc) +{ + xclose (proc->stdout_pipe[0]); + xclose (proc->stderr_pipe[0]); + + int status; + xwaitpid (proc->pid, &status, 0); + return status; +} + + +static bool +support_process_kill (int pid, int signo, int *status) +{ + /* Kill the whole process group. */ + kill (-pid, signo); + /* In case setpgid failed in the child, kill it individually too. */ + kill (pid, signo); + + /* Wait for it to terminate. */ + pid_t killed; + for (int i = 0; i < 5; ++i) + { + int status; + killed = xwaitpid (pid, &status, WNOHANG|WUNTRACED); + if (killed != 0) + break; + + /* Delay, give the system time to process the kill. If the + nanosleep() call return prematurely, all the better. We + won't restart it since this probably means the child process + finally died. */ + nanosleep (&((struct timespec) { 0, 100000000 }), NULL); + } + if (killed != 0 && killed != pid) + return false; + + return true; +} + +int +support_process_terminate (struct support_subprocess *proc) +{ + xclose (proc->stdout_pipe[0]); + xclose (proc->stderr_pipe[0]); + + int status; + pid_t killed = xwaitpid (proc->pid, &status, WNOHANG|WUNTRACED); + if (killed != 0 && killed == proc->pid) + return status; + + /* Subprocess is still running, terminate it. */ + if (!support_process_kill (proc->pid, SIGTERM, &status) ) + support_process_kill (proc->pid, SIGKILL, &status); + + return status; +} diff --git a/support/tst-support_capture_subprocess.c b/support/tst-support_capture_subprocess.c index d8ba42ea8b..ab363e41ac 100644 --- a/support/tst-support_capture_subprocess.c +++ b/support/tst-support_capture_subprocess.c @@ -23,8 +23,20 @@ #include #include #include +#include #include #include +#include +#include +#include +#include +#include + +/* Nonzero if the program gets called via 'exec'. */ +static int restart; + +/* Hold the four initial argument used to respawn the process. */ +static char *initial_argv[5]; /* Write one byte at *P to FD and advance *P. Do nothing if *P is '\0'. */ @@ -42,6 +54,30 @@ transfer (const unsigned char **p, int fd) enum write_mode { out_first, err_first, interleave, write_mode_last = interleave }; +static const char * +write_mode_to_str (enum write_mode mode) +{ + switch (mode) + { + case out_first: return "out_first"; + case err_first: return "err_first"; + case interleave: return "interleave"; + default: return "write_mode_last"; + } +} + +static enum write_mode +str_to_write_mode (const char *mode) +{ + if (strcmp (mode, "out_first") == 0) + return out_first; + else if (strcmp (mode, "err_first") == 0) + return err_first; + else if (strcmp (mode, "interleave") == 0) + return interleave; + return write_mode_last; +} + /* Describe what to write in the subprocess. */ struct test { @@ -52,11 +88,9 @@ struct test int status; }; -/* For use with support_capture_subprocess. */ -static void -callback (void *closure) +_Noreturn static void +test_common (const struct test *test) { - const struct test *test = closure; bool mode_ok = false; switch (test->write_mode) { @@ -95,6 +129,40 @@ callback (void *closure) exit (test->status); } +static int +parse_int (const char *str) +{ + char *endptr; + long int ret = strtol (str, &endptr, 10); + TEST_COMPARE (errno, 0); + TEST_VERIFY (ret >= 0 && ret <= INT_MAX); + return ret; +} + +/* For use with support_capture_subprogram. */ +_Noreturn static void +handle_restart (char *out, char *err, const char *write_mode, + const char *signal, const char *status) +{ + struct test test = + { + out, + err, + str_to_write_mode (write_mode), + parse_int (signal), + parse_int (status) + }; + test_common (&test); +} + +/* For use with support_capture_subprocess. */ +_Noreturn static void +callback (void *closure) +{ + const struct test *test = closure; + test_common (test); +} + /* Create a heap-allocated random string of letters. */ static char * random_string (size_t length) @@ -130,12 +198,59 @@ check_stream (const char *what, const struct xmemstream *stream, } } +static struct support_capture_subprocess +do_subprocess (struct test *test) +{ + return support_capture_subprocess (callback, test); +} + +static struct support_capture_subprocess +do_subprogram (const struct test *test) +{ + /* Three digits per byte plus null terminator. */ + char signalstr[3 * sizeof(int) + 1]; + snprintf (signalstr, sizeof (signalstr), "%d", test->signal); + char statusstr[3 * sizeof(int) + 1]; + snprintf (statusstr, sizeof (statusstr), "%d", test->status); + + int argc = 0; + enum { + /* 4 elements from initial_argv (path to ld.so, '--library-path', the + path', and application name'), 2 for restart argument ('--direct', + '--restart'), 5 arguments plus NULL. */ + argv_size = 12 + }; + char *args[argv_size]; + + for (char **arg = initial_argv; *arg != NULL; arg++) + args[argc++] = *arg; + + args[argc++] = (char*) "--direct"; + args[argc++] = (char*) "--restart"; + + args[argc++] = test->out; + args[argc++] = test->err; + args[argc++] = (char*) write_mode_to_str (test->write_mode); + args[argc++] = signalstr; + args[argc++] = statusstr; + args[argc] = NULL; + TEST_VERIFY (argc < argv_size); + + return support_capture_subprogram (args[0], args); +} + +enum test_type +{ + subprocess, + subprogram, +}; + static int -do_test (void) +do_multiple_tests (enum test_type type) { const int lengths[] = {0, 1, 17, 512, 20000, -1}; - /* Test multiple combinations of support_capture_subprocess. + /* Test multiple combinations of support_capture_sub{process,program}. length_idx_stdout: Index into the lengths array above, controls how many bytes are written by the subprocess to @@ -164,8 +279,10 @@ do_test (void) TEST_VERIFY (strlen (test.out) == lengths[length_idx_stdout]); TEST_VERIFY (strlen (test.err) == lengths[length_idx_stderr]); - struct support_capture_subprocess result - = support_capture_subprocess (callback, &test); + struct support_capture_subprocess result + = type == subprocess ? do_subprocess (&test) + : do_subprogram (&test); + check_stream ("stdout", &result.out, test.out); check_stream ("stderr", &result.err, test.err); @@ -199,4 +316,54 @@ do_test (void) return 0; } +static int +do_test (int argc, char *argv[]) +{ + /* We must have either: + + - one or four parameters if called initially: + + argv[1]: path for ld.so optional + + argv[2]: "--library-path" optional + + argv[3]: the library path optional + + argv[4]: the application name + + - six parameters left if called through re-execution: + + argv[1]: the application name + + argv[2]: the stdout to print + + argv[3]: the stderr to print + + argv[4]: the write mode to use + + argv[5]: the signal to issue + + argv[6]: the exit status code to use + + * When built with --enable-hardcoded-path-in-tests or issued without + using the loader directly. + */ + + if (argc != (restart ? 6 : 5) && argc != (restart ? 6 : 2)) + FAIL_EXIT1 ("wrong number of arguments (%d)", argc); + + if (restart) + { + handle_restart (argv[1], /* stdout */ + argv[2], /* stderr */ + argv[3], /* write_mode */ + argv[4], /* signal */ + argv[5]); /* status */ + } + + initial_argv[0] = argv[1]; /* path for ld.so */ + initial_argv[1] = argv[2]; /* "--library-path" */ + initial_argv[2] = argv[3]; /* the library path */ + initial_argv[3] = argv[4]; /* the application name */ + initial_argv[4] = NULL; + + do_multiple_tests (subprocess); + do_multiple_tests (subprogram); + + return 0; +} + +#define CMDLINE_OPTIONS \ + { "restart", no_argument, &restart, 1 }, +#define TEST_FUNCTION_ARGV do_test #include diff --git a/support/xposix_spawn.c b/support/xposix_spawn.c new file mode 100644 index 0000000000..e846017632 --- /dev/null +++ b/support/xposix_spawn.c @@ -0,0 +1,32 @@ +/* xposix_spawn implementation. + Copyright (C) 2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +pid_t +xposix_spawn (const char *file, const posix_spawn_file_actions_t *fa, + const posix_spawnattr_t *attr, char *const args[], + char *const envp[]) +{ + pid_t pid; + int status = posix_spawn (&pid, file, fa, attr, args, envp); + if (status != 0) + FAIL_EXIT1 ("posix_spawn to %s file failed: %m", file); + return pid; +} diff --git a/support/xposix_spawn_file_actions_addclose.c b/support/xposix_spawn_file_actions_addclose.c new file mode 100644 index 0000000000..eed54a6514 --- /dev/null +++ b/support/xposix_spawn_file_actions_addclose.c @@ -0,0 +1,29 @@ +/* xposix_spawn_file_actions_addclose implementation. + Copyright (C) 2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +int +xposix_spawn_file_actions_addclose (posix_spawn_file_actions_t *fa, int fd) +{ + int status = posix_spawn_file_actions_addclose (fa, fd); + if (status == -1) + FAIL_EXIT1 ("posix_spawn_file_actions_addclose failed: %m\n"); + return status; +} diff --git a/support/xposix_spawn_file_actions_adddup2.c b/support/xposix_spawn_file_actions_adddup2.c new file mode 100644 index 0000000000..a43b6490be --- /dev/null +++ b/support/xposix_spawn_file_actions_adddup2.c @@ -0,0 +1,30 @@ +/* xposix_spawn_file_actions_adddup2 implementation. + Copyright (C) 2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include + +int +xposix_spawn_file_actions_adddup2 (posix_spawn_file_actions_t *fa, int fd, + int newfd) +{ + int status = posix_spawn_file_actions_adddup2 (fa, fd, newfd); + if (status == -1) + FAIL_EXIT1 ("posix_spawn_file_actions_adddup2 failed: %m\n"); + return status; +} diff --git a/support/xspawn.h b/support/xspawn.h new file mode 100644 index 0000000000..bbf89132e4 --- /dev/null +++ b/support/xspawn.h @@ -0,0 +1,34 @@ +/* posix_spawn with support checks. + Copyright (C) 2019 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef SUPPORT_XSPAWN_H +#define SUPPORT_XSPAWN_H + +#include + +__BEGIN_DECLS + +int xposix_spawn_file_actions_addclose (posix_spawn_file_actions_t *, int); +int xposix_spawn_file_actions_adddup2 (posix_spawn_file_actions_t *, int, int); + +pid_t xposix_spawn (const char *, const posix_spawn_file_actions_t *, + const posix_spawnattr_t *, char *const [], char *const []); + +__END_DECLS + +#endif -- cgit v1.2.3-65-gdbad