Blame lib/open.c

Packit 8f70b4
/* Open a descriptor to a file.
Packit 8f70b4
   Copyright (C) 2007-2018 Free Software Foundation, Inc.
Packit 8f70b4
Packit 8f70b4
   This program is free software: you can redistribute it and/or modify
Packit 8f70b4
   it under the terms of the GNU General Public License as published by
Packit 8f70b4
   the Free Software Foundation; either version 3 of the License, or
Packit 8f70b4
   (at your option) any later version.
Packit 8f70b4
Packit 8f70b4
   This program is distributed in the hope that it will be useful,
Packit 8f70b4
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8f70b4
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8f70b4
   GNU General Public License for more details.
Packit 8f70b4
Packit 8f70b4
   You should have received a copy of the GNU General Public License
Packit 8f70b4
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
Packit 8f70b4
Packit 8f70b4
/* Written by Bruno Haible <bruno@clisp.org>, 2007.  */
Packit 8f70b4
Packit 8f70b4
/* If the user's config.h happens to include <fcntl.h>, let it include only
Packit 8f70b4
   the system's <fcntl.h> here, so that orig_open doesn't recurse to
Packit 8f70b4
   rpl_open.  */
Packit 8f70b4
#define __need_system_fcntl_h
Packit 8f70b4
#include <config.h>
Packit 8f70b4
Packit 8f70b4
/* Get the original definition of open.  It might be defined as a macro.  */
Packit 8f70b4
#include <fcntl.h>
Packit 8f70b4
#include <sys/types.h>
Packit 8f70b4
#undef __need_system_fcntl_h
Packit 8f70b4
Packit 8f70b4
static int
Packit 8f70b4
orig_open (const char *filename, int flags, mode_t mode)
Packit 8f70b4
{
Packit 8f70b4
  return open (filename, flags, mode);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Specification.  */
Packit 8f70b4
/* Write "fcntl.h" here, not <fcntl.h>, otherwise OSF/1 5.1 DTK cc eliminates
Packit 8f70b4
   this include because of the preliminary #include <fcntl.h> above.  */
Packit 8f70b4
#include "fcntl.h"
Packit 8f70b4
Packit 8f70b4
#include "cloexec.h"
Packit 8f70b4
Packit 8f70b4
#include <errno.h>
Packit 8f70b4
#include <stdarg.h>
Packit 8f70b4
#include <string.h>
Packit 8f70b4
#include <sys/types.h>
Packit 8f70b4
#include <sys/stat.h>
Packit 8f70b4
#include <unistd.h>
Packit 8f70b4
Packit 8f70b4
#ifndef REPLACE_OPEN_DIRECTORY
Packit 8f70b4
# define REPLACE_OPEN_DIRECTORY 0
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
int
Packit 8f70b4
open (const char *filename, int flags, ...)
Packit 8f70b4
{
Packit 8f70b4
  /* 0 = unknown, 1 = yes, -1 = no.  */
Packit 8f70b4
#if GNULIB_defined_O_CLOEXEC
Packit 8f70b4
  int have_cloexec = -1;
Packit 8f70b4
#else
Packit 8f70b4
  static int have_cloexec;
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
  mode_t mode;
Packit 8f70b4
  int fd;
Packit 8f70b4
Packit 8f70b4
  mode = 0;
Packit 8f70b4
  if (flags & O_CREAT)
Packit 8f70b4
    {
Packit 8f70b4
      va_list arg;
Packit 8f70b4
      va_start (arg, flags);
Packit 8f70b4
Packit 8f70b4
      /* We have to use PROMOTED_MODE_T instead of mode_t, otherwise GCC 4
Packit 8f70b4
         creates crashing code when 'mode_t' is smaller than 'int'.  */
Packit 8f70b4
      mode = va_arg (arg, PROMOTED_MODE_T);
Packit 8f70b4
Packit 8f70b4
      va_end (arg);
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
#if GNULIB_defined_O_NONBLOCK
Packit 8f70b4
  /* The only known platform that lacks O_NONBLOCK is mingw, but it
Packit 8f70b4
     also lacks named pipes and Unix sockets, which are the only two
Packit 8f70b4
     file types that require non-blocking handling in open().
Packit 8f70b4
     Therefore, it is safe to ignore O_NONBLOCK here.  It is handy
Packit 8f70b4
     that mingw also lacks openat(), so that is also covered here.  */
Packit 8f70b4
  flags &= ~O_NONBLOCK;
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
#if defined _WIN32 && ! defined __CYGWIN__
Packit 8f70b4
  if (strcmp (filename, "/dev/null") == 0)
Packit 8f70b4
    filename = "NUL";
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
#if OPEN_TRAILING_SLASH_BUG
Packit 8f70b4
  /* If the filename ends in a slash and one of O_CREAT, O_WRONLY, O_RDWR
Packit 8f70b4
     is specified, then fail.
Packit 8f70b4
     Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
Packit 8f70b4
     says that
Packit 8f70b4
       "A pathname that contains at least one non-slash character and that
Packit 8f70b4
        ends with one or more trailing slashes shall be resolved as if a
Packit 8f70b4
        single dot character ( '.' ) were appended to the pathname."
Packit 8f70b4
     and
Packit 8f70b4
       "The special filename dot shall refer to the directory specified by
Packit 8f70b4
        its predecessor."
Packit 8f70b4
     If the named file already exists as a directory, then
Packit 8f70b4
       - if O_CREAT is specified, open() must fail because of the semantics
Packit 8f70b4
         of O_CREAT,
Packit 8f70b4
       - if O_WRONLY or O_RDWR is specified, open() must fail because POSIX
Packit 8f70b4
         <http://www.opengroup.org/susv3/functions/open.html> says that it
Packit 8f70b4
         fails with errno = EISDIR in this case.
Packit 8f70b4
     If the named file does not exist or does not name a directory, then
Packit 8f70b4
       - if O_CREAT is specified, open() must fail since open() cannot create
Packit 8f70b4
         directories,
Packit 8f70b4
       - if O_WRONLY or O_RDWR is specified, open() must fail because the
Packit 8f70b4
         file does not contain a '.' directory.  */
Packit 8f70b4
  if (flags & (O_CREAT | O_WRONLY | O_RDWR))
Packit 8f70b4
    {
Packit 8f70b4
      size_t len = strlen (filename);
Packit 8f70b4
      if (len > 0 && filename[len - 1] == '/')
Packit 8f70b4
        {
Packit 8f70b4
          errno = EISDIR;
Packit 8f70b4
          return -1;
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
  fd = orig_open (filename,
Packit 8f70b4
                  flags & ~(have_cloexec <= 0 ? O_CLOEXEC : 0), mode);
Packit 8f70b4
Packit 8f70b4
  if (flags & O_CLOEXEC)
Packit 8f70b4
    {
Packit 8f70b4
      if (! have_cloexec)
Packit 8f70b4
        {
Packit 8f70b4
          if (0 <= fd)
Packit 8f70b4
            have_cloexec = 1;
Packit 8f70b4
          else if (errno == EINVAL)
Packit 8f70b4
            {
Packit 8f70b4
              fd = orig_open (filename, flags & ~O_CLOEXEC, mode);
Packit 8f70b4
              have_cloexec = -1;
Packit 8f70b4
            }
Packit 8f70b4
        }
Packit 8f70b4
      if (have_cloexec < 0 && 0 <= fd)
Packit 8f70b4
        set_cloexec_flag (fd, true);
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
Packit 8f70b4
#if REPLACE_FCHDIR
Packit 8f70b4
  /* Implementing fchdir and fdopendir requires the ability to open a
Packit 8f70b4
     directory file descriptor.  If open doesn't support that (as on
Packit 8f70b4
     mingw), we use a dummy file that behaves the same as directories
Packit 8f70b4
     on Linux (ie. always reports EOF on attempts to read()), and
Packit 8f70b4
     override fstat() in fchdir.c to hide the fact that we have a
Packit 8f70b4
     dummy.  */
Packit 8f70b4
  if (REPLACE_OPEN_DIRECTORY && fd < 0 && errno == EACCES
Packit 8f70b4
      && ((flags & O_ACCMODE) == O_RDONLY
Packit 8f70b4
          || (O_SEARCH != O_RDONLY && (flags & O_ACCMODE) == O_SEARCH)))
Packit 8f70b4
    {
Packit 8f70b4
      struct stat statbuf;
Packit 8f70b4
      if (stat (filename, &statbuf) == 0 && S_ISDIR (statbuf.st_mode))
Packit 8f70b4
        {
Packit 8f70b4
          /* Maximum recursion depth of 1.  */
Packit 8f70b4
          fd = open ("/dev/null", flags, mode);
Packit 8f70b4
          if (0 <= fd)
Packit 8f70b4
            fd = _gl_register_fd (fd, filename);
Packit 8f70b4
        }
Packit 8f70b4
      else
Packit 8f70b4
        errno = EACCES;
Packit 8f70b4
    }
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
#if OPEN_TRAILING_SLASH_BUG
Packit 8f70b4
  /* If the filename ends in a slash and fd does not refer to a directory,
Packit 8f70b4
     then fail.
Packit 8f70b4
     Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
Packit 8f70b4
     says that
Packit 8f70b4
       "A pathname that contains at least one non-slash character and that
Packit 8f70b4
        ends with one or more trailing slashes shall be resolved as if a
Packit 8f70b4
        single dot character ( '.' ) were appended to the pathname."
Packit 8f70b4
     and
Packit 8f70b4
       "The special filename dot shall refer to the directory specified by
Packit 8f70b4
        its predecessor."
Packit 8f70b4
     If the named file without the slash is not a directory, open() must fail
Packit 8f70b4
     with ENOTDIR.  */
Packit 8f70b4
  if (fd >= 0)
Packit 8f70b4
    {
Packit 8f70b4
      /* We know len is positive, since open did not fail with ENOENT.  */
Packit 8f70b4
      size_t len = strlen (filename);
Packit 8f70b4
      if (filename[len - 1] == '/')
Packit 8f70b4
        {
Packit 8f70b4
          struct stat statbuf;
Packit 8f70b4
Packit 8f70b4
          if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
Packit 8f70b4
            {
Packit 8f70b4
              close (fd);
Packit 8f70b4
              errno = ENOTDIR;
Packit 8f70b4
              return -1;
Packit 8f70b4
            }
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
#if REPLACE_FCHDIR
Packit 8f70b4
  if (!REPLACE_OPEN_DIRECTORY && 0 <= fd)
Packit 8f70b4
    fd = _gl_register_fd (fd, filename);
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
  return fd;
Packit 8f70b4
}