LCOV - code coverage report
Current view: top level - lib - chdir-long.c (source / functions) Hit Total Coverage
Test: coreutils.info Lines: 0 71 0.0 %
Date: 2018-01-30 Functions: 0 6 0.0 %

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

Generated by: LCOV version 1.10