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