Blame lib/stat.c

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