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