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