Improved year 2038 safety
authorEric Biggers <ebiggers3@gmail.com>
Wed, 19 Apr 2017 06:58:03 +0000 (23:58 -0700)
committerEric Biggers <ebiggers3@gmail.com>
Wed, 19 Apr 2017 07:17:01 +0000 (00:17 -0700)
Make wimlib on 32-bit Windows year 2038 safe by doing the following:

- Build both the library and program with 64-bit time_t, being careful
  to avoid changing the timespec struct exposed in the API.
- Update wimlib's API to include an extended seconds field in
  wimlib_dir_entry for each timestamp, and set it when tv_sec is 32-bit.
- When needing the current time, call GetSystemTimeAsFileTime() instead
  of MinGW's gettimeofday().

This also has the advantage that due to switching to the 64-bit time_t
functions, 32-bit wimlib-imagex.exe now prints timestamps prior to year
1970 correctly.

Unfortunately, despite the API improvement, we cannot at this time make
wimlib fully Y2038-safe on 32-bit UNIX, due to lack of OS support.

configure.ac
include/wimlib.h
include/wimlib/timestamp.h
programs/imagex.c
src/iterate_dir.c
src/timestamp.c
src/win32_replacements.c

index 693e345..2161f3b 100644 (file)
@@ -34,7 +34,8 @@ case "$host_os" in
 mingw*)
        # Native Windows
        WINDOWS_NATIVE_BUILD="yes"
-       PLATFORM_CPPFLAGS="-D_POSIX -D_POSIX_THREAD_SAFE_FUNCTIONS -DUNICODE -D_UNICODE -D_CRT_NON_CONFORMING_SWPRINTFS"
+       # -D__MINGW_USE_VC2005_COMPAT: make time_t 64-bit on 32-bit Windows.
+       PLATFORM_CPPFLAGS="-D_POSIX -D_POSIX_THREAD_SAFE_FUNCTIONS -DUNICODE -D_UNICODE -D_CRT_NON_CONFORMING_SWPRINTFS -D__MINGW_USE_VC2005_COMPAT"
        PLATFORM_CFLAGS="-municode -mno-ms-bitfields"
        PLATFORM_LDFLAGS="-no-undefined"
        WITH_NTFS_3G_DEFAULT="no"
index 135f5cb..5992a43 100644 (file)
@@ -418,15 +418,18 @@ extern "C" {
 #endif
 
 /*
- * To represent file timestamps, wimlib's API uses the POSIX 'struct timespec'.
- * This was probably a mistake because it doesn't play well with Visual Studio.
- * In old VS versions it isn't present at all; in newer VS versions it is
- * supposedly present, but I wouldn't trust it to be the same size as the one
- * MinGW uses.  The solution is to define a compatible structure ourselves when
- * this header is included on Windows and the compiler is not MinGW.
- */
-#if defined(_WIN32) && !defined(__GNUC__)
-typedef struct {
+ * To represent file timestamps, wimlib's API originally used the POSIX 'struct
+ * timespec'.  This was a mistake because when building wimlib for 32-bit
+ * Windows with MinGW we ended up originally using 32-bit time_t which isn't
+ * year 2038-safe, and therefore we had to later add fields like
+ * 'creation_time_high' to hold the high 32 bits of each timestamp.  Moreover,
+ * old Visual Studio versions did not define struct timespec, while newer ones
+ * define it but with 64-bit tv_sec.  So to at least avoid a missing or
+ * incompatible 'struct timespec' definition, define the correct struct
+ * ourselves when this header is included on Windows.
+ */
+#ifdef _WIN32
+struct wimlib_timespec {
        /* Seconds since start of UNIX epoch (January 1, 1970) */
 #ifdef _WIN64
        int64_t tv_sec;
@@ -435,9 +438,9 @@ typedef struct {
 #endif
        /* Nanoseconds (0-999999999) */
        int32_t tv_nsec;
-} wimlib_timespec;
+};
 #else
-#  define wimlib_timespec  struct timespec  /* standard definition */
+#  define wimlib_timespec  timespec  /* standard definition */
 #endif
 
 /**
@@ -1591,13 +1594,13 @@ struct wimlib_dir_entry {
        uint64_t hard_link_group_id;
 
        /** Time this file was created.  */
-       wimlib_timespec creation_time;
+       struct wimlib_timespec creation_time;
 
        /** Time this file was last written to.  */
-       wimlib_timespec last_write_time;
+       struct wimlib_timespec last_write_time;
 
        /** Time this file was last accessed.  */
-       wimlib_timespec last_access_time;
+       struct wimlib_timespec last_access_time;
 
        /** The UNIX user ID of this file.  This is a wimlib extension.
         *
@@ -1626,7 +1629,21 @@ struct wimlib_dir_entry {
         * object_id.object_id is not all zeroes.  */
        struct wimlib_object_id object_id;
 
-       uint64_t reserved[6];
+       /** High 32 bits of the seconds portion of the creation timestamp,
+        * filled in if @p wimlib_timespec.tv_sec is only 32-bit. */
+       int32_t creation_time_high;
+
+       /** High 32 bits of the seconds portion of the last write timestamp,
+        * filled in if @p wimlib_timespec.tv_sec is only 32-bit. */
+       int32_t last_write_time_high;
+
+       /** High 32 bits of the seconds portion of the last access timestamp,
+        * filled in if @p wimlib_timespec.tv_sec is only 32-bit. */
+       int32_t last_access_time_high;
+
+       int32_t reserved2;
+
+       uint64_t reserved[4];
 
        /**
         * Variable-length array of streams that make up this file.
index ac69846..37666c9 100644 (file)
 
 #include "wimlib/types.h"
 
+struct wimlib_timespec;
+
 extern time_t
 wim_timestamp_to_time_t(u64 timestamp);
 
+extern void
+wim_timestamp_to_wimlib_timespec(u64 timestamp, struct wimlib_timespec *wts,
+                                s32 *high_part_ret);
+
 extern struct timeval
 wim_timestamp_to_timeval(u64 timestamp);
 
index e62757b..8305885 100644 (file)
@@ -2499,21 +2499,21 @@ static const struct {
 #define TIMESTR_MAX 100
 
 static void
-timespec_to_string(const struct timespec *spec, tchar *buf)
+print_time(const tchar *type, const struct wimlib_timespec *wts,
+          int32_t high_part)
 {
-       time_t t = spec->tv_sec;
+       tchar timestr[TIMESTR_MAX];
+       time_t t;
        struct tm tm;
-       gmtime_r(&t, &tm);
-       tstrftime(buf, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
-       buf[TIMESTR_MAX - 1] = '\0';
-}
 
-static void
-print_time(const tchar *type, const struct timespec *spec)
-{
-       tchar timestr[TIMESTR_MAX];
+       if (sizeof(wts->tv_sec) == 4 && sizeof(t) > sizeof(wts->tv_sec))
+               t = (uint32_t)wts->tv_sec | ((uint64_t)high_part << 32);
+       else
+               t = wts->tv_sec;
 
-       timespec_to_string(spec, timestr);
+       gmtime_r(&t, &tm);
+       tstrftime(timestr, TIMESTR_MAX, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
+       timestr[TIMESTR_MAX - 1] = '\0';
 
        tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
 }
@@ -2674,9 +2674,12 @@ print_dentry_detailed(const struct wimlib_dir_entry *dentry)
                                          dentry->security_descriptor_size);
        }
 
-       print_time(T("Creation Time"), &dentry->creation_time);
-       print_time(T("Last Write Time"), &dentry->last_write_time);
-       print_time(T("Last Access Time"), &dentry->last_access_time);
+       print_time(T("Creation Time"),
+                  &dentry->creation_time, dentry->creation_time_high);
+       print_time(T("Last Write Time"),
+                  &dentry->last_write_time, dentry->last_write_time_high);
+       print_time(T("Last Access Time"),
+                  &dentry->last_access_time, dentry->last_access_time_high);
 
 
        if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
index e4eb87d..940fc24 100644 (file)
@@ -122,17 +122,17 @@ init_wimlib_dentry(struct wimlib_dir_entry *wdentry, struct wim_dentry *dentry,
        wdentry->attributes = inode->i_attributes;
        wdentry->hard_link_group_id = inode->i_ino;
 
-       /* For Windows builds, to maintain ABI compatibility with previous
-        * versions of the DLLs, double-check that we're using the expected size
-        * for 'struct timespec'. */
-#ifdef _WIN64
-       STATIC_ASSERT(sizeof(struct timespec) == 16);
-#elif defined(_WIN32)
-       STATIC_ASSERT(sizeof(struct timespec) == 8);
-#endif
-       wdentry->creation_time = wim_timestamp_to_timespec(inode->i_creation_time);
-       wdentry->last_write_time = wim_timestamp_to_timespec(inode->i_last_write_time);
-       wdentry->last_access_time = wim_timestamp_to_timespec(inode->i_last_access_time);
+       wim_timestamp_to_wimlib_timespec(inode->i_creation_time,
+                                        &wdentry->creation_time,
+                                        &wdentry->creation_time_high);
+
+       wim_timestamp_to_wimlib_timespec(inode->i_last_write_time,
+                                        &wdentry->last_write_time,
+                                        &wdentry->last_write_time_high);
+
+       wim_timestamp_to_wimlib_timespec(inode->i_last_access_time,
+                                        &wdentry->last_access_time,
+                                        &wdentry->last_access_time_high);
 
        if (inode_get_unix_data(inode, &unix_data)) {
                wdentry->unix_uid = unix_data.uid;
index 16e412b..51046cd 100644 (file)
@@ -25,6 +25,7 @@
 #  include "config.h"
 #endif
 
+#include "wimlib.h" /* for struct wimlib_timespec */
 #include "wimlib/timestamp.h"
 
 /*
@@ -56,6 +57,27 @@ wim_timestamp_to_time_t(u64 timestamp)
        return (timestamp / TICKS_PER_SECOND) - EPOCH_DISTANCE;
 }
 
+void
+wim_timestamp_to_wimlib_timespec(u64 timestamp, struct wimlib_timespec *wts,
+                                s32 *high_part_ret)
+{
+       s64 sec = (timestamp / TICKS_PER_SECOND) - EPOCH_DISTANCE;
+
+       wts->tv_sec = sec;
+       wts->tv_nsec = (timestamp % TICKS_PER_SECOND) * NANOSECONDS_PER_TICK;
+
+       if (sizeof(wts->tv_sec) == 4)
+               *high_part_ret = sec >> 32;
+}
+
+#ifdef __WIN32__
+static _unused_attribute void
+check_sizeof_time_t(void)
+{
+       /* Windows builds should always be using 64-bit time_t now. */
+       STATIC_ASSERT(sizeof(time_t) == 8);
+}
+#else
 struct timeval
 wim_timestamp_to_timeval(u64 timestamp)
 {
@@ -102,13 +124,10 @@ now_as_wim_timestamp(void)
 {
        struct timeval tv;
 
-       /* On Windows we rely on MinGW providing gettimeofday() for us.  This
-        * could be changed to calling GetSystemTimeAsFileTime() directly, but
-        * now_as_wim_timestamp() isn't called much and it's simpler to keep the
-        * code for all platforms the same.  */
        gettimeofday(&tv, NULL);
        return timeval_to_wim_timestamp(&tv);
 }
+#endif /* !__WIN32__ */
 
 /* Translate a WIM timestamp into a human-readable string.  */
 void
@@ -116,6 +135,7 @@ wim_timestamp_to_str(u64 timestamp, tchar *buf, size_t len)
 {
        struct tm tm;
        time_t t = wim_timestamp_to_time_t(timestamp);
+
        gmtime_r(&t, &tm);
        tstrftime(buf, len, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
 }
index 2a16500..c2fb556 100644 (file)
@@ -36,6 +36,7 @@
 #include "wimlib/assert.h"
 #include "wimlib/glob.h"
 #include "wimlib/error.h"
+#include "wimlib/timestamp.h"
 #include "wimlib/util.h"
 
 static int
@@ -774,4 +775,15 @@ get_random_bytes(void *p, size_t n)
        }
 }
 
+/* Retrieve the current time as a WIM timestamp.  */
+u64
+now_as_wim_timestamp(void)
+{
+       FILETIME ft;
+
+       GetSystemTimeAsFileTime(&ft);
+
+       return ((u64)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
+}
+
 #endif /* __WIN32__ */