Blame gl/stat.c

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