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