Avoid running into nonexistent system calls repeatedly.
[gnulib.git] / lib / dup3.c
1 /* Copy a file descriptor, applying specific flags.
2    Copyright (C) 2009 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License along
15    with this program; if not, write to the Free Software Foundation,
16    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
17
18 #include <config.h>
19
20 /* Specification.  */
21 #include <unistd.h>
22
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <limits.h>
26
27 #include "binary-io.h"
28
29 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
30 /* Native Woe32 API.  */
31
32 /* Get declarations of the Win32 API functions.  */
33 # define WIN32_LEAN_AND_MEAN
34 # include <windows.h>
35
36 /* Upper bound on getdtablesize().  See lib/getdtablesize.c.  */
37 # define OPEN_MAX_MAX 0x10000
38
39 #else
40 /* Unix API.  */
41
42 # ifndef O_CLOEXEC
43 #  define O_CLOEXEC 0
44 # endif
45
46 #endif
47
48 int
49 dup3 (int oldfd, int newfd, int flags)
50 {
51 #if HAVE_DUP3
52 # undef dup3
53   /* Try the system call first, if it exists.  (We may be running with a glibc
54      that has the function but with an older kernel that lacks it.)  */
55   {
56     /* Cache the information whether the system call really exists.  */
57     static int have_dup3_really; /* 0 = unknown, 1 = yes, -1 = no */
58     if (have_dup3_really >= 0)
59       {
60         int result = dup3 (oldfd, newfd, flags);
61         if (!(result < 0 && errno == ENOSYS))
62           {
63             have_dup3_really = 1;
64             return result;
65           }
66         have_dup3_really = -1;
67       }
68   }
69 #endif
70
71   if (oldfd < 0 || newfd < 0 || newfd >= getdtablesize ())
72     {
73       errno = EBADF;
74       return -1;
75     }
76
77   if (newfd == oldfd)
78     {
79       errno = EINVAL;
80       return -1;
81     }
82
83   /* Check the supported flags.
84      Note that O_NONBLOCK is not supported, because setting it on newfd
85      would implicitly also set it on oldfd.  */
86   if ((flags & ~(O_CLOEXEC | O_BINARY | O_TEXT)) != 0)
87     {
88       errno = EINVAL;
89       return -1;
90     }
91
92 #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
93 /* Native Woe32 API.  */
94
95   if (flags & O_CLOEXEC)
96     {
97       /* Neither dup() nor dup2() can create a file descriptor with
98          O_CLOEXEC = O_NOINHERIT set.  We need to use the low-level function
99          _open_osfhandle for this.  Iterate until all file descriptors less
100          than newfd are filled up.  */
101       HANDLE curr_process = GetCurrentProcess ();
102       HANDLE old_handle = (HANDLE) _get_osfhandle (oldfd);
103       unsigned char fds_to_close[OPEN_MAX_MAX / CHAR_BIT];
104       unsigned int fds_to_close_bound = 0;
105       int result;
106
107       if (old_handle == INVALID_HANDLE_VALUE)
108         {
109           /* oldfd is not open, or is an unassigned standard file
110              descriptor.  */
111           errno = EBADF;
112           return -1;
113         }
114
115       close (newfd);
116
117       for (;;)
118         {
119           HANDLE new_handle;
120           int duplicated_fd;
121           unsigned int index;
122
123           if (!DuplicateHandle (curr_process,         /* SourceProcessHandle */
124                                 old_handle,           /* SourceHandle */
125                                 curr_process,         /* TargetProcessHandle */
126                                 (PHANDLE) &new_handle, /* TargetHandle */
127                                 (DWORD) 0,            /* DesiredAccess */
128                                 FALSE,                /* InheritHandle */
129                                 DUPLICATE_SAME_ACCESS)) /* Options */
130             {
131               errno = EBADF; /* arbitrary */
132               result = -1;
133               break;
134             }
135           duplicated_fd = _open_osfhandle ((long) new_handle, flags);
136           if (duplicated_fd < 0)
137             {
138               CloseHandle (new_handle);
139               result = -1;
140               break;
141             }
142           if (duplicated_fd > newfd)
143             /* Shouldn't happen, since newfd is still closed.  */
144             abort ();
145           if (duplicated_fd == newfd)
146             {
147               result = newfd;
148               break;
149             }
150
151           /* Set the bit duplicated_fd in fds_to_close[].  */
152           index = (unsigned int) duplicated_fd / CHAR_BIT;
153           if (index >= fds_to_close_bound)
154             {
155               if (index >= sizeof (fds_to_close))
156                 /* Need to increase OPEN_MAX_MAX.  */
157                 abort ();
158               memset (fds_to_close + fds_to_close_bound, '\0',
159                       index + 1 - fds_to_close_bound);
160               fds_to_close_bound = index + 1;
161             }
162           fds_to_close[index] |= 1 << ((unsigned int) duplicated_fd % CHAR_BIT);
163         }
164
165       /* Close the previous fds that turned out to be too small.  */
166       {
167         int saved_errno = errno;
168         unsigned int duplicated_fd;
169
170         for (duplicated_fd = 0;
171              duplicated_fd < fds_to_close_bound * CHAR_BIT;
172              duplicated_fd++)
173           if ((fds_to_close[duplicated_fd / CHAR_BIT]
174                >> (duplicated_fd % CHAR_BIT))
175               & 1)
176             close (duplicated_fd);
177
178         errno = saved_errno;
179       }
180
181       return result;
182     }
183
184   if (dup2 (oldfd, newfd) < 0)
185     return -1;
186
187 #else
188 /* Unix API.  */
189
190   if (dup2 (oldfd, newfd) < 0)
191     return -1;
192
193   /* POSIX <http://www.opengroup.org/onlinepubs/9699919799/functions/dup.html>
194      says that initially, the FD_CLOEXEC flag is cleared on newfd.  */
195
196   if (flags & O_CLOEXEC)
197     {
198       int fcntl_flags;
199
200       if ((fcntl_flags = fcntl (newfd, F_GETFD, 0)) < 0
201           || fcntl (newfd, F_SETFD, fcntl_flags | FD_CLOEXEC) == -1)
202         {
203           int saved_errno = errno;
204           close (newfd);
205           errno = saved_errno;
206           return -1;
207         }
208     }
209
210 #endif
211
212 #if O_BINARY
213   if (flags & O_BINARY)
214     setmode (newfd, O_BINARY);
215   else if (flags & O_TEXT)
216     setmode (newfd, O_TEXT);
217 #endif
218
219   return newfd;
220 }