Blame lib/openat.c

Packit 709fb3
/* provide a replacement openat function
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
/* If the user's config.h happens to include <fcntl.h>, let it include only
Packit 709fb3
   the system's <fcntl.h> here, so that orig_openat doesn't recurse to
Packit 709fb3
   rpl_openat.  */
Packit 709fb3
#define __need_system_fcntl_h
Packit 709fb3
#include <config.h>
Packit 709fb3
Packit 709fb3
/* Get the original definition of open.  It might be defined as a macro.  */
Packit 709fb3
#include <fcntl.h>
Packit 709fb3
#include <sys/types.h>
Packit 709fb3
#undef __need_system_fcntl_h
Packit 709fb3
Packit 709fb3
#if HAVE_OPENAT
Packit 709fb3
static int
Packit 709fb3
orig_openat (int fd, char const *filename, int flags, mode_t mode)
Packit 709fb3
{
Packit 709fb3
  return openat (fd, filename, flags, mode);
Packit 709fb3
}
Packit 709fb3
#endif
Packit 709fb3
Packit 709fb3
/* Write "fcntl.h" here, not <fcntl.h>, otherwise OSF/1 5.1 DTK cc eliminates
Packit 709fb3
   this include because of the preliminary #include <fcntl.h> above.  */
Packit 709fb3
#include "fcntl.h"
Packit 709fb3
Packit 709fb3
#include "openat.h"
Packit 709fb3
Packit 709fb3
#include <stdarg.h>
Packit 709fb3
#include <stdbool.h>
Packit 709fb3
#include <stddef.h>
Packit 709fb3
#include <string.h>
Packit 709fb3
#include <sys/stat.h>
Packit 709fb3
#include <errno.h>
Packit 709fb3
Packit 709fb3
#if HAVE_OPENAT
Packit 709fb3
Packit 709fb3
/* Like openat, but work around Solaris 9 bugs with trailing slash.  */
Packit 709fb3
int
Packit 709fb3
rpl_openat (int dfd, char const *filename, int flags, ...)
Packit 709fb3
{
Packit 709fb3
  mode_t mode;
Packit 709fb3
  int fd;
Packit 709fb3
Packit 709fb3
  mode = 0;
Packit 709fb3
  if (flags & O_CREAT)
Packit 709fb3
    {
Packit 709fb3
      va_list arg;
Packit 709fb3
      va_start (arg, flags);
Packit 709fb3
Packit 709fb3
      /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
Packit 709fb3
         creates crashing code when 'mode_t' is smaller than 'int'.  */
Packit 709fb3
      mode = va_arg (arg, PROMOTED_MODE_T);
Packit 709fb3
Packit 709fb3
      va_end (arg);
Packit 709fb3
    }
Packit 709fb3
Packit 709fb3
# if OPEN_TRAILING_SLASH_BUG
Packit 709fb3
  /* If the filename ends in a slash and one of O_CREAT, O_WRONLY, O_RDWR
Packit 709fb3
     is specified, then fail.
Packit 709fb3
     Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
Packit 709fb3
     says that
Packit 709fb3
       "A pathname that contains at least one non-slash character and that
Packit 709fb3
        ends with one or more trailing slashes shall be resolved as if a
Packit 709fb3
        single dot character ( '.' ) were appended to the pathname."
Packit 709fb3
     and
Packit 709fb3
       "The special filename dot shall refer to the directory specified by
Packit 709fb3
        its predecessor."
Packit 709fb3
     If the named file already exists as a directory, then
Packit 709fb3
       - if O_CREAT is specified, open() must fail because of the semantics
Packit 709fb3
         of O_CREAT,
Packit 709fb3
       - if O_WRONLY or O_RDWR is specified, open() must fail because POSIX
Packit 709fb3
         <http://www.opengroup.org/susv3/functions/open.html> says that it
Packit 709fb3
         fails with errno = EISDIR in this case.
Packit 709fb3
     If the named file does not exist or does not name a directory, then
Packit 709fb3
       - if O_CREAT is specified, open() must fail since open() cannot create
Packit 709fb3
         directories,
Packit 709fb3
       - if O_WRONLY or O_RDWR is specified, open() must fail because the
Packit 709fb3
         file does not contain a '.' directory.  */
Packit 709fb3
  if (flags & (O_CREAT | O_WRONLY | O_RDWR))
Packit 709fb3
    {
Packit 709fb3
      size_t len = strlen (filename);
Packit 709fb3
      if (len > 0 && filename[len - 1] == '/')
Packit 709fb3
        {
Packit 709fb3
          errno = EISDIR;
Packit 709fb3
          return -1;
Packit 709fb3
        }
Packit 709fb3
    }
Packit 709fb3
# endif
Packit 709fb3
Packit 709fb3
  fd = orig_openat (dfd, filename, flags, mode);
Packit 709fb3
Packit 709fb3
# if OPEN_TRAILING_SLASH_BUG
Packit 709fb3
  /* If the filename ends in a slash and fd does not refer to a directory,
Packit 709fb3
     then fail.
Packit 709fb3
     Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
Packit 709fb3
     says that
Packit 709fb3
       "A pathname that contains at least one non-slash character and that
Packit 709fb3
        ends with one or more trailing slashes shall be resolved as if a
Packit 709fb3
        single dot character ( '.' ) were appended to the pathname."
Packit 709fb3
     and
Packit 709fb3
       "The special filename dot shall refer to the directory specified by
Packit 709fb3
        its predecessor."
Packit 709fb3
     If the named file without the slash is not a directory, open() must fail
Packit 709fb3
     with ENOTDIR.  */
Packit 709fb3
  if (fd >= 0)
Packit 709fb3
    {
Packit 709fb3
      /* We know len is positive, since open did not fail with ENOENT.  */
Packit 709fb3
      size_t len = strlen (filename);
Packit 709fb3
      if (filename[len - 1] == '/')
Packit 709fb3
        {
Packit 709fb3
          struct stat statbuf;
Packit 709fb3
Packit 709fb3
          if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
Packit 709fb3
            {
Packit 709fb3
              close (fd);
Packit 709fb3
              errno = ENOTDIR;
Packit 709fb3
              return -1;
Packit 709fb3
            }
Packit 709fb3
        }
Packit 709fb3
    }
Packit 709fb3
# endif
Packit 709fb3
Packit 709fb3
  return fd;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
#else /* !HAVE_OPENAT */
Packit 709fb3
Packit 709fb3
# include "dosname.h" /* solely for definition of IS_ABSOLUTE_FILE_NAME */
Packit 709fb3
# include "openat-priv.h"
Packit 709fb3
# include "save-cwd.h"
Packit 709fb3
Packit 709fb3
/* Replacement for Solaris' openat function.
Packit 709fb3
   <http://www.google.com/search?q=openat+site:docs.sun.com>
Packit 709fb3
   First, try to simulate it via open ("/proc/self/fd/FD/FILE").
Packit 709fb3
   Failing that, simulate it by doing save_cwd/fchdir/open/restore_cwd.
Packit 709fb3
   If either the save_cwd or the restore_cwd fails (relatively unlikely),
Packit 709fb3
   then give a diagnostic and exit nonzero.
Packit 709fb3
   Otherwise, upon failure, set errno and return -1, as openat does.
Packit 709fb3
   Upon successful completion, return a file descriptor.  */
Packit 709fb3
int
Packit 709fb3
openat (int fd, char const *file, int flags, ...)
Packit 709fb3
{
Packit 709fb3
  mode_t mode = 0;
Packit 709fb3
Packit 709fb3
  if (flags & O_CREAT)
Packit 709fb3
    {
Packit 709fb3
      va_list arg;
Packit 709fb3
      va_start (arg, flags);
Packit 709fb3
Packit 709fb3
      /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
Packit 709fb3
         creates crashing code when 'mode_t' is smaller than 'int'.  */
Packit 709fb3
      mode = va_arg (arg, PROMOTED_MODE_T);
Packit 709fb3
Packit 709fb3
      va_end (arg);
Packit 709fb3
    }
Packit 709fb3
Packit 709fb3
  return openat_permissive (fd, file, flags, mode, NULL);
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* Like openat (FD, FILE, FLAGS, MODE), but if CWD_ERRNO is
Packit 709fb3
   nonnull, set *CWD_ERRNO to an errno value if unable to save
Packit 709fb3
   or restore the initial working directory.  This is needed only
Packit 709fb3
   the first time remove.c's remove_dir opens a command-line
Packit 709fb3
   directory argument.
Packit 709fb3
Packit 709fb3
   If a previous attempt to restore the current working directory
Packit 709fb3
   failed, then we must not even try to access a '.'-relative name.
Packit 709fb3
   It is the caller's responsibility not to call this function
Packit 709fb3
   in that case.  */
Packit 709fb3
Packit 709fb3
int
Packit 709fb3
openat_permissive (int fd, char const *file, int flags, mode_t mode,
Packit 709fb3
                   int *cwd_errno)
Packit 709fb3
{
Packit 709fb3
  struct saved_cwd saved_cwd;
Packit 709fb3
  int saved_errno;
Packit 709fb3
  int err;
Packit 709fb3
  bool save_ok;
Packit 709fb3
Packit 709fb3
  if (fd == AT_FDCWD || IS_ABSOLUTE_FILE_NAME (file))
Packit 709fb3
    return open (file, flags, mode);
Packit 709fb3
Packit 709fb3
  {
Packit 709fb3
    char buf[OPENAT_BUFFER_SIZE];
Packit 709fb3
    char *proc_file = openat_proc_name (buf, fd, file);
Packit 709fb3
    if (proc_file)
Packit 709fb3
      {
Packit 709fb3
        int open_result = open (proc_file, flags, mode);
Packit 709fb3
        int open_errno = errno;
Packit 709fb3
        if (proc_file != buf)
Packit 709fb3
          free (proc_file);
Packit 709fb3
        /* If the syscall succeeds, or if it fails with an unexpected
Packit 709fb3
           errno value, then return right away.  Otherwise, fall through
Packit 709fb3
           and resort to using save_cwd/restore_cwd.  */
Packit 709fb3
        if (0 <= open_result || ! EXPECTED_ERRNO (open_errno))
Packit 709fb3
          {
Packit 709fb3
            errno = open_errno;
Packit 709fb3
            return open_result;
Packit 709fb3
          }
Packit 709fb3
      }
Packit 709fb3
  }
Packit 709fb3
Packit 709fb3
  save_ok = (save_cwd (&saved_cwd) == 0);
Packit 709fb3
  if (! save_ok)
Packit 709fb3
    {
Packit 709fb3
      if (! cwd_errno)
Packit 709fb3
        openat_save_fail (errno);
Packit 709fb3
      *cwd_errno = errno;
Packit 709fb3
    }
Packit 709fb3
  if (0 <= fd && fd == saved_cwd.desc)
Packit 709fb3
    {
Packit 709fb3
      /* If saving the working directory collides with the user's
Packit 709fb3
         requested fd, then the user's fd must have been closed to
Packit 709fb3
         begin with.  */
Packit 709fb3
      free_cwd (&saved_cwd);
Packit 709fb3
      errno = EBADF;
Packit 709fb3
      return -1;
Packit 709fb3
    }
Packit 709fb3
Packit 709fb3
  err = fchdir (fd);
Packit 709fb3
  saved_errno = errno;
Packit 709fb3
Packit 709fb3
  if (! err)
Packit 709fb3
    {
Packit 709fb3
      err = open (file, flags, mode);
Packit 709fb3
      saved_errno = errno;
Packit 709fb3
      if (save_ok && restore_cwd (&saved_cwd) != 0)
Packit 709fb3
        {
Packit 709fb3
          if (! cwd_errno)
Packit 709fb3
            {
Packit 709fb3
              /* Don't write a message to just-created fd 2.  */
Packit 709fb3
              saved_errno = errno;
Packit 709fb3
              if (err == STDERR_FILENO)
Packit 709fb3
                close (err);
Packit 709fb3
              openat_restore_fail (saved_errno);
Packit 709fb3
            }
Packit 709fb3
          *cwd_errno = errno;
Packit 709fb3
        }
Packit 709fb3
    }
Packit 709fb3
Packit 709fb3
  free_cwd (&saved_cwd);
Packit 709fb3
  errno = saved_errno;
Packit 709fb3
  return err;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
/* Return true if our openat implementation must resort to
Packit 709fb3
   using save_cwd and restore_cwd.  */
Packit 709fb3
bool
Packit 709fb3
openat_needs_fchdir (void)
Packit 709fb3
{
Packit 709fb3
  bool needs_fchdir = true;
Packit 709fb3
  int fd = open ("/", O_SEARCH);
Packit 709fb3
Packit 709fb3
  if (0 <= fd)
Packit 709fb3
    {
Packit 709fb3
      char buf[OPENAT_BUFFER_SIZE];
Packit 709fb3
      char *proc_file = openat_proc_name (buf, fd, ".");
Packit 709fb3
      if (proc_file)
Packit 709fb3
        {
Packit 709fb3
          needs_fchdir = false;
Packit 709fb3
          if (proc_file != buf)
Packit 709fb3
            free (proc_file);
Packit 709fb3
        }
Packit 709fb3
      close (fd);
Packit 709fb3
    }
Packit 709fb3
Packit 709fb3
  return needs_fchdir;
Packit 709fb3
}
Packit 709fb3
Packit 709fb3
#endif /* !HAVE_OPENAT */