Commit | Line | Data |
---|---|---|
6ec2e0f5 | 1 | /* provide a chdir function that tries not to fail due to ENAMETOOLONG |
5e8754f9 | 2 | Copyright (C) 2004-2016 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 | |
5e8754f9 | 15 | along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
6ec2e0f5 SDJ |
16 | |
17 | /* written by Jim Meyering */ | |
18 | ||
19 | #include <config.h> | |
20 | ||
21 | #include "chdir-long.h" | |
22 | ||
23 | #include <errno.h> | |
24 | #include <fcntl.h> | |
25 | #include <stdlib.h> | |
26 | #include <stdbool.h> | |
27 | #include <string.h> | |
28 | #include <stdio.h> | |
29 | ||
30 | #include "assure.h" | |
31 | ||
32 | #ifndef PATH_MAX | |
33 | # error "compile this file only if your system defines PATH_MAX" | |
34 | #endif | |
35 | ||
36 | /* The results of openat() in this file are not leaked to any | |
37 | single-threaded code that could use stdio. | |
38 | FIXME - if the kernel ever adds support for multi-thread safety for | |
39 | avoiding standard fds, then we should use openat_safer. */ | |
40 | ||
41 | struct cd_buf | |
42 | { | |
43 | int fd; | |
44 | }; | |
45 | ||
46 | static void | |
47 | cdb_init (struct cd_buf *cdb) | |
48 | { | |
49 | cdb->fd = AT_FDCWD; | |
50 | } | |
51 | ||
52 | static int | |
53 | cdb_fchdir (struct cd_buf const *cdb) | |
54 | { | |
55 | return fchdir (cdb->fd); | |
56 | } | |
57 | ||
58 | static void | |
59 | cdb_free (struct cd_buf const *cdb) | |
60 | { | |
61 | if (0 <= cdb->fd) | |
62 | { | |
63 | bool close_fail = close (cdb->fd); | |
64 | assure (! close_fail); | |
65 | } | |
66 | } | |
67 | ||
68 | /* Given a file descriptor of an open directory (or AT_FDCWD), CDB->fd, | |
69 | try to open the CDB->fd-relative directory, DIR. If the open succeeds, | |
70 | update CDB->fd with the resulting descriptor, close the incoming file | |
71 | descriptor, and return zero. Upon failure, return -1 and set errno. */ | |
72 | static int | |
73 | cdb_advance_fd (struct cd_buf *cdb, char const *dir) | |
74 | { | |
75 | int new_fd = openat (cdb->fd, dir, | |
76 | O_SEARCH | O_DIRECTORY | O_NOCTTY | O_NONBLOCK); | |
77 | if (new_fd < 0) | |
78 | return -1; | |
79 | ||
80 | cdb_free (cdb); | |
81 | cdb->fd = new_fd; | |
82 | ||
83 | return 0; | |
84 | } | |
85 | ||
86 | /* Return a pointer to the first non-slash in S. */ | |
87 | static char * _GL_ATTRIBUTE_PURE | |
88 | find_non_slash (char const *s) | |
89 | { | |
90 | size_t n_slash = strspn (s, "/"); | |
91 | return (char *) s + n_slash; | |
92 | } | |
93 | ||
94 | /* This is a function much like chdir, but without the PATH_MAX limitation | |
95 | on the length of the directory name. A significant difference is that | |
96 | it must be able to modify (albeit only temporarily) the directory | |
97 | name. It handles an arbitrarily long directory name by operating | |
98 | on manageable portions of the name. On systems without the openat | |
99 | syscall, this means changing the working directory to more and more | |
100 | "distant" points along the long directory name and then restoring | |
101 | the working directory. If any of those attempts to save or restore | |
102 | the working directory fails, this function exits nonzero. | |
103 | ||
104 | Note that this function may still fail with errno == ENAMETOOLONG, but | |
105 | only if the specified directory name contains a component that is long | |
106 | enough to provoke such a failure all by itself (e.g. if the component | |
107 | has length PATH_MAX or greater on systems that define PATH_MAX). */ | |
108 | ||
109 | int | |
110 | chdir_long (char *dir) | |
111 | { | |
112 | int e = chdir (dir); | |
113 | if (e == 0 || errno != ENAMETOOLONG) | |
114 | return e; | |
115 | ||
116 | { | |
117 | size_t len = strlen (dir); | |
118 | char *dir_end = dir + len; | |
119 | struct cd_buf cdb; | |
120 | size_t n_leading_slash; | |
121 | ||
122 | cdb_init (&cdb); | |
123 | ||
124 | /* If DIR is the empty string, then the chdir above | |
125 | must have failed and set errno to ENOENT. */ | |
126 | assure (0 < len); | |
127 | assure (PATH_MAX <= len); | |
128 | ||
129 | /* Count leading slashes. */ | |
130 | n_leading_slash = strspn (dir, "/"); | |
131 | ||
132 | /* Handle any leading slashes as well as any name that matches | |
133 | the regular expression, m!^//hostname[/]*! . Handling this | |
134 | prefix separately usually results in a single additional | |
135 | cdb_advance_fd call, but it's worthwhile, since it makes the | |
136 | code in the following loop cleaner. */ | |
137 | if (n_leading_slash == 2) | |
138 | { | |
139 | int err; | |
140 | /* Find next slash. | |
141 | We already know that dir[2] is neither a slash nor '\0'. */ | |
142 | char *slash = memchr (dir + 3, '/', dir_end - (dir + 3)); | |
143 | if (slash == NULL) | |
144 | { | |
145 | errno = ENAMETOOLONG; | |
146 | return -1; | |
147 | } | |
148 | *slash = '\0'; | |
149 | err = cdb_advance_fd (&cdb, dir); | |
150 | *slash = '/'; | |
151 | if (err != 0) | |
152 | goto Fail; | |
153 | dir = find_non_slash (slash + 1); | |
154 | } | |
155 | else if (n_leading_slash) | |
156 | { | |
157 | if (cdb_advance_fd (&cdb, "/") != 0) | |
158 | goto Fail; | |
159 | dir += n_leading_slash; | |
160 | } | |
161 | ||
162 | assure (*dir != '/'); | |
163 | assure (dir <= dir_end); | |
164 | ||
165 | while (PATH_MAX <= dir_end - dir) | |
166 | { | |
167 | int err; | |
168 | /* Find a slash that is PATH_MAX or fewer bytes away from dir. | |
169 | I.e. see if there is a slash that will give us a name of | |
170 | length PATH_MAX-1 or less. */ | |
171 | char *slash = memrchr (dir, '/', PATH_MAX); | |
172 | if (slash == NULL) | |
173 | { | |
174 | errno = ENAMETOOLONG; | |
175 | return -1; | |
176 | } | |
177 | ||
178 | *slash = '\0'; | |
179 | assure (slash - dir < PATH_MAX); | |
180 | err = cdb_advance_fd (&cdb, dir); | |
181 | *slash = '/'; | |
182 | if (err != 0) | |
183 | goto Fail; | |
184 | ||
185 | dir = find_non_slash (slash + 1); | |
186 | } | |
187 | ||
188 | if (dir < dir_end) | |
189 | { | |
190 | if (cdb_advance_fd (&cdb, dir) != 0) | |
191 | goto Fail; | |
192 | } | |
193 | ||
194 | if (cdb_fchdir (&cdb) != 0) | |
195 | goto Fail; | |
196 | ||
197 | cdb_free (&cdb); | |
198 | return 0; | |
199 | ||
200 | Fail: | |
201 | { | |
202 | int saved_errno = errno; | |
203 | cdb_free (&cdb); | |
204 | errno = saved_errno; | |
205 | return -1; | |
206 | } | |
207 | } | |
208 | } | |
209 | ||
210 | #if TEST_CHDIR | |
211 | ||
212 | # include "closeout.h" | |
213 | # include "error.h" | |
214 | ||
215 | int | |
216 | main (int argc, char *argv[]) | |
217 | { | |
218 | char *line = NULL; | |
219 | size_t n = 0; | |
220 | int len; | |
221 | ||
222 | atexit (close_stdout); | |
223 | ||
224 | len = getline (&line, &n, stdin); | |
225 | if (len < 0) | |
226 | { | |
227 | int saved_errno = errno; | |
228 | if (feof (stdin)) | |
229 | exit (0); | |
230 | ||
231 | error (EXIT_FAILURE, saved_errno, | |
232 | "reading standard input"); | |
233 | } | |
234 | else if (len == 0) | |
235 | exit (0); | |
236 | ||
237 | if (line[len-1] == '\n') | |
238 | line[len-1] = '\0'; | |
239 | ||
240 | if (chdir_long (line) != 0) | |
241 | error (EXIT_FAILURE, errno, | |
242 | "chdir_long failed: %s", line); | |
243 | ||
244 | if (argc <= 1) | |
245 | { | |
246 | /* Using 'pwd' here makes sense only if it is a robust implementation, | |
247 | like the one in coreutils after the 2004-04-19 changes. */ | |
248 | char const *cmd = "pwd"; | |
249 | execlp (cmd, (char *) NULL); | |
250 | error (EXIT_FAILURE, errno, "%s", cmd); | |
251 | } | |
252 | ||
253 | fclose (stdin); | |
254 | fclose (stderr); | |
255 | ||
256 | exit (EXIT_SUCCESS); | |
257 | } | |
258 | #endif | |
259 | ||
260 | /* | |
261 | Local Variables: | |
262 | compile-command: "gcc -DTEST_CHDIR=1 -g -O -W -Wall chdir-long.c libcoreutils.a" | |
263 | End: | |
264 | */ |