Discussion:
windows-stat-inodes: new module
(too old to reply)
Bruno Haible
2017-05-14 15:59:53 UTC
Permalink
Raw Message
This patch implements the 'windows-stat-inodes' module, that provide good
st_dev and st_ino values on native Windows - at least for most files.

On files where it cannot do so (unreadable files or directories), it sets
both fields to 0. This is not as good as Cygwin does; Cygwin provides
non-zero st_ino in these cases too. I don't know whether this is because
Cygwin uses NtOpenFile and NtQueryInformationFile instead of Windows API,
or because Cygwin uses a hash code of the file name as a fallback.
I could have used a hash code of the file name as well (say, 64 bits
from an SHA-1 hash sum), but this could lead to bugs (e.g. with symbolic
links or hard links). Therefore I find it preferable to just set
st_dev = st_ino = 0 in this case, and let the users of st_ino deal with
it with care.


2017-05-14 Bruno Haible <***@clisp.org>

windows-stat-inodes: New module.
* m4/windows-stat-inodes.m4: New file.
* m4/sys_types_h.m4 (gl_SYS_TYPES_H): Set WINDOWS_STAT_INODES.
* modules/sys_types (Makefile.am): Substitute WINDOWS_STAT_INODES.
* lib/sys_types.in.h [WINDOWS_STAT_INODES]: Override dev_t and ino_t.
(_GL_WINDOWS_STAT_INODES): New macro.
* lib/stat-w32.c: Set _WIN32_WINNT. Include <string.h>, verify.h.
(GetFileInformationByHandleExFunc): New variable.
(initialize): Initialize it.
(_gl_fstat_by_handle) [_GL_WINDOWS_STAT_INODES]: Initialize st_dev and
st_ino appropriately.
* lib/stat.c (rpl_stat): Use the directory entry based approach only as
a fallback, because it does not provide st_dev and st_ino values.
* modules/fstat (Depends-on): Add 'verify'.
* modules/windows-stat-inodes: New file.
* doc/windows-stat-inodes.texi: New file.
* doc/gnulib.texi: Include it.
* doc/posix-headers/sys_stat.texi: Mention the new module.

diff --git a/doc/gnulib.texi b/doc/gnulib.texi
index 5cadf46..3eb7a1d 100644
--- a/doc/gnulib.texi
+++ b/doc/gnulib.texi
@@ -6324,6 +6324,7 @@ to POSIX that it can be treated like any other Unix-like platform.
@menu
* Libtool and Windows::
* Large File Support::
+* Inode numbers on Windows::
* Precise file timestamps on Windows::
* Avoiding the year 2038 problem::
* Windows sockets::
@@ -6334,6 +6335,8 @@ to POSIX that it can be treated like any other Unix-like platform.

@include largefile.texi

+@include windows-stat-inodes.texi
+
@include windows-stat-timespec.texi

@include year2038.texi
diff --git a/doc/posix-headers/sys_stat.texi b/doc/posix-headers/sys_stat.texi
index 4c176aa..42a27ba 100644
--- a/doc/posix-headers/sys_stat.texi
+++ b/doc/posix-headers/sys_stat.texi
@@ -5,7 +5,7 @@ POSIX specification:@* @url{http://www.opengroup.org/onlinepubs/9699919799/based

Gnulib module: sys_stat

-Portability problems fixed by Gnulib:
+Portability problems fixed by Gnulib module @code{sys_stat}:
@itemize
@item
The type @code{mode_t} is not defined on some platforms:
@@ -31,14 +31,18 @@ On some platforms, @code{struct stat} does not include @code{st_atim},
@samp{stat-time} for accessors to portably get at subsecond resolution.
@end itemize

+Portability problems fixed by Gnulib module @code{sys_stat}, together with module @code{windows-stat-inodes}:
+@itemize
+@item
+On Windows platforms (excluding Cygwin), @code{st_ino} is always 0.
+@end itemize
+
Portability problems not fixed by Gnulib:
@itemize
@item
The macro @code{S_IFBLK} is missing on some platforms:
MSVC 9.
@item
-On Windows platforms (excluding Cygwin), @code{st_ino} is always 0.
-@item
On OpenVMS, @code{st_ino} is an array of three @code{ino_t} values,
not a single value.
@item
diff --git a/doc/windows-stat-inodes.texi b/doc/windows-stat-inodes.texi
new file mode 100644
index 0000000..f3c0581
--- /dev/null
+++ b/doc/windows-stat-inodes.texi
@@ -0,0 +1,14 @@
+@node Inode numbers on Windows
+@section Inode numbers on Windows
+
+The module @samp{windows-stat-inodes} ensures that,
+on native Windows platforms, @code{struct stat} contains
+@code{st_dev}, @code{st_ino} fields that are able to distinguish
+different inodes.
+
+Note: Such values can only be provided for most files on the
+file system. For a few files (such as inaccessible files),
+@code{st_dev} and @code{st_ino} are set to 0. Therefore,
+you should test whether @code{st_dev != 0 && st_ino != 0},
+before going to make inferences based on the file identity
+based on @code{st_dev} and @code{st_ino}.
diff --git a/lib/stat-w32.c b/lib/stat-w32.c
index 515311d..b4c762c 100644
--- a/lib/stat-w32.c
+++ b/lib/stat-w32.c
@@ -20,10 +20,15 @@

#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__

+/* Ensure that <windows.h> defines FILE_ID_INFO. */
+#undef _WIN32_WINNT
+#define _WIN32_WINNT _WIN32_WINNT_WIN8
+
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>
+#include <string.h>
#include <unistd.h>
#include <windows.h>

@@ -31,7 +36,16 @@
#include "stat-w32.h"

#include "pathmax.h"
+#include "verify.h"

+#if _GL_WINDOWS_STAT_INODES == 2
+/* GetFileInformationByHandleEx was introduced only in Windows Vista. */
+typedef DWORD (WINAPI * GetFileInformationByHandleExFuncType) (HANDLE hFile,
+ FILE_INFO_BY_HANDLE_CLASS fiClass,
+ LPVOID lpBuffer,
+ DWORD dwBufferSize);
+static GetFileInformationByHandleExFuncType GetFileInformationByHandleExFunc = NULL;
+#endif
/* GetFinalPathNameByHandle was introduced only in Windows Vista. */
typedef DWORD (WINAPI * GetFinalPathNameByHandleFuncType) (HANDLE hFile,
LPTSTR lpFilePath,
@@ -46,8 +60,12 @@ initialize (void)
HMODULE kernel32 = LoadLibrary ("kernel32.dll");
if (kernel32 != NULL)
{
+#if _GL_WINDOWS_STAT_INODES == 2
+ GetFileInformationByHandleExFunc =
+ (GetFileInformationByHandleExFuncType) GetProcAddress (kernel32, "GetFileInformationByHandleEx");
+#endif
GetFinalPathNameByHandleFunc =
- (GetFinalPathNameByHandleFuncType) GetProcAddress (kernel32, "GetFinalPathNameByHandleA");
+ (GetFinalPathNameByHandleFuncType) GetProcAddress (kernel32, "GetFinalPathNameByHandleA");
}
initialized = TRUE;
}
@@ -137,10 +155,71 @@ _gl_fstat_by_handle (HANDLE h, const char *path, struct stat *buf)
return -1;
}

+#if _GL_WINDOWS_STAT_INODES
+ /* st_ino can be determined through
+ GetFileInformationByHandle
+ <https://msdn.microsoft.com/en-us/library/aa364952.aspx>
+ <https://msdn.microsoft.com/en-us/library/aa363788.aspx>
+ as 64 bits, or through
+ GetFileInformationByHandleEx with argument FileIdInfo
+ <https://msdn.microsoft.com/en-us/library/aa364953.aspx>
+ <https://msdn.microsoft.com/en-us/library/hh802691.aspx>
+ as 128 bits.
+ The latter requires -D_WIN32_WINNT=_WIN32_WINNT_WIN8 or higher. */
+ /* Experiments show that GetFileInformationByHandleEx does not provide
+ much more information than GetFileInformationByHandle:
+ * The dwVolumeSerialNumber from GetFileInformationByHandle is equal
+ to the low 32 bits of the 64-bit VolumeSerialNumber from
+ GetFileInformationByHandleEx, and is apparently sufficient for
+ identifying the device.
+ * The nFileIndex from GetFileInformationByHandle is equal to the low
+ 64 bits of the 128-bit FileId from GetFileInformationByHandleEx,
+ and the high 64 bits of this 128-bit FileId are zero.
+ * On a FAT file system, GetFileInformationByHandleEx fails with error
+ ERROR_INVALID_PARAMETER, whereas GetFileInformationByHandle
+ succeeds.
+ * On a CIFS/SMB file system, GetFileInformationByHandleEx fails with
+ error ERROR_INVALID_LEVEL, whereas GetFileInformationByHandle
+ succeeds. */
+# if _GL_WINDOWS_STAT_INODES == 2
+ if (GetFileInformationByHandleExFunc != NULL)
+ {
+ FILE_ID_INFO id;
+ if (GetFileInformationByHandleExFunc (h, FileIdInfo, &id, sizeof (id)))
+ {
+ buf->st_dev = id.VolumeSerialNumber;
+ verify (sizeof (ino_t) == sizeof (id.FileId));
+ memcpy (&buf->st_ino, &id.FileId, sizeof (ino_t));
+ goto ino_done;
+ }
+ else
+ {
+ switch (GetLastError ())
+ {
+ case ERROR_INVALID_PARAMETER: /* older Windows version, or FAT */
+ case ERROR_INVALID_LEVEL: /* CIFS/SMB file system */
+ goto fallback;
+ default:
+ goto failed;
+ }
+ }
+ }
+ fallback: ;
+ /* Fallback for older Windows versions. */
+ buf->st_dev = info.dwVolumeSerialNumber;
+ buf->st_ino._gl_ino[0] = ((ULONGLONG) info.nFileIndexHigh << 32) | (ULONGLONG) info.nFileIndexLow;
+ buf->st_ino._gl_ino[1] = 0;
+ ino_done: ;
+# else /* _GL_WINDOWS_STAT_INODES == 1 */
+ buf->st_dev = info.dwVolumeSerialNumber;
+ buf->st_ino = ((ULONGLONG) info.nFileIndexHigh << 32) | (ULONGLONG) info.nFileIndexLow;
+# endif
+#else
/* 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;
+#endif

/* st_mode. */
unsigned int mode =
@@ -263,7 +342,11 @@ _gl_fstat_by_handle (HANDLE h, const char *path, struct stat *buf)
else if (type == FILE_TYPE_CHAR || type == FILE_TYPE_PIPE)
{
buf->st_dev = 0;
+#if _GL_WINDOWS_STAT_INODES == 2
+ buf->st_ino._gl_ino[0] = buf->st_ino._gl_ino[1] = 0;
+#else
buf->st_ino = 0;
+#endif
buf->st_mode = (type == FILE_TYPE_PIPE ? _S_IFIFO : _S_IFCHR);
buf->st_nlink = 1;
buf->st_uid = 0;
diff --git a/lib/stat.c b/lib/stat.c
index 199e216..a73a344 100644
--- a/lib/stat.c
+++ b/lib/stat.c
@@ -176,134 +176,160 @@ rpl_stat (char const *name, struct stat *buf)
UNC root directories (e.g. '\\server\share').
The second approach fails for some system files (e.g. 'C:\pagefile.sys'
and 'C:\hiberfil.sys'): ERROR_SHARING_VIOLATION.
- So we use the first approach for nearly all files, and the second one
- only for root and UNC root directories. */
+ The second approach gives more information (in particular, correct
+ st_dev, st_ino, st_nlink fields).
+ So we use the second approach and, as a fallback except for root and
+ UNC root directories, also the first approach. */
{
int ret;
- if (!((rlen == drive_prefix_len + 1 && ISSLASH (rname[drive_prefix_len]))
- || is_unc_root (rname)))
- {
- /* Approach based on the directory entry. */
-
- if (strchr (rname, '?') != NULL || strchr (rname, '*') != NULL)
- {
- /* Other Windows API functions would fail with error
- ERROR_INVALID_NAME. */
- if (malloca_rname != NULL)
- freea (malloca_rname);
- errno = ENOENT;
- return -1;
- }
-
- /* Get the details about the directory entry. */
- WIN32_FIND_DATA info;
- HANDLE h = FindFirstFile (rname, &info);
- if (h == INVALID_HANDLE_VALUE)
- goto failed;
-
- /* Test for error conditions before starting to fill *buf. */
- if (sizeof (buf->st_size) <= 4 && info.nFileSizeHigh > 0)
- {
- FindClose (h);
- if (malloca_rname != NULL)
- freea (malloca_rname);
- 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 (info.nFileSizeHigh > 0 || info.nFileSizeLow > 0)
- {
- const char *last_dot = NULL;
- const char *p;
- for (p = info.cFileName; *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;
- }
- }
- }
- buf->st_mode = mode;
-
- /* st_nlink. Ignore hard links here. */
- buf->st_nlink = 1;
-
- /* 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. */
- 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. */
+
+ {
+ /* Approach based on the file. */
+
+ /* Open a handle to the file.
+ CreateFile
+ <https://msdn.microsoft.com/en-us/library/aa363858.aspx>
+ <https://msdn.microsoft.com/en-us/library/aa363874.aspx> */
+ HANDLE h =
+ CreateFile (rname,
+ FILE_READ_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ OPEN_EXISTING,
+ /* FILE_FLAG_POSIX_SEMANTICS (treat file names that differ only
+ in case as different) makes sense only when applied to *all*
+ filesystem operations. */
+ FILE_FLAG_BACKUP_SEMANTICS /* | FILE_FLAG_POSIX_SEMANTICS */,
+ NULL);
+ if (h != INVALID_HANDLE_VALUE)
+ {
+ ret = _gl_fstat_by_handle (h, rname, buf);
+ CloseHandle (h);
+ goto done;
+ }
+ }
+
+ /* Test for root and UNC root directories. */
+ if ((rlen == drive_prefix_len + 1 && ISSLASH (rname[drive_prefix_len]))
+ || is_unc_root (rname))
+ goto failed;
+
+ /* Fallback. */
+ {
+ /* Approach based on the directory entry. */
+
+ if (strchr (rname, '?') != NULL || strchr (rname, '*') != NULL)
+ {
+ /* Other Windows API functions would fail with error
+ ERROR_INVALID_NAME. */
+ if (malloca_rname != NULL)
+ freea (malloca_rname);
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* Get the details about the directory entry. This can be done through
+ FindFirstFile
+ <https://msdn.microsoft.com/en-us/library/aa364418.aspx>
+ <https://msdn.microsoft.com/en-us/library/aa365740.aspx>
+ or through
+ FindFirstFileEx with argument FindExInfoBasic
+ <https://msdn.microsoft.com/en-us/library/aa364419.aspx>
+ <https://msdn.microsoft.com/en-us/library/aa364415.aspx>
+ <https://msdn.microsoft.com/en-us/library/aa365740.aspx> */
+ WIN32_FIND_DATA info;
+ HANDLE h = FindFirstFile (rname, &info);
+ if (h == INVALID_HANDLE_VALUE)
+ goto failed;
+
+ /* Test for error conditions before starting to fill *buf. */
+ if (sizeof (buf->st_size) <= 4 && info.nFileSizeHigh > 0)
+ {
+ FindClose (h);
+ if (malloca_rname != NULL)
+ freea (malloca_rname);
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+# if _GL_WINDOWS_STAT_INODES
+ buf->st_dev = 0;
+# if _GL_WINDOWS_STAT_INODES == 2
+ buf->st_ino._gl_ino[0] = buf->st_ino._gl_ino[1] = 0;
+# else /* _GL_WINDOWS_STAT_INODES == 1 */
+ buf->st_ino = 0;
+# endif
+# else
+ /* 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;
+# endif
+
+ /* 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 (info.nFileSizeHigh > 0 || info.nFileSizeLow > 0)
+ {
+ const char *last_dot = NULL;
+ const char *p;
+ for (p = info.cFileName; *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;
+ }
+ }
+ }
+ buf->st_mode = mode;
+
+ /* st_nlink. Ignore hard links here. */
+ buf->st_nlink = 1;
+
+ /* 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. */
+ 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. */
# if _GL_WINDOWS_STAT_TIMESPEC
- buf->st_atim = _gl_convert_FILETIME_to_timespec (&info.ftLastAccessTime);
- buf->st_mtim = _gl_convert_FILETIME_to_timespec (&info.ftLastWriteTime);
- buf->st_ctim = _gl_convert_FILETIME_to_timespec (&info.ftCreationTime);
+ buf->st_atim = _gl_convert_FILETIME_to_timespec (&info.ftLastAccessTime);
+ buf->st_mtim = _gl_convert_FILETIME_to_timespec (&info.ftLastWriteTime);
+ buf->st_ctim = _gl_convert_FILETIME_to_timespec (&info.ftCreationTime);
# else
- 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);
+ 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);
# endif

- FindClose (h);
+ FindClose (h);

- ret = 0;
- }
- else
- {
- /* Approach based on the file. */
-
- /* Open a handle to the file.
- CreateFile
- <https://msdn.microsoft.com/en-us/library/aa363858.aspx>
- <https://msdn.microsoft.com/en-us/library/aa363874.aspx> */
- HANDLE h =
- CreateFile (rname,
- FILE_READ_ATTRIBUTES,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
- NULL,
- OPEN_EXISTING,
- /* FILE_FLAG_POSIX_SEMANTICS (treat file names that differ only
- in case as different) makes sense only when applied to *all*
- filesystem operations. */
- FILE_FLAG_BACKUP_SEMANTICS /* | FILE_FLAG_POSIX_SEMANTICS */,
- NULL);
- if (h == INVALID_HANDLE_VALUE)
- goto failed;
-
- ret = _gl_fstat_by_handle (h, rname, buf);
- CloseHandle (h);
- }
+ ret = 0;
+ }

+ done:
if (ret >= 0 && check_dir && !S_ISDIR (buf->st_mode))
{
errno = ENOTDIR;
diff --git a/lib/sys_types.in.h b/lib/sys_types.in.h
index c5fea83..fd11fdc 100644
--- a/lib/sys_types.in.h
+++ b/lib/sys_types.in.h
@@ -42,6 +42,48 @@
# define _GL_WINDOWS_64_BIT_OFF_T 1
#endif

+/* Override dev_t and ino_t if distinguishable inodes support is requested
+ on native Windows. */
+#if @WINDOWS_STAT_INODES@
+
+# if @WINDOWS_STAT_INODES@ == 2
+/* Experimental, not useful in Windows 10. */
+
+/* Define dev_t to a 64-bit type. */
+# if !defined GNULIB_defined_dev_t
+typedef unsigned long long int rpl_dev_t;
+# undef dev_t
+# define dev_t rpl_dev_t
+# define GNULIB_defined_dev_t 1
+# endif
+
+/* Define ino_t to a 128-bit type. */
+# if !defined GNULIB_defined_ino_t
+/* MSVC does not have a 128-bit integer type.
+ GCC has a 128-bit integer type __int128, but only on 64-bit targets. */
+typedef struct { unsigned long long int _gl_ino[2]; } rpl_ino_t;
+# undef ino_t
+# define ino_t rpl_ino_t
+# define GNULIB_defined_ino_t 1
+# endif
+
+# else /* @WINDOWS_STAT_INODES@ == 1 */
+
+/* Define ino_t to a 64-bit type. */
+# if !defined GNULIB_defined_ino_t
+typedef unsigned long long int rpl_ino_t;
+# undef ino_t
+# define ino_t rpl_ino_t
+# define GNULIB_defined_ino_t 1
+# endif
+
+# endif
+
+/* Indicator, for gnulib internal purposes. */
+# define _GL_WINDOWS_STAT_INODES @WINDOWS_STAT_INODES@
+
+#endif
+
/* MSVC 9 defines size_t in <stddef.h>, not in <sys/types.h>. */
/* But avoid namespace pollution on glibc systems. */
#if ((defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__) \
diff --git a/m4/sys_types_h.m4 b/m4/sys_types_h.m4
index 2eb4e9e..e590670 100644
--- a/m4/sys_types_h.m4
+++ b/m4/sys_types_h.m4
@@ -1,4 +1,4 @@
-# sys_types_h.m4 serial 6
+# sys_types_h.m4 serial 7
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,
@@ -17,6 +17,14 @@ AC_DEFUN_ONCE([gl_SYS_TYPES_H],

dnl Whether to override the 'off_t' type.
AC_REQUIRE([gl_TYPE_OFF_T])
+
+ dnl Whether to override the 'dev_t' and 'ino_t' types.
+ m4_ifdef([gl_WINDOWS_STAT_INODES], [
+ AC_REQUIRE([gl_WINDOWS_STAT_INODES])
+ ], [
+ WINDOWS_STAT_INODES=0
+ ])
+ AC_SUBST([WINDOWS_STAT_INODES])
])

AC_DEFUN([gl_SYS_TYPES_H_DEFAULTS],
diff --git a/m4/windows-stat-inodes.m4 b/m4/windows-stat-inodes.m4
new file mode 100644
index 0000000..d3739a3
--- /dev/null
+++ b/m4/windows-stat-inodes.m4
@@ -0,0 +1,19 @@
+# windows-stat-inodes.m4 serial 1
+dnl Copyright (C) 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,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl Enable inode identification in 'struct stat' on native Windows platforms.
+dnl Set WINDOWS_STAT_INODES to
+dnl - 0 -> keep the default (dev_t = 32-bit, ino_t = 16-bit),
+dnl - 1 -> override types normally (dev_t = 32-bit, ino_t = 64-bit),
+dnl - 2 -> override types in an extended way (dev_t = 64-bit, ino_t = 128-bit).
+AC_DEFUN([gl_WINDOWS_STAT_INODES],
+[
+ AC_REQUIRE([AC_CANONICAL_HOST])
+ case "$host_os" in
+ mingw*) WINDOWS_STAT_INODES=1 ;;
+ *) WINDOWS_STAT_INODES=0 ;;
+ esac
+])
diff --git a/modules/fstat b/modules/fstat
index 076b5f7..7a62c6a 100644
--- a/modules/fstat
+++ b/modules/fstat
@@ -12,6 +12,7 @@ sys_stat
largefile
pathmax [test $REPLACE_FSTAT = 1]
unistd [test $REPLACE_FSTAT = 1]
+verify [test $REPLACE_FSTAT = 1]
msvc-nothrow [test $REPLACE_FSTAT = 1]

configure.ac:
diff --git a/modules/sys_types b/modules/sys_types
index 2e9c427..81a11aa 100644
--- a/modules/sys_types
+++ b/modules/sys_types
@@ -29,6 +29,7 @@ sys/types.h: sys_types.in.h $(top_builddir)/config.status
-e 's|@''PRAGMA_COLUMNS''@|@PRAGMA_COLUMNS@|g' \
-e 's|@''NEXT_SYS_TYPES_H''@|$(NEXT_SYS_TYPES_H)|g' \
-e 's|@''WINDOWS_64_BIT_OFF_T''@|$(WINDOWS_64_BIT_OFF_T)|g' \
+ -e 's|@''WINDOWS_STAT_INODES''@|$(WINDOWS_STAT_INODES)|g' \
< $(srcdir)/sys_types.in.h; \
} > $@-t && \
mv $@-t $@
diff --git a/modules/windows-stat-inodes b/modules/windows-stat-inodes
new file mode 100644
index 0000000..0607937
--- /dev/null
+++ b/modules/windows-stat-inodes
@@ -0,0 +1,27 @@
+Description:
+On native Windows platforms, ensure that 'struct stat' contains values
+in the st_dev, st_ino fields that are able to distinguish different inodes.
+
+Comment:
+This module should not be used as a dependency from a test module,
+otherwise when this module occurs as a tests-related module, it will
+have side effects on the compilation of the main modules in lib/.
+
+Files:
+m4/windows-stat-inodes.m4
+
+Depends-on:
+windows-stat-override
+
+configure.ac:
+AC_REQUIRE([gl_WINDOWS_STAT_INODES])
+
+Makefile.am:
+
+Include:
+
+License:
+LGPLv2+
+
+Maintainer:
+Bruno Haible
Bruno Haible
2017-05-14 16:03:05 UTC
Permalink
Raw Message
With this adaptation of the 'same-inode' module, the 'stat' test
now passes on native Windows. (Well, it says "SKIP" because of missing
symbolic link support.)


2017-05-14 Bruno Haible <***@clisp.org>

same-inode: Adapt for windows-stat-inodes.
* lib/same-inode.h: Include <sys/types.h>.
(SAME_INODE) [_GL_WINDOWS_STAT_INODES]: Define specifically.
* modules/same-inode (Depends-on): Add sys_types.

diff --git a/lib/same-inode.h b/lib/same-inode.h
index 7cece6d..a08bc4e 100644
--- a/lib/same-inode.h
+++ b/lib/same-inode.h
@@ -18,6 +18,8 @@
#ifndef SAME_INODE_H
# define SAME_INODE_H 1

+# include <sys/types.h>
+
# ifdef __VMS
# define SAME_INODE(a, b) \
((a).st_ino[0] == (b).st_ino[0] \
@@ -25,9 +27,17 @@
&& (a).st_ino[2] == (b).st_ino[2] \
&& (a).st_dev == (b).st_dev)
# elif (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
-/* On MinGW, struct stat lacks necessary info, so always return 0.
- Callers can use !a.st_ino to deduce that the information is unknown. */
-# define SAME_INODE(a, b) 0
+ /* Native Windows. */
+# if _GL_WINDOWS_STAT_INODES
+ /* stat() and fstat() set st_dev and st_ino to 0 if information about
+ the inode is not available. */
+# define SAME_INODE(a, b) \
+ (!((a).st_ino == 0 && (a).st_dev == 0) \
+ && (a).st_ino == (b).st_ino && (a).st_dev == (b).st_dev)
+# else
+ /* stat() and fstat() set st_ino to 0 always. */
+# define SAME_INODE(a, b) 0
+# endif
# else
# define SAME_INODE(a, b) \
((a).st_ino == (b).st_ino \
diff --git a/modules/same-inode b/modules/same-inode
index 27da5d2..53964d0 100644
--- a/modules/same-inode
+++ b/modules/same-inode
@@ -5,6 +5,7 @@ Files:
lib/same-inode.h

Depends-on:
+sys_types

configure.ac:

Loading...