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