Commit | Line | Data |
---|---|---|
6ec2e0f5 | 1 | /* fchdir replacement. |
5df4cba6 | 2 | Copyright (C) 2006-2020 Free Software Foundation, Inc. |
6ec2e0f5 SDJ |
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 | |
c0c3707f | 15 | along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
6ec2e0f5 SDJ |
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" | |
698be2d8 | 32 | #include "filename.h" |
6ec2e0f5 SDJ |
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 | ||
698be2d8 CB |
87 | /* Return an absolute name of DIR in malloc'd storage. |
88 | Upon failure, return NULL with errno set. */ | |
6ec2e0f5 SDJ |
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 | |
698be2d8 CB |
129 | and return -1 with errno set if there is insufficient memory to track |
130 | the directory name; otherwise return FD. */ | |
6ec2e0f5 SDJ |
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 | } |