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.
mingw*)
# Native Windows
WINDOWS_NATIVE_BUILD="yes"
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"
PLATFORM_CFLAGS="-municode -mno-ms-bitfields"
PLATFORM_LDFLAGS="-no-undefined"
WITH_NTFS_3G_DEFAULT="no"
- * 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;
/* Seconds since start of UNIX epoch (January 1, 1970) */
#ifdef _WIN64
int64_t tv_sec;
#endif
/* Nanoseconds (0-999999999) */
int32_t tv_nsec;
#endif
/* Nanoseconds (0-999999999) */
int32_t tv_nsec;
-# define wimlib_timespec struct timespec /* standard definition */
+# define wimlib_timespec timespec /* standard definition */
uint64_t hard_link_group_id;
/** Time this file was created. */
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. */
/** Time this file was last written to. */
- wimlib_timespec last_write_time;
+ struct wimlib_timespec last_write_time;
/** Time this file was last accessed. */
/** 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.
*
/** The UNIX user ID of this file. This is a wimlib extension.
*
* object_id.object_id is not all zeroes. */
struct wimlib_object_id object_id;
* object_id.object_id is not all zeroes. */
struct wimlib_object_id object_id;
+ /** 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.
/**
* Variable-length array of streams that make up this file.
#include "wimlib/types.h"
#include "wimlib/types.h"
+struct wimlib_timespec;
+
extern time_t
wim_timestamp_to_time_t(u64 timestamp);
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);
extern struct timeval
wim_timestamp_to_timeval(u64 timestamp);
#define TIMESTR_MAX 100
static void
#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;
- 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);
}
tprintf(T("%-20"TS"= %"TS"\n"), type, timestr);
}
dentry->security_descriptor_size);
}
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)
if (dentry->attributes & WIMLIB_FILE_ATTRIBUTE_REPARSE_POINT)
wdentry->attributes = inode->i_attributes;
wdentry->hard_link_group_id = inode->i_ino;
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;
if (inode_get_unix_data(inode, &unix_data)) {
wdentry->unix_uid = unix_data.uid;
# include "config.h"
#endif
# include "config.h"
#endif
+#include "wimlib.h" /* for struct wimlib_timespec */
#include "wimlib/timestamp.h"
/*
#include "wimlib/timestamp.h"
/*
return (timestamp / TICKS_PER_SECOND) - EPOCH_DISTANCE;
}
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)
{
struct timeval
wim_timestamp_to_timeval(u64 timestamp)
{
- /* 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);
}
gettimeofday(&tv, NULL);
return timeval_to_wim_timestamp(&tv);
}
/* Translate a WIM timestamp into a human-readable string. */
void
/* Translate a WIM timestamp into a human-readable string. */
void
{
struct tm tm;
time_t t = wim_timestamp_to_time_t(timestamp);
{
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);
}
gmtime_r(&t, &tm);
tstrftime(buf, len, T("%a %b %d %H:%M:%S %Y UTC"), &tm);
}
#include "wimlib/assert.h"
#include "wimlib/glob.h"
#include "wimlib/error.h"
#include "wimlib/assert.h"
#include "wimlib/glob.h"
#include "wimlib/error.h"
+#include "wimlib/timestamp.h"
#include "wimlib/util.h"
static int
#include "wimlib/util.h"
static int
+/* 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;
+}
+