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 <carlos@redhat.com>
(cherry picked from commit 0e169691290a6d2187a4ff41495fc5678cbfdcdc)
This commit is contained in:
Adhemerval Zanella 2019-04-12 17:39:53 -03:00 committed by Florian Weimer
parent 0744a268bc
commit f62d21a1f0
11 changed files with 573 additions and 50 deletions

View File

@ -1,3 +1,25 @@
2019-04-17 Adhemerval Zanella <adhemerval.zanella@linaro.org>
* 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.
2019-04-09 Carlos O'Donell <carlos@redhat.com>
Kwok Cheung Yeung <kcy@codesourcery.com>

View File

@ -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 \
@ -148,6 +149,9 @@ libsupport-routines = \
xsignal \
xsigstack \
xsocket \
xposix_spawn \
xposix_spawn_file_actions_addclose \
xposix_spawn_file_actions_adddup2 \
xstrdup \
xstrndup \
xsymlink \
@ -223,4 +227,6 @@ endif
$(objpfx)tst-support_format_dns_packet: $(common-objpfx)resolv/libresolv.so
tst-support_capture_subprocess-ARGS = -- $(host-test-program-cmd)
include ../Rules

View File

@ -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 *);

49
support/subprocess.h Normal file
View File

@ -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
<http://www.gnu.org/licenses/>. */
#ifndef SUPPORT_SUBPROCESS_H
#define SUPPORT_SUBPROCESS_H
#include <sys/types.h>
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

View File

@ -16,6 +16,7 @@
License along with the GNU C Library; if not, see
<http://www.gnu.org/licenses/>. */
#include <support/subprocess.h>
#include <support/capture_subprocess.h>
#include <errno.h>
@ -23,6 +24,7 @@
#include <support/check.h>
#include <support/xunistd.h>
#include <support/xsocket.h>
#include <support/xspawn.h>
static void
transfer (const char *what, struct pollfd *pfd, struct xmemstream *stream)
@ -50,6 +52,30 @@ transfer (const char *what, struct pollfd *pfd, struct xmemstream *stream)
}
}
static void
support_capture_poll (struct support_capture_subprocess *result,
struct support_subprocess *proc)
{
struct pollfd fds[2] =
{
{ .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);
}
while (fds[0].events != 0 || fds[1].events != 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)
{
@ -57,52 +83,22 @@ support_capture_subprocess (void (*callback) (void *), void *closure)
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);
struct support_subprocess proc = support_subprocess (callback, closure);
TEST_VERIFY (fflush (stdout) == 0);
TEST_VERIFY (fflush (stderr) == 0);
support_capture_poll (&result, &proc);
return result;
}
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 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 pollfd fds[2] =
{
{ .fd = stdout_pipe[0], .events = POLLIN },
{ .fd = stderr_pipe[0], .events = POLLIN },
};
struct support_subprocess proc = support_subprogram (file, argv);
do
{
xpoll (fds, 2, -1);
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);
support_capture_poll (&result, &proc);
return result;
}

View File

@ -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
<http://www.gnu.org/licenses/>. */
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <support/xspawn.h>
#include <support/check.h>
#include <support/xunistd.h>
#include <support/subprocess.h>
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;
}

View File

@ -23,8 +23,20 @@
#include <support/capture_subprocess.h>
#include <support/check.h>
#include <support/support.h>
#include <support/temp_file.h>
#include <sys/wait.h>
#include <unistd.h>
#include <paths.h>
#include <getopt.h>
#include <limits.h>
#include <errno.h>
#include <array_length.h>
/* 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 <support/test-driver.c>

32
support/xposix_spawn.c Normal file
View File

@ -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
<http://www.gnu.org/licenses/>. */
#include <support/xspawn.h>
#include <support/check.h>
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;
}

View File

@ -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
<http://www.gnu.org/licenses/>. */
#include <support/xspawn.h>
#include <support/check.h>
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;
}

View File

@ -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
<http://www.gnu.org/licenses/>. */
#include <support/xspawn.h>
#include <support/check.h>
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;
}

34
support/xspawn.h Normal file
View File

@ -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
<http://www.gnu.org/licenses/>. */
#ifndef SUPPORT_XSPAWN_H
#define SUPPORT_XSPAWN_H
#include <spawn.h>
__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