Commit | Line | Data |
---|---|---|
6ec2e0f5 | 1 | /* fchdir replacement. |
7a6dbc2f | 2 | Copyright (C) 2006-2018 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 | |
7a6dbc2f | 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" | |
32 | #include "dosname.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 | static char * | |
89 | get_name (char const *dir) | |
90 | { | |
91 | char *cwd; | |
92 | char *result; | |
93 | int saved_errno; | |
94 | ||
95 | if (IS_ABSOLUTE_FILE_NAME (dir)) | |
96 | return strdup (dir); | |
97 | ||
98 | /* We often encounter "."; treat it as a special case. */ | |
99 | cwd = getcwd (NULL, 0); | |
100 | if (!cwd || (dir[0] == '.' && dir[1] == '\0')) | |
101 | return cwd; | |
102 | ||
103 | result = mfile_name_concat (cwd, dir, NULL); | |
104 | saved_errno = errno; | |
105 | free (cwd); | |
106 | errno = saved_errno; | |
107 | return result; | |
108 | } | |
109 | ||
110 | /* Hook into the gnulib replacements for open() and close() to keep track | |
111 | of the open file descriptors. */ | |
112 | ||
113 | /* Close FD, cleaning up any fd to name mapping if fd was visiting a | |
114 | directory. */ | |
115 | void | |
116 | _gl_unregister_fd (int fd) | |
117 | { | |
118 | if (fd >= 0 && fd < dirs_allocated) | |
119 | { | |
120 | free (dirs[fd].name); | |
121 | dirs[fd].name = NULL; | |
122 | } | |
123 | } | |
124 | ||
125 | /* Mark FD as visiting FILENAME. FD must be non-negative, and refer | |
126 | to an open file descriptor. If REPLACE_OPEN_DIRECTORY is non-zero, | |
127 | this should only be called if FD is visiting a directory. Close FD | |
128 | and return -1 if there is insufficient memory to track the | |
129 | directory name; otherwise return FD. */ | |
130 | int | |
131 | _gl_register_fd (int fd, const char *filename) | |
132 | { | |
133 | struct stat statbuf; | |
134 | ||
135 | assure (0 <= fd); | |
136 | if (REPLACE_OPEN_DIRECTORY | |
137 | || (fstat (fd, &statbuf) == 0 && S_ISDIR (statbuf.st_mode))) | |
138 | { | |
139 | if (!ensure_dirs_slot (fd) | |
140 | || (dirs[fd].name = get_name (filename)) == NULL) | |
141 | { | |
142 | int saved_errno = errno; | |
143 | close (fd); | |
144 | errno = saved_errno; | |
145 | return -1; | |
146 | } | |
147 | } | |
148 | return fd; | |
149 | } | |
150 | ||
151 | /* Mark NEWFD as a duplicate of OLDFD; useful from dup, dup2, dup3, | |
152 | and fcntl. Both arguments must be valid and distinct file | |
153 | descriptors. Close NEWFD and return -1 if OLDFD is tracking a | |
154 | directory, but there is insufficient memory to track the same | |
155 | directory in NEWFD; otherwise return NEWFD. */ | |
156 | int | |
157 | _gl_register_dup (int oldfd, int newfd) | |
158 | { | |
159 | assure (0 <= oldfd && 0 <= newfd && oldfd != newfd); | |
160 | if (oldfd < dirs_allocated && dirs[oldfd].name) | |
161 | { | |
162 | /* Duplicated a directory; must ensure newfd is allocated. */ | |
163 | if (!ensure_dirs_slot (newfd) | |
164 | || (dirs[newfd].name = strdup (dirs[oldfd].name)) == NULL) | |
165 | { | |
166 | int saved_errno = errno; | |
167 | close (newfd); | |
168 | errno = saved_errno; | |
169 | newfd = -1; | |
170 | } | |
171 | } | |
172 | else if (newfd < dirs_allocated) | |
173 | { | |
174 | /* Duplicated a non-directory; ensure newfd is cleared. */ | |
175 | free (dirs[newfd].name); | |
176 | dirs[newfd].name = NULL; | |
177 | } | |
178 | return newfd; | |
179 | } | |
180 | ||
181 | /* If FD is currently visiting a directory, then return the name of | |
182 | that directory. Otherwise, return NULL and set errno. */ | |
183 | const char * | |
184 | _gl_directory_name (int fd) | |
185 | { | |
186 | if (0 <= fd && fd < dirs_allocated && dirs[fd].name != NULL) | |
187 | return dirs[fd].name; | |
188 | /* At this point, fd is either invalid, or open but not a directory. | |
189 | If dup2 fails, errno is correctly EBADF. */ | |
190 | if (0 <= fd) | |
191 | { | |
192 | if (dup2 (fd, fd) == fd) | |
193 | errno = ENOTDIR; | |
194 | } | |
195 | else | |
196 | errno = EBADF; | |
197 | return NULL; | |
198 | } | |
199 | ||
200 | ||
201 | /* Implement fchdir() in terms of chdir(). */ | |
202 | ||
203 | int | |
204 | fchdir (int fd) | |
205 | { | |
206 | const char *name = _gl_directory_name (fd); | |
207 | return name ? chdir (name) : -1; | |
208 | } |