fdopendir: fix C89 compilation
[gnulib.git] / lib / fdopendir.c
1 /* provide a replacement fdopendir function
2    Copyright (C) 2004-2010 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 3 of the License, or
7    (at your option) 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
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17 /* written by Jim Meyering */
18
19 #include <config.h>
20
21 #include <dirent.h>
22
23 #include <stdlib.h>
24 #include <unistd.h>
25
26 #if !HAVE_FDOPENDIR
27
28 # include "openat.h"
29 # include "openat-priv.h"
30 # include "save-cwd.h"
31
32 # if GNULIB_DIRENT_SAFER
33 #  include "dirent--.h"
34 # endif
35
36 static DIR *fdopendir_with_dup (int, int);
37 static DIR *fd_clone_opendir (int);
38
39 /* Replacement for POSIX fdopendir.
40
41    First, try to simulate it via opendir ("/proc/self/fd/FD").  Failing
42    that, simulate it by using fchdir metadata, or by doing
43    save_cwd/fchdir/opendir(".")/restore_cwd.
44    If either the save_cwd or the restore_cwd fails (relatively unlikely),
45    then give a diagnostic and exit nonzero.
46
47    If successful, the resulting stream is based on FD in
48    implementations where streams are based on file descriptors and in
49    applications where no other thread or signal handler allocates or
50    frees file descriptors.  In other cases, consult dirfd on the result
51    to find out whether FD is still being used.
52
53    Otherwise, this function works just like POSIX fdopendir.
54
55    W A R N I N G:
56
57    Unlike other fd-related functions, this one places constraints on FD.
58    If this function returns successfully, FD is under control of the
59    dirent.h system, and the caller should not close or modify the state of
60    FD other than by the dirent.h functions.  */
61 DIR *
62 fdopendir (int fd)
63 {
64   return fdopendir_with_dup (fd, -1);
65 }
66
67 /* Like fdopendir, except that if OLDER_DUPFD is not -1, it is known
68    to be a dup of FD which is less than FD - 1 and which will be
69    closed by the caller and not otherwise used by the caller.  This
70    function makes sure that FD is closed and all file descriptors less
71    than FD are open, and then calls fd_clone_opendir on a dup of FD.
72    That way, barring race conditions, fd_clone_opendir returns a
73    stream whose file descriptor is FD.  */
74 static DIR *
75 fdopendir_with_dup (int fd, int older_dupfd)
76 {
77   int dupfd = dup (fd);
78   if (dupfd < 0 && errno == EMFILE)
79     dupfd = older_dupfd;
80   if (dupfd < 0)
81     return NULL;
82   else
83     {
84       DIR *dir;
85       int saved_errno;
86       if (dupfd < fd - 1 && dupfd != older_dupfd)
87         {
88           dir = fdopendir_with_dup (fd, dupfd);
89           saved_errno = errno;
90         }
91       else
92         {
93           close (fd);
94           dir = fd_clone_opendir (dupfd);
95           saved_errno = errno;
96           if (! dir)
97             {
98               int fd1 = dup (dupfd);
99               if (fd1 != fd)
100                 openat_save_fail (fd1 < 0 ? errno : EBADF);
101             }
102         }
103
104       if (dupfd != older_dupfd)
105         close (dupfd);
106       errno = saved_errno;
107       return dir;
108     }
109 }
110
111 /* Like fdopendir, except the result controls a clone of FD.  It is
112    the caller's responsibility both to close FD and (if the result is
113    not null) to closedir the result.  */
114 static DIR *
115 fd_clone_opendir (int fd)
116 {
117   int saved_errno;
118   DIR *dir;
119
120   char buf[OPENAT_BUFFER_SIZE];
121   char *proc_file = openat_proc_name (buf, fd, ".");
122   if (proc_file)
123     {
124       dir = opendir (proc_file);
125       saved_errno = errno;
126     }
127   else
128     {
129       dir = NULL;
130       saved_errno = EOPNOTSUPP;
131     }
132
133   /* If the syscall fails with an expected errno value, resort to
134      save_cwd/restore_cwd.  */
135   if (! dir && EXPECTED_ERRNO (saved_errno))
136     {
137 # if REPLACE_FCHDIR
138       const char *name = _gl_directory_name (fd);
139       if (name)
140         dir = opendir (name);
141       saved_errno = errno;
142 # else /* !REPLACE_FCHDIR */
143
144       /* Occupy the destination FD slot, so that save_cwd cannot hijack it.  */
145       struct saved_cwd saved_cwd;
146       int fd_reserve = dup (fd);
147       if (fd_reserve < 0)
148         {
149           saved_errno = errno;
150           dir = NULL;
151           goto fail;
152         }
153
154       if (save_cwd (&saved_cwd) != 0)
155         openat_save_fail (errno);
156
157       /* Liberate the target file descriptor, so that opendir uses it.  */
158       close (fd_reserve);
159
160       if (fchdir (fd) != 0)
161         {
162           dir = NULL;
163           saved_errno = errno;
164         }
165       else
166         {
167           dir = opendir (".");
168           saved_errno = errno;
169
170           if (restore_cwd (&saved_cwd) != 0)
171             openat_restore_fail (errno);
172         }
173
174       free_cwd (&saved_cwd);
175 # endif /* !REPLACE_FCHDIR */
176     }
177
178  fail:
179   if (proc_file != buf)
180     free (proc_file);
181   errno = saved_errno;
182   return dir;
183 }
184
185 #else /* HAVE_FDOPENDIR */
186
187 # include <errno.h>
188 # include <sys/stat.h>
189
190 # undef fdopendir
191
192 /* Like fdopendir, but work around GNU/Hurd bug by validating FD.  */
193
194 DIR *
195 rpl_fdopendir (int fd)
196 {
197   struct stat st;
198   if (fstat (fd, &st))
199     return NULL;
200   if (!S_ISDIR (st.st_mode))
201     {
202       errno = ENOTDIR;
203       return NULL;
204     }
205   return fdopendir (fd);
206 }
207
208 #endif /* HAVE_FDOPENDIR */