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