fchdir: fix off-by-one bug in previous patch
[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 <dirent.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <stdarg.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30
31 #include "canonicalize.h"
32
33 #ifndef REPLACE_OPEN_DIRECTORY
34 # define REPLACE_OPEN_DIRECTORY 0
35 #endif
36
37 /* This replacement assumes that a directory is not renamed while opened
38    through a file descriptor.
39
40    FIXME: On mingw, this would be possible to enforce if we were to
41    also open a HANDLE to each directory currently visited by a file
42    descriptor, since mingw refuses to rename any in-use file system
43    object.  */
44
45 /* Array of file descriptors opened.  If it points to a directory, it stores
46    info about this directory; otherwise it stores an errno value of ENOTDIR.  */
47 typedef struct
48 {
49   char *name;       /* Absolute name of the directory, or NULL.  */
50   int saved_errno;  /* If name == NULL: The error code describing the failure
51                        reason.  */
52 } dir_info_t;
53 static dir_info_t *dirs;
54 static size_t dirs_allocated;
55
56 /* Try to ensure dirs has enough room for a slot at index fd.  */
57 static void
58 ensure_dirs_slot (size_t fd)
59 {
60   if (fd >= dirs_allocated)
61     {
62       size_t new_allocated;
63       dir_info_t *new_dirs;
64       size_t i;
65
66       new_allocated = 2 * dirs_allocated + 1;
67       if (new_allocated <= fd)
68         new_allocated = fd + 1;
69       new_dirs =
70         (dirs != NULL
71          ? (dir_info_t *) realloc (dirs, new_allocated * sizeof (dir_info_t))
72          : (dir_info_t *) malloc (new_allocated * sizeof (dir_info_t)));
73       if (new_dirs != NULL)
74         {
75           for (i = dirs_allocated; i < new_allocated; i++)
76             {
77               new_dirs[i].name = NULL;
78               new_dirs[i].saved_errno = ENOTDIR;
79             }
80           dirs = new_dirs;
81           dirs_allocated = new_allocated;
82         }
83     }
84 }
85
86 /* Hook into the gnulib replacements for open() and close() to keep track
87    of the open file descriptors.  */
88
89 /* Close FD, cleaning up any fd to name mapping if fd was visiting a
90    directory.  */
91 void
92 _gl_unregister_fd (int fd)
93 {
94   if (fd >= 0 && fd < dirs_allocated)
95     {
96       free (dirs[fd].name);
97       dirs[fd].name = NULL;
98       dirs[fd].saved_errno = ENOTDIR;
99     }
100 }
101
102 /* Mark FD as visiting FILENAME.  FD must be positive, and refer to an
103    open file descriptor.  If REPLACE_OPEN_DIRECTORY is non-zero, this
104    should only be called if FD is visiting a directory.  */
105 void
106 _gl_register_fd (int fd, const char *filename)
107 {
108   struct stat statbuf;
109
110   ensure_dirs_slot (fd);
111   if (fd < dirs_allocated
112       && (REPLACE_OPEN_DIRECTORY
113           || (fstat (fd, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))))
114     {
115       dirs[fd].name = canonicalize_file_name (filename);
116       if (dirs[fd].name == NULL)
117         dirs[fd].saved_errno = errno;
118     }
119 }
120
121 /* Return stat information about FD in STATBUF.  Needed when
122    rpl_open() used a dummy file to work around an open() that can't
123    normally visit directories.  */
124 #if REPLACE_OPEN_DIRECTORY
125 int
126 rpl_fstat (int fd, struct stat *statbuf)
127 {
128   if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
129     return stat (dirs[fd].name, statbuf);
130   return fstat (fd, statbuf);
131 }
132 #endif
133
134 /* Override opendir() and closedir(), to keep track of the open file
135    descriptors.  Needed because there is a function dirfd().  */
136
137 int
138 rpl_closedir (DIR *dp)
139 #undef closedir
140 {
141   int fd = dirfd (dp);
142   int retval = closedir (dp);
143
144   if (retval >= 0)
145     _gl_unregister_fd (fd);
146   return retval;
147 }
148
149 DIR *
150 rpl_opendir (const char *filename)
151 #undef opendir
152 {
153   DIR *dp;
154
155   dp = opendir (filename);
156   if (dp != NULL)
157     {
158       int fd = dirfd (dp);
159       if (fd >= 0)
160         _gl_register_fd (fd, filename);
161     }
162   return dp;
163 }
164
165 /* Override dup() and dup2(), to keep track of open file descriptors.  */
166
167 int
168 rpl_dup (int oldfd)
169 #undef dup
170 {
171   int newfd = dup (oldfd);
172
173   if (oldfd >= 0 && newfd >= 0)
174     {
175       ensure_dirs_slot (newfd);
176       if (newfd < dirs_allocated)
177         {
178           if (oldfd < dirs_allocated)
179             {
180               if (dirs[oldfd].name != NULL)
181                 {
182                   dirs[newfd].name = strdup (dirs[oldfd].name);
183                   if (dirs[newfd].name == NULL)
184                     dirs[newfd].saved_errno = ENOMEM;
185                 }
186               else
187                 {
188                   dirs[newfd].name = NULL;
189                   dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
190                 }
191             }
192           else
193             {
194               dirs[newfd].name = NULL;
195               dirs[newfd].saved_errno = ENOMEM;
196             }
197         }
198     }
199   return newfd;
200 }
201
202 /* Our <unistd.h> replacement overrides dup2 twice; be sure to pick
203    the one we want.  */
204 #if REPLACE_DUP2
205 # undef dup2
206 # define dup2 rpl_dup2
207 #endif
208
209 int
210 rpl_dup2_fchdir (int oldfd, int newfd)
211 {
212   int retval = dup2 (oldfd, newfd);
213
214   if (retval >= 0 && newfd != oldfd)
215     {
216       ensure_dirs_slot (newfd);
217       if (newfd < dirs_allocated)
218         {
219           if (oldfd < dirs_allocated)
220             {
221               if (dirs[oldfd].name != NULL)
222                 {
223                   dirs[newfd].name = strdup (dirs[oldfd].name);
224                   if (dirs[newfd].name == NULL)
225                     dirs[newfd].saved_errno = ENOMEM;
226                 }
227               else
228                 {
229                   dirs[newfd].name = NULL;
230                   dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
231                 }
232             }
233           else
234             {
235               dirs[newfd].name = NULL;
236               dirs[newfd].saved_errno = ENOMEM;
237             }
238         }
239     }
240   return retval;
241 }
242
243 /* Implement fchdir() in terms of chdir().  */
244
245 int
246 fchdir (int fd)
247 {
248   if (fd >= 0 && fd < dirs_allocated && dirs[fd].name != NULL)
249     return chdir (dirs[fd].name);
250   /* At this point, fd is either invalid, or open but not a directory.
251      If dup2 fails, errno is correctly EBADF.  */
252   if (0 <= fd)
253     {
254       if (dup2 (fd, fd) == fd)
255         errno = ENOTDIR;
256     }
257   else
258     errno = EBADF;
259   return -1;
260 }