summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorLOLi <[email protected]>2017-11-13 18:24:26 +0100
committerTony Hutter <[email protected]>2017-11-21 13:11:29 -0600
commitfedc1d96a8d8251237916fdf230e981bc396aa02 (patch)
treee33191d4f7afb811854f4ddf1dc8f67b14b4cb63 /tests
parent59511072b47175bc3b757b4cc27f30b752f59662 (diff)
Fix truncate(2) mtime and ctime handling
On Linux, ftruncate(2) always changes the file timestamps, even if the file size is not changed. However, in case of a successfull truncate(2), the timestamps are updated only if the file size changes. This translates to the VFS calling the ZFS Posix Layer "setattr" function (zpl_setattr) with ATTR_MTIME and ATTR_CTIME unconditionally set on the iattr mask only when doing a ftruncate(2), while the truncate(2) is left to the filesystem implementation to be dealt with. This behaviour is consistent with POSIX:2004/SUSv3 specifications where there's no explicit requirement for file size changes to update the timestamps only for ftruncate(2): http://pubs.opengroup.org/onlinepubs/009695399/functions/truncate.html http://pubs.opengroup.org/onlinepubs/009695399/functions/ftruncate.html This has been later updated in POSIX:2008/SUSv4 where, for both truncate(2)/ftruncate(2), there's no mention of this size change requirement: http://austingroupbugs.net/view.php?id=489 http://pubs.opengroup.org/onlinepubs/9699919799/functions/truncate.html http://pubs.opengroup.org/onlinepubs/9699919799/functions/ftruncate.html Unfortunately the Linux VFS is still calling into the ZPL without ATTR_MTIME/ATTR_CTIME set in the truncate(2) case: we fix this by explicitly updating the timestamps when detecting the ATTR_SIZE bit, which is always set in do_truncate(), on the iattr mask. Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: loli10K <[email protected]> Closes #6811 Closes #6819
Diffstat (limited to 'tests')
-rw-r--r--tests/runfiles/linux.run3
-rw-r--r--tests/zfs-tests/include/math.shlib30
-rw-r--r--tests/zfs-tests/tests/functional/truncate/.gitignore1
-rw-r--r--tests/zfs-tests/tests/functional/truncate/Makefile.am11
-rw-r--r--tests/zfs-tests/tests/functional/truncate/truncate_test.c103
-rwxr-xr-xtests/zfs-tests/tests/functional/truncate/truncate_timestamps.ksh74
6 files changed, 220 insertions, 2 deletions
diff --git a/tests/runfiles/linux.run b/tests/runfiles/linux.run
index 5583a2554..7d6d13b4b 100644
--- a/tests/runfiles/linux.run
+++ b/tests/runfiles/linux.run
@@ -537,7 +537,8 @@ tests = ['threadsappend_001_pos']
tests = ['tmpfile_001_pos', 'tmpfile_002_pos', 'tmpfile_003_pos']
[tests/functional/truncate]
-tests = ['truncate_001_pos', 'truncate_002_pos']
+tests = ['truncate_001_pos', 'truncate_002_pos', 'truncate_timestamps']
+tags = ['functional', 'truncate']
[tests/functional/upgrade]
tests = [ 'upgrade_userobj_001_pos' ]
diff --git a/tests/zfs-tests/include/math.shlib b/tests/zfs-tests/include/math.shlib
index 4ed11fb4b..66658cdda 100644
--- a/tests/zfs-tests/include/math.shlib
+++ b/tests/zfs-tests/include/math.shlib
@@ -66,3 +66,33 @@ function to_bytes
return 0
}
+
+#
+# Verify $a is equal to $b, otherwise raise an error specifying
+# the $type of values being compared
+#
+function verify_eq # <a> <b> <type>
+{
+ typeset a=$1
+ typeset b=$2
+ typeset type=$3
+
+ if [[ $a -ne $b ]]; then
+ log_fail "Compared $type should be equal: $a != $b"
+ fi
+}
+
+#
+# Verify $a is not equal to $b, otherwise raise an error specifying
+# the $type of values being compared
+#
+function verify_ne # <a> <b> <type>
+{
+ typeset a=$1
+ typeset b=$2
+ typeset type=$3
+
+ if [[ $a -eq $b ]]; then
+ log_fail "Compared $type should be not equal: $a == $b"
+ fi
+}
diff --git a/tests/zfs-tests/tests/functional/truncate/.gitignore b/tests/zfs-tests/tests/functional/truncate/.gitignore
new file mode 100644
index 000000000..f28d93573
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/truncate/.gitignore
@@ -0,0 +1 @@
+/truncate_test
diff --git a/tests/zfs-tests/tests/functional/truncate/Makefile.am b/tests/zfs-tests/tests/functional/truncate/Makefile.am
index 16cadf29d..0071e8f87 100644
--- a/tests/zfs-tests/tests/functional/truncate/Makefile.am
+++ b/tests/zfs-tests/tests/functional/truncate/Makefile.am
@@ -1,7 +1,16 @@
+include $(top_srcdir)/config/Rules.am
+
pkgdatadir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/truncate
+
dist_pkgdata_SCRIPTS = \
setup.ksh \
cleanup.ksh \
truncate.cfg \
truncate_001_pos.ksh \
- truncate_002_pos.ksh
+ truncate_002_pos.ksh \
+ truncate_timestamps.ksh
+
+pkgexecdir = $(datadir)/@PACKAGE@/zfs-tests/tests/functional/truncate
+
+pkgexec_PROGRAMS = truncate_test
+truncate_test_SOURCES = truncate_test.c
diff --git a/tests/zfs-tests/tests/functional/truncate/truncate_test.c b/tests/zfs-tests/tests/functional/truncate/truncate_test.c
new file mode 100644
index 000000000..3e277e865
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/truncate/truncate_test.c
@@ -0,0 +1,103 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2012, 2014 by Delphix. All rights reserved.
+ * Copyright 2017, loli10K <[email protected]>. All rights reserved.
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define FSIZE 256*1024*1024
+
+static long fsize = FSIZE;
+static int errflag = 0;
+static char *filename = NULL;
+static int ftruncflag = 0;
+
+static void parse_options(int argc, char *argv[]);
+
+static void
+usage(char *execname)
+{
+ (void) fprintf(stderr,
+ "usage: %s [-s filesize] [-f] /path/to/file\n", execname);
+ (void) exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+
+ parse_options(argc, argv);
+
+ if (ftruncflag) {
+ fd = open(filename, O_RDWR|O_CREAT, 0666);
+ if (fd < 0) {
+ perror("open");
+ return (1);
+ }
+ if (ftruncate(fd, fsize) < 0) {
+ perror("ftruncate");
+ return (1);
+ }
+ if (close(fd)) {
+ perror("close");
+ return (1);
+ }
+ } else {
+ if (truncate(filename, fsize) < 0) {
+ perror("truncate");
+ return (1);
+ }
+ }
+ return (0);
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+ int c;
+ extern char *optarg;
+ extern int optind, optopt;
+
+ while ((c = getopt(argc, argv, "s:f")) != -1) {
+ switch (c) {
+ case 's':
+ fsize = atoi(optarg);
+ break;
+ case 'f':
+ ftruncflag++;
+ break;
+ case ':':
+ (void) fprintf(stderr,
+ "Option -%c requires an operand\n", optopt);
+ errflag++;
+ break;
+ }
+ if (errflag) {
+ (void) usage(argv[0]);
+ }
+ }
+
+ if (argc <= optind) {
+ (void) fprintf(stderr, "No filename specified\n");
+ usage(argv[0]);
+ }
+ filename = argv[optind];
+}
diff --git a/tests/zfs-tests/tests/functional/truncate/truncate_timestamps.ksh b/tests/zfs-tests/tests/functional/truncate/truncate_timestamps.ksh
new file mode 100755
index 000000000..c365c7415
--- /dev/null
+++ b/tests/zfs-tests/tests/functional/truncate/truncate_timestamps.ksh
@@ -0,0 +1,74 @@
+#!/bin/ksh -p
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2017, loli10K <[email protected]>. All rights reserved.
+#
+
+. $STF_SUITE/tests/functional/truncate/truncate.cfg
+. $STF_SUITE/include/libtest.shlib
+. $STF_SUITE/include/math.shlib
+
+#
+# DESCRIPTION:
+# Ensure both truncate(2)/ftruncate(2) update target file mtime/ctime attributes
+#
+# STRATEGY:
+# 1. Create a file
+# 2. Truncate the file
+# 3. Verify both mtime/ctime are updated
+# 4. Rinse and repeat for both truncate(2) and ftruncate(2) with various sizes
+#
+
+verify_runnable "both"
+
+function verify_truncate # <filename> <filesize> <option>
+{
+ typeset filename="$1"
+ typeset -i size="$2"
+ typeset option="$3"
+
+ log_must mkfile $sizeavg $filename # always start with $sizeavg
+ typeset -i timestm="$(stat -c %Y $filename)"
+ typeset -i timestc="$(stat -c %Z $filename)"
+ log_must sleep 1
+ log_must $STF_SUITE/tests/functional/truncate/truncate_test -s $size $filename $option
+ verify_eq $size "$(stat -c %s $filename)" "size"
+ verify_ne $timestm "$(stat -c %Y $filename)" "mtime"
+ verify_ne $timestc "$(stat -c %Z $filename)" "ctime"
+ log_must rm -f $filename
+}
+
+function cleanup
+{
+ [[ -f $truncfile ]] && rm -f $truncfile
+}
+
+log_assert "Ensure both truncate(2)/ftruncate(2) update target file timestamps"
+log_onexit cleanup
+
+truncfile="$TESTDIR/truncate.$$"
+sizemin="123"
+sizeavg="$((256*1024))"
+sizemax="$((1024*1024))"
+
+# truncate(2)
+verify_truncate $truncfile $sizemin ""
+verify_truncate $truncfile $sizeavg ""
+verify_truncate $truncfile $sizemax ""
+
+# ftruncate(2)
+verify_truncate $truncfile $sizemin "-f"
+verify_truncate $truncfile $sizeavg "-f"
+verify_truncate $truncfile $sizemax "-f"
+
+log_pass "Successful truncation correctly update timestamps"