fcntl: work around cygwin bug in F_DUPFD
[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 - duplicate FD, with int ARG being the minimum target fd.
39    If successful, return the duplicate, which will be inheritable;
40    otherwise return -1 and set errno.
41
42    F_DUPFD_CLOEXEC - duplicate FD, with int ARG being the minimum
43    target fd.  If successful, return the duplicate, which will not be
44    inheritable; otherwise return -1 and set errno.  */
45
46 int
47 rpl_fcntl (int fd, int action, /* arg */...)
48 {
49   va_list arg;
50   int result = -1;
51   va_start (arg, action);
52   switch (action)
53     {
54
55 #if FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR
56     case F_DUPFD:
57       {
58         int target = va_arg (arg, int);
59         /* Detect invalid target; needed for cygwin 1.5.x.  */
60         if (target < 0 || getdtablesize () <= target)
61           errno = EINVAL;
62         else
63           {
64             result = fcntl (fd, action, target);
65 # if REPLACE_FCHDIR
66             if (0 <= result)
67               result = _gl_register_dup (fd, result);
68 # endif
69           }
70         break;
71       } /* F_DUPFD */
72 #endif /* FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR */
73
74     case F_DUPFD_CLOEXEC:
75       {
76         int target = va_arg (arg, int);
77
78         /* Try the system call first, if the headers claim it exists
79            (that is, if GNULIB_defined_F_DUPFD_CLOEXEC is 0), since we
80            may be running with a glibc that has the macro but with an
81            older kernel that does not support it.  Cache the
82            information on whether the system call really works, but
83            avoid caching failure if the corresponding F_DUPFD fails
84            for any reason.  0 = unknown, 1 = yes, -1 = no.  */
85         static int have_dupfd_cloexec = GNULIB_defined_F_DUPFD_CLOEXEC ? -1 : 0;
86         if (0 <= have_dupfd_cloexec)
87           {
88             result = fcntl (fd, action, target);
89             if (0 <= result || errno != EINVAL)
90               {
91                 have_dupfd_cloexec = 1;
92 #if REPLACE_FCHDIR
93                 if (0 <= result)
94                   result = _gl_register_dup (fd, result);
95 #endif
96               }
97             else
98               {
99                 result = rpl_fcntl (fd, F_DUPFD, target);
100                 if (result < 0)
101                   break;
102                 have_dupfd_cloexec = -1;
103               }
104           }
105         else
106           result = rpl_fcntl (fd, F_DUPFD, target);
107         if (0 <= result && have_dupfd_cloexec == -1)
108           {
109             int flags = fcntl (result, F_GETFD);
110             if (flags < 0 || fcntl (result, F_SETFD, flags | FD_CLOEXEC) == -1)
111               {
112                 int saved_errno = errno;
113                 close (result);
114                 errno = saved_errno;
115                 result = -1;
116               }
117           }
118         break;
119       } /* F_DUPFD_CLOEXEC */
120
121     default:
122       {
123         void *p = va_arg (arg, void *);
124         result = fcntl (fd, action, p);
125         break;
126       }
127     }
128   va_end (arg);
129   return result;
130 }