fcntl: support F_DUPFD_CLOEXEC on systems with fcntl
[gnulib.git] / lib / fcntl.c
1 /* Provide file descriptor control.
2
3    Copyright (C) 2009 Free Software Foundation, Inc.
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 /* Written by Eric Blake <ebb9@byu.net>.  */
19
20 #include <config.h>
21
22 /* Specification.  */
23 #include <fcntl.h>
24
25 #include <errno.h>
26 #include <stdarg.h>
27
28 #if !HAVE_FCNTL
29 # error not ported to mingw yet
30 #endif
31 #undef fcntl
32
33 /* Perform the specified ACTION on the file descriptor FD, possibly
34    using the argument ARG further described below.  This replacement
35    handles the following actions, and forwards all others on to the
36    native fcntl.
37
38    F_DUPFD_CLOEXEC - duplicate FD, with int ARG being the minimum
39    target fd.  If successful, return the duplicate, which will not be
40    inheritable; otherwise return -1 and set errno.  */
41
42 int
43 rpl_fcntl (int fd, int action, /* arg */...)
44 {
45   va_list arg;
46   int result = -1;
47   va_start (arg, action);
48   switch (action)
49     {
50     case F_DUPFD_CLOEXEC:
51       {
52         int target = va_arg (arg, int);
53
54         /* Try the system call first, if the headers claim it exists
55            (that is, if GNULIB_defined_F_DUPFD_CLOEXEC is 0), since we
56            may be running with a glibc that has the macro but with an
57            older kernel that does not support it.  Cache the
58            information on whether the system call really works, but
59            avoid caching failure if the corresponding F_DUPFD fails
60            for any reason.  0 = unknown, 1 = yes, -1 = no.  */
61         static int have_dupfd_cloexec = GNULIB_defined_F_DUPFD_CLOEXEC ? -1 : 0;
62         if (0 <= have_dupfd_cloexec)
63           {
64             result = fcntl (fd, action, target);
65             if (0 <= result || errno != EINVAL)
66               have_dupfd_cloexec = 1;
67             else
68               {
69                 result = fcntl (fd, F_DUPFD, target);
70                 if (result < 0)
71                   break;
72                 have_dupfd_cloexec = -1;
73               }
74           }
75         else
76           result = fcntl (fd, F_DUPFD, target);
77         if (0 <= result && have_dupfd_cloexec == -1)
78           {
79             int flags = fcntl (result, F_GETFD);
80             if (flags < 0 || fcntl (result, F_SETFD, flags | FD_CLOEXEC) == -1)
81               {
82                 int saved_errno = errno;
83                 close (result);
84                 errno = saved_errno;
85                 result = -1;
86               }
87           }
88 #if REPLACE_FCHDIR
89         if (0 <= result)
90           result = _gl_register_dup (fd, result);
91 #endif
92         break;
93       } /* F_DUPFD_CLOEXEC */
94
95     default:
96       {
97         void *p = va_arg (arg, void *);
98         result = fcntl (fd, action, p);
99         break;
100       }
101     }
102   va_end (arg);
103   return result;
104 }