Merge branch 'upstream' into stable
[gnulib.git] / lib / fchdir.c
1 /* fchdir replacement.
2    Copyright (C) 2006-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 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 #include <config.h>
18
19 /* Specification.  */
20 #include <unistd.h>
21
22 #include <assert.h>
23 #include <dirent.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <stdbool.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31
32 #ifndef REPLACE_OPEN_DIRECTORY
33 # define REPLACE_OPEN_DIRECTORY 0
34 #endif
35
36 #ifndef HAVE_CANONICALIZE_FILE_NAME
37 # if GNULIB_CANONICALIZE || GNULIB_CANONICALIZE_LGPL
38 #  define HAVE_CANONICALIZE_FILE_NAME 1
39 # else
40 #  define HAVE_CANONICALIZE_FILE_NAME 0
41 #  define canonicalize_file_name(name) NULL
42 # endif
43 #endif
44
45 /* This replacement assumes that a directory is not renamed while opened
46    through a file descriptor.
47
48    FIXME: On mingw, this would be possible to enforce if we were to
49    also open a HANDLE to each directory currently visited by a file
50    descriptor, since mingw refuses to rename any in-use file system
51    object.  */
52
53 /* Array of file descriptors opened.  If REPLACE_OPEN_DIRECTORY or if it points
54    to a directory, it stores info about this directory.  */
55 typedef struct
56 {
57   char *name;       /* Absolute name of the directory, or NULL.  */
58   /* FIXME - add a DIR* member to make dirfd possible on mingw?  */
59 } dir_info_t;
60 static dir_info_t *dirs;
61 static size_t dirs_allocated;
62
63 /* Try to ensure dirs has enough room for a slot at index fd; free any
64    contents already in that slot.  Return false and set errno to
65    ENOMEM on allocation failure.  */
66 static bool
67 ensure_dirs_slot (size_t fd)
68 {
69   if (fd < dirs_allocated)
70     free (dirs[fd].name);
71   else
72     {
73       size_t new_allocated;
74       dir_info_t *new_dirs;
75
76       new_allocated = 2 * dirs_allocated + 1;
77       if (new_allocated <= fd)
78         new_allocated = fd + 1;
79       new_dirs =
80         (dirs != NULL
81          ? (dir_info_t *) realloc (dirs, new_allocated * sizeof *dirs)
82          : (dir_info_t *) malloc (new_allocated * sizeof *dirs));
83       if (new_dirs == NULL)
84         return false;
85       memset (new_dirs + dirs_allocated, 0,
86               (new_allocated - dirs_allocated) * sizeof *dirs);
87       dirs = new_dirs;
88       dirs_allocated = new_allocated;
89     }
90   return true;
91 }
92
93 /* Return the canonical name of DIR in malloc'd storage.  */
94 static char *
95 get_name (char const *dir)
96 {
97   char *result;
98   if (REPLACE_OPEN_DIRECTORY || !HAVE_CANONICALIZE_FILE_NAME)
99     {
100       /* The function canonicalize_file_name has not yet been ported
101          to mingw, with all its drive letter and backslash quirks.
102          Fortunately, getcwd is reliable in this case, but we ensure
103          we can get back to where we started before using it.  Treat
104          "." as a special case, as it is frequently encountered.  */
105       char *cwd = getcwd (NULL, 0);
106       int saved_errno;
107       if (dir[0] == '.' && dir[1] == '\0')
108         return cwd;
109       if (chdir (cwd))
110         return NULL;
111       result = chdir (dir) ? NULL : getcwd (NULL, 0);
112       saved_errno = errno;
113       if (chdir (cwd))
114         abort ();
115       free (cwd);
116       errno = saved_errno;
117     }
118   else
119     {
120       /* Avoid changing the directory.  */
121       result = canonicalize_file_name (dir);
122     }
123   return result;
124 }
125
126 /* Hook into the gnulib replacements for open() and close() to keep track
127    of the open file descriptors.  */
128
129 /* Close FD, cleaning up any fd to name mapping if fd was visiting a
130    directory.  */
131 void
132 _gl_unregister_fd (int fd)
133 {
134   if (fd >= 0 && fd < dirs_allocated)
135     {
136       free (dirs[fd].name);
137       dirs[fd].name = NULL;
138     }
139 }
140
141 /* Mark FD as visiting FILENAME.  FD must be non-negative, and refer
142    to an open file descriptor.  If REPLACE_OPEN_DIRECTORY is non-zero,
143    this should only be called if FD is visiting a directory.  Close FD
144    and return -1 if there is insufficient memory to track the
145    directory name; otherwise return FD.  */
146 int
147 _gl_register_fd (int fd, const char *filename)
148 {
149   struct stat statbuf;
150
151   assert (0 <= fd);
152   if (REPLACE_OPEN_DIRECTORY
153       || (fstat (fd, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)))
154     {
155       if (!ensure_dirs_slot (fd)
156           || (dirs[fd].name = get_name (filename)) == NULL)
157         {
158           int saved_errno = errno;
159           close (fd);
160           errno = saved_errno;
161           return -1;
162         }
163     }
164   return fd;
165 }
166
167 /* Mark NEWFD as a duplicate of OLDFD; useful from dup, dup2, dup3,
168    and fcntl.  Both arguments must be valid and distinct file
169    descriptors.  Close NEWFD and return -1 if OLDFD is tracking a
170    directory, but there is insufficient memory to track the same
171    directory in NEWFD; otherwise return NEWFD.
172
173    FIXME: Need to implement rpl_fcntl in gnulib, and have it call
174    this.  */
175 int
176 _gl_register_dup (int oldfd, int newfd)
177 {
178   assert (0 <= oldfd && 0 <= newfd && oldfd != newfd);
179   if (oldfd < dirs_allocated && dirs[oldfd].name)
180     {
181       /* Duplicated a directory; must ensure newfd is allocated.  */
182       if (!ensure_dirs_slot (newfd)
183           || (dirs[newfd].name = strdup (dirs[oldfd].name)) == NULL)
184         {
185           int saved_errno = errno;
186           close (newfd);
187           errno = saved_errno;
188           newfd = -1;
189         }
190     }
191   else if (newfd < dirs_allocated)
192     {
193       /* Duplicated a non-directory; ensure newfd is cleared.  */
194       free (dirs[newfd].name);
195       dirs[newfd].name = NULL;
196     }
197   return newfd;
198 }
199
200 /* If FD is currently visiting a directory, then return the name of
201    that directory.  Otherwise, return NULL and set errno.  */
202 const char *
203 _gl_directory_name (int fd)
204 {
205   if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
206     return dirs[fd].name;
207   /* At this point, fd is either invalid, or open but not a directory.
208      If dup2 fails, errno is correctly EBADF.  */
209   if (0 <= fd)
210     {
211       if (dup2 (fd, fd) == fd)
212         errno = ENOTDIR;
213     }
214   else
215     errno = EBADF;
216   return NULL;
217 }
218
219 #if REPLACE_OPEN_DIRECTORY
220 /* Return stat information about FD in STATBUF.  Needed when
221    rpl_open() used a dummy file to work around an open() that can't
222    normally visit directories.  */
223 # undef fstat
224 int
225 rpl_fstat (int fd, struct stat *statbuf)
226 {
227   if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
228     return stat (dirs[fd].name, statbuf);
229   return fstat (fd, statbuf);
230 }
231 #endif
232
233 /* Override opendir() and closedir(), to keep track of the open file
234    descriptors.  Needed because there is a function dirfd().  */
235
236 int
237 rpl_closedir (DIR *dp)
238 #undef closedir
239 {
240   int fd = dirfd (dp);
241   int retval = closedir (dp);
242
243   if (retval >= 0)
244     _gl_unregister_fd (fd);
245   return retval;
246 }
247
248 DIR *
249 rpl_opendir (const char *filename)
250 #undef opendir
251 {
252   DIR *dp;
253
254   dp = opendir (filename);
255   if (dp != NULL)
256     {
257       int fd = dirfd (dp);
258       if (0 <= fd && _gl_register_fd (fd, filename) != fd)
259         {
260           int saved_errno = errno;
261           closedir (dp);
262           errno = saved_errno;
263           return NULL;
264         }
265     }
266   return dp;
267 }
268
269 /* Override dup(), to keep track of open file descriptors.  */
270
271 int
272 rpl_dup (int oldfd)
273 #undef dup
274 {
275   int newfd = dup (oldfd);
276
277   if (0 <= newfd)
278     newfd = _gl_register_dup (oldfd, newfd);
279   return newfd;
280 }
281
282
283 /* Implement fchdir() in terms of chdir().  */
284
285 int
286 fchdir (int fd)
287 {
288   const char *name = _gl_directory_name (fd);
289   return name ? chdir (name) : -1;
290 }