Blame gl/fopen.c

Packit Service 4684c1
/* Open a stream 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 <stdio.h>, let it include only
Packit Service 4684c1
   the system's <stdio.h> here, so that orig_fopen doesn't recurse to
Packit Service 4684c1
   rpl_fopen.  */
Packit Service 4684c1
#define __need_FILE
Packit Service 4684c1
#include <config.h>
Packit Service 4684c1
Packit Service 4684c1
/* Get the original definition of fopen.  It might be defined as a macro.  */
Packit Service 4684c1
#include <stdio.h>
Packit Service 4684c1
#undef __need_FILE
Packit Service 4684c1
Packit Service 4684c1
static FILE *
Packit Service 4684c1
orig_fopen (const char *filename, const char *mode)
Packit Service 4684c1
{
Packit Service 4684c1
  return fopen (filename, mode);
Packit Service 4684c1
}
Packit Service 4684c1
Packit Service 4684c1
/* Specification.  */
Packit Service 4684c1
/* Write "stdio.h" here, not <stdio.h>, otherwise OSF/1 5.1 DTK cc eliminates
Packit Service 4684c1
   this include because of the preliminary #include <stdio.h> above.  */
Packit Service 4684c1
#include "stdio.h"
Packit Service 4684c1
Packit Service 4684c1
#include <errno.h>
Packit Service 4684c1
#include <fcntl.h>
Packit Service 4684c1
#include <string.h>
Packit Service 4684c1
#include <unistd.h>
Packit Service 4684c1
#include <sys/types.h>
Packit Service 4684c1
#include <sys/stat.h>
Packit Service 4684c1
Packit Service 4684c1
FILE *
Packit Service 4684c1
rpl_fopen (const char *filename, const char *mode)
Packit Service 4684c1
{
Packit Service 4684c1
  int open_direction;
Packit Service 4684c1
  int open_flags_standard;
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
  int open_flags_gnu;
Packit Service 4684c1
# define BUF_SIZE 80
Packit Service 4684c1
  char fdopen_mode_buf[BUF_SIZE + 1];
Packit Service 4684c1
#endif
Packit Service 4684c1
  int open_flags;
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
  /* Parse the mode.  */
Packit Service 4684c1
  open_direction = 0;
Packit Service 4684c1
  open_flags_standard = 0;
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
  open_flags_gnu = 0;
Packit Service 4684c1
#endif
Packit Service 4684c1
  {
Packit Service 4684c1
    const char *p = mode;
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
    char *q = fdopen_mode_buf;
Packit Service 4684c1
#endif
Packit Service 4684c1
Packit Service 4684c1
    for (; *p != '\0'; p++)
Packit Service 4684c1
      {
Packit Service 4684c1
        switch (*p)
Packit Service 4684c1
          {
Packit Service 4684c1
          case 'r':
Packit Service 4684c1
            open_direction = O_RDONLY;
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
            if (q < fdopen_mode_buf + BUF_SIZE)
Packit Service 4684c1
              *q++ = *p;
Packit Service 4684c1
#endif
Packit Service 4684c1
            continue;
Packit Service 4684c1
          case 'w':
Packit Service 4684c1
            open_direction = O_WRONLY;
Packit Service 4684c1
            open_flags_standard |= O_CREAT | O_TRUNC;
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
            if (q < fdopen_mode_buf + BUF_SIZE)
Packit Service 4684c1
              *q++ = *p;
Packit Service 4684c1
#endif
Packit Service 4684c1
            continue;
Packit Service 4684c1
          case 'a':
Packit Service 4684c1
            open_direction = O_WRONLY;
Packit Service 4684c1
            open_flags_standard |= O_CREAT | O_APPEND;
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
            if (q < fdopen_mode_buf + BUF_SIZE)
Packit Service 4684c1
              *q++ = *p;
Packit Service 4684c1
#endif
Packit Service 4684c1
            continue;
Packit Service 4684c1
          case 'b':
Packit Service 4684c1
            /* While it is non-standard, O_BINARY is guaranteed by
Packit Service 4684c1
               gnulib <fcntl.h>.  We can also assume that orig_fopen
Packit Service 4684c1
               supports the 'b' flag.  */
Packit Service 4684c1
            open_flags_standard |= O_BINARY;
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
            if (q < fdopen_mode_buf + BUF_SIZE)
Packit Service 4684c1
              *q++ = *p;
Packit Service 4684c1
#endif
Packit Service 4684c1
            continue;
Packit Service 4684c1
          case '+':
Packit Service 4684c1
            open_direction = O_RDWR;
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
            if (q < fdopen_mode_buf + BUF_SIZE)
Packit Service 4684c1
              *q++ = *p;
Packit Service 4684c1
#endif
Packit Service 4684c1
            continue;
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
          case 'x':
Packit Service 4684c1
            open_flags_gnu |= O_EXCL;
Packit Service 4684c1
            continue;
Packit Service 4684c1
          case 'e':
Packit Service 4684c1
            open_flags_gnu |= O_CLOEXEC;
Packit Service 4684c1
            continue;
Packit Service 4684c1
#endif
Packit Service 4684c1
          default:
Packit Service 4684c1
            break;
Packit Service 4684c1
          }
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
        /* The rest of the mode string can be a platform-dependent extension.
Packit Service 4684c1
           Copy it unmodified.  */
Packit Service 4684c1
        {
Packit Service 4684c1
          size_t len = strlen (p);
Packit Service 4684c1
          if (len > fdopen_mode_buf + BUF_SIZE - q)
Packit Service 4684c1
            len = fdopen_mode_buf + BUF_SIZE - q;
Packit Service 4684c1
          memcpy (q, p, len);
Packit Service 4684c1
          q += len;
Packit Service 4684c1
        }
Packit Service 4684c1
#endif
Packit Service 4684c1
        break;
Packit Service 4684c1
      }
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
    *q = '\0';
Packit Service 4684c1
#endif
Packit Service 4684c1
  }
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
  open_flags = open_flags_standard | open_flags_gnu;
Packit Service 4684c1
#else
Packit Service 4684c1
  open_flags = open_flags_standard;
Packit Service 4684c1
#endif
Packit Service 4684c1
Packit Service 4684c1
#if FOPEN_TRAILING_SLASH_BUG
Packit Service 4684c1
  /* Fail if the mode requires write access and the filename ends in a slash,
Packit Service 4684c1
     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 if a mode that
Packit Service 4684c1
     requires write access is specified, fopen() must fail because POSIX
Packit Service 4684c1
     <https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.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
     fopen() must fail since the file does not contain a '.' directory.  */
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
        int fd;
Packit Service 4684c1
        struct stat statbuf;
Packit Service 4684c1
        FILE *fp;
Packit Service 4684c1
Packit Service 4684c1
        if (open_direction != O_RDONLY)
Packit Service 4684c1
          {
Packit Service 4684c1
            errno = EISDIR;
Packit Service 4684c1
            return NULL;
Packit Service 4684c1
          }
Packit Service 4684c1
Packit Service 4684c1
        fd = open (filename, open_direction | open_flags);
Packit Service 4684c1
        if (fd < 0)
Packit Service 4684c1
          return NULL;
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 NULL;
Packit Service 4684c1
          }
Packit Service 4684c1
Packit Service 4684c1
# if GNULIB_FOPEN_GNU
Packit Service 4684c1
        fp = fdopen (fd, fdopen_mode_buf);
Packit Service 4684c1
# else
Packit Service 4684c1
        fp = fdopen (fd, mode);
Packit Service 4684c1
# endif
Packit Service 4684c1
        if (fp == NULL)
Packit Service 4684c1
          {
Packit Service 4684c1
            int saved_errno = errno;
Packit Service 4684c1
            close (fd);
Packit Service 4684c1
            errno = saved_errno;
Packit Service 4684c1
          }
Packit Service 4684c1
        return fp;
Packit Service 4684c1
      }
Packit Service 4684c1
  }
Packit Service 4684c1
#endif
Packit Service 4684c1
Packit Service 4684c1
#if GNULIB_FOPEN_GNU
Packit Service 4684c1
  if (open_flags_gnu != 0)
Packit Service 4684c1
    {
Packit Service 4684c1
      int fd;
Packit Service 4684c1
      FILE *fp;
Packit Service 4684c1
Packit Service 4684c1
      fd = open (filename, open_direction | open_flags);
Packit Service 4684c1
      if (fd < 0)
Packit Service 4684c1
        return NULL;
Packit Service 4684c1
Packit Service 4684c1
      fp = fdopen (fd, fdopen_mode_buf);
Packit Service 4684c1
      if (fp == NULL)
Packit Service 4684c1
        {
Packit Service 4684c1
          int saved_errno = errno;
Packit Service 4684c1
          close (fd);
Packit Service 4684c1
          errno = saved_errno;
Packit Service 4684c1
        }
Packit Service 4684c1
      return fp;
Packit Service 4684c1
    }
Packit Service 4684c1
#endif
Packit Service 4684c1
Packit Service 4684c1
  return orig_fopen (filename, mode);
Packit Service 4684c1
}