fchdir: port to mingw
[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       if (dirs[fd].name != NULL)
97         free (dirs[fd].name);
98       dirs[fd].name = NULL;
99       dirs[fd].saved_errno = ENOTDIR;
100     }
101 }
102
103 /* Mark FD as visiting FILENAME.  FD must be positive, and refer to an
104    open file descriptor.  If REPLACE_OPEN_DIRECTORY is non-zero, this
105    should only be called if FD is visiting a directory.  */
106 void
107 _gl_register_fd (int fd, const char *filename)
108 {
109   struct stat statbuf;
110
111   ensure_dirs_slot (fd);
112   if (fd < dirs_allocated
113       && (REPLACE_OPEN_DIRECTORY
114           || (fstat (fd, &statbuf) >= 0 && S_ISDIR (statbuf.st_mode))))
115     {
116       dirs[fd].name = canonicalize_file_name (filename);
117       if (dirs[fd].name == NULL)
118         dirs[fd].saved_errno = errno;
119     }
120 }
121
122 /* Return stat information about FD in STATBUF.  Needed when
123    rpl_open() used a dummy file to work around an open() that can't
124    normally visit directories.  */
125 #if REPLACE_OPEN_DIRECTORY
126 int
127 rpl_fstat (int fd, struct stat *statbuf)
128 {
129   if (0 <= fd && fd <= dirs_allocated && dirs[fd].name != NULL)
130     return stat (dirs[fd].name, statbuf);
131   return fstat (fd, statbuf);
132 }
133 #endif
134
135 /* Override opendir() and closedir(), to keep track of the open file
136    descriptors.  Needed because there is a function dirfd().  */
137
138 int
139 rpl_closedir (DIR *dp)
140 #undef closedir
141 {
142   int fd = dirfd (dp);
143   int retval = closedir (dp);
144
145   if (retval >= 0)
146     _gl_unregister_fd (fd);
147   return retval;
148 }
149
150 DIR *
151 rpl_opendir (const char *filename)
152 #undef opendir
153 {
154   DIR *dp;
155
156   dp = opendir (filename);
157   if (dp != NULL)
158     {
159       int fd = dirfd (dp);
160       if (fd >= 0)
161         _gl_register_fd (fd, filename);
162     }
163   return dp;
164 }
165
166 /* Override dup() and dup2(), to keep track of open file descriptors.  */
167
168 int
169 rpl_dup (int oldfd)
170 #undef dup
171 {
172   int newfd = dup (oldfd);
173
174   if (oldfd >= 0 && newfd >= 0)
175     {
176       ensure_dirs_slot (newfd);
177       if (newfd < dirs_allocated)
178         {
179           if (oldfd < dirs_allocated)
180             {
181               if (dirs[oldfd].name != NULL)
182                 {
183                   dirs[newfd].name = strdup (dirs[oldfd].name);
184                   if (dirs[newfd].name == NULL)
185                     dirs[newfd].saved_errno = ENOMEM;
186                 }
187               else
188                 {
189                   dirs[newfd].name = NULL;
190                   dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
191                 }
192             }
193           else
194             {
195               dirs[newfd].name = NULL;
196               dirs[newfd].saved_errno = ENOMEM;
197             }
198         }
199     }
200   return newfd;
201 }
202
203 /* Our <unistd.h> replacement overrides dup2 twice; be sure to pick
204    the one we want.  */
205 #if REPLACE_DUP2
206 # undef dup2
207 # define dup2 rpl_dup2
208 #endif
209
210 int
211 rpl_dup2_fchdir (int oldfd, int newfd)
212 {
213   int retval = dup2 (oldfd, newfd);
214
215   if (retval >= 0 && newfd != oldfd)
216     {
217       ensure_dirs_slot (newfd);
218       if (newfd < dirs_allocated)
219         {
220           if (oldfd < dirs_allocated)
221             {
222               if (dirs[oldfd].name != NULL)
223                 {
224                   dirs[newfd].name = strdup (dirs[oldfd].name);
225                   if (dirs[newfd].name == NULL)
226                     dirs[newfd].saved_errno = ENOMEM;
227                 }
228               else
229                 {
230                   dirs[newfd].name = NULL;
231                   dirs[newfd].saved_errno = dirs[oldfd].saved_errno;
232                 }
233             }
234           else
235             {
236               dirs[newfd].name = NULL;
237               dirs[newfd].saved_errno = ENOMEM;
238             }
239         }
240     }
241   return retval;
242 }
243
244 /* Implement fchdir() in terms of chdir().  */
245
246 int
247 fchdir (int fd)
248 {
249   if (fd >= 0 && fd < dirs_allocated && dirs[fd].name != NULL)
250     return chdir (dirs[fd].name);
251   /* At this point, fd is either invalid, or open but not a directory.
252      If dup2 fails, errno is correctly EBADF.  */
253   if (0 <= fd)
254     {
255       if (dup2 (fd, fd) == fd)
256         errno = ENOTDIR;
257     }
258   else
259     errno = EBADF;
260   return -1;
261 }