From 25c4a7a40806e943e0a60d9da323b3d6fab4068a Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 10 Mar 2020 18:26:49 +0100 Subject: [PATCH 1/8] s4/torture: fix a timestamps test to work on ext filesystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ext filesystem has a time_t limit of 15032385535 (0x0x37fffffff). From Documentation/filesystems/ext4/inodes.rst: If the inode structure size ``sb->s_inode_size`` is larger than 128 bytes and the ``i_inode_extra`` field is large enough to encompass the respective ``i_[cma]time_extra`` field, the ctime, atime, and mtime inode fields are widened to 64 bits. Within this “extra” 32-bit field, the lower two bits are used to extend the 32-bit seconds field to be 34 bit wide; the upper 30 bits are used to provide nanosecond timestamp accuracy. Therefore, timestamps should not overflow until May 2446. ... Changing the test to use the value 0x37fffffff instead of 100000000000 allows running the test locally on ext filesytems. Signed-off-by: Ralph Boehme Reviewed-by: Jeremy Allison (cherry picked from commit 73fedf014bbe02a408360d48e35bce4a6dbc9c36) --- source4/torture/smb2/timestamps.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source4/torture/smb2/timestamps.c b/source4/torture/smb2/timestamps.c index 9655e5bc164..4a9f1e9bf88 100644 --- a/source4/torture/smb2/timestamps.c +++ b/source4/torture/smb2/timestamps.c @@ -235,11 +235,11 @@ done: return ret; } -static bool test_time_t_100000000000(struct torture_context *tctx, +static bool test_time_t_15032385535(struct torture_context *tctx, struct smb2_tree *tree) { - return test_time_t(tctx, tree, "test_time_t_100000000000.txt", - 100000000000 /* >> INT32_MAX */); + return test_time_t(tctx, tree, "test_time_t_15032385535.txt", + 15032385535 /* >> INT32_MAX, limit on ext */); } static bool test_time_t_10000000000(struct torture_context *tctx, @@ -294,7 +294,7 @@ struct torture_suite *torture_smb2_timestamps_init(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "timestamps"); - torture_suite_add_1smb2_test(suite, "time_t_100000000000", test_time_t_100000000000); + torture_suite_add_1smb2_test(suite, "time_t_15032385535", test_time_t_15032385535); torture_suite_add_1smb2_test(suite, "time_t_10000000000", test_time_t_10000000000); torture_suite_add_1smb2_test(suite, "time_t_4294967295", test_time_t_4294967295); torture_suite_add_1smb2_test(suite, "time_t_1", test_time_t_1); -- 2.20.1 From d4deb23880bc0ca879c820489c907e2c9cfc9e33 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Fri, 6 Mar 2020 11:50:57 +0100 Subject: [PATCH 2/8] torture/smb2: mtime update logic with 2 handles: write io on handle 1, then set mtime on handle 2 BUG: https://bugzilla.samba.org/show_bug.cgi?id=14150 Signed-off-by: Ralph Boehme Reviewed-by: Jeremy Allison (cherry picked from commit 47508c5ecf0713ef404d5226499a4269651dc020) --- selftest/knownfail.d/samba3.smb2.timestamps | 1 + source4/torture/smb2/timestamps.c | 151 ++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 selftest/knownfail.d/samba3.smb2.timestamps diff --git a/selftest/knownfail.d/samba3.smb2.timestamps b/selftest/knownfail.d/samba3.smb2.timestamps new file mode 100644 index 00000000000..b603ffa6e2c --- /dev/null +++ b/selftest/knownfail.d/samba3.smb2.timestamps @@ -0,0 +1 @@ +^samba3.smb2.timestamps.delayed-write-vs-seteof\(.*\)$ diff --git a/source4/torture/smb2/timestamps.c b/source4/torture/smb2/timestamps.c index 4a9f1e9bf88..20add0592d8 100644 --- a/source4/torture/smb2/timestamps.c +++ b/source4/torture/smb2/timestamps.c @@ -27,6 +27,7 @@ #include "torture/smb2/proto.h" #define BASEDIR "smb2-timestamps" +#define FNAME "testfile.dat" static bool test_time_t(struct torture_context *tctx, struct smb2_tree *tree, @@ -287,6 +288,151 @@ static bool test_time_t_1968(struct torture_context *tctx, -63158400 /* 1968 */); } +static bool test_delayed_write_vs_seteof(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + struct smb2_handle h2 = {{0}}; + NTTIME create_time; + NTTIME set_time; + union smb_fileinfo finfo; + union smb_setfileinfo setinfo; + struct smb2_close c; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file-handle 1\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file-handle 1\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + torture_comment(tctx, "Check writetime hasn't been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + torture_assert_nttime_equal(tctx, + finfo.all_info.out.write_time, + create_time, + "Writetime != set_time (wrong!)\n"); + + torture_comment(tctx, "Setinfo EOF on file-handle 1," + " should flush pending writetime update\n"); + + setinfo = (union smb_setfileinfo) { + .generic.level = RAW_SFILEINFO_END_OF_FILE_INFORMATION, + }; + setinfo.end_of_file_info.in.file.handle = h1; + setinfo.end_of_file_info.in.size = 1; /* same size! */ + + status = smb2_setinfo_file(tree, &setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Check writetime has been updated " + "by the setinfo EOF\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + if (!(finfo.all_info.out.write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, "setinfo EOF hasn't updated writetime\n"); + } + + torture_comment(tctx, "Open file-handle 2\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FILE_WRITE_ATTRIBUTE, + .in.create_disposition = NTCREATEX_DISP_OPEN, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h2 = cr.out.file.handle; + + torture_comment(tctx, "Set write time on file-handle 2\n"); + + setinfo = (union smb_setfileinfo) { + .generic.level = RAW_FILEINFO_BASIC_INFORMATION, + }; + setinfo.generic.in.file.handle = h2; + unix_to_nt_time(&set_time, time(NULL) + 86400); + setinfo.basic_info.in.write_time = set_time; + + status = smb2_setinfo_file(tree, &setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + status = smb2_util_close(tree, h2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h2); + + torture_comment(tctx, "Close file-handle 1, write-time should not be updated\n"); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + + torture_assert_nttime_equal(tctx, + c.out.write_time, + set_time, + "Writetime != set_time (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree, h2); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + /* basic testing of SMB2 timestamps */ @@ -303,6 +449,11 @@ struct torture_suite *torture_smb2_timestamps_init(TALLOC_CTX *ctx) torture_suite_add_1smb2_test(suite, "time_t_-2", test_time_t_minus_2); torture_suite_add_1smb2_test(suite, "time_t_1968", test_time_t_1968); + /* + * Testing of delayed write-time udpates + */ + torture_suite_add_1smb2_test(suite, "delayed-write-vs-seteof", test_delayed_write_vs_seteof); + suite->description = talloc_strdup(suite, "SMB2 timestamp tests"); return suite; -- 2.20.1 From 4334142c227e9b3305f0347fed15796d6c5dfade Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 12 Mar 2020 16:48:09 +0100 Subject: [PATCH 3/8] torture/smb2: add a test verifying a flush flushes a pending writetime update BUG: https://bugzilla.samba.org/show_bug.cgi?id=14150 Signed-off-by: Ralph Boehme Reviewed-by: Jeremy Allison (cherry picked from commit c63d6c9e256cdf6a3620373ef0f595e61d43fa97) --- selftest/knownfail.d/samba3.smb2.timestamps | 1 + source4/torture/smb2/timestamps.c | 111 ++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/selftest/knownfail.d/samba3.smb2.timestamps b/selftest/knownfail.d/samba3.smb2.timestamps index b603ffa6e2c..27f21744a4f 100644 --- a/selftest/knownfail.d/samba3.smb2.timestamps +++ b/selftest/knownfail.d/samba3.smb2.timestamps @@ -1 +1,2 @@ ^samba3.smb2.timestamps.delayed-write-vs-seteof\(.*\)$ +^samba3.smb2.timestamps.delayed-write-vs-flush\(.*\)$ diff --git a/source4/torture/smb2/timestamps.c b/source4/torture/smb2/timestamps.c index 20add0592d8..13fadf548f2 100644 --- a/source4/torture/smb2/timestamps.c +++ b/source4/torture/smb2/timestamps.c @@ -433,6 +433,116 @@ done: return ret; } +static bool test_delayed_write_vs_flush(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + union smb_fileinfo finfo; + struct smb2_flush f; + struct smb2_close c; + NTTIME create_time; + NTTIME flush_time; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Open file-handle 1\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_OPEN_IF, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = BASEDIR "\\" FNAME, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + sleep(1); + + torture_comment(tctx, "Write to file-handle 1\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + torture_comment(tctx, "Check writetime hasn't been updated\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + torture_assert_nttime_equal(tctx, + finfo.all_info.out.write_time, + create_time, + "Writetime != create_time (wrong!)\n"); + + torture_comment(tctx, "Flush file, " + "should flush pending writetime update\n"); + + f = (struct smb2_flush) { + .in.file.handle = h1, + }; + + status = smb2_flush(tree, &f); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "flush failed\n"); + + torture_comment(tctx, "Check writetime has been updated " + "by the setinfo EOF\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + flush_time = finfo.all_info.out.write_time; + if (!(flush_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, "flush hasn't updated writetime\n"); + } + + torture_comment(tctx, "Close file-handle 1, write-time should not be updated\n"); + + c = (struct smb2_close) { + .in.file.handle = h1, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + + torture_assert_nttime_equal(tctx, + c.out.write_time, + flush_time, + "writetime != flushtime (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + /* basic testing of SMB2 timestamps */ @@ -453,6 +563,7 @@ struct torture_suite *torture_smb2_timestamps_init(TALLOC_CTX *ctx) * Testing of delayed write-time udpates */ torture_suite_add_1smb2_test(suite, "delayed-write-vs-seteof", test_delayed_write_vs_seteof); + torture_suite_add_1smb2_test(suite, "delayed-write-vs-flush", test_delayed_write_vs_flush); suite->description = talloc_strdup(suite, "SMB2 timestamp tests"); -- 2.20.1 From f152b75024c140cc969a8f82d2362e2e31429e8f Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 12 Mar 2020 16:48:09 +0100 Subject: [PATCH 4/8] torture/smb2: add a test verifying a setinfo(basicinfo) flushes a pending writetime update BUG: https://bugzilla.samba.org/show_bug.cgi?id=14150 Signed-off-by: Ralph Boehme Reviewed-by: Jeremy Allison (cherry picked from commit 4e3c2afbd6f12159252405f6efc6528fa9345f08) --- selftest/knownfail.d/samba3.smb2.timestamps | 1 + source4/torture/smb2/timestamps.c | 158 ++++++++++++++++++++ 2 files changed, 159 insertions(+) diff --git a/selftest/knownfail.d/samba3.smb2.timestamps b/selftest/knownfail.d/samba3.smb2.timestamps index 27f21744a4f..33c9d9a4268 100644 --- a/selftest/knownfail.d/samba3.smb2.timestamps +++ b/selftest/knownfail.d/samba3.smb2.timestamps @@ -1,2 +1,3 @@ ^samba3.smb2.timestamps.delayed-write-vs-seteof\(.*\)$ ^samba3.smb2.timestamps.delayed-write-vs-flush\(.*\)$ +^samba3.smb2.timestamps.delayed-write-vs-setbasic\(.*\)$ diff --git a/source4/torture/smb2/timestamps.c b/source4/torture/smb2/timestamps.c index 13fadf548f2..afa0fe24106 100644 --- a/source4/torture/smb2/timestamps.c +++ b/source4/torture/smb2/timestamps.c @@ -543,6 +543,163 @@ done: return ret; } +static bool test_delayed_write_vs_setbasic_do(struct torture_context *tctx, + struct smb2_tree *tree, + union smb_setfileinfo *setinfo, + bool expect_update) +{ + char *path = NULL; + struct smb2_create cr; + struct smb2_handle h1 = {{0}}; + NTTIME create_time; + union smb_fileinfo finfo; + NTSTATUS status; + bool ret = true; + + torture_comment(tctx, "Create testfile\n"); + + path = talloc_asprintf(tree, BASEDIR "\\" FNAME ".%" PRIu32, + generate_random()); + torture_assert_not_null_goto(tctx, path, ret, done, "OOM\n"); + + cr = (struct smb2_create) { + .in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED, + .in.create_disposition = NTCREATEX_DISP_CREATE, + .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, + .in.fname = path, + }; + status = smb2_create(tree, tctx, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h1 = cr.out.file.handle; + create_time = cr.out.create_time; + + torture_comment(tctx, "Write to file\n"); + + status = smb2_util_write(tree, h1, "s", 0, 1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + torture_comment(tctx, "Get timestamps\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + torture_assert_nttime_equal(tctx, + finfo.all_info.out.write_time, + create_time, + "Writetime != create_time (wrong!)\n"); + + torture_comment(tctx, "Set timestamps\n"); + + setinfo->end_of_file_info.in.file.handle = h1; + status = smb2_setinfo_file(tree, setinfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Check timestamps\n"); + + finfo = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h1, + }; + status = smb2_getinfo_file(tree, tree, &finfo); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + if (expect_update) { + if (!(finfo.all_info.out.write_time > create_time)) { + ret = false; + torture_fail_goto(tctx, done, "setinfo basicinfo " + "hasn't updated writetime\n"); + } + } else { + if (finfo.all_info.out.write_time != create_time) { + ret = false; + torture_fail_goto(tctx, done, "setinfo basicinfo " + "hasn't updated writetime\n"); + } + } + + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h1); + + status = smb2_util_unlink(tree, path); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + +done: + TALLOC_FREE(path); + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree, h1); + } + return ret; +} + +static bool test_delayed_write_vs_setbasic(struct torture_context *tctx, + struct smb2_tree *tree) +{ + struct smb2_handle h1 = {{0}}; + union smb_setfileinfo setinfo; + time_t t = time(NULL) - 86400; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + /* + * Yes, this is correct, tested against Windows 2016: even if all + * timestamp fields are 0, a pending write time is flushed. + */ + torture_comment(tctx, "Test: setting all-0 timestamps flushes?\n"); + + setinfo = (union smb_setfileinfo) { + .generic.level = RAW_SFILEINFO_BASIC_INFORMATION, + }; + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + + torture_comment(tctx, "Test: setting create_time flushes?\n"); + unix_to_nt_time(&setinfo.basic_info.in.create_time, t); + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + + torture_comment(tctx, "Test: setting access_time flushes?\n"); + unix_to_nt_time(&setinfo.basic_info.in.access_time, t); + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + + torture_comment(tctx, "Test: setting change_time flushes?\n"); + unix_to_nt_time(&setinfo.basic_info.in.change_time, t); + ret = test_delayed_write_vs_setbasic_do(tctx, tree, &setinfo, true); + if (ret != true) { + goto done; + } + +done: + smb2_deltree(tree, BASEDIR); + return ret; +} + /* basic testing of SMB2 timestamps */ @@ -564,6 +721,7 @@ struct torture_suite *torture_smb2_timestamps_init(TALLOC_CTX *ctx) */ torture_suite_add_1smb2_test(suite, "delayed-write-vs-seteof", test_delayed_write_vs_seteof); torture_suite_add_1smb2_test(suite, "delayed-write-vs-flush", test_delayed_write_vs_flush); + torture_suite_add_1smb2_test(suite, "delayed-write-vs-setbasic", test_delayed_write_vs_setbasic); suite->description = talloc_strdup(suite, "SMB2 timestamp tests"); -- 2.20.1 From a2528fa34905cb90712e4b8d944203a78b8da2e2 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 12 Mar 2020 16:52:34 +0100 Subject: [PATCH 5/8] smbd: always flush pending write time update when setting filesize We need to flush a pending write time update even when we're setting the filesize to current filesize. Note that we're already doing it this way in the relevant places listed my dochelp@MS in https://lists.samba.org/archive/cifs-protocol/2019-December/003364.html Cleanup (= Close) SetBasicInfo SetAllocationInfo SetEndOfFileInfo SetValidDataLengthInfo Flush FSCTL_SET_ENCRYPTION FSCTL_OFFLOAD_WRITE Cleanup (= Close): Already implemented by update_write_time_on_close() and friends. SetBasicInfo: Currently doesn't flush pending updates. Fixed by a subsequent commit. SetAllocationInfo: smb_set_file_allocation_info() when setting a file's allocation size. SetEndOfFileInfo: Currently doesn't flush pending updates. Fixed by a subsequent commit. SetValidDataLengthInfo: Not implemented, returns NT_STATUS_NOT_SUPPORTED which seems wrong btw, as SetValidDataLengthInfo IS listed in MS-SMB2 2.2.39. Flush: Currently doesn't flush pending updates. Fixed by subsequent commit. FSCTL_SET_ENCRYPTION: Windows 2016 doesn't flush a pending writetime update, verified with a smbtorture test. FSCTL_OFFLOAD_WRITE: NT_STATUS_NOT_IMPLEMENTED BUG: https://bugzilla.samba.org/show_bug.cgi?id=14150 Signed-off-by: Ralph Boehme Reviewed-by: Jeremy Allison (cherry picked from commit 79d7d6b9d01b8547f16b74a62926d0b471f18c39) --- selftest/knownfail.d/samba3.smb2.timestamps | 1 - source3/smbd/trans2.c | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/selftest/knownfail.d/samba3.smb2.timestamps b/selftest/knownfail.d/samba3.smb2.timestamps index 33c9d9a4268..c7d0ff2849e 100644 --- a/selftest/knownfail.d/samba3.smb2.timestamps +++ b/selftest/knownfail.d/samba3.smb2.timestamps @@ -1,3 +1,2 @@ -^samba3.smb2.timestamps.delayed-write-vs-seteof\(.*\)$ ^samba3.smb2.timestamps.delayed-write-vs-flush\(.*\)$ ^samba3.smb2.timestamps.delayed-write-vs-setbasic\(.*\)$ diff --git a/source3/smbd/trans2.c b/source3/smbd/trans2.c index 2cf669f4b4d..9bda39c27d3 100644 --- a/source3/smbd/trans2.c +++ b/source3/smbd/trans2.c @@ -6675,6 +6675,13 @@ static NTSTATUS smb_set_file_size(connection_struct *conn, get_file_size_stat(psbuf)); if (size == get_file_size_stat(psbuf)) { + if (fsp == NULL) { + return NT_STATUS_OK; + } + if (!fsp->modified) { + return NT_STATUS_OK; + } + trigger_write_time_update_immediate(fsp); return NT_STATUS_OK; } -- 2.20.1 From 6ecbbd877a1b93e8c4dd8134792ab766727da373 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 12 Mar 2020 19:23:40 +0100 Subject: [PATCH 6/8] smbd: flush pending writetime update when flushing file Cf the explanations in the previous commit. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14150 Signed-off-by: Ralph Boehme Reviewed-by: Jeremy Allison (cherry picked from commit d99d5bf2c6d0a818ef2f3920e0c93fac38761c36) --- selftest/knownfail.d/samba3.smb2.timestamps | 1 - source3/smbd/reply.c | 7 +++++++ source3/smbd/smb2_flush.c | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/selftest/knownfail.d/samba3.smb2.timestamps b/selftest/knownfail.d/samba3.smb2.timestamps index c7d0ff2849e..8a52d4a2ad6 100644 --- a/selftest/knownfail.d/samba3.smb2.timestamps +++ b/selftest/knownfail.d/samba3.smb2.timestamps @@ -1,2 +1 @@ -^samba3.smb2.timestamps.delayed-write-vs-flush\(.*\)$ ^samba3.smb2.timestamps.delayed-write-vs-setbasic\(.*\)$ diff --git a/source3/smbd/reply.c b/source3/smbd/reply.c index 2d4bb07305e..1e55faff765 100644 --- a/source3/smbd/reply.c +++ b/source3/smbd/reply.c @@ -5701,6 +5701,10 @@ static struct files_struct *file_sync_one_fn(struct files_struct *fsp, } sync_file(conn, fsp, True /* write through */); + if (fsp->modified) { + trigger_write_time_update_immediate(fsp); + } + return NULL; } @@ -5739,6 +5743,9 @@ void reply_flush(struct smb_request *req) END_PROFILE(SMBflush); return; } + if (fsp->modified) { + trigger_write_time_update_immediate(fsp); + } } reply_outbuf(req, 0, 0); diff --git a/source3/smbd/smb2_flush.c b/source3/smbd/smb2_flush.c index 86d5bbc58f0..08539e95807 100644 --- a/source3/smbd/smb2_flush.c +++ b/source3/smbd/smb2_flush.c @@ -112,6 +112,7 @@ static void smbd_smb2_request_flush_done(struct tevent_req *subreq) struct smbd_smb2_flush_state { struct smbd_smb2_request *smb2req; + struct files_struct *fsp; }; static void smbd_smb2_flush_done(struct tevent_req *subreq); @@ -132,6 +133,7 @@ static struct tevent_req *smbd_smb2_flush_send(TALLOC_CTX *mem_ctx, return NULL; } state->smb2req = smb2req; + state->fsp = fsp; DEBUG(10,("smbd_smb2_flush: %s - %s\n", fsp_str_dbg(fsp), fsp_fnum_dbg(fsp))); @@ -207,6 +209,8 @@ static void smbd_smb2_flush_done(struct tevent_req *subreq) { struct tevent_req *req = tevent_req_callback_data( subreq, struct tevent_req); + struct smbd_smb2_flush_state *state = tevent_req_data( + req, struct smbd_smb2_flush_state); int ret; struct vfs_aio_state vfs_aio_state; @@ -216,6 +220,9 @@ static void smbd_smb2_flush_done(struct tevent_req *subreq) tevent_req_nterror(req, map_nt_error_from_unix(vfs_aio_state.error)); return; } + if (state->fsp->modified) { + trigger_write_time_update_immediate(state->fsp); + } tevent_req_done(req); } -- 2.20.1 From cfb2e68f8c264d737d4d64533bcd5501590331dc Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 12 Mar 2020 19:23:40 +0100 Subject: [PATCH 7/8] smbd: flush pending writetime update when setting timestamps file Cf the explanations in the previous commits. BUG: https://bugzilla.samba.org/show_bug.cgi?id=14150 Signed-off-by: Ralph Boehme Reviewed-by: Jeremy Allison (cherry picked from commit 7b90fe69a865ae8648b6548eabbcf2fa8237ebd8) --- selftest/knownfail.d/samba3.smb2.timestamps | 1 - source3/smbd/reply.c | 4 ++++ source3/smbd/trans2.c | 25 +++++++++++++++------ 3 files changed, 22 insertions(+), 8 deletions(-) delete mode 100644 selftest/knownfail.d/samba3.smb2.timestamps diff --git a/selftest/knownfail.d/samba3.smb2.timestamps b/selftest/knownfail.d/samba3.smb2.timestamps deleted file mode 100644 index 8a52d4a2ad6..00000000000 --- a/selftest/knownfail.d/samba3.smb2.timestamps +++ /dev/null @@ -1 +0,0 @@ -^samba3.smb2.timestamps.delayed-write-vs-setbasic\(.*\)$ diff --git a/source3/smbd/reply.c b/source3/smbd/reply.c index 1e55faff765..54f3d330c95 100644 --- a/source3/smbd/reply.c +++ b/source3/smbd/reply.c @@ -9411,6 +9411,10 @@ void reply_setattrE(struct smb_request *req) goto out; } + if (fsp->modified) { + trigger_write_time_update_immediate(fsp); + } + DEBUG( 3, ( "reply_setattrE %s actime=%u modtime=%u " " createtime=%u\n", fsp_fnum_dbg(fsp), diff --git a/source3/smbd/trans2.c b/source3/smbd/trans2.c index 9bda39c27d3..339dd36b6af 100644 --- a/source3/smbd/trans2.c +++ b/source3/smbd/trans2.c @@ -7824,8 +7824,15 @@ static NTSTATUS smb_set_file_basic_info(connection_struct *conn, DEBUG(10, ("smb_set_file_basic_info: file %s\n", smb_fname_str_dbg(smb_fname))); - return smb_set_file_time(conn, fsp, smb_fname, &ft, - true); + status = smb_set_file_time(conn, fsp, smb_fname, &ft, true); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (fsp != NULL && fsp->modified) { + trigger_write_time_update_immediate(fsp); + } + return NT_STATUS_OK; } /**************************************************************************** @@ -7862,11 +7869,15 @@ static NTSTATUS smb_set_info_standard(connection_struct *conn, return status; } - return smb_set_file_time(conn, - fsp, - smb_fname, - &ft, - true); + status = smb_set_file_time(conn, fsp, smb_fname, &ft, true); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (fsp != NULL && fsp->modified) { + trigger_write_time_update_immediate(fsp); + } + return NT_STATUS_OK; } /**************************************************************************** -- 2.20.1 From 419e4ab8bd0c499c26040bb37a0bab0703b43b72 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Fri, 6 Mar 2020 16:21:47 +0100 Subject: [PATCH 8/8] torture/smb2: Windows 2019 15 ms timestamp resolution This test demonstrates that Windows has a timestamp resolution of ~15ms. When a smaller amount of time than that has passed between modifying operations on a file, it's not necessarily detectable on a Windows 2019 server that implements immediate timestamp updates (no delayed magic). Note that this test relies on a low latency SMB connection. Even with a low latency connection of eg 1m there's a chance of 1/15 that the first part of the test expecting no timestamp change fails as the writetime is updated. Due to this timing dependency this test is skipped in Samba CI, but it is preserved here for future SMB2 timestamps behaviour archealogists. See also: https://lists.samba.org/archive/cifs-protocol/2019-December/003358.html Signed-off-by: Ralph Boehme Reviewed-by: Jeremy Allison (cherry picked from commit 6f7d1d8a37bfb877b3f07423cbcffd15710e8d08) --- selftest/skip | 2 + source4/torture/smb2/smb2.c | 1 + source4/torture/smb2/timestamps.c | 145 ++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) diff --git a/selftest/skip b/selftest/skip index 11bf29599fa..549ba202021 100644 --- a/selftest/skip +++ b/selftest/skip @@ -75,6 +75,8 @@ ^samba3.smb2.durable-open-disconnect # Not a test, but a way to create a disconnected durable ^samba3.smb2.scan # No tests ^samba3.smb2.oplock.levelii501 # No test yet +^samba3.smb2.timestamp_resolution # See the comment on the test +^samba4.smb2.timestamp_resolution ^samba3.rpc.samr.passwords.lockout\(ad_dc\) # No point running this version, it just waits 12 times longer the samba4 version of this test, covering the same code ^samba4.base.iometer ^samba4.base.casetable diff --git a/source4/torture/smb2/smb2.c b/source4/torture/smb2/smb2.c index c258c15ff91..41b9ffbc356 100644 --- a/source4/torture/smb2/smb2.c +++ b/source4/torture/smb2/smb2.c @@ -198,6 +198,7 @@ NTSTATUS torture_smb2_init(TALLOC_CTX *ctx) torture_suite_add_suite(suite, torture_smb2_multichannel_init(suite)); torture_suite_add_suite(suite, torture_smb2_samba3misc_init(suite)); torture_suite_add_suite(suite, torture_smb2_timestamps_init(suite)); + torture_suite_add_suite(suite, torture_smb2_timestamp_resolution_init(suite)); torture_suite_add_1smb2_test(suite, "openattr", torture_smb2_openattrtest); torture_suite_add_1smb2_test(suite, "winattr", torture_smb2_winattrtest); torture_suite_add_suite(suite, torture_smb2_readwrite_init(suite)); diff --git a/source4/torture/smb2/timestamps.c b/source4/torture/smb2/timestamps.c index afa0fe24106..7f1a54c8ad0 100644 --- a/source4/torture/smb2/timestamps.c +++ b/source4/torture/smb2/timestamps.c @@ -727,3 +727,148 @@ struct torture_suite *torture_smb2_timestamps_init(TALLOC_CTX *ctx) return suite; } + +/* + * This test shows that Windows has a timestamp resolution of ~15ms. When so + * when a smaller amount of time than that has passed it's not necessarily + * detectable on a Windows 2019 and newer who implement immediate timestamp + * updates. + * + * Note that this test relies on a low latency SMB connection. Even with a low + * latency connection of eg 1m there's a chance of 1/15 that the first part of + * the test expecting no timestamp change fails as the writetime is updated. + * + * Due to this timing dependency this test is skipped in Samba CI, but it is + * preserved here for future SMB2 timestamps behaviour archealogists. + * + * See also: https://lists.samba.org/archive/cifs-protocol/2019-December/003358.html + */ +static bool test_timestamp_resolution1(struct torture_context *tctx, + struct smb2_tree *tree) +{ + union smb_fileinfo finfo1; + const char *fname = BASEDIR "\\" FNAME; + struct smb2_create cr; + struct smb2_handle h = {{0}}; + struct smb2_close cl; + NTSTATUS status; + bool ret = true; + + smb2_deltree(tree, BASEDIR); + status = torture_smb2_testdir(tree, BASEDIR, &h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + status = smb2_util_close(tree, h ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + + torture_comment(tctx, "Write without delay, expect no " + "write-time change\n"); + + smb2_generic_create(&cr, NULL, false, fname, + NTCREATEX_DISP_CREATE, + smb2_util_oplock_level(""), 0, 0); + status = smb2_create(tree, tree, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h = cr.out.file.handle; + + finfo1 = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h, + }; + status = smb2_getinfo_file(tree, tree, &finfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + status = smb2_util_write(tree, h, "123456789", 0, 9); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + cl = (struct smb2_close) { + .in.file.handle = h, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h); + + torture_comment(tctx, "Initial: %s\nClose: %s\n", + nt_time_string(tctx, finfo1.basic_info.out.write_time), + nt_time_string(tctx, cl.out.write_time)); + + torture_assert_u64_equal_goto(tctx, + finfo1.basic_info.out.write_time, + cl.out.write_time, + ret, done, + "Write time changed (wrong!)\n"); + + torture_comment(tctx, "Write with 20 ms delay, expect " + "write-time change\n"); + + smb2_generic_create(&cr, NULL, false, fname, + NTCREATEX_DISP_OPEN, + smb2_util_oplock_level(""), 0, 0); + status = smb2_create(tree, tree, &cr); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "create failed\n"); + h = cr.out.file.handle; + + finfo1 = (union smb_fileinfo) { + .generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION, + .generic.in.file.handle = h, + }; + status = smb2_getinfo_file(tree, tree, &finfo1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "getinfo failed\n"); + + smb_msleep(20); + + status = smb2_util_write(tree, h, "123456789", 0, 9); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "write failed\n"); + + cl = (struct smb2_close) { + .in.file.handle = h, + .in.flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION, + }; + + status = smb2_close(tree, &cl); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "close failed\n"); + ZERO_STRUCT(h); + + torture_comment(tctx, "Initial: %s\nClose: %s\n", + nt_time_string(tctx, finfo1.basic_info.out.write_time), + nt_time_string(tctx, cl.out.write_time)); + + torture_assert_u64_not_equal_goto( + tctx, + finfo1.basic_info.out.write_time, + cl.out.write_time, + ret, done, + "Write time did not change (wrong!)\n"); + +done: + if (!smb2_util_handle_empty(h)) { + smb2_util_close(tree, h); + } + smb2_deltree(tree, BASEDIR); + return ret; +} + +/* + basic testing of SMB2 timestamps +*/ +struct torture_suite *torture_smb2_timestamp_resolution_init(TALLOC_CTX *ctx) +{ + struct torture_suite *suite = torture_suite_create(ctx, "timestamp_resolution"); + + torture_suite_add_1smb2_test(suite, "resolution1", test_timestamp_resolution1); + + suite->description = talloc_strdup(suite, "SMB2 timestamp tests"); + + return suite; +} -- 2.20.1