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