bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

fstat: fix time_t values on native Windows


From: Bruno Haible
Subject: fstat: fix time_t values on native Windows
Date: Sat, 29 Apr 2017 22:53:38 +0200
User-agent: KMail/5.1.3 (Linux/4.4.0-75-generic; KDE/5.18.0; x86_64; ; )

Per the discussion in the thread of
<https://lists.gnu.org/archive/html/bug-gnulib/2017-04/msg00134.html>.


2017-04-29  Bruno Haible  <address@hidden>

        fstat: Fix time_t values on native Windows platforms.
        * doc/posix-functions/fstat.texi: Mention the problem with st_*time.
        * lib/stat-w32.h: New file.
        * lib/stat-w32.c: New file.
        * lib/fstat.c: Don't include msvc-inval.h. Include msvc-nothrow.h,
        stat-w32.h instead.
        (fstat_nothrow): Remove function.
        (rpl_fstat): Implement by means of _gl_fstat_by_handle.
        * m4/fstat.m4 (gl_FUNC_FSTAT): On native Windows, set REPLACE_FSTAT
        always.
        (gl_PREREQ_FSTAT): Require gl_HEADER_SYS_STAT_H.
        * modules/fstat (Files): Add lib/stat-w32.h, lib/stat-w32.c.
        (Depends-on): Remove msvc-inval. Add pathmax, msvc-nothrow.
        (configure.ac): Also compile lib/stat-w32.c.

diff --git a/doc/posix-functions/fstat.texi b/doc/posix-functions/fstat.texi
index 0f5b860..cb662fc 100644
--- a/doc/posix-functions/fstat.texi
+++ b/doc/posix-functions/fstat.texi
@@ -15,6 +15,11 @@ MSVC 9.
 On platforms where @code{off_t} is a 32-bit type, @code{fstat} may not 
correctly
 report the size of files or block devices larger than 2 GB.
 (Cf. @code{AC_SYS_LARGEFILE}.)
address@hidden
+The @code{st_atime}, @code{st_ctime}, @code{st_mtime} field are affected by
+the current time zone and by the DST flag of the current time zone on some
+platforms:
+mingw, MSVC 14 (when the environment variable @code{TZ} is set).
 @end itemize
 
 Portability problems not fixed by Gnulib:
diff --git a/lib/fstat.c b/lib/fstat.c
index a5aabc5..a4896e3 100644
--- a/lib/fstat.c
+++ b/lib/fstat.c
@@ -23,20 +23,28 @@
 /* Get the original definition of fstat.  It might be defined as a macro.  */
 #include <sys/types.h>
 #include <sys/stat.h>
-#if _GL_WINDOWS_64_BIT_ST_SIZE
-# undef stat /* avoid warning on mingw64 with _FILE_OFFSET_BITS=64 */
-# define stat _stati64
-# undef fstat /* avoid warning on mingw64 with _FILE_OFFSET_BITS=64 */
-# define fstat _fstati64
-#endif
 #undef __need_system_sys_stat_h
 
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# define WINDOWS_NATIVE
+# if _GL_WINDOWS_64_BIT_ST_SIZE
+#  undef stat /* avoid warning on mingw64 with _FILE_OFFSET_BITS=64 */
+#  define stat _stati64
+#  undef fstat /* avoid warning on mingw64 with _FILE_OFFSET_BITS=64 */
+#  define fstat _fstati64
+# endif
+#endif
+
+#if !defined WINDOWS_NATIVE
+
 static int
 orig_fstat (int fd, struct stat *buf)
 {
   return fstat (fd, buf);
 }
 
+#endif
+
 /* Specification.  */
 /* Write "sys/stat.h" here, not <sys/stat.h>, otherwise OSF/1 5.1 DTK cc
    eliminates this include because of the preliminary #include <sys/stat.h>
@@ -45,32 +53,11 @@ orig_fstat (int fd, struct stat *buf)
 
 #include <errno.h>
 #include <unistd.h>
-
-#if HAVE_MSVC_INVALID_PARAMETER_HANDLER
-# include "msvc-inval.h"
-#endif
-
-#if HAVE_MSVC_INVALID_PARAMETER_HANDLER
-static int
-fstat_nothrow (int fd, struct stat *buf)
-{
-  int result;
-
-  TRY_MSVC_INVAL
-    {
-      result = orig_fstat (fd, buf);
-    }
-  CATCH_MSVC_INVAL
-    {
-      result = -1;
-      errno = EBADF;
-    }
-  DONE_MSVC_INVAL;
-
-  return result;
-}
-#else
-# define fstat_nothrow orig_fstat
+#ifdef WINDOWS_NATIVE
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# include "msvc-nothrow.h"
+# include "stat-w32.h"
 #endif
 
 int
@@ -84,5 +71,20 @@ rpl_fstat (int fd, struct stat *buf)
     return stat (name, buf);
 #endif
 
-  return fstat_nothrow (fd, buf);
+#ifdef WINDOWS_NATIVE
+  /* Fill the fields ourselves, because the original fstat function returns
+     values for st_atime, st_mtime, st_ctime that depend on the current time
+     zone.  See
+     <https://lists.gnu.org/archive/html/bug-gnulib/2017-04/msg00134.html>  */
+  HANDLE h = (HANDLE) _get_osfhandle (fd);
+
+  if (h == INVALID_HANDLE_VALUE)
+    {
+      errno = EBADF;
+      return -1;
+    }
+  return _gl_fstat_by_handle (h, NULL, buf);
+#else
+  return orig_fstat (fd, buf);
+#endif
 }
diff --git a/lib/stat-w32.c b/lib/stat-w32.c
new file mode 100644
index 0000000..4f4a105
--- /dev/null
+++ b/lib/stat-w32.c
@@ -0,0 +1,293 @@
+/* Core of implementation of fstat and stat for native Windows.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible.  */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# if _GL_WINDOWS_64_BIT_ST_SIZE
+#  undef stat /* avoid warning on mingw64 with _FILE_OFFSET_BITS=64 */
+#  define stat _stati64
+# endif
+# include <errno.h>
+# include <limits.h>
+# include <unistd.h>
+# include <windows.h>
+
+/* Specification.  */
+# include "stat-w32.h"
+
+# include "pathmax.h"
+
+/* GetFinalPathNameByHandle was introduced only in Windows Vista.  */
+typedef DWORD (WINAPI * GetFinalPathNameByHandleFuncType) (HANDLE hFile,
+                                                           LPTSTR lpFilePath,
+                                                           DWORD lenFilePath,
+                                                           DWORD dwFlags);
+static GetFinalPathNameByHandleFuncType GetFinalPathNameByHandleFunc = NULL;
+static BOOL initialized = FALSE;
+
+static void
+initialize (void)
+{
+  HMODULE kernel32 = LoadLibrary ("kernel32.dll");
+  if (kernel32 != NULL)
+    {
+      GetFinalPathNameByHandleFunc =
+       (GetFinalPathNameByHandleFuncType) GetProcAddress (kernel32, 
"GetFinalPathNameByHandleA");
+    }
+  initialized = TRUE;
+}
+
+/* Converts a FILETIME to GMT time since 1970-01-01 00:00:00.  */
+time_t
+_gl_convert_FILETIME_to_POSIX (const FILETIME *ft)
+{
+  /* FILETIME: <https://msdn.microsoft.com/en-us/library/ms724284.aspx> */
+  unsigned long long since_1601 =
+    ((unsigned long long) ft->dwHighDateTime << 32)
+    | (unsigned long long) ft->dwLowDateTime;
+  if (since_1601 == 0)
+    return 0;
+  /* Between 1601-01-01 and 1970-01-01 there were 280 normal years and 89 leap
+     years, in total 134774 days.  */
+  unsigned long long since_1970 =
+    since_1601 - (unsigned long long) 134774 * (unsigned long long) 86400 * 
(unsigned long long) 10000000;
+  return since_1970 / (unsigned long long) 10000000;
+}
+
+/* Fill *BUF with information about the file designated by H.
+   PATH is the file name, if known, otherwise NULL.
+   Return 0 if successful, or -1 with errno set upon failure.  */
+int
+_gl_fstat_by_handle (HANDLE h, const char *path, struct stat *buf)
+{
+  /* GetFileType
+     <https://msdn.microsoft.com/en-us/library/aa364960.aspx> */
+  DWORD type = GetFileType (h);
+  if (type == FILE_TYPE_DISK)
+    {
+      if (!initialized)
+        initialize ();
+
+      /* st_mode can be determined through
+         GetFileAttributesEx
+         <https://msdn.microsoft.com/en-us/library/aa364946.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa365739.aspx>
+         or through
+         GetFileInformationByHandle
+         <https://msdn.microsoft.com/en-us/library/aa364952.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa363788.aspx>
+         or through
+         GetFileInformationByHandleEx with argument FileBasicInfo
+         <https://msdn.microsoft.com/en-us/library/aa364953.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa364217.aspx>
+         The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
+      BY_HANDLE_FILE_INFORMATION info;
+      if (! GetFileInformationByHandle (h, &info))
+        goto failed;
+
+      /* Test for error conditions before starting to fill *buf.  */
+      if (sizeof (buf->st_size) <= 4 && info.nFileSizeHigh > 0)
+        {
+          errno = EOVERFLOW;
+          return -1;
+        }
+
+      /* st_ino is not wide enough for identifying a file on a device.
+         Without st_ino, st_dev is pointless.  */
+      buf->st_dev = 0;
+      buf->st_ino = 0;
+
+      /* st_mode.  */
+      unsigned int mode =
+        /* XXX How to handle FILE_ATTRIBUTE_REPARSE_POINT ?  */
+        ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? _S_IFDIR | 
S_IEXEC_UGO : _S_IFREG)
+        | S_IREAD_UGO
+        | ((info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) ? 0 : 
S_IWRITE_UGO);
+      if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+        {
+          /* Determine whether the file is executable by looking at the file
+             name suffix.
+             If the file name is already known, use it. Otherwise, for
+             non-empty files, it can be determined through
+             GetFinalPathNameByHandle
+             <https://msdn.microsoft.com/en-us/library/aa364962.aspx>
+             or through
+             GetFileInformationByHandleEx with argument FileNameInfo
+             <https://msdn.microsoft.com/en-us/library/aa364953.aspx>
+             <https://msdn.microsoft.com/en-us/library/aa364388.aspx>
+             Both require -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
+          if (info.nFileSizeHigh > 0 || info.nFileSizeLow > 0)
+            {
+              char fpath[PATH_MAX];
+              if (path != NULL
+                  || (GetFinalPathNameByHandleFunc != NULL
+                      && GetFinalPathNameByHandleFunc (h, fpath, sizeof 
(fpath), VOLUME_NAME_NONE)
+                         < sizeof (fpath)
+                      && (path = fpath, 1)))
+                {
+                  const char *last_dot = NULL;
+                  const char *p;
+                  for (p = path; *p != '\0'; p++)
+                    if (*p == '.')
+                      last_dot = p;
+                  if (last_dot != NULL)
+                    {
+                      const char *suffix = last_dot + 1;
+                      if (_stricmp (suffix, "exe") == 0
+                          || _stricmp (suffix, "bat") == 0
+                          || _stricmp (suffix, "cmd") == 0
+                          || _stricmp (suffix, "com") == 0)
+                        mode |= S_IEXEC_UGO;
+                    }
+                }
+              else
+                /* Cannot determine file name.  Pretend that it is executable. 
 */
+                mode |= S_IEXEC_UGO;
+            }
+        }
+      buf->st_mode = mode;
+
+      /* st_nlink can be determined through
+         GetFileInformationByHandle
+         <https://msdn.microsoft.com/en-us/library/aa364952.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa363788.aspx>
+         or through
+         GetFileInformationByHandleEx with argument FileStandardInfo
+         <https://msdn.microsoft.com/en-us/library/aa364953.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa364401.aspx>
+         The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
+      buf->st_nlink = (info.nNumberOfLinks > SHRT_MAX ? SHRT_MAX : 
info.nNumberOfLinks);
+
+      /* There's no easy way to map the Windows SID concept to an integer.  */
+      buf->st_uid = 0;
+      buf->st_gid = 0;
+
+      /* st_rdev is irrelevant for normal files and directories.  */
+      buf->st_rdev = 0;
+
+      /* st_size can be determined through
+         GetFileSizeEx
+         <https://msdn.microsoft.com/en-us/library/aa364957.aspx>
+         or through
+         GetFileAttributesEx
+         <https://msdn.microsoft.com/en-us/library/aa364946.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa365739.aspx>
+         or through
+         GetFileInformationByHandle
+         <https://msdn.microsoft.com/en-us/library/aa364952.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa363788.aspx>
+         or through
+         GetFileInformationByHandleEx with argument FileStandardInfo
+         <https://msdn.microsoft.com/en-us/library/aa364953.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa364401.aspx>
+         The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
+      if (sizeof (buf->st_size) <= 4)
+        /* Range check already done above.  */
+        buf->st_size = info.nFileSizeLow;
+      else
+        buf->st_size = ((long long) info.nFileSizeHigh << 32) | (long long) 
info.nFileSizeLow;
+
+      /* st_atime, st_mtime, st_ctime can be determined through
+         GetFileTime
+         <https://msdn.microsoft.com/en-us/library/ms724320.aspx>
+         or through
+         GetFileAttributesEx
+         <https://msdn.microsoft.com/en-us/library/aa364946.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa365739.aspx>
+         or through
+         GetFileInformationByHandle
+         <https://msdn.microsoft.com/en-us/library/aa364952.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa363788.aspx>
+         or through
+         GetFileInformationByHandleEx with argument FileBasicInfo
+         <https://msdn.microsoft.com/en-us/library/aa364953.aspx>
+         <https://msdn.microsoft.com/en-us/library/aa364217.aspx>
+         The latter requires -D_WIN32_WINNT=_WIN32_WINNT_VISTA or higher.  */
+      buf->st_atime = _gl_convert_FILETIME_to_POSIX (&info.ftLastAccessTime);
+      buf->st_mtime = _gl_convert_FILETIME_to_POSIX (&info.ftLastWriteTime);
+      buf->st_ctime = _gl_convert_FILETIME_to_POSIX (&info.ftCreationTime);
+
+      return 0;
+    }
+  else if (type == FILE_TYPE_CHAR || type == FILE_TYPE_PIPE)
+    {
+      buf->st_dev = 0;
+      buf->st_ino = 0;
+      buf->st_mode = (type == FILE_TYPE_PIPE ? _S_IFIFO : _S_IFCHR);
+      buf->st_nlink = 1;
+      buf->st_uid = 0;
+      buf->st_gid = 0;
+      buf->st_rdev = 0;
+      if (type == FILE_TYPE_PIPE)
+        {
+          /* PeekNamedPipe
+             <https://msdn.microsoft.com/en-us/library/aa365779.aspx> */
+          DWORD bytes_available;
+          if (PeekNamedPipe (h, NULL, 0, NULL, &bytes_available, NULL))
+            buf->st_size = bytes_available;
+          else
+            buf->st_size = 0;
+        }
+      else
+        buf->st_size = 0;
+      buf->st_atime = 0;
+      buf->st_mtime = 0;
+      buf->st_ctime = 0;
+      return 0;
+    }
+  else
+    {
+      errno = ENOENT;
+      return -1;
+    }
+
+ failed:
+  {
+    DWORD error = GetLastError ();
+    #if 0
+    fprintf (stderr, "_gl_fstat_by_handle error 0x%x\n", (unsigned int) error);
+    #endif
+    switch (error)
+      {
+      case ERROR_ACCESS_DENIED:
+      case ERROR_SHARING_VIOLATION:
+        errno = EACCES;
+        break;
+
+      case ERROR_OUTOFMEMORY:
+        errno = ENOMEM;
+        break;
+
+      case ERROR_WRITE_FAULT:
+      case ERROR_READ_FAULT:
+      case ERROR_GEN_FAILURE:
+        errno = EIO;
+        break;
+
+      default:
+        errno = EINVAL;
+        break;
+      }
+    return -1;
+  }
+}
+
+#endif
diff --git a/lib/stat-w32.h b/lib/stat-w32.h
new file mode 100644
index 0000000..68cac29
--- /dev/null
+++ b/lib/stat-w32.h
@@ -0,0 +1,33 @@
+/* Core of implementation of fstat and stat for native Windows.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#ifndef _STAT_W32_H
+#define _STAT_W32_H 1
+
+/* Converts a FILETIME to GMT time since 1970-01-01 00:00:00.  */
+extern time_t _gl_convert_FILETIME_to_POSIX (const FILETIME *ft);
+
+/* Fill *BUF with information about the file designated by H.
+   PATH is the file name, if known, otherwise NULL.
+   Return 0 if successful, or -1 with errno set upon failure.  */
+extern int _gl_fstat_by_handle (HANDLE h, const char *path, struct stat *buf);
+
+/* Bitmasks for st_mode.  */
+#define S_IREAD_UGO  (_S_IREAD | (_S_IREAD >> 3) | (_S_IREAD >> 6))
+#define S_IWRITE_UGO (_S_IWRITE | (_S_IWRITE >> 3) | (_S_IWRITE >> 6))
+#define S_IEXEC_UGO  (_S_IEXEC | (_S_IEXEC >> 3) | (_S_IEXEC >> 6))
+
+#endif /* _STAT_W32_H */
diff --git a/m4/fstat.m4 b/m4/fstat.m4
index 14c871a..14f87be 100644
--- a/m4/fstat.m4
+++ b/m4/fstat.m4
@@ -1,4 +1,4 @@
-# fstat.m4 serial 4
+# fstat.m4 serial 5
 dnl Copyright (C) 2011-2017 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -8,15 +8,13 @@ AC_DEFUN([gl_FUNC_FSTAT],
 [
   AC_REQUIRE([gl_SYS_STAT_H_DEFAULTS])
 
-  AC_REQUIRE([gl_MSVC_INVAL])
-  if test $HAVE_MSVC_INVALID_PARAMETER_HANDLER = 1; then
-    REPLACE_FSTAT=1
-  fi
-
-  AC_REQUIRE([gl_HEADER_SYS_STAT_H])
-  if test $WINDOWS_64_BIT_ST_SIZE = 1; then
-    REPLACE_FSTAT=1
-  fi
+  case "$host_os" in
+    mingw*)
+      dnl On this platform, the original stat() returns st_atime, st_mtime,
+      dnl st_ctime values that are affected by the time zone.
+      REPLACE_FSTAT=1
+      ;;
+  esac
 
   dnl Replace fstat() for supporting the gnulib-defined open() on directories.
   m4_ifdef([gl_FUNC_FCHDIR], [
@@ -32,5 +30,8 @@ AC_DEFUN([gl_FUNC_FSTAT],
   ])
 ])
 
-# Prerequisites of lib/fstat.c.
-AC_DEFUN([gl_PREREQ_FSTAT], [:])
+# Prerequisites of lib/fstat.c and lib/stat-w32.c.
+AC_DEFUN([gl_PREREQ_FSTAT], [
+  AC_REQUIRE([gl_HEADER_SYS_STAT_H])
+  :
+])
diff --git a/modules/fstat b/modules/fstat
index 2db555a..3f2357b 100644
--- a/modules/fstat
+++ b/modules/fstat
@@ -3,18 +3,22 @@ fstat() function: return information about an open file.
 
 Files:
 lib/fstat.c
+lib/stat-w32.h
+lib/stat-w32.c
 m4/fstat.m4
 
 Depends-on:
 sys_stat
 largefile
+pathmax         [test $REPLACE_STAT = 1]
 unistd          [test $REPLACE_STAT = 1]
-msvc-inval      [test $REPLACE_STAT = 1]
+msvc-nothrow    [test $REPLACE_STAT = 1]
 
 configure.ac:
 gl_FUNC_FSTAT
 if test $REPLACE_FSTAT = 1; then
   AC_LIBOBJ([fstat])
+  AC_LIBOBJ([stat-w32])
   gl_PREREQ_FSTAT
 fi
 gl_SYS_STAT_MODULE_INDICATOR([fstat])




reply via email to

[Prev in Thread] Current Thread [Next in Thread]