Blame gl/open.c

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