Discussion:
utimens: support for native Windows
Bruno Haible
2017-05-01 09:56:41 UTC
Permalink
With this patch, the test suite of utimens and fdutimens passes
on native Windows (mingw and MSVC 14). Some tests say "SKIP" rather than
"PASS" because symbolic links are not supported (just cosmetics).


2017-04-30 Bruno Haible <***@clisp.org>

utimens: Add support for native Windows.
* lib/utimens.c: Include <windows.h>, msvc-nothrow.h.
(fdutimens): Provide a native Windows implementation, like utime.c with
added tv_nsec support.
* modules/utimens (Depends-on): Add msvc-nothrow, utime.
Suggested by Tim Rühsen <***@gmx.de>.

diff --git a/lib/utimens.c b/lib/utimens.c
index 3b45119..0b3b8e2 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -35,6 +35,12 @@
#include "stat-time.h"
#include "timespec.h"

+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+# include "msvc-nothrow.h"
+#endif
+
/* Avoid recursion with rpl_futimens or rpl_utimensat. */
#undef futimens
#undef utimensat
@@ -271,6 +277,82 @@ fdutimens (int fd, char const *file, struct timespec const timespec[2])
lutimensat_works_really = -1;
#endif /* HAVE_UTIMENSAT || HAVE_FUTIMENS */

+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+ /* On native Windows, use SetFileTime(). See
+ <https://msdn.microsoft.com/en-us/library/ms724933.aspx>
+ <https://msdn.microsoft.com/en-us/library/ms724284.aspx> */
+ if (0 <= fd)
+ {
+ HANDLE handle;
+ FILETIME current_time;
+ FILETIME last_access_time;
+ FILETIME last_write_time;
+
+ handle = (HANDLE) _get_osfhandle (fd);
+ if (handle == INVALID_HANDLE_VALUE)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ if (ts == NULL || ts[0].tv_nsec == UTIME_NOW || ts[1].tv_nsec == UTIME_NOW)
+ {
+ /* GetSystemTimeAsFileTime
+ <https://msdn.microsoft.com/en-us/library/ms724397.aspx>.
+ It would be overkill to use
+ GetSystemTimePreciseAsFileTime
+ <https://msdn.microsoft.com/en-us/library/hh706895.aspx>. */
+ GetSystemTimeAsFileTime (&current_time);
+ }
+
+ if (ts == NULL || ts[0].tv_nsec == UTIME_NOW)
+ {
+ last_access_time = current_time;
+ }
+ else if (ts[0].tv_nsec == UTIME_OMIT)
+ {
+ last_access_time.dwLowDateTime = 0;
+ last_access_time.dwHighDateTime = 0;
+ }
+ else
+ {
+ ULONGLONG time_since_16010101 =
+ (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
+ last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
+ last_access_time.dwHighDateTime = time_since_16010101 >> 32;
+ }
+
+ if (ts == NULL || ts[1].tv_nsec == UTIME_NOW)
+ {
+ last_write_time = current_time;
+ }
+ else if (ts[1].tv_nsec == UTIME_OMIT)
+ {
+ last_write_time.dwLowDateTime = 0;
+ last_write_time.dwHighDateTime = 0;
+ }
+ else
+ {
+ ULONGLONG time_since_16010101 =
+ (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
+ last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
+ last_write_time.dwHighDateTime = time_since_16010101 >> 32;
+ }
+
+ if (SetFileTime (handle, NULL, &last_access_time, &last_write_time))
+ return 0;
+ else
+ {
+ #if 0
+ DWORD sft_error = GetLastError ();
+ fprintf (stderr, "utime SetFileTime error 0x%x\n", (unsigned int) sft_error);
+ #endif
+ errno = EINVAL;
+ return -1;
+ }
+ }
+#endif
+
/* The platform lacks an interface to set file timestamps with
nanosecond resolution, so do the best we can, discarding any
fractional part of the timestamp. */
diff --git a/modules/utimens b/modules/utimens
index c8167ce..dbe24df 100644
--- a/modules/utimens
+++ b/modules/utimens
@@ -14,11 +14,13 @@ fcntl-h
fstat
lstat
gettime
+msvc-nothrow
stat-time
stdbool
sys_stat
sys_time
time
+utime
utime-h

configure.ac:
Bruno Haible
2017-05-08 09:59:33 UTC
Permalink
A small correction to this new code:


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

utimens: Improve error code on native Windows.
* lib/utimens.c (fdutimens): If fd was not opened with O_RDWR, fail with
error code EACCES, not EINVAL.

diff --git a/lib/utimens.c b/lib/utimens.c
index 5f3a846..b027cfb 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -349,11 +349,19 @@ fdutimens (int fd, char const *file, struct timespec const timespec[2])
return 0;
else
{
- #if 0
DWORD sft_error = GetLastError ();
- fprintf (stderr, "utime SetFileTime error 0x%x\n", (unsigned int) sft_error);
+ #if 0
+ fprintf (stderr, "fdutimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
#endif
- errno = EINVAL;
+ switch (sft_error)
+ {
+ case ERROR_ACCESS_DENIED: /* fd was opened without O_RDWR */
+ errno = EACCES; /* not specified by POSIX */
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
return -1;
}
}
Bruno Haible
2017-05-08 10:02:42 UTC
Permalink
Oops, I had verified the subsecond resolution support (through Cygwin's 'ls',
as "ls -l --full-time") only for the case fd >= 0. For the case fd < 0, this
patch adds the subsecond resolution support.


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

utimens: On native Windows, support 100ns resolution also if fd < 0.
* lib/utime.in.h: Include <time.h>.
(_gl_utimens_windows): New declaration.
* lib/utime.c (_gl_utimens_windows): New function, based on utime.
(utime): Invoke it.
* lib/utimens.c (fdutimens): On native Windows, call _gl_utimens_windows
instead of utime.
* modules/utime (Depends-on): Add 'time'.

diff --git a/lib/utime.c b/lib/utime.c
index 230d36b..6226934 100644
--- a/lib/utime.c
+++ b/lib/utime.c
@@ -30,7 +30,7 @@
# include "malloca.h"

int
-utime (const char *name, const struct utimbuf *ts)
+_gl_utimens_windows (const char *name, struct timespec ts[2])
{
/* POSIX <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>
specifies: "More than two leading <slash> characters shall be treated as
@@ -146,13 +146,13 @@ utime (const char *name, const struct utimbuf *ts)
{
{
ULONGLONG time_since_16010101 =
- (ULONGLONG) ts->actime * 10000000 + 116444736000000000LL;
+ (ULONGLONG) ts[0].tv_sec * 10000000 + ts[0].tv_nsec / 100 + 116444736000000000LL;
last_access_time.dwLowDateTime = (DWORD) time_since_16010101;
last_access_time.dwHighDateTime = time_since_16010101 >> 32;
}
{
ULONGLONG time_since_16010101 =
- (ULONGLONG) ts->modtime * 10000000 + 116444736000000000LL;
+ (ULONGLONG) ts[1].tv_sec * 10000000 + ts[1].tv_nsec / 100 + 116444736000000000LL;
last_write_time.dwLowDateTime = (DWORD) time_since_16010101;
last_write_time.dwHighDateTime = time_since_16010101 >> 32;
}
@@ -168,7 +168,7 @@ utime (const char *name, const struct utimbuf *ts)
{
#if 0
DWORD sft_error = GetLastError ();
- fprintf (stderr, "utime SetFileTime error 0x%x\n", (unsigned int) sft_error);
+ fprintf (stderr, "utimens SetFileTime error 0x%x\n", (unsigned int) sft_error);
#endif
CloseHandle (handle);
if (malloca_rname != NULL)
@@ -181,7 +181,7 @@ utime (const char *name, const struct utimbuf *ts)
failed:
{
#if 0
- fprintf (stderr, "utime CreateFile/GetFileAttributes error 0x%x\n", (unsigned int) error);
+ fprintf (stderr, "utimens CreateFile/GetFileAttributes error 0x%x\n", (unsigned int) error);
#endif
if (malloca_rname != NULL)
freea (malloca_rname);
@@ -237,4 +237,20 @@ utime (const char *name, const struct utimbuf *ts)
}
}

+int
+utime (const char *name, const struct utimbuf *ts)
+{
+ if (ts == NULL)
+ return _gl_utimens_windows (name, NULL);
+ else
+ {
+ struct timespec ts_with_nanoseconds[2];
+ ts_with_nanoseconds[0].tv_sec = ts->actime;
+ ts_with_nanoseconds[0].tv_nsec = 0;
+ ts_with_nanoseconds[1].tv_sec = ts->modtime;
+ ts_with_nanoseconds[1].tv_nsec = 0;
+ return _gl_utimens_windows (name, ts_with_nanoseconds);
+ }
+}
+
#endif
diff --git a/lib/utime.in.h b/lib/utime.in.h
index 8847e72..df80d8c 100644
--- a/lib/utime.in.h
+++ b/lib/utime.in.h
@@ -33,6 +33,11 @@
# include <sys/utime.h>
#endif

+#if @GNULIB_UTIME@
+/* Get struct timespec. */
+# include <time.h>
+#endif
+
/* The definitions of _GL_FUNCDECL_RPL etc. are copied here. */

/* The definition of _GL_ARG_NONNULL is copied here. */
@@ -74,6 +79,10 @@ _GL_WARN_ON_USE (utime,
# endif
#endif

+#if @GNULIB_UTIME@
+extern int _gl_utimens_windows (const char *filename, struct timespec ts[2]);
+#endif
+

#endif /* ***@GUARD_PREFIX@_UTIME_H */
#endif /* ***@GUARD_PREFIX@_UTIME_H */
diff --git a/lib/utimens.c b/lib/utimens.c
index b027cfb..b4bfa8e 100644
--- a/lib/utimens.c
+++ b/lib/utimens.c
@@ -473,7 +473,9 @@ fdutimens (int fd, char const *file, struct timespec const timespec[2])
return -1;
}

-#if HAVE_WORKING_UTIMES
+#ifdef USE_SETFILETIME
+ return _gl_utimens_windows (file, ts);
+#elif HAVE_WORKING_UTIMES
return utimes (file, t);
#else
{
diff --git a/modules/utime b/modules/utime
index 545a24c..0fc34eb 100644
--- a/modules/utime
+++ b/modules/utime
@@ -7,6 +7,7 @@ m4/utime.m4

Depends-on:
utime-h
+time
filename [test $HAVE_UTIME = 0 || test $REPLACE_UTIME = 1]
malloca [test $HAVE_UTIME = 0 || test $REPLACE_UTIME = 1]
Loading...