]> git.cworth.org Git - tar/blob - gnu/fchdir.c
upstream: Fix extraction of device nodes.
[tar] / gnu / fchdir.c
1 /* -*- buffer-read-only: t -*- vi: set ro: */
2 /* DO NOT EDIT! GENERATED AUTOMATICALLY! */
3 /* fchdir replacement.
4    Copyright (C) 2006-2010 Free Software Foundation, Inc.
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 #include <config.h>
20
21 /* Specification.  */
22 #include <unistd.h>
23
24 #include <assert.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <stdbool.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <sys/stat.h>
33
34 #ifndef REPLACE_OPEN_DIRECTORY
35 # define REPLACE_OPEN_DIRECTORY 0
36 #endif
37
38 #ifndef HAVE_CANONICALIZE_FILE_NAME
39 # if GNULIB_CANONICALIZE || GNULIB_CANONICALIZE_LGPL
40 #  define HAVE_CANONICALIZE_FILE_NAME 1
41 # else
42 #  define HAVE_CANONICALIZE_FILE_NAME 0
43 #  define canonicalize_file_name(name) NULL
44 # endif
45 #endif
46
47 /* This replacement assumes that a directory is not renamed while opened
48    through a file descriptor.
49
50    FIXME: On mingw, this would be possible to enforce if we were to
51    also open a HANDLE to each directory currently visited by a file
52    descriptor, since mingw refuses to rename any in-use file system
53    object.  */
54
55 /* Array of file descriptors opened.  If REPLACE_OPEN_DIRECTORY or if it points
56    to a directory, it stores info about this directory.  */
57 typedef struct
58 {
59   char *name;       /* Absolute name of the directory, or NULL.  */
60   /* FIXME - add a DIR* member to make dirfd possible on mingw?  */
61 } dir_info_t;
62 static dir_info_t *dirs;
63 static size_t dirs_allocated;
64
65 /* Try to ensure dirs has enough room for a slot at index fd; free any
66    contents already in that slot.  Return false and set errno to
67    ENOMEM on allocation failure.  */
68 static bool
69 ensure_dirs_slot (size_t fd)
70 {
71   if (fd < dirs_allocated)
72     free (dirs[fd].name);
73   else
74     {
75       size_t new_allocated;
76       dir_info_t *new_dirs;
77
78       new_allocated = 2 * dirs_allocated + 1;
79       if (new_allocated <= fd)
80         new_allocated = fd + 1;
81       new_dirs =
82         (dirs != NULL
83          ? (dir_info_t *) realloc (dirs, new_allocated * sizeof *dirs)
84          : (dir_info_t *) malloc (new_allocated * sizeof *dirs));
85       if (new_dirs == NULL)
86         return false;
87       memset (new_dirs + dirs_allocated, 0,
88               (new_allocated - dirs_allocated) * sizeof *dirs);
89       dirs = new_dirs;
90       dirs_allocated = new_allocated;
91     }
92   return true;
93 }
94
95 /* Return the canonical name of DIR in malloc'd storage.  */
96 static char *
97 get_name (char const *dir)
98 {
99   char *result;
100   if (REPLACE_OPEN_DIRECTORY || !HAVE_CANONICALIZE_FILE_NAME)
101     {
102       /* The function canonicalize_file_name has not yet been ported
103          to mingw, with all its drive letter and backslash quirks.
104          Fortunately, getcwd is reliable in this case, but we ensure
105          we can get back to where we started before using it.  Treat
106          "." as a special case, as it is frequently encountered.  */
107       char *cwd = getcwd (NULL, 0);
108       int saved_errno;
109       if (dir[0] == '.' && dir[1] == '\0')
110         return cwd;
111       if (chdir (cwd))
112         return NULL;
113       result = chdir (dir) ? NULL : getcwd (NULL, 0);
114       saved_errno = errno;
115       if (chdir (cwd))
116         abort ();
117       free (cwd);
118       errno = saved_errno;
119     }
120   else
121     {
122       /* Avoid changing the directory.  */
123       result = canonicalize_file_name (dir);
124     }
125   return result;
126 }
127
128 /* Hook into the gnulib replacements for open() and close() to keep track
129    of the open file descriptors.  */
130
131 /* Close FD, cleaning up any fd to name mapping if fd was visiting a
132    directory.  */
133 void
134 _gl_unregister_fd (int fd)
135 {
136   if (fd >= 0 && fd < dirs_allocated)
137     {
138       free (dirs[fd].name);
139       dirs[fd].name = NULL;
140     }
141 }
142
143 /* Mark FD as visiting FILENAME.  FD must be non-negative, and refer
144    to an open file descriptor.  If REPLACE_OPEN_DIRECTORY is non-zero,
145    this should only be called if FD is visiting a directory.  Close FD
146    and return -1 if there is insufficient memory to track the
147    directory name; otherwise return FD.  */
148 int
149 _gl_register_fd (int fd, const char *filename)
150 {
151   struct stat statbuf;
152
153   assert (0 <= fd);
154   if (REPLACE_OPEN_DIRECTORY
155       || (fstat (fd, &statbuf) == 0 && S_ISDIR (statbuf.st_mode)))
156     {
157       if (!ensure_dirs_slot (fd)
158           || (dirs[fd].name = get_name (filename)) == NULL)
159         {
160           int saved_errno = errno;
161           close (fd);
162           errno = saved_errno;
163           return -1;
164         }
165     }
166   return fd;
167 }
168
169 /* Mark NEWFD as a duplicate of OLDFD; useful from dup, dup2, dup3,
170    and fcntl.  Both arguments must be valid and distinct file
171    descriptors.  Close NEWFD and return -1 if OLDFD is tracking a
172    directory, but there is insufficient memory to track the same
173    directory in NEWFD; otherwise return NEWFD.  */
174 int
175 _gl_register_dup (int oldfd, int newfd)
176 {
177   assert (0 <= oldfd && 0 <= newfd && oldfd != newfd);
178   if (oldfd < dirs_allocated && dirs[oldfd].name)
179     {
180       /* Duplicated a directory; must ensure newfd is allocated.  */
181       if (!ensure_dirs_slot (newfd)
182           || (dirs[newfd].name = strdup (dirs[oldfd].name)) == NULL)
183         {
184           int saved_errno = errno;
185           close (newfd);
186           errno = saved_errno;
187           newfd = -1;
188         }
189     }
190   else if (newfd < dirs_allocated)
191     {
192       /* Duplicated a non-directory; ensure newfd is cleared.  */
193       free (dirs[newfd].name);
194       dirs[newfd].name = NULL;
195     }
196   return newfd;
197 }
198
199 /* If FD is currently visiting a directory, then return the name of
200    that directory.  Otherwise, return NULL and set errno.  */
201 const char *
202 _gl_directory_name (int fd)
203 {
204   if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
205     return dirs[fd].name;
206   /* At this point, fd is either invalid, or open but not a directory.
207      If dup2 fails, errno is correctly EBADF.  */
208   if (0 <= fd)
209     {
210       if (dup2 (fd, fd) == fd)
211         errno = ENOTDIR;
212     }
213   else
214     errno = EBADF;
215   return NULL;
216 }
217
218 #if REPLACE_OPEN_DIRECTORY
219 /* Return stat information about FD in STATBUF.  Needed when
220    rpl_open() used a dummy file to work around an open() that can't
221    normally visit directories.  */
222 # undef fstat
223 int
224 rpl_fstat (int fd, struct stat *statbuf)
225 {
226   if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL)
227     return stat (dirs[fd].name, statbuf);
228   return fstat (fd, statbuf);
229 }
230 #endif
231
232 /* Override opendir() and closedir(), to keep track of the open file
233    descriptors.  Needed because there is a function dirfd().  */
234
235 int
236 rpl_closedir (DIR *dp)
237 #undef closedir
238 {
239   int fd = dirfd (dp);
240   int retval = closedir (dp);
241
242   if (retval >= 0)
243     _gl_unregister_fd (fd);
244   return retval;
245 }
246
247 DIR *
248 rpl_opendir (const char *filename)
249 #undef opendir
250 {
251   DIR *dp;
252
253   dp = opendir (filename);
254   if (dp != NULL)
255     {
256       int fd = dirfd (dp);
257       if (0 <= fd && _gl_register_fd (fd, filename) != fd)
258         {
259           int saved_errno = errno;
260           closedir (dp);
261           errno = saved_errno;
262           return NULL;
263         }
264     }
265   return dp;
266 }
267
268 /* Override dup(), to keep track of open file descriptors.  */
269
270 int
271 rpl_dup (int oldfd)
272 #undef dup
273 {
274   int newfd = dup (oldfd);
275
276   if (0 <= newfd)
277     newfd = _gl_register_dup (oldfd, newfd);
278   return newfd;
279 }
280
281
282 /* Implement fchdir() in terms of chdir().  */
283
284 int
285 fchdir (int fd)
286 {
287   const char *name = _gl_directory_name (fd);
288   return name ? chdir (name) : -1;
289 }