Blame lib/stat.c

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