src: prefer using SetFileTime() on Windows

This commit is contained in:
Markus F.X.J. Oberhumer
2023-10-27 06:58:14 +02:00
parent 27f155374a
commit 37d393cbc8
2 changed files with 70 additions and 34 deletions
+1 -1
View File
@@ -37,7 +37,7 @@
#pragma warning(disable : 4201 4214 4514) #pragma warning(disable : 4201 4214 4514)
#endif #endif
#if defined(_MSC_VER) && !defined(__clang__) #if defined(_MSC_VER) && !defined(__clang__)
/* avoid warnings in some versions of <windows.h> */ /* avoid warnings in some older versions of <windows.h> */
#pragma warning(disable : 5105) #pragma warning(disable : 5105)
#endif #endif
+69 -33
View File
@@ -36,6 +36,9 @@
#include <fcntl.h> #include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#endif #endif
#if defined(_WIN32) || defined(__CYGWIN__)
#include "util/windows_lean.h" // _get_osfhandle, GetFileTime, SetFileTime
#endif
#include "conf.h" #include "conf.h"
#include "file.h" #include "file.h"
#include "packmast.h" #include "packmast.h"
@@ -43,6 +46,8 @@
#include "util/membuffer.h" #include "util/membuffer.h"
#if USE_UTIMENSAT && defined(AT_FDCWD) #if USE_UTIMENSAT && defined(AT_FDCWD)
#elif (defined(_WIN32) || defined(__CYGWIN__)) && 1
#define USE_SETFILETIME 1
#elif (ACC_OS_DOS32) && defined(__DJGPP__) #elif (ACC_OS_DOS32) && defined(__DJGPP__)
#define USE_FTIME 1 #define USE_FTIME 1
#elif ((ACC_OS_WIN32 || ACC_OS_WIN64) && (ACC_CC_INTELC || ACC_CC_MSC)) #elif ((ACC_OS_WIN32 || ACC_OS_WIN64) && (ACC_CC_INTELC || ACC_CC_MSC))
@@ -62,6 +67,18 @@
// util // util
**************************************************************************/ **************************************************************************/
namespace {
struct XStat {
struct stat st;
#if USE_SETFILETIME
FILETIME ft_atime;
FILETIME ft_mtime;
#elif USE_FTIME
struct ftime ft_ftime;
#endif
};
// ignore errors in some cases and silence __attribute__((__warn_unused_result__)) // ignore errors in some cases and silence __attribute__((__warn_unused_result__))
#define IGNORE_ERROR(var) ACC_UNUSED(var) #define IGNORE_ERROR(var) ACC_UNUSED(var)
@@ -79,7 +96,29 @@ static constexpr int get_open_flags(OpenMode om) noexcept {
return O_RDONLY | O_BINARY; // will cause an error if file does not exist return O_RDONLY | O_BINARY; // will cause an error if file does not exist
} }
static void copy_file_contents(const char *iname, const char *oname, OpenMode om) may_throw { // set file time of an open file
static void set_fd_timestamp(int fd, const XStat *xst) noexcept {
#if USE_SETFILETIME
BOOL r = SetFileTime((HANDLE) _get_osfhandle(fd), nullptr, &xst->ft_atime, &xst->ft_mtime);
IGNORE_ERROR(r);
#elif USE_FTIME
auto ft_ftime = xst->ft_ftime; // djgpp2 libc bug: not const, so use a copy
int r = setftime(fd, &ft_ftime);
IGNORE_ERROR(r);
#elif USE__FUTIME
struct _utimbuf u = {};
u.actime = xst->st.st_atime;
u.modtime = xst->st.st_mtime;
int r = _futime(fd, &u);
IGNORE_ERROR(r);
#endif
// maybe unused
UNUSED(fd);
UNUSED(xst);
}
static void copy_file_contents(const char *iname, const char *oname, OpenMode om,
const XStat *oname_timestamp) may_throw {
InputFile fi; InputFile fi;
fi.sopen(iname, get_open_flags(RO_MUST_EXIST), SH_DENYWR); fi.sopen(iname, get_open_flags(RO_MUST_EXIST), SH_DENYWR);
fi.seek(0, SEEK_SET); fi.seek(0, SEEK_SET);
@@ -96,17 +135,19 @@ static void copy_file_contents(const char *iname, const char *oname, OpenMode om
break; break;
fo.write(buf, bytes); fo.write(buf, bytes);
} }
if (oname_timestamp != nullptr)
set_fd_timestamp(fo.getFd(), oname_timestamp);
fi.closex(); fi.closex();
fo.closex(); fo.closex();
} }
static void copy_file_attributes(const struct stat *st, const char *oname, bool preserve_mode, static void copy_file_attributes(const XStat *xst, const char *oname, bool preserve_mode,
bool preserve_ownership, bool preserve_timestamp) noexcept { bool preserve_ownership, bool preserve_timestamp) noexcept {
const struct stat *const st = &xst->st;
// copy time stamp // copy time stamp
if (preserve_timestamp) { if (preserve_timestamp) {
#if USE_UTIMENSAT && defined(AT_FDCWD) #if USE_UTIMENSAT && defined(AT_FDCWD)
struct timespec times[2]; struct timespec times[2] = {};
memset(&times[0], 0, sizeof(times));
#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC #if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
// macOS // macOS
times[0] = st->st_atimespec; times[0] = st->st_atimespec;
@@ -119,8 +160,7 @@ static void copy_file_attributes(const struct stat *st, const char *oname, bool
int r = utimensat(AT_FDCWD, oname, &times[0], 0); int r = utimensat(AT_FDCWD, oname, &times[0], 0);
IGNORE_ERROR(r); IGNORE_ERROR(r);
#elif USE_UTIME #elif USE_UTIME
struct utimbuf u; struct utimbuf u = {};
mem_clear(&u);
u.actime = st->st_atime; u.actime = st->st_atime;
u.modtime = st->st_mtime; u.modtime = st->st_mtime;
int r = utime(oname, &u); int r = utime(oname, &u);
@@ -149,6 +189,7 @@ static void copy_file_attributes(const struct stat *st, const char *oname, bool
} }
#endif #endif
// maybe unused // maybe unused
UNUSED(xst);
UNUSED(st); UNUSED(st);
UNUSED(oname); UNUSED(oname);
UNUSED(preserve_mode); UNUSED(preserve_mode);
@@ -156,6 +197,8 @@ static void copy_file_attributes(const struct stat *st, const char *oname, bool
UNUSED(preserve_timestamp); UNUSED(preserve_timestamp);
} }
} // namespace
/************************************************************************* /*************************************************************************
// process one file // process one file
**************************************************************************/ **************************************************************************/
@@ -164,8 +207,8 @@ void do_one_file(const char *const iname, char *const oname) may_throw {
oname[0] = 0; // make empty oname[0] = 0; // make empty
// check iname stat // check iname stat
struct stat st; XStat xst = {};
mem_clear(&st); struct stat &st = xst.st;
#if HAVE_LSTAT #if HAVE_LSTAT
int rr = lstat(iname, &st); int rr = lstat(iname, &st);
#else #else
@@ -212,14 +255,16 @@ void do_one_file(const char *const iname, char *const oname) may_throw {
InputFile fi; InputFile fi;
fi.sopen(iname, get_open_flags(RO_MUST_EXIST), SH_DENYWR); fi.sopen(iname, get_open_flags(RO_MUST_EXIST), SH_DENYWR);
#if USE_FTIME
struct ftime fi_ftime;
mem_clear(&fi_ftime);
if (opt->preserve_timestamp) { if (opt->preserve_timestamp) {
if (getftime(fi.getFd(), &fi_ftime) != 0) #if USE_SETFILETIME
if (GetFileTime((HANDLE) _get_osfhandle(fi.getFd()), nullptr, &xst.ft_atime,
&xst.ft_mtime) == 0)
throwIOException("cannot determine file timestamp");
#elif USE_FTIME
if (getftime(fi.getFd(), &xst.ft_ftime) != 0)
throwIOException("cannot determine file timestamp"); throwIOException("cannot determine file timestamp");
}
#endif #endif
}
// open output file // open output file
// NOTE: only use "preserve_link" if you really need it, e.g. it can fail // NOTE: only use "preserve_link" if you really need it, e.g. it can fail
@@ -248,8 +293,7 @@ void do_one_file(const char *const iname, char *const oname) may_throw {
if (opt->output_name && preserve_link) { if (opt->output_name && preserve_link) {
flags = get_open_flags(WO_CREATE_OR_TRUNCATE); flags = get_open_flags(WO_CREATE_OR_TRUNCATE);
#if HAVE_LSTAT #if HAVE_LSTAT
struct stat ost; struct stat ost = {};
mem_clear(&ost);
int r = lstat(tname, &ost); int r = lstat(tname, &ost);
if (r == 0 && S_ISREG(ost.st_mode)) { if (r == 0 && S_ISREG(ost.st_mode)) {
preserve_link = ost.st_nlink >= 2; preserve_link = ost.st_nlink >= 2;
@@ -298,18 +342,8 @@ void do_one_file(const char *const iname, char *const oname) may_throw {
throwInternalError("invalid command"); throwInternalError("invalid command");
// copy time stamp // copy time stamp
if (oname[0] && opt->preserve_timestamp && fo.isOpen()) { if (oname[0] && opt->preserve_timestamp && fo.isOpen())
#if USE_FTIME set_fd_timestamp(fo.getFd(), &xst);
int r = setftime(fo.getFd(), &fi_ftime);
IGNORE_ERROR(r);
#elif USE__FUTIME
struct _utimbuf u;
u.actime = st.st_atime;
u.modtime = st.st_mtime;
int r = _futime(fo.getFd(), &u);
IGNORE_ERROR(r);
#endif
}
// close files // close files
fi.closex(); fi.closex();
@@ -323,9 +357,10 @@ void do_one_file(const char *const iname, char *const oname) may_throw {
if (!makebakname(bakname, sizeof(bakname), iname)) if (!makebakname(bakname, sizeof(bakname), iname))
throwIOException("could not create a backup file name"); throwIOException("could not create a backup file name");
if (preserve_link) { if (preserve_link) {
copy_file_contents(iname, bakname, WO_MUST_CREATE); copy_file_contents(iname, bakname, WO_MUST_CREATE, &xst);
copy_file_attributes(&st, bakname, true, true, true); copy_file_attributes(&xst, bakname, true, true, true);
copy_file_contents(oname, iname, WO_MUST_EXIST_TRUNCATE); const XStat *xstamp = opt->preserve_timestamp ? &xst : nullptr;
copy_file_contents(oname, iname, WO_MUST_EXIST_TRUNCATE, xstamp);
FileBase::unlink(oname); FileBase::unlink(oname);
copy_timestamp_only = true; copy_timestamp_only = true;
} else { } else {
@@ -333,7 +368,8 @@ void do_one_file(const char *const iname, char *const oname) may_throw {
FileBase::rename(oname, iname); FileBase::rename(oname, iname);
} }
} else if (preserve_link) { } else if (preserve_link) {
copy_file_contents(oname, iname, WO_MUST_EXIST_TRUNCATE); const XStat *xstamp = opt->preserve_timestamp ? &xst : nullptr;
copy_file_contents(oname, iname, WO_MUST_EXIST_TRUNCATE, xstamp);
FileBase::unlink(oname); FileBase::unlink(oname);
copy_timestamp_only = true; copy_timestamp_only = true;
} else { } else {
@@ -348,9 +384,9 @@ void do_one_file(const char *const iname, char *const oname) may_throw {
oname[0] = 0; // done with oname oname[0] = 0; // done with oname
const char *name = opt->output_name ? opt->output_name : iname; const char *name = opt->output_name ? opt->output_name : iname;
if (copy_timestamp_only) if (copy_timestamp_only)
copy_file_attributes(&st, name, false, false, opt->preserve_timestamp); copy_file_attributes(&xst, name, false, false, opt->preserve_timestamp);
else else
copy_file_attributes(&st, name, opt->preserve_mode, opt->preserve_ownership, copy_file_attributes(&xst, name, opt->preserve_mode, opt->preserve_ownership,
opt->preserve_timestamp); opt->preserve_timestamp);
} }