rename, fchdir: don't ignore chdir failure
[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       if (chdir (cwd))
110         abort ();
111       free (cwd);
112       errno = saved_errno;
113     }
114   else
115     {
116       /* Avoid changing the directory.  */
117       result = canonicalize_file_name (dir);
118     }
119   return result;
120 }
121
122 /* Hook into the gnulib replacements for open() and close() to keep track
123    of the open file descriptors.  */
124
125 /* Close FD, cleaning up any fd to name mapping if fd was visiting a
126    directory.  */
127 void
128 _gl_unregister_fd (int fd)
129 {
130   if (fd >= 0 && fd < dirs_allocated)
131     {
132       free (dirs[fd].name);
133       dirs[fd].name = NULL;
134     }
135 }
136
137 /* Mark FD as visiting FILENAME.  FD must be non-negative, and refer
138    to an open file descriptor.  If REPLACE_OPEN_DIRECTORY is non-zero,
139    this should only be called if FD is visiting a directory.  Close FD
140    and return -1 if there is insufficient memory to track the
141    directory name; otherwise return FD.  */
142 int
143 _gl_register_fd (int fd, const char *filename)
144 {
145   struct stat statbuf;
146
147   assert (0 <= fd);
148   if (REPLACE_OPEN_DIRECTORY
149       || (fstat (fd, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)))
150     {
151       if (!ensure_dirs_slot (fd)
152           || (dirs[fd].name = get_name (filename)) == NULL)
153         {
154           int saved_errno = errno;
155           close (fd);
156           errno = saved_errno;
157           return -1;
158         }
159     }
160   return fd;
161 }
162
163 /* Mark NEWFD as a duplicate of OLDFD; useful from dup, dup2, dup3,
164    and fcntl.  Both arguments must be valid and distinct file
165    descriptors.  Close NEWFD and return -1 if OLDFD is tracking a
166    directory, but there is insufficient memory to track the same
167    directory in NEWFD; otherwise return NEWFD.
168
169    FIXME: Need to implement rpl_fcntl in gnulib, and have it call
170    this.  */
171 int
172 _gl_register_dup (int oldfd, int newfd)
173 {
174   assert (0 <= oldfd && 0 <= newfd && oldfd != newfd);
175   if (oldfd < dirs_allocated && dirs[oldfd].name)
176     {
177       /* Duplicated a directory; must ensure newfd is allocated.  */
178       if (!ensure_dirs_slot (newfd)
179           || (dirs[newfd].name = strdup (dirs[oldfd].name)) == NULL)
180         {
181           int saved_errno = errno;
182           close (newfd);
183           errno = saved_errno;
184           newfd = -1;
185         }
186     }
187   else if (newfd < dirs_allocated)
188     {
189       /* Duplicated a non-directory; ensure newfd is cleared.  */
190       free (dirs[newfd].name);
191       dirs[newfd].name = NULL;
192     }
193   return newfd;
194 }
195
196 /* If FD is currently visiting a directory, then return the name of
197    that directory.  Otherwise, return NULL and set errno.  */
198 const char *
199 _gl_directory_name (int fd)
200 {
201   if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
202     return dirs[fd].name;
203   /* At this point, fd is either invalid, or open but not a directory.
204      If dup2 fails, errno is correctly EBADF.  */
205   if (0 <= fd)
206     {
207       if (dup2 (fd, fd) == fd)
208         errno = ENOTDIR;
209     }
210   else
211     errno = EBADF;
212   return NULL;
213 }
214
215 /* Return stat information about FD in STATBUF.  Needed when
216    rpl_open() used a dummy file to work around an open() that can't
217    normally visit directories.  */
218 #if REPLACE_OPEN_DIRECTORY
219 int
220 rpl_fstat (int fd, struct stat *statbuf)
221 {
222   if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
223     return stat (dirs[fd].name, statbuf);
224   return fstat (fd, statbuf);
225 }
226 #endif
227
228 /* Override opendir() and closedir(), to keep track of the open file
229    descriptors.  Needed because there is a function dirfd().  */
230
231 int
232 rpl_closedir (DIR *dp)
233 #undef closedir
234 {
235   int fd = dirfd (dp);
236   int retval = closedir (dp);
237
238   if (retval >= 0)
239     _gl_unregister_fd (fd);
240   return retval;
241 }
242
243 DIR *
244 rpl_opendir (const char *filename)
245 #undef opendir
246 {
247   DIR *dp;
248
249   dp = opendir (filename);
250   if (dp != NULL)
251     {
252       int fd = dirfd (dp);
253       if (0 <= fd && _gl_register_fd (fd, filename) != fd)
254         {
255           int saved_errno = errno;
256           closedir (dp);
257           errno = saved_errno;
258           return NULL;
259         }
260     }
261   return dp;
262 }
263
264 /* Override dup(), to keep track of open file descriptors.  */
265
266 int
267 rpl_dup (int oldfd)
268 #undef dup
269 {
270   int newfd = dup (oldfd);
271
272   if (0 <= newfd)
273     newfd = _gl_register_dup (oldfd, newfd);
274   return newfd;
275 }
276
277
278 /* Implement fchdir() in terms of chdir().  */
279
280 int
281 fchdir (int fd)
282 {
283   const char *name = _gl_directory_name (fd);
284   return name ? chdir (name) : -1;
285 }