1cf28ed85SJohn Marino /* provide a chdir function that tries not to fail due to ENAMETOOLONG 2*dc7c36e4SJohn Marino Copyright (C) 2004-2015 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 15cf28ed85SJohn Marino along with this program. If not, see <http://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 30*dc7c36e4SJohn Marino #include "assure.h" 31*dc7c36e4SJohn 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 47cf28ed85SJohn Marino cdb_init (struct cd_buf *cdb) 48cf28ed85SJohn Marino { 49cf28ed85SJohn Marino cdb->fd = AT_FDCWD; 50cf28ed85SJohn Marino } 51cf28ed85SJohn Marino 52680a9cb8SJohn Marino static int 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 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); 64*dc7c36e4SJohn 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 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 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 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. */ 126*dc7c36e4SJohn Marino assure (0 < len); 127*dc7c36e4SJohn 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 162*dc7c36e4SJohn Marino assure (*dir != '/'); 163*dc7c36e4SJohn 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'; 179*dc7c36e4SJohn 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 char *program_name; 216cf28ed85SJohn Marino 217cf28ed85SJohn Marino int 218cf28ed85SJohn Marino main (int argc, char *argv[]) 219cf28ed85SJohn Marino { 220cf28ed85SJohn Marino char *line = NULL; 221cf28ed85SJohn Marino size_t n = 0; 222cf28ed85SJohn Marino int len; 223cf28ed85SJohn Marino 224cf28ed85SJohn Marino program_name = argv[0]; 225cf28ed85SJohn Marino atexit (close_stdout); 226cf28ed85SJohn Marino 227cf28ed85SJohn Marino len = getline (&line, &n, stdin); 228cf28ed85SJohn Marino if (len < 0) 229cf28ed85SJohn Marino { 230cf28ed85SJohn Marino int saved_errno = errno; 231cf28ed85SJohn Marino if (feof (stdin)) 232cf28ed85SJohn Marino exit (0); 233cf28ed85SJohn Marino 234cf28ed85SJohn Marino error (EXIT_FAILURE, saved_errno, 235cf28ed85SJohn Marino "reading standard input"); 236cf28ed85SJohn Marino } 237cf28ed85SJohn Marino else if (len == 0) 238cf28ed85SJohn Marino exit (0); 239cf28ed85SJohn Marino 240cf28ed85SJohn Marino if (line[len-1] == '\n') 241cf28ed85SJohn Marino line[len-1] = '\0'; 242cf28ed85SJohn Marino 243cf28ed85SJohn Marino if (chdir_long (line) != 0) 244cf28ed85SJohn Marino error (EXIT_FAILURE, errno, 245cf28ed85SJohn Marino "chdir_long failed: %s", line); 246cf28ed85SJohn Marino 247cf28ed85SJohn Marino if (argc <= 1) 248cf28ed85SJohn Marino { 249cf28ed85SJohn Marino /* Using 'pwd' here makes sense only if it is a robust implementation, 250cf28ed85SJohn Marino like the one in coreutils after the 2004-04-19 changes. */ 251cf28ed85SJohn Marino char const *cmd = "pwd"; 252cf28ed85SJohn Marino execlp (cmd, (char *) NULL); 253cf28ed85SJohn Marino error (EXIT_FAILURE, errno, "%s", cmd); 254cf28ed85SJohn Marino } 255cf28ed85SJohn Marino 256cf28ed85SJohn Marino fclose (stdin); 257cf28ed85SJohn Marino fclose (stderr); 258cf28ed85SJohn Marino 259cf28ed85SJohn Marino exit (EXIT_SUCCESS); 260cf28ed85SJohn Marino } 261cf28ed85SJohn Marino #endif 262cf28ed85SJohn Marino 263cf28ed85SJohn Marino /* 264cf28ed85SJohn Marino Local Variables: 265cf28ed85SJohn Marino compile-command: "gcc -DTEST_CHDIR=1 -g -O -W -Wall chdir-long.c libcoreutils.a" 266cf28ed85SJohn Marino End: 267cf28ed85SJohn Marino */ 268