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