Line data Source code
1 : /* provide a chdir function that tries not to fail due to ENAMETOOLONG
2 : Copyright (C) 2004, 2005, 2006, 2007 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 <http://www.gnu.org/licenses/>. */
16 :
17 : /* written by Jim Meyering */
18 :
19 : #include <config.h>
20 :
21 : #include "chdir-long.h"
22 :
23 : #include <fcntl.h>
24 : #include <stdlib.h>
25 : #include <stdbool.h>
26 : #include <string.h>
27 : #include <errno.h>
28 : #include <stdio.h>
29 : #include <assert.h>
30 :
31 : #include "openat.h"
32 :
33 : #ifndef PATH_MAX
34 : # error "compile this file only if your system defines PATH_MAX"
35 : #endif
36 :
37 : struct cd_buf
38 : {
39 : int fd;
40 : };
41 :
42 : static inline void
43 0 : cdb_init (struct cd_buf *cdb)
44 : {
45 0 : cdb->fd = AT_FDCWD;
46 0 : }
47 :
48 : static inline int
49 0 : cdb_fchdir (struct cd_buf const *cdb)
50 : {
51 0 : return fchdir (cdb->fd);
52 : }
53 :
54 : static inline void
55 0 : cdb_free (struct cd_buf const *cdb)
56 : {
57 0 : if (0 <= cdb->fd)
58 : {
59 0 : bool close_fail = close (cdb->fd);
60 0 : assert (! close_fail);
61 : }
62 0 : }
63 :
64 : /* Given a file descriptor of an open directory (or AT_FDCWD), CDB->fd,
65 : try to open the CDB->fd-relative directory, DIR. If the open succeeds,
66 : update CDB->fd with the resulting descriptor, close the incoming file
67 : descriptor, and return zero. Upon failure, return -1 and set errno. */
68 : static int
69 0 : cdb_advance_fd (struct cd_buf *cdb, char const *dir)
70 : {
71 0 : int new_fd = openat (cdb->fd, dir,
72 : O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK);
73 0 : if (new_fd < 0)
74 0 : return -1;
75 :
76 0 : cdb_free (cdb);
77 0 : cdb->fd = new_fd;
78 :
79 0 : return 0;
80 : }
81 :
82 : /* Return a pointer to the first non-slash in S. */
83 : static inline char *
84 0 : find_non_slash (char const *s)
85 : {
86 0 : size_t n_slash = strspn (s, "/");
87 0 : return (char *) s + n_slash;
88 : }
89 :
90 : /* This is a function much like chdir, but without the PATH_MAX limitation
91 : on the length of the directory name. A significant difference is that
92 : it must be able to modify (albeit only temporarily) the directory
93 : name. It handles an arbitrarily long directory name by operating
94 : on manageable portions of the name. On systems without the openat
95 : syscall, this means changing the working directory to more and more
96 : `distant' points along the long directory name and then restoring
97 : the working directory. If any of those attempts to save or restore
98 : the working directory fails, this function exits nonzero.
99 :
100 : Note that this function may still fail with errno == ENAMETOOLONG, but
101 : only if the specified directory name contains a component that is long
102 : enough to provoke such a failure all by itself (e.g. if the component
103 : has length PATH_MAX or greater on systems that define PATH_MAX). */
104 :
105 : int
106 0 : chdir_long (char *dir)
107 : {
108 0 : int e = chdir (dir);
109 0 : if (e == 0 || errno != ENAMETOOLONG)
110 0 : return e;
111 :
112 : {
113 0 : size_t len = strlen (dir);
114 0 : char *dir_end = dir + len;
115 : struct cd_buf cdb;
116 : size_t n_leading_slash;
117 :
118 0 : cdb_init (&cdb);
119 :
120 : /* If DIR is the empty string, then the chdir above
121 : must have failed and set errno to ENOENT. */
122 0 : assert (0 < len);
123 0 : assert (PATH_MAX <= len);
124 :
125 : /* Count leading slashes. */
126 0 : n_leading_slash = strspn (dir, "/");
127 :
128 : /* Handle any leading slashes as well as any name that matches
129 : the regular expression, m!^//hostname[/]*! . Handling this
130 : prefix separately usually results in a single additional
131 : cdb_advance_fd call, but it's worthwhile, since it makes the
132 : code in the following loop cleaner. */
133 0 : if (n_leading_slash == 2)
134 : {
135 : int err;
136 : /* Find next slash.
137 : We already know that dir[2] is neither a slash nor '\0'. */
138 0 : char *slash = memchr (dir + 3, '/', dir_end - (dir + 3));
139 0 : if (slash == NULL)
140 : {
141 0 : errno = ENAMETOOLONG;
142 0 : return -1;
143 : }
144 0 : *slash = '\0';
145 0 : err = cdb_advance_fd (&cdb, dir);
146 0 : *slash = '/';
147 0 : if (err != 0)
148 0 : goto Fail;
149 0 : dir = find_non_slash (slash + 1);
150 : }
151 0 : else if (n_leading_slash)
152 : {
153 0 : if (cdb_advance_fd (&cdb, "/") != 0)
154 0 : goto Fail;
155 0 : dir += n_leading_slash;
156 : }
157 :
158 0 : assert (*dir != '/');
159 0 : assert (dir <= dir_end);
160 :
161 0 : while (PATH_MAX <= dir_end - dir)
162 : {
163 : int err;
164 : /* Find a slash that is PATH_MAX or fewer bytes away from dir.
165 : I.e. see if there is a slash that will give us a name of
166 : length PATH_MAX-1 or less. */
167 0 : char *slash = memrchr (dir, '/', PATH_MAX);
168 0 : if (slash == NULL)
169 : {
170 0 : errno = ENAMETOOLONG;
171 0 : return -1;
172 : }
173 :
174 0 : *slash = '\0';
175 0 : assert (slash - dir < PATH_MAX);
176 0 : err = cdb_advance_fd (&cdb, dir);
177 0 : *slash = '/';
178 0 : if (err != 0)
179 0 : goto Fail;
180 :
181 0 : dir = find_non_slash (slash + 1);
182 : }
183 :
184 0 : if (dir < dir_end)
185 : {
186 0 : if (cdb_advance_fd (&cdb, dir) != 0)
187 0 : goto Fail;
188 : }
189 :
190 0 : if (cdb_fchdir (&cdb) != 0)
191 0 : goto Fail;
192 :
193 0 : cdb_free (&cdb);
194 0 : return 0;
195 :
196 0 : Fail:
197 : {
198 0 : int saved_errno = errno;
199 0 : cdb_free (&cdb);
200 0 : errno = saved_errno;
201 0 : return -1;
202 : }
203 : }
204 : }
205 :
206 : #if TEST_CHDIR
207 :
208 : # include <stdio.h>
209 : # include "closeout.h"
210 : # include "error.h"
211 :
212 : char *program_name;
213 :
214 : int
215 : main (int argc, char *argv[])
216 : {
217 : char *line = NULL;
218 : size_t n = 0;
219 : int len;
220 :
221 : program_name = argv[0];
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 : */
|