05d73a94fb7a4b20ca2d41947a5c8f271b0498fa
[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 <limits.h>
27 #include <stdarg.h>
28
29 #if !HAVE_FCNTL
30 # define rpl_fcntl fcntl
31 #endif
32 #undef fcntl
33
34 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
35 /* Get declarations of the Win32 API functions.  */
36 # define WIN32_LEAN_AND_MEAN
37 # include <windows.h>
38
39 /* Upper bound on getdtablesize().  See lib/getdtablesize.c.  */
40 # define OPEN_MAX_MAX 0x10000
41
42 /* Duplicate OLDFD into the first available slot of at least NEWFD,
43    which must be positive, with FLAGS determining whether the duplicate
44    will be inheritable.  */
45 static int
46 dupfd (int oldfd, int newfd, int flags)
47 {
48   /* Mingw has no way to create an arbitrary fd.  Iterate until all
49      file descriptors less than newfd are filled up.  */
50   HANDLE curr_process = GetCurrentProcess ();
51   HANDLE old_handle = (HANDLE) _get_osfhandle (oldfd);
52   unsigned char fds_to_close[OPEN_MAX_MAX / CHAR_BIT];
53   unsigned int fds_to_close_bound = 0;
54   int result;
55   BOOL inherit = flags & O_CLOEXEC ? FALSE : TRUE;
56   int mode;
57
58   if (newfd < 0 || getdtablesize () <= newfd)
59     {
60       errno = EINVAL;
61       return -1;
62     }
63   if (old_handle == INVALID_HANDLE_VALUE
64       || (mode = setmode (oldfd, O_BINARY)) == -1)
65     {
66       /* oldfd is not open, or is an unassigned standard file
67          descriptor.  */
68       errno = EBADF;
69       return -1;
70     }
71   setmode (oldfd, mode);
72   flags |= mode;
73
74   for (;;)
75     {
76       HANDLE new_handle;
77       int duplicated_fd;
78       unsigned int index;
79
80       if (!DuplicateHandle (curr_process,           /* SourceProcessHandle */
81                             old_handle,             /* SourceHandle */
82                             curr_process,           /* TargetProcessHandle */
83                             (PHANDLE) &new_handle,  /* TargetHandle */
84                             (DWORD) 0,              /* DesiredAccess */
85                             inherit,                /* InheritHandle */
86                             DUPLICATE_SAME_ACCESS)) /* Options */
87         {
88           /* TODO: Translate GetLastError () into errno.  */
89           errno = EMFILE;
90           result = -1;
91           break;
92         }
93       duplicated_fd = _open_osfhandle ((long) new_handle, flags);
94       if (duplicated_fd < 0)
95         {
96           CloseHandle (new_handle);
97           errno = EMFILE;
98           result = -1;
99           break;
100         }
101       if (newfd <= duplicated_fd)
102         {
103           result = duplicated_fd;
104           break;
105         }
106
107       /* Set the bit duplicated_fd in fds_to_close[].  */
108       index = (unsigned int) duplicated_fd / CHAR_BIT;
109       if (fds_to_close_bound <= index)
110         {
111           if (sizeof fds_to_close <= index)
112             /* Need to increase OPEN_MAX_MAX.  */
113             abort ();
114           memset (fds_to_close + fds_to_close_bound, '\0',
115                   index + 1 - fds_to_close_bound);
116           fds_to_close_bound = index + 1;
117         }
118       fds_to_close[index] |= 1 << ((unsigned int) duplicated_fd % CHAR_BIT);
119     }
120
121   /* Close the previous fds that turned out to be too small.  */
122   {
123     int saved_errno = errno;
124     unsigned int duplicated_fd;
125
126     for (duplicated_fd = 0;
127          duplicated_fd < fds_to_close_bound * CHAR_BIT;
128          duplicated_fd++)
129       if ((fds_to_close[duplicated_fd / CHAR_BIT]
130            >> (duplicated_fd % CHAR_BIT))
131           & 1)
132         close (duplicated_fd);
133
134     errno = saved_errno;
135   }
136
137 # if REPLACE_FCHDIR
138   if (0 <= result)
139     result = _gl_register_dup (oldfd, result);
140 # endif
141   return result;
142 }
143 #endif /* W32 */
144
145 /* Perform the specified ACTION on the file descriptor FD, possibly
146    using the argument ARG further described below.  This replacement
147    handles the following actions, and forwards all others on to the
148    native fcntl.  An unrecognized ACTION returns -1 with errno set to
149    EINVAL.
150
151    F_DUPFD - duplicate FD, with int ARG being the minimum target fd.
152    If successful, return the duplicate, which will be inheritable;
153    otherwise return -1 and set errno.
154
155    F_DUPFD_CLOEXEC - duplicate FD, with int ARG being the minimum
156    target fd.  If successful, return the duplicate, which will not be
157    inheritable; otherwise return -1 and set errno.
158
159    F_GETFD - ARG need not be present.  If successful, return a
160    non-negative value containing the descriptor flags of FD (only
161    FD_CLOEXEC is portable, but other flags may be present); otherwise
162    return -1 and set errno.  */
163
164 int
165 rpl_fcntl (int fd, int action, /* arg */...)
166 {
167   va_list arg;
168   int result = -1;
169   va_start (arg, action);
170   switch (action)
171     {
172
173 #if !HAVE_FCNTL
174     case F_DUPFD:
175       {
176         int target = va_arg (arg, int);
177         result = dupfd (fd, target, 0);
178         break;
179       }
180 #elif FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR
181     case F_DUPFD:
182       {
183         int target = va_arg (arg, int);
184         /* Detect invalid target; needed for cygwin 1.5.x.  */
185         if (target < 0 || getdtablesize () <= target)
186           errno = EINVAL;
187         else
188           {
189             result = fcntl (fd, action, target);
190 # if REPLACE_FCHDIR
191             if (0 <= result)
192               result = _gl_register_dup (fd, result);
193 # endif
194           }
195         break;
196       } /* F_DUPFD */
197 #endif /* FCNTL_DUPFD_BUGGY || REPLACE_FCHDIR */
198
199     case F_DUPFD_CLOEXEC:
200       {
201         int target = va_arg (arg, int);
202
203 #if !HAVE_FCNTL
204         result = dupfd (fd, target, O_CLOEXEC);
205         break;
206 #else /* HAVE_FCNTL */
207         /* Try the system call first, if the headers claim it exists
208            (that is, if GNULIB_defined_F_DUPFD_CLOEXEC is 0), since we
209            may be running with a glibc that has the macro but with an
210            older kernel that does not support it.  Cache the
211            information on whether the system call really works, but
212            avoid caching failure if the corresponding F_DUPFD fails
213            for any reason.  0 = unknown, 1 = yes, -1 = no.  */
214         static int have_dupfd_cloexec = GNULIB_defined_F_DUPFD_CLOEXEC ? -1 : 0;
215         if (0 <= have_dupfd_cloexec)
216           {
217             result = fcntl (fd, action, target);
218             if (0 <= result || errno != EINVAL)
219               {
220                 have_dupfd_cloexec = 1;
221 # if REPLACE_FCHDIR
222                 if (0 <= result)
223                   result = _gl_register_dup (fd, result);
224 # endif
225               }
226             else
227               {
228                 result = rpl_fcntl (fd, F_DUPFD, target);
229                 if (result < 0)
230                   break;
231                 have_dupfd_cloexec = -1;
232               }
233           }
234         else
235           result = rpl_fcntl (fd, F_DUPFD, target);
236         if (0 <= result && have_dupfd_cloexec == -1)
237           {
238             int flags = fcntl (result, F_GETFD);
239             if (flags < 0 || fcntl (result, F_SETFD, flags | FD_CLOEXEC) == -1)
240               {
241                 int saved_errno = errno;
242                 close (result);
243                 errno = saved_errno;
244                 result = -1;
245               }
246           }
247         break;
248 #endif /* HAVE_FCNTL */
249       } /* F_DUPFD_CLOEXEC */
250
251 #if !HAVE_FCNTL
252     case F_GETFD:
253       {
254 # if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
255         HANDLE handle = (HANDLE) _get_osfhandle (fd);
256         DWORD flags;
257         if (handle == INVALID_HANDLE_VALUE
258             || GetHandleInformation (handle, &flags) == 0)
259           errno = EBADF;
260         else
261           result = (flags & HANDLE_FLAG_INHERIT) ? 0 : FD_CLOEXEC;
262 # else /* !W32 */
263         /* Use dup2 to reject invalid file descriptors.  No way to
264            access this information, so punt.  */
265         if (0 <= dup2 (fd, fd))
266           result = 0;
267 # endif /* !W32 */
268         break;
269       } /* F_GETFD */
270 #endif /* !HAVE_FCNTL */
271
272       /* Implementing F_SETFD on mingw is not trivial - there is no
273          API for changing the O_NOINHERIT bit on an fd, and merely
274          changing the HANDLE_FLAG_INHERIT bit on the underlying handle
275          can lead to odd state.  It may be possible by duplicating the
276          handle, using _open_osfhandle with the right flags, then
277          using dup2 to move the duplicate onto the original, but that
278          is not supported for now.  */
279
280     default:
281       {
282 #if HAVE_FCNTL
283         void *p = va_arg (arg, void *);
284         result = fcntl (fd, action, p);
285 #else
286         errno = EINVAL;
287 #endif
288         break;
289       }
290     }
291   va_end (arg);
292   return result;
293 }