(linenum→info "unix/slp.c:2238")

coreutils/6.9/lib/chdir-long.c

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