Blame gnulib/tests/open.c

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