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