Blame lib/chdir-long.c

Packit 709fb3
/* provide a chdir function that tries not to fail due to ENAMETOOLONG
Packit 709fb3
   Copyright (C) 2004-2017 Free Software Foundation, Inc.
Packit 709fb3
Packit 709fb3
   This program is free software: you can redistribute it and/or modify
Packit 709fb3
   it under the terms of the GNU General Public License as published by
Packit 709fb3
   the Free Software Foundation; either version 3 of the License, or
Packit 709fb3
   (at your option) any later version.
Packit 709fb3
Packit 709fb3
   This program is distributed in the hope that it will be useful,
Packit 709fb3
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 709fb3
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 709fb3
   GNU General Public License for more details.
Packit 709fb3
Packit 709fb3
   You should have received a copy of the GNU General Public License
Packit 709fb3
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit 709fb3
Packit 709fb3
/* written by Jim Meyering */
Packit 709fb3
Packit 709fb3
#include <config.h>
Packit 709fb3
Packit 709fb3
#include "chdir-long.h"
Packit 709fb3
Packit 709fb3
#include <errno.h>
Packit 709fb3
#include <fcntl.h>
Packit 709fb3
#include <stdlib.h>
Packit 709fb3
#include <stdbool.h>
Packit 709fb3
#include <string.h>
Packit 709fb3
#include <stdio.h>
Packit 709fb3
Packit 709fb3
#include "assure.h"
Packit 709fb3
Packit 709fb3
#ifndef PATH_MAX
Packit 709fb3
# error "compile this file only if your system defines PATH_MAX"
Packit 709fb3
#endif
Packit 709fb3
Packit 709fb3
/* The results of openat() in this file are not leaked to any
Packit 709fb3
   single-threaded code that could use stdio.
Packit 709fb3
   FIXME - if the kernel ever adds support for multi-thread safety for
Packit 709fb3
   avoiding standard fds, then we should use openat_safer.  */
Packit 709fb3
Packit 709fb3
struct cd_buf
Packit 709fb3
{
Packit 709fb3
  int fd;
Packit 709fb3
};
Packit 709fb3
Packit 709fb3
static void
Packit 709fb3
cdb_init (struct cd_buf *cdb)
Packit 709fb3
{
Packit 709fb3
  cdb->fd = AT_FDCWD;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
static int
Packit 709fb3
cdb_fchdir (struct cd_buf const *cdb)
Packit 709fb3
{
Packit 709fb3
  return fchdir (cdb->fd);
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
static void
Packit 709fb3
cdb_free (struct cd_buf const *cdb)
Packit 709fb3
{
Packit 709fb3
  if (0 <= cdb->fd)
Packit 709fb3
    {
Packit 709fb3
      bool close_fail = close (cdb->fd);
Packit 709fb3
      assure (! close_fail);
Packit 709fb3
    }
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* Given a file descriptor of an open directory (or AT_FDCWD), CDB->fd,
Packit 709fb3
   try to open the CDB->fd-relative directory, DIR.  If the open succeeds,
Packit 709fb3
   update CDB->fd with the resulting descriptor, close the incoming file
Packit 709fb3
   descriptor, and return zero.  Upon failure, return -1 and set errno.  */
Packit 709fb3
static int
Packit 709fb3
cdb_advance_fd (struct cd_buf *cdb, char const *dir)
Packit 709fb3
{
Packit 709fb3
  int new_fd = openat (cdb->fd, dir,
Packit 709fb3
                       O_SEARCH | O_DIRECTORY | O_NOCTTY | O_NONBLOCK);
Packit 709fb3
  if (new_fd < 0)
Packit 709fb3
    return -1;
Packit 709fb3
Packit 709fb3
  cdb_free (cdb);
Packit 709fb3
  cdb->fd = new_fd;
Packit 709fb3
Packit 709fb3
  return 0;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* Return a pointer to the first non-slash in S.  */
Packit 709fb3
static char * _GL_ATTRIBUTE_PURE
Packit 709fb3
find_non_slash (char const *s)
Packit 709fb3
{
Packit 709fb3
  size_t n_slash = strspn (s, "/");
Packit 709fb3
  return (char *) s + n_slash;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* This is a function much like chdir, but without the PATH_MAX limitation
Packit 709fb3
   on the length of the directory name.  A significant difference is that
Packit 709fb3
   it must be able to modify (albeit only temporarily) the directory
Packit 709fb3
   name.  It handles an arbitrarily long directory name by operating
Packit 709fb3
   on manageable portions of the name.  On systems without the openat
Packit 709fb3
   syscall, this means changing the working directory to more and more
Packit 709fb3
   "distant" points along the long directory name and then restoring
Packit 709fb3
   the working directory.  If any of those attempts to save or restore
Packit 709fb3
   the working directory fails, this function exits nonzero.
Packit 709fb3
Packit 709fb3
   Note that this function may still fail with errno == ENAMETOOLONG, but
Packit 709fb3
   only if the specified directory name contains a component that is long
Packit 709fb3
   enough to provoke such a failure all by itself (e.g. if the component
Packit 709fb3
   has length PATH_MAX or greater on systems that define PATH_MAX).  */
Packit 709fb3
Packit 709fb3
int
Packit 709fb3
chdir_long (char *dir)
Packit 709fb3
{
Packit 709fb3
  int e = chdir (dir);
Packit 709fb3
  if (e == 0 || errno != ENAMETOOLONG)
Packit 709fb3
    return e;
Packit 709fb3
Packit 709fb3
  {
Packit 709fb3
    size_t len = strlen (dir);
Packit 709fb3
    char *dir_end = dir + len;
Packit 709fb3
    struct cd_buf cdb;
Packit 709fb3
    size_t n_leading_slash;
Packit 709fb3
Packit 709fb3
    cdb_init (&cdb;;
Packit 709fb3
Packit 709fb3
    /* If DIR is the empty string, then the chdir above
Packit 709fb3
       must have failed and set errno to ENOENT.  */
Packit 709fb3
    assure (0 < len);
Packit 709fb3
    assure (PATH_MAX <= len);
Packit 709fb3
Packit 709fb3
    /* Count leading slashes.  */
Packit 709fb3
    n_leading_slash = strspn (dir, "/");
Packit 709fb3
Packit 709fb3
    /* Handle any leading slashes as well as any name that matches
Packit 709fb3
       the regular expression, m!^//hostname[/]*! .  Handling this
Packit 709fb3
       prefix separately usually results in a single additional
Packit 709fb3
       cdb_advance_fd call, but it's worthwhile, since it makes the
Packit 709fb3
       code in the following loop cleaner.  */
Packit 709fb3
    if (n_leading_slash == 2)
Packit 709fb3
      {
Packit 709fb3
        int err;
Packit 709fb3
        /* Find next slash.
Packit 709fb3
           We already know that dir[2] is neither a slash nor '\0'.  */
Packit 709fb3
        char *slash = memchr (dir + 3, '/', dir_end - (dir + 3));
Packit 709fb3
        if (slash == NULL)
Packit 709fb3
          {
Packit 709fb3
            errno = ENAMETOOLONG;
Packit 709fb3
            return -1;
Packit 709fb3
          }
Packit 709fb3
        *slash = '\0';
Packit 709fb3
        err = cdb_advance_fd (&cdb, dir);
Packit 709fb3
        *slash = '/';
Packit 709fb3
        if (err != 0)
Packit 709fb3
          goto Fail;
Packit 709fb3
        dir = find_non_slash (slash + 1);
Packit 709fb3
      }
Packit 709fb3
    else if (n_leading_slash)
Packit 709fb3
      {
Packit 709fb3
        if (cdb_advance_fd (&cdb, "/") != 0)
Packit 709fb3
          goto Fail;
Packit 709fb3
        dir += n_leading_slash;
Packit 709fb3
      }
Packit 709fb3
Packit 709fb3
    assure (*dir != '/');
Packit 709fb3
    assure (dir <= dir_end);
Packit 709fb3
Packit 709fb3
    while (PATH_MAX <= dir_end - dir)
Packit 709fb3
      {
Packit 709fb3
        int err;
Packit 709fb3
        /* Find a slash that is PATH_MAX or fewer bytes away from dir.
Packit 709fb3
           I.e. see if there is a slash that will give us a name of
Packit 709fb3
           length PATH_MAX-1 or less.  */
Packit 709fb3
        char *slash = memrchr (dir, '/', PATH_MAX);
Packit 709fb3
        if (slash == NULL)
Packit 709fb3
          {
Packit 709fb3
            errno = ENAMETOOLONG;
Packit 709fb3
            return -1;
Packit 709fb3
          }
Packit 709fb3
Packit 709fb3
        *slash = '\0';
Packit 709fb3
        assure (slash - dir < PATH_MAX);
Packit 709fb3
        err = cdb_advance_fd (&cdb, dir);
Packit 709fb3
        *slash = '/';
Packit 709fb3
        if (err != 0)
Packit 709fb3
          goto Fail;
Packit 709fb3
Packit 709fb3
        dir = find_non_slash (slash + 1);
Packit 709fb3
      }
Packit 709fb3
Packit 709fb3
    if (dir < dir_end)
Packit 709fb3
      {
Packit 709fb3
        if (cdb_advance_fd (&cdb, dir) != 0)
Packit 709fb3
          goto Fail;
Packit 709fb3
      }
Packit 709fb3
Packit 709fb3
    if (cdb_fchdir (&cdb) != 0)
Packit 709fb3
      goto Fail;
Packit 709fb3
Packit 709fb3
    cdb_free (&cdb;;
Packit 709fb3
    return 0;
Packit 709fb3
Packit 709fb3
   Fail:
Packit 709fb3
    {
Packit 709fb3
      int saved_errno = errno;
Packit 709fb3
      cdb_free (&cdb;;
Packit 709fb3
      errno = saved_errno;
Packit 709fb3
      return -1;
Packit 709fb3
    }
Packit 709fb3
  }
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
#if TEST_CHDIR
Packit 709fb3
Packit 709fb3
# include "closeout.h"
Packit 709fb3
# include "error.h"
Packit 709fb3
Packit 709fb3
int
Packit 709fb3
main (int argc, char *argv[])
Packit 709fb3
{
Packit 709fb3
  char *line = NULL;
Packit 709fb3
  size_t n = 0;
Packit 709fb3
  int len;
Packit 709fb3
Packit 709fb3
  atexit (close_stdout);
Packit 709fb3
Packit 709fb3
  len = getline (&line, &n, stdin);
Packit 709fb3
  if (len < 0)
Packit 709fb3
    {
Packit 709fb3
      int saved_errno = errno;
Packit 709fb3
      if (feof (stdin))
Packit 709fb3
        exit (0);
Packit 709fb3
Packit 709fb3
      error (EXIT_FAILURE, saved_errno,
Packit 709fb3
             "reading standard input");
Packit 709fb3
    }
Packit 709fb3
  else if (len == 0)
Packit 709fb3
    exit (0);
Packit 709fb3
Packit 709fb3
  if (line[len-1] == '\n')
Packit 709fb3
    line[len-1] = '\0';
Packit 709fb3
Packit 709fb3
  if (chdir_long (line) != 0)
Packit 709fb3
    error (EXIT_FAILURE, errno,
Packit 709fb3
           "chdir_long failed: %s", line);
Packit 709fb3
Packit 709fb3
  if (argc <= 1)
Packit 709fb3
    {
Packit 709fb3
      /* Using 'pwd' here makes sense only if it is a robust implementation,
Packit 709fb3
         like the one in coreutils after the 2004-04-19 changes.  */
Packit 709fb3
      char const *cmd = "pwd";
Packit 709fb3
      execlp (cmd, (char *) NULL);
Packit 709fb3
      error (EXIT_FAILURE, errno, "%s", cmd);
Packit 709fb3
    }
Packit 709fb3
Packit 709fb3
  fclose (stdin);
Packit 709fb3
  fclose (stderr);
Packit 709fb3
Packit 709fb3
  exit (EXIT_SUCCESS);
Packit 709fb3
}
Packit 709fb3
#endif
Packit 709fb3
Packit 709fb3
/*
Packit 709fb3
Local Variables:
Packit 709fb3
compile-command: "gcc -DTEST_CHDIR=1 -g -O -W -Wall chdir-long.c libcoreutils.a"
Packit 709fb3
End:
Packit 709fb3
*/