From db1b9ac63e5c0a1dffb51a1a162cf69a01a5cf24 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 18 Apr 2017 23:58:03 -0700 Subject: [PATCH] Improved year 2038 safety 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 | 3 ++- include/wimlib.h | 47 ++++++++++++++++++++++++++------------ include/wimlib/timestamp.h | 6 +++++ programs/imagex.c | 31 +++++++++++++------------ src/iterate_dir.c | 22 +++++++++--------- src/timestamp.c | 28 +++++++++++++++++++---- src/win32_replacements.c | 12 ++++++++++ 7 files changed, 104 insertions(+), 45 deletions(-) diff --git a/configure.ac b/configure.ac index 693e345a..2161f3ba 100644 --- a/configure.ac +++ b/configure.ac @@ -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" diff --git a/include/wimlib.h b/include/wimlib.h index 135f5cb4..5992a438 100644 --- a/include/wimlib.h +++ b/include/wimlib.h @@ -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. diff --git a/include/wimlib/timestamp.h b/include/wimlib/timestamp.h index ac69846b..37666c9a 100644 --- a/include/wimlib/timestamp.h +++ b/include/wimlib/timestamp.h @@ -12,9 +12,15 @@ #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); diff --git a/programs/imagex.c b/programs/imagex.c index e62757b8..83058852 100644 --- a/programs/imagex.c +++ b/programs/imagex.c @@ -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) diff --git a/src/iterate_dir.c b/src/iterate_dir.c index e4eb87d7..940fc24b 100644 --- a/src/iterate_dir.c +++ b/src/iterate_dir.c @@ -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; diff --git a/src/timestamp.c b/src/timestamp.c index 16e412b3..51046cdb 100644 --- a/src/timestamp.c +++ b/src/timestamp.c @@ -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); } diff --git a/src/win32_replacements.c b/src/win32_replacements.c index 2a16500d..c2fb556d 100644 --- a/src/win32_replacements.c +++ b/src/win32_replacements.c @@ -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__ */ -- 2.43.0