Blame lib/stat.c

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