Blame lib/stat.c

Packit 8f70b4
/* Work around platform bugs in stat.
Packit 8f70b4
   Copyright (C) 2009-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 Eric Blake and Bruno Haible.  */
Packit 8f70b4
Packit 8f70b4
/* If the user's config.h happens to include <sys/stat.h>, let it include only
Packit 8f70b4
   the system's <sys/stat.h> here, so that orig_stat doesn't recurse to
Packit 8f70b4
   rpl_stat.  */
Packit 8f70b4
#define __need_system_sys_stat_h
Packit 8f70b4
#include <config.h>
Packit 8f70b4
Packit 8f70b4
/* Get the original definition of stat.  It might be defined as a macro.  */
Packit 8f70b4
#include <sys/types.h>
Packit 8f70b4
#include <sys/stat.h>
Packit 8f70b4
#undef __need_system_sys_stat_h
Packit 8f70b4
Packit 8f70b4
#if defined _WIN32 && ! defined __CYGWIN__
Packit 8f70b4
# define WINDOWS_NATIVE
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
#if !defined WINDOWS_NATIVE
Packit 8f70b4
Packit 8f70b4
static int
Packit 8f70b4
orig_stat (const char *filename, struct stat *buf)
Packit 8f70b4
{
Packit 8f70b4
  return stat (filename, buf);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
/* Specification.  */
Packit 8f70b4
/* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc
Packit 8f70b4
   eliminates this include because of the preliminary #include <sys/stat.h>
Packit 8f70b4
   above.  */
Packit 8f70b4
#include "sys/stat.h"
Packit 8f70b4
Packit 8f70b4
#include "stat-time.h"
Packit 8f70b4
Packit 8f70b4
#include <errno.h>
Packit 8f70b4
#include <limits.h>
Packit 8f70b4
#include <stdbool.h>
Packit 8f70b4
#include <string.h>
Packit 8f70b4
#include "filename.h"
Packit 8f70b4
#include "malloca.h"
Packit 8f70b4
#include "verify.h"
Packit 8f70b4
Packit 8f70b4
#ifdef WINDOWS_NATIVE
Packit 8f70b4
# define WIN32_LEAN_AND_MEAN
Packit 8f70b4
# include <windows.h>
Packit 8f70b4
# include "stat-w32.h"
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
#ifdef WINDOWS_NATIVE
Packit 8f70b4
/* Return TRUE if the given file name denotes an UNC root.  */
Packit 8f70b4
static BOOL
Packit 8f70b4
is_unc_root (const char *rname)
Packit 8f70b4
{
Packit 8f70b4
  /* Test whether it has the syntax '\\server\share'.  */
Packit 8f70b4
  if (ISSLASH (rname[0]) && ISSLASH (rname[1]))
Packit 8f70b4
    {
Packit 8f70b4
      /* It starts with two slashes.  Find the next slash.  */
Packit 8f70b4
      const char *p = rname + 2;
Packit 8f70b4
      const char *q = p;
Packit 8f70b4
      while (*q != '\0' && !ISSLASH (*q))
Packit 8f70b4
        q++;
Packit 8f70b4
      if (q > p && *q != '\0')
Packit 8f70b4
        {
Packit 8f70b4
          /* Found the next slash at q.  */
Packit 8f70b4
          q++;
Packit 8f70b4
          const char *r = q;
Packit 8f70b4
          while (*r != '\0' && !ISSLASH (*r))
Packit 8f70b4
            r++;
Packit 8f70b4
          if (r > q && *r == '\0')
Packit 8f70b4
            return TRUE;
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
  return FALSE;
Packit 8f70b4
}
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
/* Store information about NAME into ST.  Work around bugs with
Packit 8f70b4
   trailing slashes.  Mingw has other bugs (such as st_ino always
Packit 8f70b4
   being 0 on success) which this wrapper does not work around.  But
Packit 8f70b4
   at least this implementation provides the ability to emulate fchdir
Packit 8f70b4
   correctly.  */
Packit 8f70b4
Packit 8f70b4
int
Packit 8f70b4
rpl_stat (char const *name, struct stat *buf)
Packit 8f70b4
{
Packit 8f70b4
#ifdef WINDOWS_NATIVE
Packit 8f70b4
  /* Fill the fields ourselves, because the original stat function returns
Packit 8f70b4
     values for st_atime, st_mtime, st_ctime that depend on the current time
Packit 8f70b4
     zone.  See
Packit 8f70b4
     <https://lists.gnu.org/r/bug-gnulib/2017-04/msg00134.html>  */
Packit 8f70b4
  /* XXX Should we convert to wchar_t* and prepend '\\?\', in order to work
Packit 8f70b4
     around length limitations
Packit 8f70b4
     <https://msdn.microsoft.com/en-us/library/aa365247.aspx> ?  */
Packit 8f70b4
Packit 8f70b4
  /* POSIX <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>
Packit 8f70b4
     specifies: "More than two leading <slash> characters shall be treated as
Packit 8f70b4
     a single <slash> character."  */
Packit 8f70b4
  if (ISSLASH (name[0]) && ISSLASH (name[1]) && ISSLASH (name[2]))
Packit 8f70b4
    {
Packit 8f70b4
      name += 2;
Packit 8f70b4
      while (ISSLASH (name[1]))
Packit 8f70b4
        name++;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  size_t len = strlen (name);
Packit 8f70b4
  size_t drive_prefix_len = (HAS_DEVICE (name) ? 2 : 0);
Packit 8f70b4
Packit 8f70b4
  /* Remove trailing slashes (except the very first one, at position
Packit 8f70b4
     drive_prefix_len), but remember their presence.  */
Packit 8f70b4
  size_t rlen;
Packit 8f70b4
  bool check_dir = false;
Packit 8f70b4
Packit 8f70b4
  rlen = len;
Packit 8f70b4
  while (rlen > drive_prefix_len && ISSLASH (name[rlen-1]))
Packit 8f70b4
    {
Packit 8f70b4
      check_dir = true;
Packit 8f70b4
      if (rlen == drive_prefix_len + 1)
Packit 8f70b4
        break;
Packit 8f70b4
      rlen--;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  /* Handle '' and 'C:'.  */
Packit 8f70b4
  if (!check_dir && rlen == drive_prefix_len)
Packit 8f70b4
    {
Packit 8f70b4
      errno = ENOENT;
Packit 8f70b4
      return -1;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  /* Handle '\\'.  */
Packit 8f70b4
  if (rlen == 1 && ISSLASH (name[0]) && len >= 2)
Packit 8f70b4
    {
Packit 8f70b4
      errno = ENOENT;
Packit 8f70b4
      return -1;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  const char *rname;
Packit 8f70b4
  char *malloca_rname;
Packit 8f70b4
  if (rlen == len)
Packit 8f70b4
    {
Packit 8f70b4
      rname = name;
Packit 8f70b4
      malloca_rname = NULL;
Packit 8f70b4
    }
Packit 8f70b4
  else
Packit 8f70b4
    {
Packit 8f70b4
      malloca_rname = malloca (rlen + 1);
Packit 8f70b4
      if (malloca_rname == NULL)
Packit 8f70b4
        {
Packit 8f70b4
          errno = ENOMEM;
Packit 8f70b4
          return -1;
Packit 8f70b4
        }
Packit 8f70b4
      memcpy (malloca_rname, name, rlen);
Packit 8f70b4
      malloca_rname[rlen] = '\0';
Packit 8f70b4
      rname = malloca_rname;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  /* There are two ways to get at the requested information:
Packit 8f70b4
       - by scanning the parent directory and examining the relevant
Packit 8f70b4
         directory entry,
Packit 8f70b4
       - by opening the file directly.
Packit 8f70b4
     The first approach fails for root directories (e.g. 'C:\') and
Packit 8f70b4
     UNC root directories (e.g. '\\server\share').
Packit 8f70b4
     The second approach fails for some system files (e.g. 'C:\pagefile.sys'
Packit 8f70b4
     and 'C:\hiberfil.sys'): ERROR_SHARING_VIOLATION.
Packit 8f70b4
     The second approach gives more information (in particular, correct
Packit 8f70b4
     st_dev, st_ino, st_nlink fields).
Packit 8f70b4
     So we use the second approach and, as a fallback except for root and
Packit 8f70b4
     UNC root directories, also the first approach.  */
Packit 8f70b4
  {
Packit 8f70b4
    int ret;
Packit 8f70b4
Packit 8f70b4
    {
Packit 8f70b4
      /* Approach based on the file.  */
Packit 8f70b4
Packit 8f70b4
      /* Open a handle to the file.
Packit 8f70b4
         CreateFile
Packit 8f70b4
         <https://msdn.microsoft.com/en-us/library/aa363858.aspx>
Packit 8f70b4
         <https://msdn.microsoft.com/en-us/library/aa363874.aspx>  */
Packit 8f70b4
      HANDLE h =
Packit 8f70b4
        CreateFile (rname,
Packit 8f70b4
                    FILE_READ_ATTRIBUTES,
Packit 8f70b4
                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
Packit 8f70b4
                    NULL,
Packit 8f70b4
                    OPEN_EXISTING,
Packit 8f70b4
                    /* FILE_FLAG_POSIX_SEMANTICS (treat file names that differ only
Packit 8f70b4
                       in case as different) makes sense only when applied to *all*
Packit 8f70b4
                       filesystem operations.  */
Packit 8f70b4
                    FILE_FLAG_BACKUP_SEMANTICS /* | FILE_FLAG_POSIX_SEMANTICS */,
Packit 8f70b4
                    NULL);
Packit 8f70b4
      if (h != INVALID_HANDLE_VALUE)
Packit 8f70b4
        {
Packit 8f70b4
          ret = _gl_fstat_by_handle (h, rname, buf);
Packit 8f70b4
          CloseHandle (h);
Packit 8f70b4
          goto done;
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
    /* Test for root and UNC root directories.  */
Packit 8f70b4
    if ((rlen == drive_prefix_len + 1 && ISSLASH (rname[drive_prefix_len]))
Packit 8f70b4
        || is_unc_root (rname))
Packit 8f70b4
      goto failed;
Packit 8f70b4
Packit 8f70b4
    /* Fallback.  */
Packit 8f70b4
    {
Packit 8f70b4
      /* Approach based on the directory entry.  */
Packit 8f70b4
Packit 8f70b4
      if (strchr (rname, '?') != NULL || strchr (rname, '*') != NULL)
Packit 8f70b4
        {
Packit 8f70b4
          /* Other Windows API functions would fail with error
Packit 8f70b4
             ERROR_INVALID_NAME.  */
Packit 8f70b4
          if (malloca_rname != NULL)
Packit 8f70b4
            freea (malloca_rname);
Packit 8f70b4
          errno = ENOENT;
Packit 8f70b4
          return -1;
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
      /* Get the details about the directory entry.  This can be done through
Packit 8f70b4
         FindFirstFile
Packit 8f70b4
         <https://msdn.microsoft.com/en-us/library/aa364418.aspx>
Packit 8f70b4
         <https://msdn.microsoft.com/en-us/library/aa365740.aspx>
Packit 8f70b4
         or through
Packit 8f70b4
         FindFirstFileEx with argument FindExInfoBasic
Packit 8f70b4
         <https://msdn.microsoft.com/en-us/library/aa364419.aspx>
Packit 8f70b4
         <https://msdn.microsoft.com/en-us/library/aa364415.aspx>
Packit 8f70b4
         <https://msdn.microsoft.com/en-us/library/aa365740.aspx>  */
Packit 8f70b4
      WIN32_FIND_DATA info;
Packit 8f70b4
      HANDLE h = FindFirstFile (rname, &info;;
Packit 8f70b4
      if (h == INVALID_HANDLE_VALUE)
Packit 8f70b4
        goto failed;
Packit 8f70b4
Packit 8f70b4
      /* Test for error conditions before starting to fill *buf.  */
Packit 8f70b4
      if (sizeof (buf->st_size) <= 4 && info.nFileSizeHigh > 0)
Packit 8f70b4
        {
Packit 8f70b4
          FindClose (h);
Packit 8f70b4
          if (malloca_rname != NULL)
Packit 8f70b4
            freea (malloca_rname);
Packit 8f70b4
          errno = EOVERFLOW;
Packit 8f70b4
          return -1;
Packit 8f70b4
        }
Packit 8f70b4
Packit 8f70b4
# if _GL_WINDOWS_STAT_INODES
Packit 8f70b4
      buf->st_dev = 0;
Packit 8f70b4
#  if _GL_WINDOWS_STAT_INODES == 2
Packit 8f70b4
      buf->st_ino._gl_ino[0] = buf->st_ino._gl_ino[1] = 0;
Packit 8f70b4
#  else /* _GL_WINDOWS_STAT_INODES == 1 */
Packit 8f70b4
      buf->st_ino = 0;
Packit 8f70b4
#  endif
Packit 8f70b4
# else
Packit 8f70b4
      /* st_ino is not wide enough for identifying a file on a device.
Packit 8f70b4
         Without st_ino, st_dev is pointless.  */
Packit 8f70b4
      buf->st_dev = 0;
Packit 8f70b4
      buf->st_ino = 0;
Packit 8f70b4
# endif
Packit 8f70b4
Packit 8f70b4
      /* st_mode.  */
Packit 8f70b4
      unsigned int mode =
Packit 8f70b4
        /* XXX How to handle FILE_ATTRIBUTE_REPARSE_POINT ?  */
Packit 8f70b4
        ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? _S_IFDIR | S_IEXEC_UGO : _S_IFREG)
Packit 8f70b4
        | S_IREAD_UGO
Packit 8f70b4
        | ((info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? 0 : S_IWRITE_UGO);
Packit 8f70b4
      if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
Packit 8f70b4
        {
Packit 8f70b4
          /* Determine whether the file is executable by looking at the file
Packit 8f70b4
             name suffix.  */
Packit 8f70b4
          if (info.nFileSizeHigh > 0 || info.nFileSizeLow > 0)
Packit 8f70b4
            {
Packit 8f70b4
              const char *last_dot = NULL;
Packit 8f70b4
              const char *p;
Packit 8f70b4
              for (p = info.cFileName; *p != '\0'; p++)
Packit 8f70b4
                if (*p == '.')
Packit 8f70b4
                  last_dot = p;
Packit 8f70b4
              if (last_dot != NULL)
Packit 8f70b4
                {
Packit 8f70b4
                  const char *suffix = last_dot + 1;
Packit 8f70b4
                  if (_stricmp (suffix, "exe") == 0
Packit 8f70b4
                      || _stricmp (suffix, "bat") == 0
Packit 8f70b4
                      || _stricmp (suffix, "cmd") == 0
Packit 8f70b4
                      || _stricmp (suffix, "com") == 0)
Packit 8f70b4
                    mode |= S_IEXEC_UGO;
Packit 8f70b4
                }
Packit 8f70b4
            }
Packit 8f70b4
        }
Packit 8f70b4
      buf->st_mode = mode;
Packit 8f70b4
Packit 8f70b4
      /* st_nlink.  Ignore hard links here.  */
Packit 8f70b4
      buf->st_nlink = 1;
Packit 8f70b4
Packit 8f70b4
      /* There's no easy way to map the Windows SID concept to an integer.  */
Packit 8f70b4
      buf->st_uid = 0;
Packit 8f70b4
      buf->st_gid = 0;
Packit 8f70b4
Packit 8f70b4
      /* st_rdev is irrelevant for normal files and directories.  */
Packit 8f70b4
      buf->st_rdev = 0;
Packit 8f70b4
Packit 8f70b4
      /* st_size.  */
Packit 8f70b4
      if (sizeof (buf->st_size) <= 4)
Packit 8f70b4
        /* Range check already done above.  */
Packit 8f70b4
        buf->st_size = info.nFileSizeLow;
Packit 8f70b4
      else
Packit 8f70b4
        buf->st_size = ((long long) info.nFileSizeHigh << 32) | (long long) info.nFileSizeLow;
Packit 8f70b4
Packit 8f70b4
      /* st_atime, st_mtime, st_ctime.  */
Packit 8f70b4
# if _GL_WINDOWS_STAT_TIMESPEC
Packit 8f70b4
      buf->st_atim = _gl_convert_FILETIME_to_timespec (&info.ftLastAccessTime);
Packit 8f70b4
      buf->st_mtim = _gl_convert_FILETIME_to_timespec (&info.ftLastWriteTime);
Packit 8f70b4
      buf->st_ctim = _gl_convert_FILETIME_to_timespec (&info.ftCreationTime);
Packit 8f70b4
# else
Packit 8f70b4
      buf->st_atime = _gl_convert_FILETIME_to_POSIX (&info.ftLastAccessTime);
Packit 8f70b4
      buf->st_mtime = _gl_convert_FILETIME_to_POSIX (&info.ftLastWriteTime);
Packit 8f70b4
      buf->st_ctime = _gl_convert_FILETIME_to_POSIX (&info.ftCreationTime);
Packit 8f70b4
# endif
Packit 8f70b4
Packit 8f70b4
      FindClose (h);
Packit 8f70b4
Packit 8f70b4
      ret = 0;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
   done:
Packit 8f70b4
    if (ret >= 0 && check_dir && !S_ISDIR (buf->st_mode))
Packit 8f70b4
      {
Packit 8f70b4
        errno = ENOTDIR;
Packit 8f70b4
        ret = -1;
Packit 8f70b4
      }
Packit 8f70b4
    if (malloca_rname != NULL)
Packit 8f70b4
      {
Packit 8f70b4
        int saved_errno = errno;
Packit 8f70b4
        freea (malloca_rname);
Packit 8f70b4
        errno = saved_errno;
Packit 8f70b4
      }
Packit 8f70b4
    return ret;
Packit 8f70b4
  }
Packit 8f70b4
Packit 8f70b4
 failed:
Packit 8f70b4
  {
Packit 8f70b4
    DWORD error = GetLastError ();
Packit 8f70b4
    #if 0
Packit 8f70b4
    fprintf (stderr, "rpl_stat error 0x%x\n", (unsigned int) error);
Packit 8f70b4
    #endif
Packit 8f70b4
Packit 8f70b4
    if (malloca_rname != NULL)
Packit 8f70b4
      freea (malloca_rname);
Packit 8f70b4
Packit 8f70b4
    switch (error)
Packit 8f70b4
      {
Packit 8f70b4
      /* Some of these errors probably cannot happen with the specific flags
Packit 8f70b4
         that we pass to CreateFile.  But who knows...  */
Packit 8f70b4
      case ERROR_FILE_NOT_FOUND: /* The last component of rname does not exist.  */
Packit 8f70b4
      case ERROR_PATH_NOT_FOUND: /* Some directory component in rname does not exist.  */
Packit 8f70b4
      case ERROR_BAD_PATHNAME:   /* rname is such as '\\server'.  */
Packit 8f70b4
      case ERROR_BAD_NET_NAME:   /* rname is such as '\\server\nonexistentshare'.  */
Packit 8f70b4
      case ERROR_INVALID_NAME:   /* rname contains wildcards, misplaced colon, etc.  */
Packit 8f70b4
      case ERROR_DIRECTORY:
Packit 8f70b4
        errno = ENOENT;
Packit 8f70b4
        break;
Packit 8f70b4
Packit 8f70b4
      case ERROR_ACCESS_DENIED:  /* rname is such as 'C:\System Volume Information\foo'.  */
Packit 8f70b4
      case ERROR_SHARING_VIOLATION: /* rname is such as 'C:\pagefile.sys' (second approach only).  */
Packit 8f70b4
                                    /* XXX map to EACCESS or EPERM? */
Packit 8f70b4
        errno = EACCES;
Packit 8f70b4
        break;
Packit 8f70b4
Packit 8f70b4
      case ERROR_OUTOFMEMORY:
Packit 8f70b4
        errno = ENOMEM;
Packit 8f70b4
        break;
Packit 8f70b4
Packit 8f70b4
      case ERROR_WRITE_PROTECT:
Packit 8f70b4
        errno = EROFS;
Packit 8f70b4
        break;
Packit 8f70b4
Packit 8f70b4
      case ERROR_WRITE_FAULT:
Packit 8f70b4
      case ERROR_READ_FAULT:
Packit 8f70b4
      case ERROR_GEN_FAILURE:
Packit 8f70b4
        errno = EIO;
Packit 8f70b4
        break;
Packit 8f70b4
Packit 8f70b4
      case ERROR_BUFFER_OVERFLOW:
Packit 8f70b4
      case ERROR_FILENAME_EXCED_RANGE:
Packit 8f70b4
        errno = ENAMETOOLONG;
Packit 8f70b4
        break;
Packit 8f70b4
Packit 8f70b4
      case ERROR_DELETE_PENDING: /* XXX map to EACCESS or EPERM? */
Packit 8f70b4
        errno = EPERM;
Packit 8f70b4
        break;
Packit 8f70b4
Packit 8f70b4
      default:
Packit 8f70b4
        errno = EINVAL;
Packit 8f70b4
        break;
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
    return -1;
Packit 8f70b4
  }
Packit 8f70b4
#else
Packit 8f70b4
  int result = orig_stat (name, buf);
Packit 8f70b4
  if (result == 0)
Packit 8f70b4
    {
Packit 8f70b4
# if REPLACE_FUNC_STAT_FILE
Packit 8f70b4
      /* Solaris 9 mistakenly succeeds when given a non-directory with a
Packit 8f70b4
         trailing slash.  */
Packit 8f70b4
      if (!S_ISDIR (buf->st_mode))
Packit 8f70b4
        {
Packit 8f70b4
          size_t len = strlen (name);
Packit 8f70b4
          if (ISSLASH (name[len - 1]))
Packit 8f70b4
            {
Packit 8f70b4
              errno = ENOTDIR;
Packit 8f70b4
              return -1;
Packit 8f70b4
            }
Packit 8f70b4
        }
Packit 8f70b4
# endif /* REPLACE_FUNC_STAT_FILE */
Packit 8f70b4
      result = stat_time_normalize (result, buf);
Packit 8f70b4
    }
Packit 8f70b4
  return result;
Packit 8f70b4
#endif
Packit 8f70b4
}