Mercurial > gnulib
changeset 38504:b3244d443b55
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.
author | Bruno Haible <bruno@clisp.org> |
---|---|
date | Sat, 29 Apr 2017 14:55:22 +0200 |
parents | 9c8f0d9a64e0 |
children | 7a32650d21f3 |
files | ChangeLog doc/posix-functions/fstat.texi lib/fstat.c lib/stat-w32.c lib/stat-w32.h m4/fstat.m4 modules/fstat |
diffstat | 7 files changed, 401 insertions(+), 46 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Sat Apr 29 11:09:39 2017 -0700 +++ b/ChangeLog Sat Apr 29 14:55:22 2017 +0200 @@ -1,3 +1,20 @@ +2017-04-29 Bruno Haible <bruno@clisp.org> + + 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. + 2017-04-29 Paul Eggert <eggert@cs.ucla.edu> getopt: port to Solaris 10 with circa-1997 glibc getopt.h
--- a/doc/posix-functions/fstat.texi Sat Apr 29 11:09:39 2017 -0700 +++ b/doc/posix-functions/fstat.texi Sat Apr 29 14:55:22 2017 +0200 @@ -15,6 +15,11 @@ 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}.) +@item +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:
--- a/lib/fstat.c Sat Apr 29 11:09:39 2017 -0700 +++ b/lib/fstat.c Sat Apr 29 14:55:22 2017 +0200 @@ -23,13 +23,19 @@ /* 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 +#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 -#undef __need_system_sys_stat_h + +#if !defined WINDOWS_NATIVE static int orig_fstat (int fd, struct stat *buf) @@ -37,6 +43,8 @@ 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 @@ #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 @@ 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 }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/stat-w32.c Sat Apr 29 14:55:22 2017 +0200 @@ -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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/stat-w32.h Sat Apr 29 14:55:22 2017 +0200 @@ -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 */
--- a/m4/fstat.m4 Sat Apr 29 11:09:39 2017 -0700 +++ b/m4/fstat.m4 Sat Apr 29 14:55:22 2017 +0200 @@ -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_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 @@ ]) ]) -# 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]) + : +])
--- a/modules/fstat Sat Apr 29 11:09:39 2017 -0700 +++ b/modules/fstat Sat Apr 29 14:55:22 2017 +0200 @@ -3,18 +3,22 @@ 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])