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