* modules/chdir-long, modules/openat: New files.
[gnulib.git] / lib / chdir-long.c
1 /* provide a chdir function that tries not to fail due to ENAMETOOLONG
2    Copyright (C) 2004 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 2, or (at your option)
7    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, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* written by Jim Meyering */
19
20 #include <config.h>
21
22 #include "chdir-long.h"
23
24 #include <stdlib.h>
25 #include <stdbool.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <errno.h>
29 #include <stdio.h>
30 #include <assert.h>
31 #include <limits.h>
32
33 #include "mempcpy.h"
34 #include "openat.h"
35
36 #ifndef O_DIRECTORY
37 # define O_DIRECTORY 0
38 #endif
39
40 #ifndef MIN
41 # define MIN(a, b) ((a) < (b) ? (a) : (b))
42 #endif
43
44 #ifndef PATH_MAX
45 # error "compile this file only if your system defines PATH_MAX"
46 #endif
47
48 /* FIXME: this use of `MIN' is our sole concession to arbitrary limitations.
49    If, for some system, PATH_MAX is larger than 8191 and you call
50    chdir_long with a directory name that is longer than PATH_MAX,
51    yet that contains a single component that is more than 8191 bytes
52    long, then this function will fail.  */
53 #define MAX_COMPONENT_LENGTH MIN (PATH_MAX - 1, 8 * 1024)
54
55 struct cd_buf
56 {
57   /* FIXME maybe allocate this via malloc, rather than using the stack.
58      But that would be the sole use of malloc.  Is it worth it to
59      let chdir_long fail due to a low-memory condition?
60      But when using malloc, and assuming we remove the `concession'
61      above, we'll still have to avoid allocating 2^31 bytes on
62      systems that define PATH_MAX to very large number.
63      Ideally, we'd allocate enough to deal with most names, and
64      dynamically increase the buffer size only when necessary.  */
65   char buffer[MAX_COMPONENT_LENGTH + 1];
66   char *avail;
67   int fd;
68 };
69
70 /* Like memchr, but return the number of bytes from MEM
71    to the first occurrence of C thereafter.  Search only
72    LEN bytes.  Return LEN if C is not found.  */
73 static inline size_t
74 memchrcspn (char const *mem, int c, size_t len)
75 {
76   char const *found = memchr (mem, c, len);
77   if (!found)
78     return len;
79
80   len = found - mem;
81   return len;
82 }
83
84 static void
85 cdb_init (struct cd_buf *cdb)
86 {
87   cdb->avail = cdb->buffer;
88   cdb->fd = AT_FDCWD;
89 }
90
91 static inline bool
92 cdb_empty (struct cd_buf const *cdb)
93 {
94   return cdb->avail == cdb->buffer;
95 }
96
97 static inline int
98 cdb_fchdir (struct cd_buf const *cdb)
99 {
100   return fchdir (cdb->fd);
101 }
102
103 static int
104 cdb_advance_fd (struct cd_buf *cdb, char const *dir)
105 {
106   int new_fd = openat (cdb->fd, dir, O_RDONLY | O_DIRECTORY);
107   if (new_fd < 0)
108     {
109       new_fd = openat (cdb->fd, dir, O_WRONLY | O_DIRECTORY);
110       if (new_fd < 0)
111         return -1;
112     }
113
114   if (cdb->fd != AT_FDCWD)
115     close (cdb->fd);
116   cdb->fd = new_fd;
117
118   return 0;
119 }
120
121 static int
122 cdb_flush (struct cd_buf *cdb)
123 {
124   if (cdb_empty (cdb))
125     return 0;
126
127   cdb->avail[0] = '\0';
128   if (cdb_advance_fd (cdb, cdb->buffer) != 0)
129     return -1;
130
131   cdb->avail = cdb->buffer;
132
133   return 0;
134 }
135
136 static void
137 cdb_free (struct cd_buf *cdb)
138 {
139   if (0 <= cdb->fd && close (cdb->fd) != 0)
140     abort ();
141 }
142
143 static int
144 cdb_append (struct cd_buf *cdb, char const *s, size_t len)
145 {
146   char const *end = cdb->buffer + sizeof cdb->buffer;
147
148   /* Insert a slash separator if there is a preceding byte
149      and it's not a slash.  */
150   bool need_slash = (cdb->buffer < cdb->avail && cdb->avail[-1] != '/');
151   size_t n_free;
152
153   if (sizeof cdb->buffer < len + 1)
154     {
155       /* This single component is too long.  */
156       errno = ENAMETOOLONG;
157       return -1;
158     }
159
160   /* See if there's enough room for the `/', the new component and
161      a trailing NUL.  */
162   n_free = end - cdb->avail;
163   if (n_free < need_slash + len + 1)
164     {
165       if (cdb_flush (cdb) != 0)
166         return -1;
167       need_slash = false;
168     }
169
170   if (need_slash)
171     *(cdb->avail)++ = '/';
172
173   cdb->avail = mempcpy (cdb->avail, s, len);
174   return 0;
175 }
176
177 /* This is a wrapper around chdir that works even on PATH_MAX-limited
178    systems.  It handles an arbitrarily long directory name by extracting
179    and processing manageable portions of the name.  On systems without
180    the openat syscall, this means changing the working directory to
181    more and more `distant' points along the long directory name and
182    then restoring the working directory.
183    If any of those attempts to change or restore the working directory
184    fails, this function exits nonzero.
185
186    Note that this function may still fail with errno == ENAMETOOLONG,
187    but only if the specified directory name contains a component that
188    is long enough to provoke such a failure all by itself (e.g. if the
189    component is longer than PATH_MAX on systems that define PATH_MAX).  */
190
191 int
192 chdir_long (char const *dir)
193 {
194   int e = chdir (dir);
195   if (e == 0 || errno != ENAMETOOLONG)
196     return e;
197
198   {
199     size_t len = strlen (dir);
200     char const *dir_end = dir + len;
201     char const *d;
202     struct cd_buf cdb;
203
204     cdb_init (&cdb);
205
206     /* If DIR is the empty string, then the chdir above
207        must have failed and set errno to ENOENT.  */
208     assert (0 < len);
209
210     if (*dir == '/')
211       {
212         /* Names starting with exactly two slashes followed by at least
213            one non-slash are special --
214            for example, in some environments //Hostname/file may
215            denote a file on a different host.
216            Preserve those two leading slashes.  Treat all other
217            sequences of slashes like a single one.  */
218         if (3 <= len && dir[1] == '/' && dir[2] != '/')
219           {
220             size_t name_len = 1 + strcspn (dir + 3, "/");
221             if (cdb_append (&cdb, dir, 2 + name_len) != 0)
222               goto Fail;
223             /* Advance D to next slash or to end of string. */
224             d = dir + 2 + name_len;
225             assert (*d == '/' || *d == '\0');
226           }
227         else
228           {
229             if (cdb_append (&cdb, "/", 1) != 0)
230               goto Fail;
231             d = dir + 1;
232           }
233       }
234     else
235       {
236         d = dir;
237       }
238
239     while (1)
240       {
241         /* Skip any slashes to find start of next component --
242            or the end of DIR. */
243         char const *start = d + strspn (d, "/");
244         if (*start == '\0')
245           {
246             if (cdb_flush (&cdb) != 0)
247               goto Fail;
248             break;
249           }
250         /* If the remaining portion is no longer than PATH_MAX, then
251            flush anything that is buffered and do the rest in one chunk.  */
252         if (dir_end - start <= PATH_MAX)
253           {
254             if (cdb_flush (&cdb) != 0
255                 || cdb_advance_fd (&cdb, start) != 0)
256               goto Fail;
257             break;
258           }
259
260         len = memchrcspn (start, '/', dir_end - start);
261         assert (len == strcspn (start, "/"));
262         d = start + len;
263         if (cdb_append (&cdb, start, len) != 0)
264           goto Fail;
265       }
266
267     if (cdb_fchdir (&cdb) != 0)
268       goto Fail;
269
270     cdb_free (&cdb);
271     return 0;
272
273    Fail:
274     {
275       int saved_errno = errno;
276       cdb_free (&cdb);
277       errno = saved_errno;
278       return -1;
279     }
280   }
281 }
282
283 #if TEST_CHDIR
284
285 # include <stdio.h>
286 # include "closeout.h"
287 # include "error.h"
288
289 char *program_name;
290
291 int
292 main (int argc, char *argv[])
293 {
294   char *line = NULL;
295   size_t n = 0;
296   int len;
297
298   program_name = argv[0];
299   atexit (close_stdout);
300
301   len = getline (&line, &n, stdin);
302   if (len < 0)
303     {
304       int saved_errno = errno;
305       if (feof (stdin))
306         exit (0);
307
308       error (EXIT_FAILURE, saved_errno,
309              "reading standard input");
310     }
311   else if (len == 0)
312     exit (0);
313
314   if (line[len-1] == '\n')
315     line[len-1] = '\0';
316
317   if (chdir_long (line) != 0)
318     error (EXIT_FAILURE, errno,
319            "chdir_long failed: %s", line);
320
321   {
322     /* Using `pwd' here makes sense only if it is a robust implementation,
323        like the one in coreutils after the 2004-04-19 changes.  */
324     char const *cmd = "pwd";
325     execlp (cmd, (char *) NULL);
326     error (EXIT_FAILURE, errno, "%s", cmd);
327   }
328
329   /* not reached */
330   abort ();
331 }
332 #endif
333
334 /*
335 Local Variables:
336 compile-command: "gcc -DTEST_CHDIR=1 -DHAVE_CONFIG_H -I.. -g -O -W -Wall chdir-long.c libfetish.a"
337 End:
338 */