# HG changeset patch # User Eric Blake # Date 1259989844 25200 # Node ID f203ad5f7ea83ee4314f0d4021cf6f56eeaffcad # Parent cfc6e617b2906e8a0578486e7a596726ed00d7d0 cloexec: add dup_cloexec This is needed to enforce correct semantics of mkostemp_safer. Meanwhile, it is one step closer to providing O_CLOEXEC support to open, as well as implementing portions of fcntl for mingw. * lib/cloexec.h (dup_cloexec): New prototype. Add copyright header and comments. * lib/cloexec.c (set_cloexec_flag): Add comments. (dup_cloexec): New function, with mingw implementation borrowed from... * lib/w32spawn.h (dup_noinherit): ...here. * modules/execute (Depends-on): Add cloexec. * modules/pipe (Depends-on): Likewise. * modules/cloexec (Depends-on): Add dup2. * modules/cloexec-tests (Files): New file. * tests/test-cloexec.c: Likewise. Signed-off-by: Eric Blake diff --git a/ChangeLog b/ChangeLog --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,18 @@ 2009-12-05 Eric Blake + cloexec: add dup_cloexec + * lib/cloexec.h (dup_cloexec): New prototype. Add copyright + header and comments. + * lib/cloexec.c (set_cloexec_flag): Add comments. + (dup_cloexec): New function, with mingw implementation borrowed + from... + * lib/w32spawn.h (dup_noinherit): ...here. + * modules/execute (Depends-on): Add cloexec. + * modules/pipe (Depends-on): Likewise. + * modules/cloexec (Depends-on): Add dup2. + * modules/cloexec-tests (Files): New file. + * tests/test-cloexec.c: Likewise. + test-xalloc-die: fix test for mingw * modules/xalloc-die-tests (Files): Add tests/init.sh. * tests/test-xalloc-die.sh: Rewrite to use init.sh. Strip diff --git a/lib/cloexec.c b/lib/cloexec.c --- a/lib/cloexec.c +++ b/lib/cloexec.c @@ -1,6 +1,7 @@ /* closexec.c - set or clear the close-on-exec descriptor flag - Copyright (C) 1991, 2004, 2005, 2006 Free Software Foundation, Inc. + Copyright (C) 1991, 2004, 2005, 2006, 2009 Free Software + Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,12 +22,27 @@ #include "cloexec.h" +#include +#include #include -#include + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +/* Native Woe32 API. */ +# define WIN32_LEAN_AND_MEAN +# include +# include +#endif + /* Set the `FD_CLOEXEC' flag of DESC if VALUE is true, or clear the flag if VALUE is false. - Return 0 on success, or -1 on error with `errno' set. */ + Return 0 on success, or -1 on error with `errno' set. + + Note that on MingW, this function does NOT protect DESC from being + inherited into spawned children. Instead, either use dup_cloexec + followed by closing the original DESC, or use interfaces such as + open or pipe2 that accept flags like O_CLOEXEC to create DESC + non-inheritable in the first place. */ int set_cloexec_flag (int desc, bool value) @@ -40,15 +56,98 @@ int newflags = (value ? flags | FD_CLOEXEC : flags & ~FD_CLOEXEC); if (flags == newflags - || fcntl (desc, F_SETFD, newflags) != -1) - return 0; + || fcntl (desc, F_SETFD, newflags) != -1) + return 0; } return -1; #else - return 0; + if (desc < 0) + { + errno = EBADF; + return -1; + } + return dup2 (desc, desc) == desc ? 0 : -1; #endif } + + +/* Duplicates a file handle FD, while marking the copy to be closed + prior to exec or spawn. Returns -1 and sets errno if FD could not + be duplicated. */ + +int dup_cloexec (int fd) +{ + int nfd; + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + + /* Native Woe32 API. */ + HANDLE curr_process = GetCurrentProcess (); + HANDLE old_handle = (HANDLE) _get_osfhandle (fd); + HANDLE new_handle; + + if (old_handle == INVALID_HANDLE_VALUE) + { + /* fd is closed, or is open to no handle at all. + We cannot duplicate fd in this case, because _open_osfhandle + fails for an INVALID_HANDLE_VALUE argument. */ + errno = EBADF; + return -1; + } + + if (!DuplicateHandle (curr_process, /* SourceProcessHandle */ + old_handle, /* SourceHandle */ + curr_process, /* TargetProcessHandle */ + (PHANDLE) &new_handle, /* TargetHandle */ + (DWORD) 0, /* DesiredAccess */ + FALSE, /* InheritHandle */ + DUPLICATE_SAME_ACCESS)) /* Options */ + { + errno = EMFILE; + return -1; + } + + nfd = _open_osfhandle ((long) new_handle, O_BINARY | O_NOINHERIT); + if (nfd < 0) + { + CloseHandle (new_handle); + errno = EMFILE; + return -1; + } + +# if REPLACE_FCHDIR + if (0 <= nfd) + result = _gl_register_dup (fd, nfd); +# endif + return nfd; + +#else /* !_WIN32 */ + + /* Unix API. */ + +# ifdef F_DUPFD_CLOEXEC + nfd = fcntl (fd, F_DUPFD_CLOEXEC, 0); +# if REPLACE_FCHDIR + if (0 <= nfd) + result = _gl_register_dup (fd, nfd); +# endif + +# else /* !F_DUPFD_CLOEXEC */ + nfd = dup (fd); + if (0 <= nfd && set_cloexec_flag (nfd, true)) + { + int saved_errno = errno; + close (nfd); + nfd = -1; + errno = saved_errno; + } +# endif /* !F_DUPFD_CLOEXEC */ + + return nfd; + +#endif /* !_WIN32 */ +} diff --git a/lib/cloexec.h b/lib/cloexec.h --- a/lib/cloexec.h +++ b/lib/cloexec.h @@ -1,2 +1,38 @@ +/* closexec.c - set or clear the close-on-exec descriptor flag + + Copyright (C) 2004, 2009 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + #include + +/* Set the `FD_CLOEXEC' flag of DESC if VALUE is true, + or clear the flag if VALUE is false. + Return 0 on success, or -1 on error with `errno' set. + + Note that on MingW, this function does NOT protect DESC from being + inherited into spawned children. Instead, either use dup_cloexec + followed by closing the original DESC, or use interfaces such as + open or pipe2 that accept flags like O_CLOEXEC to create DESC + non-inheritable in the first place. */ + int set_cloexec_flag (int desc, bool value); + +/* Duplicates a file handle FD, while marking the copy to be closed + prior to exec or spawn. Returns -1 and sets errno if FD could not + be duplicated. */ + +int dup_cloexec (int fd); diff --git a/lib/w32spawn.h b/lib/w32spawn.h --- a/lib/w32spawn.h +++ b/lib/w32spawn.h @@ -27,6 +27,7 @@ #include #include +#include "cloexec.h" #include "xalloc.h" /* Duplicates a file handle, making the copy uninheritable. @@ -34,32 +35,11 @@ static int dup_noinherit (int fd) { - HANDLE curr_process = GetCurrentProcess (); - HANDLE old_handle = (HANDLE) _get_osfhandle (fd); - HANDLE new_handle; - int nfd; - - if (old_handle == INVALID_HANDLE_VALUE) - /* fd is closed, or is open to no handle at all. - We cannot duplicate fd in this case, because _open_osfhandle fails for - an INVALID_HANDLE_VALUE argument. */ - return -1; - - if (!DuplicateHandle (curr_process, /* SourceProcessHandle */ - old_handle, /* SourceHandle */ - curr_process, /* TargetProcessHandle */ - (PHANDLE) &new_handle, /* TargetHandle */ - (DWORD) 0, /* DesiredAccess */ - FALSE, /* InheritHandle */ - DUPLICATE_SAME_ACCESS)) /* Options */ - error (EXIT_FAILURE, 0, _("DuplicateHandle failed with error code 0x%08x"), - (unsigned int) GetLastError ()); - - nfd = _open_osfhandle ((long) new_handle, O_BINARY | O_NOINHERIT); - if (nfd < 0) + fd = dup_cloexec (fd); + if (fd < 0 && errno == EMFILE) error (EXIT_FAILURE, errno, _("_open_osfhandle failed")); - return nfd; + return fd; } /* Returns a file descriptor equivalent to FD, except that the resulting file diff --git a/modules/cloexec b/modules/cloexec --- a/modules/cloexec +++ b/modules/cloexec @@ -7,6 +7,7 @@ m4/cloexec.m4 Depends-on: +dup2 stdbool configure.ac: diff --git a/modules/cloexec-tests b/modules/cloexec-tests new file mode 100644 --- /dev/null +++ b/modules/cloexec-tests @@ -0,0 +1,10 @@ +Files: +tests/test-cloexec.c + +Depends-on: + +configure.ac: + +Makefile.am: +TESTS += test-cloexec +check_PROGRAMS += test-cloexec diff --git a/modules/execute b/modules/execute --- a/modules/execute +++ b/modules/execute @@ -8,6 +8,7 @@ m4/execute.m4 Depends-on: +cloexec dup2 error exit diff --git a/modules/pipe b/modules/pipe --- a/modules/pipe +++ b/modules/pipe @@ -8,6 +8,7 @@ m4/pipe.m4 Depends-on: +cloexec dup2 environ error diff --git a/tests/test-cloexec.c b/tests/test-cloexec.c new file mode 100644 --- /dev/null +++ b/tests/test-cloexec.c @@ -0,0 +1,122 @@ +/* Test duplicating non-inheritable file descriptors. + Copyright (C) 2009 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Eric Blake , 2009. */ + +#include + +#include "cloexec.h" + +#include +#include +#include +#include +#include + +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ +/* Get declarations of the Win32 API functions. */ +# define WIN32_LEAN_AND_MEAN +# include +#endif + +#define ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \ + fflush (stderr); \ + abort (); \ + } \ + } \ + while (0) + +/* Return non-zero if FD is open and inheritable across exec/spawn. */ +static int +is_inheritable (int fd) +{ +#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + /* On Win32, the initial state of unassigned standard file + descriptors is that they are open but point to an + INVALID_HANDLE_VALUE, and there is no fcntl. */ + HANDLE h = (HANDLE) _get_osfhandle (fd); + DWORD flags; + if (h == INVALID_HANDLE_VALUE || GetHandleInformation (h, &flags) == 0) + return 0; + return (flags & HANDLE_FLAG_INHERIT) != 0; +#else +# ifndef F_GETFD +# error Please port fcntl to your platform +# endif + int i = fcntl (fd, F_GETFD); + return 0 <= i && (i & FD_CLOEXEC) == 0; +#endif +} + +int +main (void) +{ + const char *file = "test-cloexec.tmp"; + int fd = creat (file, 0600); + int fd2; + + /* Assume std descriptors were provided by invoker. */ + ASSERT (STDERR_FILENO < fd); + ASSERT (is_inheritable (fd)); + + /* Normal use of set_cloexec_flag. */ + ASSERT (set_cloexec_flag (fd, true) == 0); +#if !((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__) + ASSERT (!is_inheritable (fd)); +#endif + ASSERT (set_cloexec_flag (fd, false) == 0); + ASSERT (is_inheritable (fd)); + + /* Normal use of dup_cloexec. */ + fd2 = dup_cloexec (fd); + ASSERT (fd < fd2); + ASSERT (!is_inheritable (fd2)); + ASSERT (close (fd) == 0); + ASSERT (dup_cloexec (fd2) == fd); + ASSERT (!is_inheritable (fd)); + ASSERT (close (fd2) == 0); + + /* Test error handling. */ + errno = 0; + ASSERT (set_cloexec_flag (-1, false) == -1); + ASSERT (errno == EBADF); + errno = 0; + ASSERT (set_cloexec_flag (10000000, false) == -1); + ASSERT (errno == EBADF); + errno = 0; + ASSERT (set_cloexec_flag (fd2, false) == -1); + ASSERT (errno == EBADF); + errno = 0; + ASSERT (dup_cloexec (-1) == -1); + ASSERT (errno == EBADF); + errno = 0; + ASSERT (dup_cloexec (10000000) == -1); + ASSERT (errno == EBADF); + errno = 0; + ASSERT (dup_cloexec (fd2) == -1); + ASSERT (errno == EBADF); + + /* Clean up. */ + ASSERT (close (fd) == 0); + ASSERT (unlink (file) == 0); + + return 0; +}