1 |
/* provide a chdir function that tries not to fail due to ENAMETOOLONG |
/* provide a chdir function that tries not to fail due to ENAMETOOLONG |
2 |
Copyright (C) 2004 Free Software Foundation, Inc. |
Copyright (C) 2004, 2005 Free Software Foundation, Inc. |
3 |
|
|
4 |
This program is free software; you can redistribute it and/or modify |
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 |
it under the terms of the GNU General Public License as published by |
30 |
#include <assert.h> |
#include <assert.h> |
31 |
#include <limits.h> |
#include <limits.h> |
32 |
|
|
|
#include "mempcpy.h" |
|
33 |
#include "openat.h" |
#include "openat.h" |
34 |
|
|
35 |
#ifndef O_DIRECTORY |
#ifndef O_DIRECTORY |
36 |
# define O_DIRECTORY 0 |
# define O_DIRECTORY 0 |
37 |
#endif |
#endif |
38 |
|
|
|
#ifndef MIN |
|
|
# define MIN(a, b) ((a) < (b) ? (a) : (b)) |
|
|
#endif |
|
|
|
|
39 |
#ifndef PATH_MAX |
#ifndef PATH_MAX |
40 |
# error "compile this file only if your system defines PATH_MAX" |
# error "compile this file only if your system defines PATH_MAX" |
41 |
#endif |
#endif |
42 |
|
|
|
/* FIXME: this use of `MIN' is our sole concession to arbitrary limitations. |
|
|
If, for some system, PATH_MAX is larger than 8191 and you call |
|
|
chdir_long with a directory name that is longer than PATH_MAX, |
|
|
yet that contains a single component that is more than 8191 bytes |
|
|
long, then this function will fail. */ |
|
|
#define MAX_COMPONENT_LENGTH MIN (PATH_MAX - 1, 8 * 1024) |
|
|
|
|
43 |
struct cd_buf |
struct cd_buf |
44 |
{ |
{ |
|
/* FIXME maybe allocate this via malloc, rather than using the stack. |
|
|
But that would be the sole use of malloc. Is it worth it to |
|
|
let chdir_long fail due to a low-memory condition? |
|
|
But when using malloc, and assuming we remove the `concession' |
|
|
above, we'll still have to avoid allocating 2^31 bytes on |
|
|
systems that define PATH_MAX to very large number. |
|
|
Ideally, we'd allocate enough to deal with most names, and |
|
|
dynamically increase the buffer size only when necessary. */ |
|
|
char buffer[MAX_COMPONENT_LENGTH + 1]; |
|
|
char *avail; |
|
45 |
int fd; |
int fd; |
46 |
}; |
}; |
47 |
|
|
48 |
/* Like memchr, but return the number of bytes from MEM |
static inline void |
|
to the first occurrence of C thereafter. Search only |
|
|
LEN bytes. Return LEN if C is not found. */ |
|
|
static inline size_t |
|
|
memchrcspn (char const *mem, int c, size_t len) |
|
|
{ |
|
|
char const *found = memchr (mem, c, len); |
|
|
if (!found) |
|
|
return len; |
|
|
|
|
|
len = found - mem; |
|
|
return len; |
|
|
} |
|
|
|
|
|
static void |
|
49 |
cdb_init (struct cd_buf *cdb) |
cdb_init (struct cd_buf *cdb) |
50 |
{ |
{ |
|
cdb->avail = cdb->buffer; |
|
51 |
cdb->fd = AT_FDCWD; |
cdb->fd = AT_FDCWD; |
52 |
} |
} |
53 |
|
|
|
static inline bool |
|
|
cdb_empty (struct cd_buf const *cdb) |
|
|
{ |
|
|
return cdb->avail == cdb->buffer; |
|
|
} |
|
|
|
|
54 |
static inline int |
static inline int |
55 |
cdb_fchdir (struct cd_buf const *cdb) |
cdb_fchdir (struct cd_buf const *cdb) |
56 |
{ |
{ |
57 |
return fchdir (cdb->fd); |
return fchdir (cdb->fd); |
58 |
} |
} |
59 |
|
|
60 |
|
static inline void |
61 |
|
cdb_free (struct cd_buf const *cdb) |
62 |
|
{ |
63 |
|
if (0 <= cdb->fd) |
64 |
|
{ |
65 |
|
bool close_fail = close (cdb->fd); |
66 |
|
assert (! close_fail); |
67 |
|
} |
68 |
|
} |
69 |
|
|
70 |
|
/* Given a file descriptor of an open directory (or AT_FDCWD), CDB->fd, |
71 |
|
try to open the CDB->fd-relative directory, DIR. If the open succeeds, |
72 |
|
update CDB->fd with the resulting descriptor, close the incoming file |
73 |
|
descriptor, and return zero. Upon failure, return -1 and set errno. */ |
74 |
static int |
static int |
75 |
cdb_advance_fd (struct cd_buf *cdb, char const *dir) |
cdb_advance_fd (struct cd_buf *cdb, char const *dir) |
76 |
{ |
{ |
82 |
return -1; |
return -1; |
83 |
} |
} |
84 |
|
|
85 |
if (cdb->fd != AT_FDCWD) |
cdb_free (cdb); |
|
close (cdb->fd); |
|
86 |
cdb->fd = new_fd; |
cdb->fd = new_fd; |
87 |
|
|
88 |
return 0; |
return 0; |
89 |
} |
} |
90 |
|
|
91 |
static int |
/* Return a pointer to the first non-slash in S. */ |
92 |
cdb_flush (struct cd_buf *cdb) |
static inline char * |
93 |
{ |
find_non_slash (char const *s) |
|
if (cdb_empty (cdb)) |
|
|
return 0; |
|
|
|
|
|
cdb->avail[0] = '\0'; |
|
|
if (cdb_advance_fd (cdb, cdb->buffer) != 0) |
|
|
return -1; |
|
|
|
|
|
cdb->avail = cdb->buffer; |
|
|
|
|
|
return 0; |
|
|
} |
|
|
|
|
|
static void |
|
|
cdb_free (struct cd_buf *cdb) |
|
94 |
{ |
{ |
95 |
if (0 <= cdb->fd && close (cdb->fd) != 0) |
size_t n_slash = strspn (s, "/"); |
96 |
abort (); |
return (char *) s + n_slash; |
|
} |
|
|
|
|
|
static int |
|
|
cdb_append (struct cd_buf *cdb, char const *s, size_t len) |
|
|
{ |
|
|
char const *end = cdb->buffer + sizeof cdb->buffer; |
|
|
|
|
|
/* Insert a slash separator if there is a preceding byte |
|
|
and it's not a slash. */ |
|
|
bool need_slash = (cdb->buffer < cdb->avail && cdb->avail[-1] != '/'); |
|
|
size_t n_free; |
|
|
|
|
|
if (sizeof cdb->buffer < len + 1) |
|
|
{ |
|
|
/* This single component is too long. */ |
|
|
errno = ENAMETOOLONG; |
|
|
return -1; |
|
|
} |
|
|
|
|
|
/* See if there's enough room for the `/', the new component and |
|
|
a trailing NUL. */ |
|
|
n_free = end - cdb->avail; |
|
|
if (n_free < need_slash + len + 1) |
|
|
{ |
|
|
if (cdb_flush (cdb) != 0) |
|
|
return -1; |
|
|
need_slash = false; |
|
|
} |
|
|
|
|
|
if (need_slash) |
|
|
*(cdb->avail)++ = '/'; |
|
|
|
|
|
cdb->avail = mempcpy (cdb->avail, s, len); |
|
|
return 0; |
|
97 |
} |
} |
98 |
|
|
99 |
/* This is a wrapper around chdir that works even on PATH_MAX-limited |
/* This is a function much like chdir, but without the PATH_MAX limitation |
100 |
systems. It handles an arbitrarily long directory name by extracting |
on the length of the directory name. A significant difference is that |
101 |
and processing manageable portions of the name. On systems without |
it must be able to modify (albeit only temporarily) the directory |
102 |
the openat syscall, this means changing the working directory to |
name. It handles an arbitrarily long directory name by operating |
103 |
more and more `distant' points along the long directory name and |
on manageable portions of the name. On systems without the openat |
104 |
then restoring the working directory. |
syscall, this means changing the working directory to more and more |
105 |
If any of those attempts to change or restore the working directory |
`distant' points along the long directory name and then restoring |
106 |
fails, this function exits nonzero. |
the working directory. If any of those attempts to save or restore |
107 |
|
the working directory fails, this function exits nonzero. |
108 |
Note that this function may still fail with errno == ENAMETOOLONG, |
|
109 |
but only if the specified directory name contains a component that |
Note that this function may still fail with errno == ENAMETOOLONG, but |
110 |
is long enough to provoke such a failure all by itself (e.g. if the |
only if the specified directory name contains a component that is long |
111 |
component is longer than PATH_MAX on systems that define PATH_MAX). */ |
enough to provoke such a failure all by itself (e.g. if the component |
112 |
|
has length PATH_MAX or greater on systems that define PATH_MAX). */ |
113 |
|
|
114 |
int |
int |
115 |
chdir_long (char const *dir) |
chdir_long (char *dir) |
116 |
{ |
{ |
117 |
int e = chdir (dir); |
int e = chdir (dir); |
118 |
if (e == 0 || errno != ENAMETOOLONG) |
if (e == 0 || errno != ENAMETOOLONG) |
120 |
|
|
121 |
{ |
{ |
122 |
size_t len = strlen (dir); |
size_t len = strlen (dir); |
123 |
char const *dir_end = dir + len; |
char *dir_end = dir + len; |
|
char const *d; |
|
124 |
struct cd_buf cdb; |
struct cd_buf cdb; |
125 |
|
size_t n_leading_slash; |
126 |
|
|
127 |
cdb_init (&cdb); |
cdb_init (&cdb); |
128 |
|
|
129 |
/* If DIR is the empty string, then the chdir above |
/* If DIR is the empty string, then the chdir above |
130 |
must have failed and set errno to ENOENT. */ |
must have failed and set errno to ENOENT. */ |
131 |
assert (0 < len); |
assert (0 < len); |
132 |
|
assert (PATH_MAX <= len); |
133 |
|
|
134 |
|
/* Count leading slashes. */ |
135 |
|
n_leading_slash = strspn (dir, "/"); |
136 |
|
|
137 |
if (*dir == '/') |
/* Handle any leading slashes as well as any name that matches |
138 |
|
the regular expression, m!^//hostname[/]*! . Handling this |
139 |
|
prefix separately usually results in a single additional |
140 |
|
cdb_advance_fd call, but it's worthwhile, since it makes the |
141 |
|
code in the following loop cleaner. */ |
142 |
|
if (n_leading_slash == 2) |
143 |
{ |
{ |
144 |
/* Names starting with exactly two slashes followed by at least |
int err; |
145 |
one non-slash are special -- |
/* Find next slash. |
146 |
for example, in some environments //Hostname/file may |
We already know that dir[2] is neither a slash nor '\0'. */ |
147 |
denote a file on a different host. |
char *slash = memchr (dir + 3, '/', dir_end - (dir + 3)); |
148 |
Preserve those two leading slashes. Treat all other |
if (slash == NULL) |
|
sequences of slashes like a single one. */ |
|
|
if (3 <= len && dir[1] == '/' && dir[2] != '/') |
|
149 |
{ |
{ |
150 |
size_t name_len = 1 + strcspn (dir + 3, "/"); |
errno = ENAMETOOLONG; |
151 |
if (cdb_append (&cdb, dir, 2 + name_len) != 0) |
return -1; |
|
goto Fail; |
|
|
/* Advance D to next slash or to end of string. */ |
|
|
d = dir + 2 + name_len; |
|
|
assert (*d == '/' || *d == '\0'); |
|
|
} |
|
|
else |
|
|
{ |
|
|
if (cdb_append (&cdb, "/", 1) != 0) |
|
|
goto Fail; |
|
|
d = dir + 1; |
|
152 |
} |
} |
153 |
|
*slash = '\0'; |
154 |
|
err = cdb_advance_fd (&cdb, dir); |
155 |
|
*slash = '/'; |
156 |
|
if (err != 0) |
157 |
|
goto Fail; |
158 |
|
dir = find_non_slash (slash + 1); |
159 |
} |
} |
160 |
else |
else if (n_leading_slash) |
161 |
{ |
{ |
162 |
d = dir; |
if (cdb_advance_fd (&cdb, "/") != 0) |
163 |
|
goto Fail; |
164 |
|
dir += n_leading_slash; |
165 |
} |
} |
166 |
|
|
167 |
while (1) |
assert (*dir != '/'); |
168 |
|
assert (dir <= dir_end); |
169 |
|
|
170 |
|
while (PATH_MAX <= dir_end - dir) |
171 |
{ |
{ |
172 |
/* Skip any slashes to find start of next component -- |
int err; |
173 |
or the end of DIR. */ |
/* Find a slash that is PATH_MAX or fewer bytes away from dir. |
174 |
char const *start = d + strspn (d, "/"); |
I.e. see if there is a slash that will give us a name of |
175 |
if (*start == '\0') |
length PATH_MAX-1 or less. */ |
176 |
{ |
char *slash = memrchr (dir, '/', PATH_MAX); |
177 |
if (cdb_flush (&cdb) != 0) |
if (slash == NULL) |
|
goto Fail; |
|
|
break; |
|
|
} |
|
|
/* If the remaining portion is no longer than PATH_MAX, then |
|
|
flush anything that is buffered and do the rest in one chunk. */ |
|
|
if (dir_end - start <= PATH_MAX) |
|
178 |
{ |
{ |
179 |
if (cdb_flush (&cdb) != 0 |
errno = ENAMETOOLONG; |
180 |
|| cdb_advance_fd (&cdb, start) != 0) |
return -1; |
|
goto Fail; |
|
|
break; |
|
181 |
} |
} |
182 |
|
|
183 |
len = memchrcspn (start, '/', dir_end - start); |
*slash = '\0'; |
184 |
assert (len == strcspn (start, "/")); |
assert (slash - dir < PATH_MAX); |
185 |
d = start + len; |
err = cdb_advance_fd (&cdb, dir); |
186 |
if (cdb_append (&cdb, start, len) != 0) |
*slash = '/'; |
187 |
|
if (err != 0) |
188 |
|
goto Fail; |
189 |
|
|
190 |
|
dir = find_non_slash (slash + 1); |
191 |
|
} |
192 |
|
|
193 |
|
if (dir < dir_end) |
194 |
|
{ |
195 |
|
if (cdb_advance_fd (&cdb, dir) != 0) |
196 |
goto Fail; |
goto Fail; |
197 |
} |
} |
198 |
|
|
250 |
error (EXIT_FAILURE, errno, |
error (EXIT_FAILURE, errno, |
251 |
"chdir_long failed: %s", line); |
"chdir_long failed: %s", line); |
252 |
|
|
253 |
{ |
if (argc <= 1) |
254 |
/* Using `pwd' here makes sense only if it is a robust implementation, |
{ |
255 |
like the one in coreutils after the 2004-04-19 changes. */ |
/* Using `pwd' here makes sense only if it is a robust implementation, |
256 |
char const *cmd = "pwd"; |
like the one in coreutils after the 2004-04-19 changes. */ |
257 |
execlp (cmd, (char *) NULL); |
char const *cmd = "pwd"; |
258 |
error (EXIT_FAILURE, errno, "%s", cmd); |
execlp (cmd, (char *) NULL); |
259 |
} |
error (EXIT_FAILURE, errno, "%s", cmd); |
260 |
|
} |
261 |
|
|
262 |
|
fclose (stdin); |
263 |
|
fclose (stderr); |
264 |
|
|
265 |
/* not reached */ |
exit (EXIT_SUCCESS); |
|
abort (); |
|
266 |
} |
} |
267 |
#endif |
#endif |
268 |
|
|