Reduce code duplication.
[gnulib.git] / lib / fchdir.c
1 /* fchdir replacement.
2    Copyright (C) 2006-2008 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 <errno.h>
23 #include <fcntl.h>
24 #include <stdarg.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <dirent.h>
30
31 #include "canonicalize.h"
32 #include "dirfd.h"
33
34 /* This replacement assumes that a directory is not renamed while opened
35    through a file descriptor.  */
36
37 /* Array of file descriptors opened.  If it points to a directory, it stores
38    info about this directory; otherwise it stores an errno value of ENOTDIR.  */
39 typedef struct
40 {
41   char *name;       /* Absolute name of the directory, or NULL.  */
42   int saved_errno;  /* If name == NULL: The error code describing the failure
43                        reason.  */
44 } dir_info_t;
45 static dir_info_t *dirs;
46 static size_t dirs_allocated;
47
48 /* Try to ensure dirs has enough room for a slot at index fd.  */
49 static void
50 ensure_dirs_slot (size_t fd)
51 {
52   if (fd >= dirs_allocated)
53     {
54       size_t new_allocated;
55       dir_info_t *new_dirs;
56       size_t i;
57
58       new_allocated = 2 * dirs_allocated + 1;
59       if (new_allocated <= fd)
60         new_allocated = fd + 1;
61       new_dirs =
62         (dirs != NULL
63          ? (dir_info_t *) realloc (dirs, new_allocated * sizeof (dir_info_t))
64          : (dir_info_t *) malloc (new_allocated * sizeof (dir_info_t)));
65       if (new_dirs != NULL)
66         {
67           for (i = dirs_allocated; i < new_allocated; i++)
68             {
69               new_dirs[i].name = NULL;
70               new_dirs[i].saved_errno = ENOTDIR;
71             }
72           dirs = new_dirs;
73           dirs_allocated = new_allocated;
74         }
75     }
76 }
77
78 /* Hook into the gnulib replacements for open() and close() to keep track
79    of the open file descriptors.  */
80
81 void
82 _gl_unregister_fd (int fd)
83 {
84   if (fd >= 0 && fd < dirs_allocated)
85     {
86       if (dirs[fd].name != NULL)
87         free (dirs[fd].name);
88       dirs[fd].name = NULL;
89       dirs[fd].saved_errno = ENOTDIR;
90     }
91 }
92
93 void
94 _gl_register_fd (int fd, const char *filename)
95 {
96   struct stat statbuf;
97
98   ensure_dirs_slot (fd);
99   if (fd < dirs_allocated
100       && fstat (fd, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))
101     {
102       dirs[fd].name = canonicalize_file_name (filename);
103       if (dirs[fd].name == NULL)
104         dirs[fd].saved_errno = errno;
105     }
106 }
107
108 /* Override open() and close(), to keep track of the open file descriptors.  */
109
110 int
111 rpl_close (int fd)
112 #undef close
113 {
114   int retval = close (fd);
115
116   if (retval >= 0)
117     _gl_unregister_fd (fd);
118   return retval;
119 }
120
121 int
122 rpl_open (const char *filename, int flags, ...)
123 #undef open
124 {
125   mode_t mode;
126   int fd;
127   struct stat statbuf;
128
129   mode = 0;
130   if (flags & O_CREAT)
131     {
132       va_list arg;
133       va_start (arg, flags);
134
135       /* If mode_t is narrower than int, use the promoted type (int),
136          not mode_t.  Use sizeof to guess whether mode_t is narrower;
137          we don't know of any practical counterexamples.  */
138       mode = (sizeof (mode_t) < sizeof (int)
139               ? va_arg (arg, int)
140               : va_arg (arg, mode_t));
141
142       va_end (arg);
143     }
144 #if defined GNULIB_OPEN && ((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__)
145   if (strcmp (filename, "/dev/null") == 0)
146     filename = "NUL";
147 #endif
148   fd = open (filename, flags, mode);
149   if (fd >= 0)
150     _gl_register_fd (fd, filename);
151   return fd;
152 }
153
154 /* Override opendir() and closedir(), to keep track of the open file
155    descriptors.  Needed because there is a function dirfd().  */
156
157 int
158 rpl_closedir (DIR *dp)
159 #undef closedir
160 {
161   int fd = dirfd (dp);
162   int retval = closedir (dp);
163
164   if (retval >= 0)
165     _gl_unregister_fd (fd);
166   return retval;
167 }
168
169 DIR *
170 rpl_opendir (const char *filename)
171 #undef opendir
172 {
173   DIR *dp;
174
175   dp = opendir (filename);
176   if (dp != NULL)
177     {
178       int fd = dirfd (dp);
179       if (fd >= 0)
180         _gl_register_fd (fd, filename);
181     }
182   return dp;
183 }
184
185 /* Override dup() and dup2(), to keep track of open file descriptors.  */
186
187 int
188 rpl_dup (int oldfd)
189 #undef dup
190 {
191   int newfd = dup (oldfd);
192
193   if (oldfd >= 0 && newfd >= 0)
194     {
195       ensure_dirs_slot (newfd);
196       if (newfd < dirs_allocated)
197         {
198           if (oldfd < dirs_allocated)
199             {
200               if (dirs[oldfd].name != NULL)
201                 {
202                   dirs[newfd].name = strdup (dirs[oldfd].name);
203                   if (dirs[newfd].name == NULL)
204                     dirs[newfd].saved_errno = ENOMEM;
205                 }
206               else
207                 {
208                   dirs[newfd].name = NULL;
209                   dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
210                 }
211             }
212           else
213             {
214               dirs[newfd].name = NULL;
215               dirs[newfd].saved_errno = ENOMEM;
216             }
217         }
218     }
219   return newfd;
220 }
221
222 int
223 rpl_dup2 (int oldfd, int newfd)
224 #undef dup2
225 {
226   int retval = dup2 (oldfd, newfd);
227
228   if (retval >= 0 && oldfd >= 0 && newfd >= 0 && newfd != oldfd)
229     {
230       ensure_dirs_slot (newfd);
231       if (newfd < dirs_allocated)
232         {
233           if (oldfd < dirs_allocated)
234             {
235               if (dirs[oldfd].name != NULL)
236                 {
237                   dirs[newfd].name = strdup (dirs[oldfd].name);
238                   if (dirs[newfd].name == NULL)
239                     dirs[newfd].saved_errno = ENOMEM;
240                 }
241               else
242                 {
243                   dirs[newfd].name = NULL;
244                   dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
245                 }
246             }
247           else
248             {
249               dirs[newfd].name = NULL;
250               dirs[newfd].saved_errno = ENOMEM;
251             }
252         }
253     }
254   return retval;
255 }
256
257 /* Implement fchdir() in terms of chdir().  */
258
259 int
260 fchdir (int fd)
261 {
262   if (fd >= 0)
263     {
264       if (fd < dirs_allocated)
265         {
266           if (dirs[fd].name != NULL)
267             return chdir (dirs[fd].name);
268           else
269             {
270               errno = dirs[fd].saved_errno;
271               return -1;
272             }
273         }
274       else
275         {
276           errno = ENOMEM;
277           return -1;
278         }
279     }
280   else
281     {
282       errno = EBADF;
283       return -1;
284     }
285 }