diff --git a/programs/fileio.c b/programs/fileio.c index da5412f27..b786b7e4d 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -1647,6 +1647,7 @@ static int FIO_compressFilename_dstFile(FIO_ctx_t* const fCtx, int closeDstFile = 0; int result; stat_t statbuf; + int transferMTime = 0; assert(ress.srcFile != NULL); if (ress.dstFile == NULL) { int dstFilePermissions = DEFAULT_FILE_PERMISSIONS; @@ -1654,6 +1655,7 @@ static int FIO_compressFilename_dstFile(FIO_ctx_t* const fCtx, && UTIL_stat(srcFileName, &statbuf) && UTIL_isRegularFileStat(&statbuf) ) { dstFilePermissions = statbuf.st_mode; + transferMTime = 1; } closeDstFile = 1; @@ -1680,6 +1682,9 @@ static int FIO_compressFilename_dstFile(FIO_ctx_t* const fCtx, DISPLAYLEVEL(1, "zstd: %s: %s \n", dstFileName, strerror(errno)); result=1; } + if (transferMTime) { + UTIL_utime(dstFileName, &statbuf); + } if ( (result != 0) /* operation failure */ && strcmp(dstFileName, stdoutmark) /* special case : don't remove() stdout */ ) { @@ -2553,6 +2558,7 @@ static int FIO_decompressDstFile(FIO_ctx_t* const fCtx, int result; stat_t statbuf; int releaseDstFile = 0; + int transferMTime = 0; if ((ress.dstFile == NULL) && (prefs->testMode==0)) { int dstFilePermissions = DEFAULT_FILE_PERMISSIONS; @@ -2560,6 +2566,7 @@ static int FIO_decompressDstFile(FIO_ctx_t* const fCtx, && UTIL_stat(srcFileName, &statbuf) && UTIL_isRegularFileStat(&statbuf) ) { dstFilePermissions = statbuf.st_mode; + transferMTime = 1; } releaseDstFile = 1; @@ -2585,6 +2592,10 @@ static int FIO_decompressDstFile(FIO_ctx_t* const fCtx, result = 1; } + if (transferMTime) { + UTIL_utime(dstFileName, &statbuf); + } + if ( (result != 0) /* operation failure */ && strcmp(dstFileName, stdoutmark) /* special case : don't remove() stdout */ ) { diff --git a/programs/util.c b/programs/util.c index 81c10d0ca..b29177818 100644 --- a/programs/util.c +++ b/programs/util.c @@ -159,6 +159,29 @@ int UTIL_chmod(char const* filename, const stat_t* statbuf, mode_t permissions) return chmod(filename, permissions); } +/* set access and modification times */ +int UTIL_utime(const char* filename, const stat_t *statbuf) +{ + int ret; + /* We check that st_mtime is a macro here in order to give us confidence + * that struct stat has a struct timespec st_mtim member. We need this + * check because there are some platforms that claim to be POSIX 2008 + * compliant but which do not have st_mtim... */ +#if (PLATFORM_POSIX_VERSION >= 200809L) && defined(st_mtime) + /* (atime, mtime) */ + struct timespec timebuf[2] = { {0, UTIME_NOW} }; + timebuf[1] = statbuf->st_mtim; + ret = utimensat(AT_FDCWD, filename, timebuf, 0); +#else + struct utimbuf timebuf; + timebuf.actime = time(NULL); + timebuf.modtime = statbuf->st_mtime; + ret = utime(filename, &timebuf); +#endif + errno = 0; + return ret; +} + int UTIL_setFileStat(const char *filename, const stat_t *statbuf) { int res = 0; @@ -168,25 +191,7 @@ int UTIL_setFileStat(const char *filename, const stat_t *statbuf) return -1; /* set access and modification times */ - /* We check that st_mtime is a macro here in order to give us confidence - * that struct stat has a struct timespec st_mtim member. We need this - * check because there are some platforms that claim to be POSIX 2008 - * compliant but which do not have st_mtim... */ -#if (PLATFORM_POSIX_VERSION >= 200809L) && defined(st_mtime) - { - /* (atime, mtime) */ - struct timespec timebuf[2] = { {0, UTIME_NOW} }; - timebuf[1] = statbuf->st_mtim; - res += utimensat(AT_FDCWD, filename, timebuf, 0); - } -#else - { - struct utimbuf timebuf; - timebuf.actime = time(NULL); - timebuf.modtime = statbuf->st_mtime; - res += utime(filename, &timebuf); - } -#endif + res += UTIL_utime(filename, statbuf); #if !defined(_WIN32) res += chown(filename, statbuf->st_uid, statbuf->st_gid); /* Copy ownership */ diff --git a/programs/util.h b/programs/util.h index c1c963434..7632b9a0a 100644 --- a/programs/util.h +++ b/programs/util.h @@ -136,6 +136,14 @@ int UTIL_stat(const char* filename, stat_t* statbuf); */ int UTIL_setFileStat(const char* filename, const stat_t* statbuf); +/** + * Set atime to now and mtime to the st_mtim in statbuf. + * + * Directly wraps utime() or utimensat(). Returns -1 on error. + * Does not validate filename is valid. + */ +int UTIL_utime(const char* filename, const stat_t *statbuf); + /* * These helpers operate on a pre-populated stat_t, i.e., the result of * calling one of the above functions. diff --git a/tests/playTests.sh b/tests/playTests.sh index ce585f226..d83001fee 100755 --- a/tests/playTests.sh +++ b/tests/playTests.sh @@ -124,6 +124,13 @@ case "$UNAME" in Darwin | FreeBSD | OpenBSD | NetBSD) MTIME="stat -f %m" ;; esac +assertSameMTime() { + MT1=$($MTIME "$1") + MT2=$($MTIME "$2") + echo MTIME $MT1 $MT2 + [ "$MT1" = "$MT2" ] || die "mtime on $1 doesn't match mtime on $2 ($MT1 != $MT2)" +} + GET_PERMS="stat -c %a" case "$UNAME" in Darwin | FreeBSD | OpenBSD | NetBSD) GET_PERMS="stat -f %Lp" ;; @@ -588,6 +595,17 @@ if [ -n "$READFROMBLOCKDEVICE" ] ; then rm -f tmp.img tmp.img.zst tmp.img.copy fi +println "\n===> zstd created file timestamp tests" +datagen > tmp +touch -m -t 200001010000.00 tmp +println "test : copy mtime in file -> file compression " +zstd -f tmp -o tmp.zst +assertSameMTime tmp tmp.zst +println "test : copy mtime in file -> file decompression " +zstd -f -d tmp.zst -o tmp.out +assertSameMTime tmp.zst tmp.out +rm -f tmp + println "\n===> compress multiple files into an output directory, --output-dir-flat" println henlo > tmp1 mkdir tmpInputTestDir