1 /* fchdir replacement. 2 Copyright (C) 2006-2020 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 <https://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 <stdbool.h> 26 #include <stdlib.h> 27 #include <string.h> 28 #include <sys/types.h> 29 #include <sys/stat.h> 30 31 #include "assure.h" 32 #include "filename.h" 33 #include "filenamecat.h" 34 35 #ifndef REPLACE_OPEN_DIRECTORY 36 # define REPLACE_OPEN_DIRECTORY 0 37 #endif 38 39 /* This replacement assumes that a directory is not renamed while opened 40 through a file descriptor. 41 42 FIXME: On mingw, this would be possible to enforce if we were to 43 also open a HANDLE to each directory currently visited by a file 44 descriptor, since mingw refuses to rename any in-use file system 45 object. */ 46 47 /* Array of file descriptors opened. If REPLACE_OPEN_DIRECTORY or if it points 48 to a directory, it stores info about this directory. */ 49 typedef struct 50 { 51 char *name; /* Absolute name of the directory, or NULL. */ 52 /* FIXME - add a DIR* member to make dirfd possible on mingw? */ 53 } dir_info_t; 54 static dir_info_t *dirs; 55 static size_t dirs_allocated; 56 57 /* Try to ensure dirs has enough room for a slot at index fd; free any 58 contents already in that slot. Return false and set errno to 59 ENOMEM on allocation failure. */ 60 static bool 61 ensure_dirs_slot (size_t fd) 62 { 63 if (fd < dirs_allocated) 64 free (dirs[fd].name); 65 else 66 { 67 size_t new_allocated; 68 dir_info_t *new_dirs; 69 70 new_allocated = 2 * dirs_allocated + 1; 71 if (new_allocated <= fd) 72 new_allocated = fd + 1; 73 new_dirs = 74 (dirs != NULL 75 ? (dir_info_t *) realloc (dirs, new_allocated * sizeof *dirs) 76 : (dir_info_t *) malloc (new_allocated * sizeof *dirs)); 77 if (new_dirs == NULL) 78 return false; 79 memset (new_dirs + dirs_allocated, 0, 80 (new_allocated - dirs_allocated) * sizeof *dirs); 81 dirs = new_dirs; 82 dirs_allocated = new_allocated; 83 } 84 return true; 85 } 86 87 /* Return an absolute name of DIR in malloc'd storage. 88 Upon failure, return NULL with errno set. */ 89 static char * 90 get_name (char const *dir) 91 { 92 char *cwd; 93 char *result; 94 int saved_errno; 95 96 if (IS_ABSOLUTE_FILE_NAME (dir)) 97 return strdup (dir); 98 99 /* We often encounter "."; treat it as a special case. */ 100 cwd = getcwd (NULL, 0); 101 if (!cwd || (dir[0] == '.' && dir[1] == '\0')) 102 return cwd; 103 104 result = mfile_name_concat (cwd, dir, NULL); 105 saved_errno = errno; 106 free (cwd); 107 errno = saved_errno; 108 return result; 109 } 110 111 /* Hook into the gnulib replacements for open() and close() to keep track 112 of the open file descriptors. */ 113 114 /* Close FD, cleaning up any fd to name mapping if fd was visiting a 115 directory. */ 116 void 117 _gl_unregister_fd (int fd) 118 { 119 if (fd >= 0 && fd < dirs_allocated) 120 { 121 free (dirs[fd].name); 122 dirs[fd].name = NULL; 123 } 124 } 125 126 /* Mark FD as visiting FILENAME. FD must be non-negative, and refer 127 to an open file descriptor. If REPLACE_OPEN_DIRECTORY is non-zero, 128 this should only be called if FD is visiting a directory. Close FD 129 and return -1 with errno set if there is insufficient memory to track 130 the directory name; otherwise return FD. */ 131 int 132 _gl_register_fd (int fd, const char *filename) 133 { 134 struct stat statbuf; 135 136 assure (0 <= fd); 137 if (REPLACE_OPEN_DIRECTORY 138 || (fstat (fd, &statbuf) == 0 && S_ISDIR (statbuf.st_mode))) 139 { 140 if (!ensure_dirs_slot (fd) 141 || (dirs[fd].name = get_name (filename)) == NULL) 142 { 143 int saved_errno = errno; 144 close (fd); 145 errno = saved_errno; 146 return -1; 147 } 148 } 149 return fd; 150 } 151 152 /* Mark NEWFD as a duplicate of OLDFD; useful from dup, dup2, dup3, 153 and fcntl. Both arguments must be valid and distinct file 154 descriptors. Close NEWFD and return -1 if OLDFD is tracking a 155 directory, but there is insufficient memory to track the same 156 directory in NEWFD; otherwise return NEWFD. */ 157 int 158 _gl_register_dup (int oldfd, int newfd) 159 { 160 assure (0 <= oldfd && 0 <= newfd && oldfd != newfd); 161 if (oldfd < dirs_allocated && dirs[oldfd].name) 162 { 163 /* Duplicated a directory; must ensure newfd is allocated. */ 164 if (!ensure_dirs_slot (newfd) 165 || (dirs[newfd].name = strdup (dirs[oldfd].name)) == NULL) 166 { 167 int saved_errno = errno; 168 close (newfd); 169 errno = saved_errno; 170 newfd = -1; 171 } 172 } 173 else if (newfd < dirs_allocated) 174 { 175 /* Duplicated a non-directory; ensure newfd is cleared. */ 176 free (dirs[newfd].name); 177 dirs[newfd].name = NULL; 178 } 179 return newfd; 180 } 181 182 /* If FD is currently visiting a directory, then return the name of 183 that directory. Otherwise, return NULL and set errno. */ 184 const char * 185 _gl_directory_name (int fd) 186 { 187 if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL) 188 return dirs[fd].name; 189 /* At this point, fd is either invalid, or open but not a directory. 190 If dup2 fails, errno is correctly EBADF. */ 191 if (0 <= fd) 192 { 193 if (dup2 (fd, fd) == fd) 194 errno = ENOTDIR; 195 } 196 else 197 errno = EBADF; 198 return NULL; 199 } 200 201 202 /* Implement fchdir() in terms of chdir(). */ 203 204 int 205 fchdir (int fd) 206 { 207 const char *name = _gl_directory_name (fd); 208 return name ? chdir (name) : -1; 209 } 210