From 77a25a2c4315082ead87f9dcb553431a53a92f9d Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 30 Aug 2024 14:22:24 +0200 Subject: [PATCH 01/10] s4:torture/smb2: improve error handling in durable_open.c BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme (cherry picked from commit e65e1326a0214a7dfff75ea1e528e82c8fc64517) --- source4/torture/smb2/durable_open.c | 39 ++++++++--------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c index cd07b330484e..8299995beb99 100644 --- a/source4/torture/smb2/durable_open.c +++ b/source4/torture/smb2/durable_open.c @@ -28,34 +28,17 @@ #include "torture/smb2/proto.h" #include "../libcli/smb/smbXcli_base.h" -#define CHECK_VAL(v, correct) do { \ - if ((v) != (correct)) { \ - torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should be 0x%llx\n", \ - __location__, #v, (unsigned long long)v, (unsigned long long)correct); \ - ret = false; \ - }} while (0) - -#define CHECK_NOT_VAL(v, incorrect) do { \ - if ((v) == (incorrect)) { \ - torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%llx - should not be 0x%llx\n", \ - __location__, #v, (unsigned long long)v, (unsigned long long)incorrect); \ - ret = false; \ - }} while (0) - -#define CHECK_NOT_NULL(p) do { \ - if ((p) == NULL) { \ - torture_result(tctx, TORTURE_FAIL, "(%s): %s is NULL but it should not be.\n", \ - __location__, #p); \ - ret = false; \ - }} while (0) - -#define CHECK_STATUS(status, correct) do { \ - if (!NT_STATUS_EQUAL(status, correct)) { \ - torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \ - nt_errstr(status), nt_errstr(correct)); \ - ret = false; \ - goto done; \ - }} while (0) +#define CHECK_VAL(v, correct) \ + torture_assert_u64_equal_goto(tctx, v, correct, ret, done, __location__) + +#define CHECK_NOT_VAL(v, incorrect) \ + torture_assert_u64_not_equal_goto(tctx, v, incorrect, ret, done, __location__) + +#define CHECK_NOT_NULL(p) \ + torture_assert_not_null_goto(tctx, p, ret, done, __location__) + +#define CHECK_STATUS(status, correct) \ + torture_assert_ntstatus_equal_goto(tctx, status, correct, ret, done, __location__) #define CHECK_CREATED(__io, __created, __attribute) \ do { \ -- 2.34.1 From c18211fc69fd79fff6e37e42b4fc5cc8c91ec1b6 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 30 Aug 2024 14:22:24 +0200 Subject: [PATCH 02/10] s4:torture/smb2: improve error handling in durable_v2_open.c BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme (cherry picked from commit 9b2417c2f04857709c25e3665cd783a68edf0cf2) --- source4/torture/smb2/durable_v2_open.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index 7447dd287a48..7eed4327b52e 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -27,20 +27,11 @@ #include "torture/smb2/proto.h" #include "librpc/ndr/libndr.h" -#define CHECK_VAL(v, correct) do { \ - if ((v) != (correct)) { \ - torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s got 0x%x - should be 0x%x\n", \ - __location__, #v, (int)v, (int)correct); \ - ret = false; \ - }} while (0) - -#define CHECK_STATUS(status, correct) do { \ - if (!NT_STATUS_EQUAL(status, correct)) { \ - torture_result(tctx, TORTURE_FAIL, __location__": Incorrect status %s - should be %s", \ - nt_errstr(status), nt_errstr(correct)); \ - ret = false; \ - goto done; \ - }} while (0) +#define CHECK_VAL(v, correct) \ + torture_assert_u64_equal_goto(tctx, v, correct, ret, done, __location__) + +#define CHECK_STATUS(status, correct) \ + torture_assert_ntstatus_equal_goto(tctx, status, correct, ret, done, __location__) #define CHECK_CREATED(__io, __created, __attribute) \ do { \ -- 2.34.1 From d15888ae7efb22b8934b0dcc662202d5738e77d7 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 30 Aug 2024 17:38:02 +0200 Subject: [PATCH 03/10] s4:torture/smb2: add smb2.durable-open.lock-noW-lease This demonstrates that a W lease is required for a durable handle to be durable when it has byte range locks. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme (cherry picked from commit 1cc1586d84a65046ab7804f17297c6964bb76c23) --- selftest/knownfail.d/smb2.durable.lock | 1 + source4/torture/smb2/durable_open.c | 97 +++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 selftest/knownfail.d/smb2.durable.lock diff --git a/selftest/knownfail.d/smb2.durable.lock b/selftest/knownfail.d/smb2.durable.lock new file mode 100644 index 000000000000..3e3bd80ba463 --- /dev/null +++ b/selftest/knownfail.d/smb2.durable.lock @@ -0,0 +1 @@ +^samba3.smb2.durable-open.lock-noW-lease diff --git a/source4/torture/smb2/durable_open.c b/source4/torture/smb2/durable_open.c index 8299995beb99..b730fab3c29f 100644 --- a/source4/torture/smb2/durable_open.c +++ b/source4/torture/smb2/durable_open.c @@ -2151,7 +2151,7 @@ static bool test_durable_open_lock_oplock(struct torture_context *tctx, } /* - Open, take BRL, disconnect, reconnect. + Open(RWH), take BRL, disconnect, reconnect. */ static bool test_durable_open_lock_lease(struct torture_context *tctx, struct smb2_tree *tree) @@ -2249,6 +2249,100 @@ static bool test_durable_open_lock_lease(struct torture_context *tctx, return ret; } +/* + Open(RH), take BRL, disconnect, fails reconnect without W LEASE +*/ +static bool test_durable_open_lock_noW_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct smb2_handle h = {{0}}; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease; + uint32_t caps; + struct smbcli_options options; + + options = tree->session->transport->options; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + lease = random(); + snprintf(fname, 256, "durable_open_lease_noW_lock_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree, fname); + + /* Create with lease */ + + smb2_lease_create(&io, &ls, false /* dir */, fname, lease, + smb2_util_lease_state("RH")); + io.in.durable_open = true; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + + CHECK_VAL(io.out.durable_open, true); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + CHECK_VAL(io.out.lease_response.lease_key.data[0], lease); + CHECK_VAL(io.out.lease_response.lease_key.data[1], ~lease); + CHECK_VAL(io.out.lease_response.lease_state, + SMB2_LEASE_READ|SMB2_LEASE_HANDLE); + + ZERO_STRUCT(lck); + ZERO_STRUCT(el); + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Disconnect/Reconnect. */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_handle = &h; + io.in.lease_request = &ls; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + h = io.out.file.handle; + + done: + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + talloc_free(tree); + + return ret; +} + /** * Open with a RH lease, disconnect, open in another tree, reconnect. * @@ -2823,6 +2917,7 @@ struct torture_suite *torture_smb2_durable_open_init(TALLOC_CTX *ctx) torture_suite_add_2smb2_test(suite, "lease", test_durable_open_lease); torture_suite_add_1smb2_test(suite, "lock-oplock", test_durable_open_lock_oplock); torture_suite_add_1smb2_test(suite, "lock-lease", test_durable_open_lock_lease); + torture_suite_add_1smb2_test(suite, "lock-noW-lease", test_durable_open_lock_noW_lease); torture_suite_add_2smb2_test(suite, "open2-lease", test_durable_open_open2_lease); torture_suite_add_2smb2_test(suite, "open2-oplock", -- 2.34.1 From 1326b85f0d271c43527bfc859fff595856f3b1ee Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 30 Aug 2024 17:38:02 +0200 Subject: [PATCH 04/10] s4:torture/smb2: add smb2.durable-v2-open.lock-{oplock,lease,noW-lease} This demonstrates that a W lease is required for a durable handle to be durable when it has byte range locks. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme (cherry picked from commit 8884d617310b47375e38c0386433c5e183703454) --- selftest/knownfail.d/smb2.durable.lock | 1 + source4/torture/smb2/durable_v2_open.c | 336 +++++++++++++++++++++++++ 2 files changed, 337 insertions(+) diff --git a/selftest/knownfail.d/smb2.durable.lock b/selftest/knownfail.d/smb2.durable.lock index 3e3bd80ba463..16273fb4ad45 100644 --- a/selftest/knownfail.d/smb2.durable.lock +++ b/selftest/knownfail.d/smb2.durable.lock @@ -1 +1,2 @@ ^samba3.smb2.durable-open.lock-noW-lease +^samba3.smb2.durable-v2-open.lock-noW-lease diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index 7eed4327b52e..685ef80c0cc2 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -41,6 +41,30 @@ CHECK_VAL((__io)->out.reserved2, 0); \ } while(0) +#define CHECK_LEASE_V2(__io, __state, __oplevel, __key, __flags, __parent, __epoch) \ + do { \ + CHECK_VAL((__io)->out.lease_response_v2.lease_version, 2); \ + if (__oplevel) { \ + CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], (__key)); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], ~(__key)); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_state, smb2_util_lease_state(__state)); \ + } else { \ + CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], 0); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], 0); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_state, 0); \ + } \ + \ + CHECK_VAL((__io)->out.lease_response_v2.lease_flags, __flags); \ + if (__flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET) { \ + CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[0], (__parent)); \ + CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[1], ~(__parent)); \ + } \ + CHECK_VAL((__io)->out.lease_response_v2.lease_duration, 0); \ + CHECK_VAL((__io)->out.lease_response_v2.lease_epoch, (__epoch)); \ + } while(0) + static struct { int count; struct smb2_close cl; @@ -1731,6 +1755,315 @@ done: return ret; } +/* + Open(BATCH), take BRL, disconnect, reconnect. +*/ +static bool test_durable_v2_open_lock_oplock(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct GUID create_guid = GUID_random(); + struct smb2_handle h = {{0}}; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + NTSTATUS status; + char fname[256]; + bool ret = true; + struct smbcli_options options; + + options = tree->session->transport->options; + + snprintf(fname, 256, "durable_v2_open_lock_oplock_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree, fname); + + /* Create with lease */ + + smb2_oplock_create_share(&io, fname, + smb2_util_share_access(""), + smb2_util_oplock_level("b")); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + + ZERO_STRUCT(lck); + ZERO_STRUCT(el); + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Disconnect/Reconnect. */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = &h; + io.in.create_guid = create_guid; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b")); + + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + done: + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + talloc_free(tree); + + return ret; +} + +/* + Open(RWH), take BRL, disconnect, reconnect. +*/ +static bool test_durable_v2_open_lock_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct GUID create_guid = GUID_random(); + struct smb2_handle h = {{0}}; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease; + uint32_t caps; + struct smbcli_options options; + + options = tree->session->transport->options; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + lease = random(); + snprintf(fname, 256, "durable_v2_open_lock_lease_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree, fname); + + /* Create with lease */ + + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + ls.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RWH", true, lease, + 0, 0, ls.lease_epoch); + + ZERO_STRUCT(lck); + ZERO_STRUCT(el); + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Disconnect/Reconnect. */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = &h; + io.in.create_guid = create_guid; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RWH", true, lease, + 0, 0, ls.lease_epoch); + + lck.in.file.handle = h; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + done: + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + talloc_free(tree); + + return ret; +} + +/* + Open(RH), take BRL, disconnect, fails reconnect without W LEASE +*/ +static bool test_durable_v2_open_lock_noW_lease(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls; + struct GUID create_guid = GUID_random(); + struct smb2_handle h = {{0}}; + struct smb2_lock lck; + struct smb2_lock_element el[2]; + NTSTATUS status; + char fname[256]; + bool ret = true; + uint64_t lease; + uint32_t caps; + struct smbcli_options options; + + options = tree->session->transport->options; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + /* + * Choose a random name and random lease in case the state is left a + * little funky. + */ + lease = random(); + snprintf(fname, 256, "durable_v2_open_lock_noW_lease_%s.dat", generate_random_str(tctx, 8)); + + /* Clean slate */ + smb2_util_unlink(tree, fname); + + /* Create with lease */ + + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + ls.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease, + 0, 0, ls.lease_epoch); + + ZERO_STRUCT(lck); + ZERO_STRUCT(el); + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = 0; + el[0].length = 1; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Disconnect/Reconnect. */ + talloc_free(tree); + tree = NULL; + + if (!torture_smb2_connection_ext(tctx, 0, &options, &tree)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = &h; + io.in.create_guid = create_guid; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + done: + smb2_util_close(tree, h); + smb2_util_unlink(tree, fname); + talloc_free(tree); + + return ret; +} + /** * Test durable request / reconnect with AppInstanceId */ @@ -2157,6 +2490,9 @@ struct torture_suite *torture_smb2_durable_v2_open_init(TALLOC_CTX *ctx) torture_suite_add_1smb2_test(suite, "reopen2-lease", test_durable_v2_open_reopen2_lease); torture_suite_add_1smb2_test(suite, "reopen2-lease-v2", test_durable_v2_open_reopen2_lease_v2); torture_suite_add_1smb2_test(suite, "durable-v2-setinfo", test_durable_v2_setinfo); + torture_suite_add_1smb2_test(suite, "lock-oplock", test_durable_v2_open_lock_oplock); + torture_suite_add_1smb2_test(suite, "lock-lease", test_durable_v2_open_lock_lease); + torture_suite_add_1smb2_test(suite, "lock-noW-lease", test_durable_v2_open_lock_noW_lease); torture_suite_add_2smb2_test(suite, "app-instance", test_durable_v2_open_app_instance); torture_suite_add_1smb2_test(suite, "persistent-open-oplock", test_persistent_open_oplock); torture_suite_add_1smb2_test(suite, "persistent-open-lease", test_persistent_open_lease); -- 2.34.1 From f58d4e0ac031ee1e1cf9485dbe2ac3ab35176172 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 30 Aug 2024 18:10:16 +0200 Subject: [PATCH 05/10] s3:smbd: only store durable handles with byte range locks when having WRITE lease This simplifies the reconnect assumptions, when we want to allow more than one durable handle on a file for multiple clients with READ+HANDLE leases. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme (cherry picked from commit 0893ae88180137d44f17196234f657d362543ff5) --- selftest/knownfail.d/smb2.durable.lock | 2 -- source3/smbd/durable.c | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) delete mode 100644 selftest/knownfail.d/smb2.durable.lock diff --git a/selftest/knownfail.d/smb2.durable.lock b/selftest/knownfail.d/smb2.durable.lock deleted file mode 100644 index 16273fb4ad45..000000000000 --- a/selftest/knownfail.d/smb2.durable.lock +++ /dev/null @@ -1,2 +0,0 @@ -^samba3.smb2.durable-open.lock-noW-lease -^samba3.smb2.durable-v2-open.lock-noW-lease diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c index dfb87dd3775c..2325e69cccc6 100644 --- a/source3/smbd/durable.c +++ b/source3/smbd/durable.c @@ -173,6 +173,12 @@ NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp, return NT_STATUS_NOT_SUPPORTED; } + if (fsp->current_lock_count != 0 && + (fsp_lease_type(fsp) & SMB2_LEASE_WRITE) == 0) + { + return NT_STATUS_NOT_SUPPORTED; + } + /* * For now let it be simple and do not keep * delete on close files durable open -- 2.34.1 From 3e65c9d482151cc01aeff3f2933210eeae4e6e25 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 28 Aug 2024 16:48:27 +0200 Subject: [PATCH 06/10] s4:torture/smb2: add smb2.durable-v2-open.{[non]stat[RH]-and,two-same,two-different}-lease These show that it's possible to have durable handles in addition of stat opens, as well as multiple durable opens with RH leases. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme (cherry picked from commit 77c7741f39a0a9789bede7c4722bd3f35d4af3fd) --- .../knownfail.d/smb2.durable-v2-open.bug15649 | 2 + .../knownfail.d/smb2.durable-v2-open.bug15651 | 3 + source4/torture/smb2/durable_v2_open.c | 784 ++++++++++++++++++ 3 files changed, 789 insertions(+) create mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15649 create mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15651 diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15649 b/selftest/knownfail.d/smb2.durable-v2-open.bug15649 new file mode 100644 index 000000000000..748b6c3150ec --- /dev/null +++ b/selftest/knownfail.d/smb2.durable-v2-open.bug15649 @@ -0,0 +1,2 @@ +^samba3.smb2.durable-v2-open.stat-and-lease +^samba3.smb2.durable-v2-open.nonstat-and-lease diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15651 b/selftest/knownfail.d/smb2.durable-v2-open.bug15651 new file mode 100644 index 000000000000..1bb0a70d9a0d --- /dev/null +++ b/selftest/knownfail.d/smb2.durable-v2-open.bug15651 @@ -0,0 +1,3 @@ +^samba3.smb2.durable-v2-open.statRH-and-lease +^samba3.smb2.durable-v2-open.two-same-lease +^samba3.smb2.durable-v2-open.two-different-lease diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index 685ef80c0cc2..e86b1955092a 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -26,6 +26,7 @@ #include "torture/torture.h" #include "torture/smb2/proto.h" #include "librpc/ndr/libndr.h" +#include "lease_break_handler.h" #define CHECK_VAL(v, correct) \ torture_assert_u64_equal_goto(tctx, v, correct, ret, done, __location__) @@ -2064,6 +2065,784 @@ static bool test_durable_v2_open_lock_noW_lease(struct torture_context *tctx, return ret; } +/** + * 1. stat open (without lease) => h1 + * 2. durable open with RWH => h2 + * 3. disconnect + * 4. reconnect + * 5. durable reconnect RWH => h2 + */ +static bool test_durable_v2_open_stat_and_lease(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + smb2_generic_create(&io, NULL, false /* dir */, fname, + FILE_OPEN_IF, 0, 0, 0); + io.in.desired_access = SEC_FILE_READ_ATTRIBUTE; + io.in.desired_access |= SEC_FILE_WRITE_ATTRIBUTE; + io.in.desired_access |= SEC_STD_SYNCHRONIZE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + lease_key = random(); + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease_key, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RWH", true, lease_key, + 0, 0, ls.lease_epoch); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + h1 = NULL; + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h2; + io.in.create_guid = create_guid2; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RWH", true, lease_key, + 0, 0, ls.lease_epoch); + + status = smb2_util_close(tree1, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + + if (tree1 != NULL && h1 != NULL) { + smb2_util_close(tree1, *h1); + } + if (tree1 != NULL && h2 != NULL) { + smb2_util_close(tree1, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. non stat open (without a lease) => h1 + * 2. durable open with RWH => h2 => RH + * 3. disconnect + * 4. reconnect + * 5. durable reconnect RH => h2 + */ +static bool test_durable_v2_open_nonstat_and_lease(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + smb2_generic_create(&io, NULL, false /* dir */, fname, + FILE_OPEN_IF, 0, 0, 0); + io.in.desired_access = SEC_FILE_READ_ATTRIBUTE; + io.in.desired_access |= SEC_FILE_WRITE_ATTRIBUTE; + io.in.desired_access |= SEC_STD_SYNCHRONIZE; + /* + * SEC_STD_READ_CONTROL means we no longer + * have a stat open that would allow a RWH lease + */ + io.in.desired_access |= SEC_STD_READ_CONTROL; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); + + lease_key = random(); + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease_key, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key, + 0, 0, ls.lease_epoch); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + h1 = NULL; + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h2; + io.in.create_guid = create_guid2; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key, + 0, 0, ls.lease_epoch); + + status = smb2_util_close(tree1, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + + if (tree1 != NULL && h1 != NULL) { + smb2_util_close(tree1, *h1); + } + if (tree1 != NULL && h2 != NULL) { + smb2_util_close(tree1, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. stat open with RH lease => h1 + * 2. durable open with RWH => h2 => RH + * 3. disconnect + * 4. reconnect + * 5. durable reconnect RH => h2 + */ +static bool test_durable_v2_open_statRH_and_lease(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + smb2_generic_create(&io, NULL, false /* dir */, fname, + FILE_OPEN_IF, 0, 0, 0); + lease_key = random(); + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease_key, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.desired_access = SEC_FILE_READ_ATTRIBUTE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); + ls.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key, + 0, 0, ls.lease_epoch); + + lease_key = random(); + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease_key, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key, + 0, 0, ls.lease_epoch); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + h1 = NULL; + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h2; + io.in.create_guid = create_guid2; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key, + 0, 0, ls.lease_epoch); + + status = smb2_util_close(tree1, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + + if (tree1 != NULL && h1 != NULL) { + smb2_util_close(tree1, *h1); + } + if (tree1 != NULL && h2 != NULL) { + smb2_util_close(tree1, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1(RWH) => h1 + * 2. durable open with L1(RWH) => h2 + * 3. disconnect + * 4. reconnect + * 5. durable reconnect L1(RWH) => h1 + * 6. durable reconnect L1(RWH) => h2 + */ +static bool test_durable_v2_open_two_same_lease(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls; + uint64_t lease_key; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key = random(); + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease_key, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RWH", true, lease_key, + 0, 0, ls.lease_epoch); + + smb2_lease_v2_create(&io, &ls, false /* dir */, fname, + lease_key, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RWH", true, lease_key, + 0, 0, ls.lease_epoch); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1; + io.in.create_guid = create_guid1; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RWH", true, lease_key, + 0, 0, ls.lease_epoch); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h2; + io.in.create_guid = create_guid2; + io.in.lease_request_v2 = &ls; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RWH", true, lease_key, + 0, 0, ls.lease_epoch); + + status = smb2_util_close(tree1, *h1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = NULL; + + status = smb2_util_close(tree1, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + + if (tree1 != NULL && h1 != NULL) { + smb2_util_close(tree1, *h1); + } + if (tree1 != NULL && h2 != NULL) { + smb2_util_close(tree1, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1(RH) => h1 + * 2. durable open with L2(RH) => h2 + * 3. disconnect + * 4. reconnect + * 5. durable reconnect L1(RH) => h1 + * 6. durable reconnect L2(RH) => h2 + */ +static bool test_durable_v2_open_two_different_leases(struct torture_context *tctx, + struct smb2_tree *tree1) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls1; + uint64_t lease_key1; + struct smb2_lease ls2; + uint64_t lease_key2; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key1 = random(); + smb2_lease_v2_create(&io, &ls1, false /* dir */, fname, + lease_key1, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1, + 0, 0, ls1.lease_epoch); + + lease_key2 = random(); + smb2_lease_v2_create(&io, &ls2, false /* dir */, fname, + lease_key2, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key2, + 0, 0, ls2.lease_epoch); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1; + io.in.create_guid = create_guid1; + io.in.lease_request_v2 = &ls1; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key1, + 0, 0, ls1.lease_epoch); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h2; + io.in.create_guid = create_guid2; + io.in.lease_request_v2 = &ls2; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key2, + 0, 0, ls2.lease_epoch); + + status = smb2_util_close(tree1, *h1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = NULL; + + status = smb2_util_close(tree1, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + + if (tree1 != NULL && h1 != NULL) { + smb2_util_close(tree1, *h1); + } + if (tree1 != NULL && h2 != NULL) { + smb2_util_close(tree1, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + talloc_free(mem_ctx); + + return ret; +} + /** * Test durable request / reconnect with AppInstanceId */ @@ -2493,6 +3272,11 @@ struct torture_suite *torture_smb2_durable_v2_open_init(TALLOC_CTX *ctx) torture_suite_add_1smb2_test(suite, "lock-oplock", test_durable_v2_open_lock_oplock); torture_suite_add_1smb2_test(suite, "lock-lease", test_durable_v2_open_lock_lease); torture_suite_add_1smb2_test(suite, "lock-noW-lease", test_durable_v2_open_lock_noW_lease); + torture_suite_add_1smb2_test(suite, "stat-and-lease", test_durable_v2_open_stat_and_lease); + torture_suite_add_1smb2_test(suite, "nonstat-and-lease", test_durable_v2_open_nonstat_and_lease); + torture_suite_add_1smb2_test(suite, "statRH-and-lease", test_durable_v2_open_statRH_and_lease); + torture_suite_add_1smb2_test(suite, "two-same-lease", test_durable_v2_open_two_same_lease); + torture_suite_add_1smb2_test(suite, "two-different-lease", test_durable_v2_open_two_different_leases); torture_suite_add_2smb2_test(suite, "app-instance", test_durable_v2_open_app_instance); torture_suite_add_1smb2_test(suite, "persistent-open-oplock", test_persistent_open_oplock); torture_suite_add_1smb2_test(suite, "persistent-open-lease", test_persistent_open_lease); -- 2.34.1 From a6149e4797f3ca0527eec9f38ec881990140f54d Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Wed, 4 Sep 2024 18:18:43 +0200 Subject: [PATCH 07/10] s4:torture/smb2: add smb2.durable-v2-open.{keep,purge}-disconnected-* tests These demonstrate which durables handles are kept and which are purged because of various opens, writes or renames. smb2.durable-v2-open.keep-disconnected-rh-with-stat-open smb2.durable-v2-open.keep-disconnected-rh-with-rh-open smb2.durable-v2-open.keep-disconnected-rh-with-rwh-open smb2.durable-v2-open.keep-disconnected-rwh-with-stat-open smb2.durable-v2-open.purge-disconnected-rwh-with-rwh-open smb2.durable-v2-open.purge-disconnected-rwh-with-rh-open smb2.durable-v2-open.purge-disconnected-rh-with-share-none-open smb2.durable-v2-open.purge-disconnected-rh-with-write smb2.durable-v2-open.purge-disconnected-rh-with-rename BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15708 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme (cherry picked from commit 9e98cd5c7a180521026b0d73a330bdaf2c8af73a) --- .../knownfail.d/smb2.durable-v2-open.bug15651 | 2 + .../knownfail.d/smb2.durable-v2-open.bug15708 | 7 + source4/torture/smb2/durable_v2_open.c | 1851 +++++++++++++++++ 3 files changed, 1860 insertions(+) create mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15708 diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15651 b/selftest/knownfail.d/smb2.durable-v2-open.bug15651 index 1bb0a70d9a0d..1702a3a65809 100644 --- a/selftest/knownfail.d/smb2.durable-v2-open.bug15651 +++ b/selftest/knownfail.d/smb2.durable-v2-open.bug15651 @@ -1,3 +1,5 @@ ^samba3.smb2.durable-v2-open.statRH-and-lease ^samba3.smb2.durable-v2-open.two-same-lease ^samba3.smb2.durable-v2-open.two-different-lease +^samba3.smb2.durable-v2-open.keep-disconnected-rh-with-stat-open +^samba3.smb2.durable-v2-open.keep-disconnected-rwh-with-stat-open diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15708 b/selftest/knownfail.d/smb2.durable-v2-open.bug15708 new file mode 100644 index 000000000000..3a6380c6d651 --- /dev/null +++ b/selftest/knownfail.d/smb2.durable-v2-open.bug15708 @@ -0,0 +1,7 @@ +# +# https://bugzilla.samba.org/show_bug.cgi?id=15708 is not fixed +# yet, it requires some complex changes within handle_share_mode_lease() +# merging logic of open_mode_check() and delay_for_oplock()... +# +^samba3.smb2.durable-v2-open.keep-disconnected-rh-with-rh-open +^samba3.smb2.durable-v2-open.keep-disconnected-rh-with-rwh-open diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index e86b1955092a..104796e76ad6 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -2843,6 +2843,1848 @@ done: return ret; } +/** + * 1. durable open with L1A(RH) on tree1 => h1a + * 1. durable open with L1B(RH) on tree1 => h1b + * 2. disconnect tree1 + * 3. stat open on tree2 => h2 + * 4. reconnect tree1 + * 5. durable reconnect L1A(RH) => h1a + * 6. durable reconnect L1B(RH) => h1a + */ +static bool test_durable_v2_open_keep_disconnected_rh_with_stat_open(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1a; + struct smb2_handle *h1a = NULL; + struct smb2_handle _h1b; + struct smb2_handle *h1b = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1a = GUID_random(); + struct GUID create_guid1b = GUID_random(); + struct smb2_lease ls1a; + uint64_t lease_key1a; + struct smb2_lease ls1b; + uint64_t lease_key1b; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key1a = random(); + smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname, + lease_key1a, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1a; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1a.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + lease_key1b = random(); + smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname, + lease_key1b, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1b; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1b = io.out.file.handle; + h1b = &_h1b; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1b.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1b, + 0, 0, ls1b.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + + CHECK_NO_BREAK(tctx); + + smb2_generic_create(&io, NULL, false /* dir */, fname, + FILE_OPEN_IF, 0, 0, 0); + io.in.desired_access = SEC_FILE_READ_ATTRIBUTE; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.persistent_open, false); + + CHECK_NO_BREAK(tctx); + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1a; + io.in.create_guid = create_guid1a; + io.in.lease_request_v2 = &ls1a; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1b; + io.in.create_guid = create_guid1b; + io.in.lease_request_v2 = &ls1b; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1b = io.out.file.handle; + h1b = &_h1b; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key1b, + 0, 0, ls1b.lease_epoch); + + status = smb2_util_close(tree1, *h1a); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = NULL; + + status = smb2_util_close(tree1, *h1b); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = NULL; + + status = smb2_util_close(tree2, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + + CHECK_NO_BREAK(tctx); + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + if (tree2 != NULL) { + smb2_keepalive(tree2->session->transport); + } + if (tree1 != NULL && h1a != NULL) { + smb2_util_close(tree1, *h1a); + } + if (tree1 != NULL && h1b != NULL) { + smb2_util_close(tree1, *h1b); + } + if (tree2 != NULL && h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1A(RH) on tree1 => h1a + * 1. durable open with L1B(RH) on tree1 => h1b + * 2. disconnect tree1 + * 3. durable open with L2(RH) on tree2 => h2 + * 4. reconnect tree1 + * 5. durable reconnect L1A(RH) => h1a + * 6. durable reconnect L1B(RH) => h1a + */ +static bool test_durable_v2_open_keep_disconnected_rh_with_rh_open(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1a; + struct smb2_handle *h1a = NULL; + struct smb2_handle _h1b; + struct smb2_handle *h1b = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1a = GUID_random(); + struct GUID create_guid1b = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls1a; + uint64_t lease_key1a; + struct smb2_lease ls1b; + uint64_t lease_key1b; + struct smb2_lease ls2; + uint64_t lease_key2; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key1a = random(); + smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname, + lease_key1a, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1a; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1a.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + lease_key1b = random(); + smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname, + lease_key1b, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1b; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1b = io.out.file.handle; + h1b = &_h1b; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1b.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1b, + 0, 0, ls1b.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + + CHECK_NO_BREAK(tctx); + + lease_key2 = random(); + smb2_lease_v2_create(&io, &ls2, false /* dir */, fname, + lease_key2, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key2, + 0, 0, ls2.lease_epoch); + + CHECK_NO_BREAK(tctx); + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1a; + io.in.create_guid = create_guid1a; + io.in.lease_request_v2 = &ls1a; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1b; + io.in.create_guid = create_guid1b; + io.in.lease_request_v2 = &ls1b; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1b = io.out.file.handle; + h1b = &_h1b; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key1b, + 0, 0, ls1b.lease_epoch); + + status = smb2_util_close(tree1, *h1a); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = NULL; + + status = smb2_util_close(tree1, *h1b); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = NULL; + + status = smb2_util_close(tree2, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + + CHECK_NO_BREAK(tctx); + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + if (tree2 != NULL) { + smb2_keepalive(tree2->session->transport); + } + if (tree1 != NULL && h1a != NULL) { + smb2_util_close(tree1, *h1a); + } + if (tree1 != NULL && h1b != NULL) { + smb2_util_close(tree1, *h1b); + } + if (tree2 != NULL && h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1A(RH) on tree1 => h1a + * 1. durable open with L1B(RH) on tree1 => h1b + * 2. disconnect tree1 + * 3. durable open with L2(RWH) on tree2 => h2 => RH + * 4. reconnect tree1 + * 5. durable reconnect L1A(RH) => h1a + * 6. durable reconnect L1B(RH) => h1a + */ +static bool test_durable_v2_open_keep_disconnected_rh_with_rwh_open(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1a; + struct smb2_handle *h1a = NULL; + struct smb2_handle _h1b; + struct smb2_handle *h1b = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1a = GUID_random(); + struct GUID create_guid1b = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls1a; + uint64_t lease_key1a; + struct smb2_lease ls1b; + uint64_t lease_key1b; + struct smb2_lease ls2; + uint64_t lease_key2; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key1a = random(); + smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname, + lease_key1a, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1a; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1a.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + lease_key1b = random(); + smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname, + lease_key1b, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1b; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1b = io.out.file.handle; + h1b = &_h1b; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1b.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1b, + 0, 0, ls1b.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + + CHECK_NO_BREAK(tctx); + + lease_key2 = random(); + smb2_lease_v2_create(&io, &ls2, false /* dir */, fname, + lease_key2, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key2, + 0, 0, ls2.lease_epoch); + + CHECK_NO_BREAK(tctx); + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1a; + io.in.create_guid = create_guid1a; + io.in.lease_request_v2 = &ls1a; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1b; + io.in.create_guid = create_guid1b; + io.in.lease_request_v2 = &ls1b; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1b = io.out.file.handle; + h1b = &_h1b; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RH", true, lease_key1b, + 0, 0, ls1b.lease_epoch); + + status = smb2_util_close(tree1, *h1a); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = NULL; + + status = smb2_util_close(tree1, *h1b); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = NULL; + + status = smb2_util_close(tree2, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + + CHECK_NO_BREAK(tctx); + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + if (tree2 != NULL) { + smb2_keepalive(tree2->session->transport); + } + if (tree1 != NULL && h1a != NULL) { + smb2_util_close(tree1, *h1a); + } + if (tree1 != NULL && h1b != NULL) { + smb2_util_close(tree1, *h1b); + } + if (tree2 != NULL && h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1(RWH) on tree1 => h1 + * 2. disconnect tree1 + * 3. stat open on tree2 => h2 + * 4. reconnect tree1 + * 5. durable reconnect L1(RWH) => h1 + */ +static bool test_durable_v2_open_keep_disconnected_rwh_with_stat_open(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1 = GUID_random(); + struct smb2_lease ls1; + uint64_t lease_key1; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key1 = random(); + smb2_lease_v2_create(&io, &ls1, false /* dir */, fname, + lease_key1, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RWH", true, lease_key1, + 0, 0, ls1.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + + CHECK_NO_BREAK(tctx); + + smb2_generic_create(&io, NULL, false /* dir */, fname, + FILE_OPEN_IF, 0, 0, 0); + io.in.desired_access = SEC_FILE_READ_ATTRIBUTE; + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.persistent_open, false); + + CHECK_NO_BREAK(tctx); + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1; + io.in.create_guid = create_guid1; + io.in.lease_request_v2 = &ls1; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); /* no dh2q response blob */ + CHECK_VAL(io.out.persistent_open, false); + CHECK_LEASE_V2(&io, "RWH", true, lease_key1, + 0, 0, ls1.lease_epoch); + + status = smb2_util_close(tree1, *h1); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = NULL; + + status = smb2_util_close(tree2, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + + CHECK_NO_BREAK(tctx); + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + if (tree2 != NULL) { + smb2_keepalive(tree2->session->transport); + } + if (tree1 != NULL && h1 != NULL) { + smb2_util_close(tree1, *h1); + } + if (tree2 != NULL && h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1(RWH) on tree1 => h1 + * 2. disconnect tree1 + * 3. durable open with L2(RWH) on tree2 => h2 + * 4. reconnect tree1 + * 5. durable reconnect L1(RH) => h1 => not found + */ +static bool test_durable_v2_open_purge_disconnected_rwh_with_rwh_open(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls1; + uint64_t lease_key1; + struct smb2_lease ls2; + uint64_t lease_key2; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key1 = random(); + smb2_lease_v2_create(&io, &ls1, false /* dir */, fname, + lease_key1, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RWH", true, lease_key1, + 0, 0, ls1.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + + CHECK_NO_BREAK(tctx); + + lease_key2 = random(); + smb2_lease_v2_create(&io, &ls2, false /* dir */, fname, + lease_key2, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RWH", true, lease_key2, + 0, 0, ls2.lease_epoch); + + CHECK_NO_BREAK(tctx); + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1; + io.in.create_guid = create_guid1; + io.in.lease_request_v2 = &ls1; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + ls1.lease_state = smb2_util_lease_state("RH"); + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + status = smb2_util_close(tree2, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + + CHECK_NO_BREAK(tctx); + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + if (tree2 != NULL) { + smb2_keepalive(tree2->session->transport); + } + if (tree1 != NULL && h1 != NULL) { + smb2_util_close(tree1, *h1); + } + if (tree2 != NULL && h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1(RWH) on tree1 => h1 + * 2. disconnect tree1 + * 3. durable open with L2(RH) on tree2 => h2 + * 4. reconnect tree1 + * 5. durable reconnect L1(RH) => h1 => not found + */ +static bool test_durable_v2_open_purge_disconnected_rwh_with_rh_open(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1; + struct smb2_handle *h1 = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls1; + uint64_t lease_key1; + struct smb2_lease ls2; + uint64_t lease_key2; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key1 = random(); + smb2_lease_v2_create(&io, &ls1, false /* dir */, fname, + lease_key1, 0, /* parent lease key */ + smb2_util_lease_state("RWH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1 = io.out.file.handle; + h1 = &_h1; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RWH", true, lease_key1, + 0, 0, ls1.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + + CHECK_NO_BREAK(tctx); + + lease_key2 = random(); + smb2_lease_v2_create(&io, &ls2, false /* dir */, fname, + lease_key2, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key2, + 0, 0, ls2.lease_epoch); + + CHECK_NO_BREAK(tctx); + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1; + io.in.create_guid = create_guid1; + io.in.lease_request_v2 = &ls1; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + ls1.lease_state = smb2_util_lease_state("RH"); + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + + status = smb2_util_close(tree2, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + + CHECK_NO_BREAK(tctx); + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + if (tree2 != NULL) { + smb2_keepalive(tree2->session->transport); + } + if (tree1 != NULL && h1 != NULL) { + smb2_util_close(tree1, *h1); + } + if (tree2 != NULL && h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1A(RH) on tree1 => h1a + * 2. durable open with L1B(RH) on tree1 => h1b + * 3. disconnect tree1 + * 4. open with SHARE_NONE on tree2 => h2 + * 5. reconnect tree1 + * 6. durable reconnect L1A(RH) => not found + * 7. durable reconnect L1B(RH) => not found + */ +static bool test_durable_v2_open_purge_disconnected_rh_with_share_none_open(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1a; + struct smb2_handle *h1a = NULL; + struct smb2_handle _h1b; + struct smb2_handle *h1b = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1a = GUID_random(); + struct GUID create_guid1b = GUID_random(); + struct smb2_lease ls1a; + uint64_t lease_key1a; + struct smb2_lease ls1b; + uint64_t lease_key1b; + bool ret = true; + struct smbcli_options options1; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options1 = tree1->session->transport->options; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key1a = random(); + smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname, + lease_key1a, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1a; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1a.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + lease_key1b = random(); + smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname, + lease_key1b, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1b; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1b = io.out.file.handle; + h1b = &_h1b; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1b.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1b, + 0, 0, ls1b.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree1); + + CHECK_NO_BREAK(tctx); + + smb2_generic_create_share(&io, &ls1a, false /* dir */, fname, + NTCREATEX_DISP_OPEN_IF, + FILE_SHARE_NONE, + SMB2_OPLOCK_LEVEL_NONE, 0, 0); + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, false); + CHECK_VAL(io.out.persistent_open, false); + + CHECK_NO_BREAK(tctx); + + if (!torture_smb2_connection_ext(tctx, 0, &options1, &tree1)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1a; + io.in.create_guid = create_guid1a; + io.in.lease_request_v2 = &ls1a; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + h1a = NULL; + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h1b; + io.in.create_guid = create_guid1b; + io.in.lease_request_v2 = &ls1b; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + h1b = NULL; + + status = smb2_util_close(tree2, *h2); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = NULL; + + CHECK_NO_BREAK(tctx); + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + if (tree2 != NULL) { + smb2_keepalive(tree2->session->transport); + } + if (tree1 != NULL && h1a != NULL) { + smb2_util_close(tree1, *h1a); + } + if (tree1 != NULL && h1b != NULL) { + smb2_util_close(tree1, *h1b); + } + if (tree2 != NULL && h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1A(RH) on tree1 => h1a + * 2. durable open with L1B(RH) on tree1 => h1b + * 3. durable open with L2(RH) on tree2 => h2 + * 4. disconnect tree2 + * 5.1 write to h1a + * 5.2 lease break to NONE for L1B (ack requested, but ignored) + * 6. reconnect tree2 + * 7. durable reconnect L2(RH) => h2 => not found + * 8. close h1a + * 9. durable open with L1A(RWH) on tree1 => h1a only RH + */ +static bool test_durable_v2_open_purge_disconnected_rh_with_write(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[256]; + struct smb2_handle dh; + struct smb2_handle _h1a; + struct smb2_handle *h1a = NULL; + struct smb2_handle _h1b; + struct smb2_handle *h1b = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1a = GUID_random(); + struct GUID create_guid1b = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls1a; + uint64_t lease_key1a; + struct smb2_lease ls1b; + uint64_t lease_key1b; + struct smb2_lease ls2; + uint64_t lease_key2; + struct smb2_write wrt; + bool ret = true; + struct smbcli_options options2; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options2 = tree2->session->transport->options; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 256, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + + smb2_util_unlink(tree1, fname); + + lease_key1a = random(); + smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname, + lease_key1a, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1a; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1a.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + lease_key1b = random(); + smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname, + lease_key1b, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1b; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1b = io.out.file.handle; + h1b = &_h1b; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1b.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1b, + 0, 0, ls1b.lease_epoch); + + lease_key2 = random(); + smb2_lease_v2_create(&io, &ls2, false /* dir */, fname, + lease_key2, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key2, + 0, 0, ls2.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree2); + + CHECK_NO_BREAK(tctx); + lease_break_info.lease_skip_ack = true; + + ZERO_STRUCT(wrt); + wrt.in.file.handle = *h1a; + wrt.in.offset = 0; + wrt.in.data = data_blob_string_const("data"); + status = smb2_write(tree1, &wrt); + CHECK_STATUS(status, NT_STATUS_OK); + + ls1b.lease_epoch += 1; + CHECK_BREAK_INFO_V2(tree1->session->transport, + "RH", "", lease_key1b, ls1b.lease_epoch); + torture_reset_lease_break_info(tctx, &lease_break_info); + CHECK_NO_BREAK(tctx); + + if (!torture_smb2_connection_ext(tctx, 0, &options2, &tree2)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h2; + io.in.create_guid = create_guid2; + io.in.lease_request_v2 = &ls2; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + h2 = NULL; + + status = smb2_util_close(tree1, *h1a); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = NULL; + + /* + * Now there's only lease_key2 with state NONE + * + * And that means an additional open still + * only gets RH... + */ + smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname, + lease_key1a, 0, /* parent lease key */ + smb2_util_lease_state("RHW"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1a; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_VAL(io.out.create_action, NTCREATEX_ACTION_EXISTED); + CHECK_VAL(io.out.size, wrt.in.data.length); + CHECK_VAL(io.out.file_attr, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1a.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + status = smb2_util_close(tree1, *h1a); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = NULL; + + status = smb2_util_close(tree1, *h1b); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = NULL; + + CHECK_NO_BREAK(tctx); + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + if (tree2 != NULL) { + smb2_keepalive(tree2->session->transport); + } + if (tree1 != NULL && h1a != NULL) { + smb2_util_close(tree1, *h1a); + } + if (tree1 != NULL && h1b != NULL) { + smb2_util_close(tree1, *h1b); + } + if (tree2 != NULL && h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + +/** + * 1. durable open with L1A(RH) on tree1 => h1a + * 2. durable open with L1B(RH) on tree1 => h1b + * 3. durable open with L2(RH) on tree2 => h2 + * 4. disconnect tree2 + * 5.1 rename h1a + * 5.2 lease break to R for L1B (ack requested, and required) + * 6. reconnect tree2 + * 7. durable reconnect L2(RH) => h2 => not found + */ +static bool test_durable_v2_open_purge_disconnected_rh_with_rename(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + NTSTATUS status; + char fname[128]; + char fname_renamed[140]; + struct smb2_handle dh; + struct smb2_handle _h1a; + struct smb2_handle *h1a = NULL; + struct smb2_handle _h1b; + struct smb2_handle *h1b = NULL; + struct smb2_handle _h2; + struct smb2_handle *h2 = NULL; + struct smb2_create io; + struct GUID create_guid1a = GUID_random(); + struct GUID create_guid1b = GUID_random(); + struct GUID create_guid2 = GUID_random(); + struct smb2_lease ls1a; + uint64_t lease_key1a; + struct smb2_lease ls1b; + uint64_t lease_key1b; + struct smb2_lease ls2; + uint64_t lease_key2; + union smb_setfileinfo sinfo; + bool ret = true; + struct smbcli_options options2; + uint32_t caps; + + caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + options2 = tree2->session->transport->options; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + smb2_deltree(tree1, __func__); + status = torture_smb2_testdir(tree1, __func__, &dh); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "torture_smb2_testdir failed\n"); + smb2_util_close(tree1, dh); + + /* Choose a random name in case the state is left a little funky. */ + snprintf(fname, 128, "%s\\file_%s.dat", + __func__, generate_random_str(tctx, 8)); + snprintf(fname_renamed, 140, "%s.renamed", fname); + + smb2_util_unlink(tree1, fname); + smb2_util_unlink(tree1, fname_renamed); + + lease_key1a = random(); + smb2_lease_v2_create(&io, &ls1a, false /* dir */, fname, + lease_key1a, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1a; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1a = io.out.file.handle; + h1a = &_h1a; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1a.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1a, + 0, 0, ls1a.lease_epoch); + + lease_key1b = random(); + smb2_lease_v2_create(&io, &ls1b, false /* dir */, fname, + lease_key1b, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid1b; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree1, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h1b = io.out.file.handle; + h1b = &_h1b; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls1b.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key1b, + 0, 0, ls1b.lease_epoch); + + lease_key2 = random(); + smb2_lease_v2_create(&io, &ls2, false /* dir */, fname, + lease_key2, 0, /* parent lease key */ + smb2_util_lease_state("RH"), 0 /* lease epoch */); + io.in.durable_open = false; + io.in.durable_open_v2 = true; + io.in.persistent_open = false; + io.in.create_guid = create_guid2; + io.in.timeout = UINT32_MAX; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + _h2 = io.out.file.handle; + h2 = &_h2; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_VAL(io.out.durable_open, false); + CHECK_VAL(io.out.durable_open_v2, true); + CHECK_VAL(io.out.persistent_open, false); + CHECK_VAL(io.out.timeout, 300*1000); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, lease_key2, + 0, 0, ls2.lease_epoch); + + CHECK_NO_BREAK(tctx); + + /* disconnect, reconnect and then do durable reopen */ + TALLOC_FREE(tree2); + + CHECK_NO_BREAK(tctx); + + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = *h1a; + sinfo.rename_information.in.overwrite = 0; + sinfo.rename_information.in.root_fid = 0; + sinfo.rename_information.in.new_name = fname_renamed; + status = smb2_setinfo_file(tree1, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + ls1b.lease_epoch += 1; + CHECK_BREAK_INFO_V2(tree1->session->transport, + "RH", "R", lease_key1b, ls1b.lease_epoch); + torture_reset_lease_break_info(tctx, &lease_break_info); + CHECK_NO_BREAK(tctx); + + if (!torture_smb2_connection_ext(tctx, 0, &options2, &tree2)) { + torture_warning(tctx, "couldn't reconnect, bailing\n"); + ret = false; + goto done; + } + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + + ZERO_STRUCT(io); + io.in.fname = fname; + io.in.durable_open_v2 = false; + io.in.durable_handle_v2 = h2; + io.in.create_guid = create_guid2; + io.in.lease_request_v2 = &ls2; + io.in.oplock_level = SMB2_OPLOCK_LEVEL_LEASE; + + status = smb2_create(tree2, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND); + h2 = NULL; + + status = smb2_util_close(tree1, *h1a); + CHECK_STATUS(status, NT_STATUS_OK); + h1a = NULL; + + status = smb2_util_close(tree1, *h1b); + CHECK_STATUS(status, NT_STATUS_OK); + h1b = NULL; + + CHECK_NO_BREAK(tctx); + +done: + if (tree1 != NULL) { + smb2_keepalive(tree1->session->transport); + } + if (tree2 != NULL) { + smb2_keepalive(tree2->session->transport); + } + if (tree1 != NULL && h1a != NULL) { + smb2_util_close(tree1, *h1a); + } + if (tree1 != NULL && h1b != NULL) { + smb2_util_close(tree1, *h1b); + } + if (tree2 != NULL && h2 != NULL) { + smb2_util_close(tree2, *h2); + } + + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + smb2_util_unlink(tree1, fname_renamed); + smb2_deltree(tree1, __func__); + + TALLOC_FREE(tree1); + } + + TALLOC_FREE(tree2); + + talloc_free(mem_ctx); + + return ret; +} + /** * Test durable request / reconnect with AppInstanceId */ @@ -3277,6 +5119,15 @@ struct torture_suite *torture_smb2_durable_v2_open_init(TALLOC_CTX *ctx) torture_suite_add_1smb2_test(suite, "statRH-and-lease", test_durable_v2_open_statRH_and_lease); torture_suite_add_1smb2_test(suite, "two-same-lease", test_durable_v2_open_two_same_lease); torture_suite_add_1smb2_test(suite, "two-different-lease", test_durable_v2_open_two_different_leases); + torture_suite_add_2smb2_test(suite, "keep-disconnected-rh-with-stat-open", test_durable_v2_open_keep_disconnected_rh_with_stat_open); + torture_suite_add_2smb2_test(suite, "keep-disconnected-rh-with-rh-open", test_durable_v2_open_keep_disconnected_rh_with_rh_open); + torture_suite_add_2smb2_test(suite, "keep-disconnected-rh-with-rwh-open", test_durable_v2_open_keep_disconnected_rh_with_rwh_open); + torture_suite_add_2smb2_test(suite, "keep-disconnected-rwh-with-stat-open", test_durable_v2_open_keep_disconnected_rwh_with_stat_open); + torture_suite_add_2smb2_test(suite, "purge-disconnected-rwh-with-rwh-open", test_durable_v2_open_purge_disconnected_rwh_with_rwh_open); + torture_suite_add_2smb2_test(suite, "purge-disconnected-rwh-with-rh-open", test_durable_v2_open_purge_disconnected_rwh_with_rh_open); + torture_suite_add_2smb2_test(suite, "purge-disconnected-rh-with-share-none-open", test_durable_v2_open_purge_disconnected_rh_with_share_none_open); + torture_suite_add_2smb2_test(suite, "purge-disconnected-rh-with-write", test_durable_v2_open_purge_disconnected_rh_with_write); + torture_suite_add_2smb2_test(suite, "purge-disconnected-rh-with-rename", test_durable_v2_open_purge_disconnected_rh_with_rename); torture_suite_add_2smb2_test(suite, "app-instance", test_durable_v2_open_app_instance); torture_suite_add_1smb2_test(suite, "persistent-open-oplock", test_persistent_open_oplock); torture_suite_add_1smb2_test(suite, "persistent-open-lease", test_persistent_open_lease); -- 2.34.1 From 061635ee55101b0bbfdc5b837d65008d6978e3e7 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 29 Aug 2024 20:20:23 +0200 Subject: [PATCH 08/10] s3:smbd: let durable_reconnect_fn already check for a disconnected handle with the correct file_id We'll soon allow more than one disconnected durable handle, so we need to find the correct one instead of assuming only a single one. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme (cherry picked from commit 2869bd1a507e7376f0bb0ec68ed4e045b043cfdb) --- source3/smbd/durable.c | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c index 2325e69cccc6..bd0c9f58e240 100644 --- a/source3/smbd/durable.c +++ b/source3/smbd/durable.c @@ -506,19 +506,33 @@ static bool vfs_default_durable_reconnect_check_stat( return true; } +struct durable_reconnect_state { + struct smbXsrv_open *op; + struct share_mode_entry *e; +}; + static bool durable_reconnect_fn( struct share_mode_entry *e, bool *modified, void *private_data) { - struct share_mode_entry *dst_e = private_data; + struct durable_reconnect_state *state = private_data; + uint64_t id = state->op->global->open_persistent_id; + + if (e->share_file_id != id) { + return false; /* Look at potential other entries */ + } - if (dst_e->pid.pid != 0) { + if (!server_id_is_disconnected(&e->pid)) { + return false; /* Look at potential other entries */ + } + + if (state->e->share_file_id == id) { DBG_INFO("Found more than one entry, invalidating previous\n"); - dst_e->pid.pid = 0; + *state->e = (struct share_mode_entry) { .pid = { .pid = 0, }}; return true; /* end the loop through share mode entries */ } - *dst_e = *e; + *state->e = *e; return false; /* Look at potential other entries */ } @@ -533,7 +547,8 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, const struct loadparm_substitution *lp_sub = loadparm_s3_global_substitution(); struct share_mode_lock *lck; - struct share_mode_entry e; + struct share_mode_entry e = { .pid = { .pid = 0, }}; + struct durable_reconnect_state rstate = { .op = op, .e = &e, }; struct files_struct *fsp = NULL; NTSTATUS status; bool ok; @@ -626,9 +641,7 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, return NT_STATUS_OBJECT_NAME_NOT_FOUND; } - e = (struct share_mode_entry) { .pid.pid = 0 }; - - ok = share_mode_forall_entries(lck, durable_reconnect_fn, &e); + ok = share_mode_forall_entries(lck, durable_reconnect_fn, &rstate); if (!ok) { DBG_WARNING("share_mode_forall_entries failed\n"); status = NT_STATUS_INTERNAL_DB_ERROR; -- 2.34.1 From b5981610357cdfeead4358654292d8687a82f019 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 29 Aug 2024 18:43:14 +0200 Subject: [PATCH 09/10] s3:smbd: allow reset_share_mode_entry() to handle more than one durable handle This means that multiple durable handles with RH leases can co-exist now... Before only the last remaining durable handle was able to pass the SMB_VFS_DURABLE_DISCONNECT() step. BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme (cherry picked from commit b1e5f5d8d2852b66ca4c858d14d367ffe228a88d) --- .../knownfail.d/smb2.durable-v2-open.bug15651 | 5 - source3/locking/share_mode_lock.c | 315 ++++++++++++++++-- 2 files changed, 293 insertions(+), 27 deletions(-) delete mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15651 diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15651 b/selftest/knownfail.d/smb2.durable-v2-open.bug15651 deleted file mode 100644 index 1702a3a65809..000000000000 --- a/selftest/knownfail.d/smb2.durable-v2-open.bug15651 +++ /dev/null @@ -1,5 +0,0 @@ -^samba3.smb2.durable-v2-open.statRH-and-lease -^samba3.smb2.durable-v2-open.two-same-lease -^samba3.smb2.durable-v2-open.two-different-lease -^samba3.smb2.durable-v2-open.keep-disconnected-rh-with-stat-open -^samba3.smb2.durable-v2-open.keep-disconnected-rwh-with-stat-open diff --git a/source3/locking/share_mode_lock.c b/source3/locking/share_mode_lock.c index 3fc7d56562a4..4bbccdcd3bd1 100644 --- a/source3/locking/share_mode_lock.c +++ b/source3/locking/share_mode_lock.c @@ -2703,16 +2703,25 @@ bool reset_share_mode_entry( struct share_mode_data *d = NULL; TDB_DATA key = locking_key(&id); struct locking_tdb_data *ltdb = NULL; - struct share_mode_entry e; + struct share_mode_entry e = { .pid.pid = 0 }; struct share_mode_entry_buf e_buf; + size_t old_idx; + size_t new_idx; + bool found; NTSTATUS status; - int cmp; bool ret = false; bool ok; + struct file_id_buf id_buf; + struct server_id_buf pid_buf1; + struct server_id_buf pid_buf2; + size_t low_idx1, low_idx2, low_num; + size_t mid_idx1, mid_idx2, mid_num; + size_t high_idx1, high_idx2, high_num; + TDB_DATA dbufs[4]; + size_t num_dbufs = 0; status = share_mode_lock_access_private_data(lck, &d); if (!NT_STATUS_IS_OK(status)) { - struct file_id_buf id_buf; /* Any error recovery possible here ? */ DBG_ERR("share_mode_lock_access_private_data() failed for " "%s - %s\n", @@ -2728,29 +2737,54 @@ bool reset_share_mode_entry( return false; } - if (ltdb->num_share_entries != 1) { - DBG_DEBUG("num_share_modes=%zu\n", ltdb->num_share_entries); - goto done; - } + DBG_DEBUG("%s - num_share_modes=%zu\n", + file_id_str_buf(id, &id_buf), + ltdb->num_share_entries); - ok = share_mode_entry_get(ltdb->share_entries, &e); - if (!ok) { - DBG_WARNING("share_mode_entry_get failed\n"); + new_idx = share_mode_entry_find( + ltdb->share_entries, + ltdb->num_share_entries, + new_pid, + new_share_file_id, + &e, + &found); + if (found) { + DBG_ERR("%s - num_share_modes=%zu " + "found NEW[%s][%"PRIu64"]\n", + file_id_str_buf(id, &id_buf), + ltdb->num_share_entries, + server_id_str_buf(new_pid, &pid_buf2), + new_share_file_id); goto done; } - cmp = share_mode_entry_cmp( - old_pid, old_share_file_id, e.pid, e.share_file_id); - if (cmp != 0) { - struct server_id_buf tmp1, tmp2; - DBG_WARNING("Expected pid=%s, file_id=%"PRIu64", " - "got pid=%s, file_id=%"PRIu64"\n", - server_id_str_buf(old_pid, &tmp1), - old_share_file_id, - server_id_str_buf(e.pid, &tmp2), - e.share_file_id); + old_idx = share_mode_entry_find( + ltdb->share_entries, + ltdb->num_share_entries, + old_pid, + old_share_file_id, + &e, + &found); + if (!found) { + DBG_WARNING("%s - num_share_modes=%zu " + "OLD[%s][%"PRIu64"] not found\n", + file_id_str_buf(id, &id_buf), + ltdb->num_share_entries, + server_id_str_buf(old_pid, &pid_buf1), + old_share_file_id); goto done; } + DBG_DEBUG("%s - num_share_modes=%zu " + "OLD[%s][%"PRIu64"] => idx=%zu " + "NEW[%s][%"PRIu64"] => idx=%zu\n", + file_id_str_buf(id, &id_buf), + ltdb->num_share_entries, + server_id_str_buf(old_pid, &pid_buf1), + old_share_file_id, + old_idx, + server_id_str_buf(new_pid, &pid_buf2), + new_share_file_id, + new_idx); e.pid = new_pid; if (new_mid != UINT64_MAX) { @@ -2764,11 +2798,248 @@ bool reset_share_mode_entry( goto done; } - ltdb->share_entries = e_buf.buf; + /* + * The logic to remove the existing + * entry and add the new one at the + * same time is a bit complex because + * we need to keep the entries sorted. + * + * The following examples should catch + * the corner cases and show that + * the {low,mid,high}_{idx1,num} are + * correctly calculated and the new + * entry is put before or after the mid + * elements... + * + * 1. + * 0 + * 1 + * 2 <- old_idx + * new_idx -> 3 + * 3 + * 4 + * + * low_idx1 = 0; + * low_idx2 = MIN(old_idx, new_idx); => 2 + * low_num = low_idx2 - low_idx1; => 2 + * + * if (new < old) => new; => no + * + * mid_idx1 = MIN(old_idx+1, new_idx); => 3 + * mid_idx2 = MAX(old_idx, new_idx); => 3 + * mid_num = mid_idx2 - mid_idx1; => 0 + * + * if (new >= old) => new; => yes + * + * high_idx1 = MAX(old_idx+1, new_idx); => 3 + * high_idx2 = num_share_entries; => 5 + * high_num = high_idx2 - high_idx1 = 2 + * + * 2. + * 0 + * 1 + * new_idx -> 2 + * 2 <- old_idx + * 3 + * 4 + * + * low_idx1 = 0; + * low_idx2 = MIN(old_idx, new_idx); => 2 + * low_num = low_idx2 - low_idx1; => 2 + * + * if (new < old) => new; => no + * + * mid_idx1 = MIN(old_idx+1, new_idx); => 2 + * mid_idx2 = MAX(old_idx, new_idx); => 2 + * mid_num = mid_idx2 - mid_idx1; => 0 + * + * if (new >= old) => new; => yes + * + * high_idx1 = MAX(old_idx+1, new_idx); => 3 + * high_idx2 = num_share_entries; => 5 + * high_num = high_idx2 - high_idx1 = 2 + * + * 3. + * 0 + * 1 <- old_idx + * 2 + * new_idx -> 3 + * 3 + * 4 + * + * low_idx1 = 0; + * low_idx2 = MIN(old_idx, new_idx); => 1 + * low_num = low_idx2 - low_idx1; => 1 + * + * if (new < old) => new; => no + * + * mid_idx1 = MIN(old_idx+1, new_idx); => 2 + * mid_idx2 = MAX(old_idx, new_idx); => 3 + * mid_num = mid_idx2 - mid_idx1; => 1 + * + * if (new >= old) => new; => yes + * + * high_idx1 = MAX(old_idx+1, new_idx); => 3 + * high_idx2 = num_share_entries; => 5 + * high_num = high_idx2 - high_idx1 = 2 + * + * 4. + * 0 + * new_idx -> 1 + * 1 + * 2 + * 3 <- old_idx + * 4 + * + * low_idx1 = 0; + * low_idx2 = MIN(old_idx, new_idx); => 1 + * low_num = low_idx2 - low_idx1; => 1 + * + * if (new < old) => new; => yes + * + * mid_idx1 = MIN(old_idx+1, new_idx); => 1 + * mid_idx2 = MAX(old_idx, new_idx); => 3 + * mid_num = mid_idx2 - mid_idx1; => 2 + * + * if (new >= old) => new; => no + * + * high_idx1 = MAX(old_idx+1, new_idx); => 4 + * high_idx2 = num_share_entries; => 5 + * high_num = high_idx2 - high_idx1 = 1 + * + * 5. + * new_idx -> 0 + * 0 + * 1 + * 2 + * 3 + * 4 <- old_idx + * + * low_idx1 = 0; + * low_idx2 = MIN(old_idx, new_idx); => 0 + * low_num = low_idx2 - low_idx1; => 0 + * + * if (new < old) => new; => yes + * + * mid_idx1 = MIN(old_idx+1, new_idx); => 0 + * mid_idx2 = MAX(old_idx, new_idx); => 4 + * mid_num = mid_idx2 - mid_idx1; => 4 + * + * if (new >= old) => new; => no + * + * high_idx1 = MAX(old_idx+1, new_idx); => 5 + * high_idx2 = num_share_entries; => 5 + * high_num = high_idx2 - high_idx1 = 0 + * + * 6. + * new_idx -> 0 + * 0 <- old_idx + * + * low_idx1 = 0; + * low_idx2 = MIN(old_idx, new_idx); => 0 + * low_num = low_idx2 - low_idx1; => 0 + * + * if (new < old) => new; => no + * + * mid_idx1 = MIN(old_idx+1, new_idx); => 0 + * mid_idx2 = MAX(old_idx, new_idx); => 0 + * mid_num = mid_idx2 - mid_idx1; => 0 + * + * if (new >= old) => new; => yes + * + * high_idx1 = MAX(old_idx+1, new_idx); => 1 + * high_idx2 = num_share_entries; => 1 + * high_num = high_idx2 - high_idx1 = 0 + * + * 7. + * 0 <- old_idx + * new_idx -> 1 + * + * low_idx1 = 0; + * low_idx2 = MIN(old_idx, new_idx); => 0 + * low_num = low_idx2 - low_idx1; => 0 + * + * if (new < old) => new; => no + * + * mid_idx1 = MIN(old_idx+1, new_idx); => 1 + * mid_idx2 = MAX(old_idx, new_idx); => 1 + * mid_num = mid_idx2 - mid_idx1; => 0 + * + * if (new >= old) => new; => yes + * + * high_idx1 = MAX(old_idx+1, new_idx); => 1 + * high_idx2 = num_share_entries; => 1 + * high_num = high_idx2 - high_idx1 = 0 + */ + low_idx1 = 0; + low_idx2 = MIN(old_idx, new_idx); + low_num = low_idx2 - low_idx1; + mid_idx1 = MIN(old_idx+1, new_idx); + mid_idx2 = MAX(old_idx, new_idx); + mid_num = mid_idx2 - mid_idx1; + high_idx1 = MAX(old_idx+1, new_idx); + high_idx2 = ltdb->num_share_entries; + high_num = high_idx2 - high_idx1; + + if (low_num != 0) { + dbufs[num_dbufs] = (TDB_DATA) { + .dptr = discard_const_p(uint8_t, ltdb->share_entries) + + low_idx1 * SHARE_MODE_ENTRY_SIZE, + .dsize = low_num * SHARE_MODE_ENTRY_SIZE, + }; + num_dbufs += 1; + } + + if (new_idx < old_idx) { + dbufs[num_dbufs] = (TDB_DATA) { + .dptr = e_buf.buf, .dsize = SHARE_MODE_ENTRY_SIZE, + }; + num_dbufs += 1; + } + + if (mid_num != 0) { + dbufs[num_dbufs] = (TDB_DATA) { + .dptr = discard_const_p(uint8_t, ltdb->share_entries) + + mid_idx1 * SHARE_MODE_ENTRY_SIZE, + .dsize = mid_num * SHARE_MODE_ENTRY_SIZE, + }; + num_dbufs += 1; + } + + if (new_idx >= old_idx) { + dbufs[num_dbufs] = (TDB_DATA) { + .dptr = e_buf.buf, .dsize = SHARE_MODE_ENTRY_SIZE, + }; + num_dbufs += 1; + } + + if (high_num != 0) { + dbufs[num_dbufs] = (TDB_DATA) { + .dptr = discard_const_p(uint8_t, ltdb->share_entries) + + high_idx1 * SHARE_MODE_ENTRY_SIZE, + .dsize = high_num * SHARE_MODE_ENTRY_SIZE, + }; + num_dbufs += 1; + } + { + size_t i; + for (i=0; ishare_entries = NULL; + ltdb->num_share_entries = 0; d->modified = true; - status = share_mode_data_ltdb_store(d, key, ltdb, NULL, 0); + status = share_mode_data_ltdb_store(d, key, ltdb, dbufs, num_dbufs); if (!NT_STATUS_IS_OK(status)) { DBG_ERR("share_mode_data_ltdb_store failed: %s\n", nt_errstr(status)); -- 2.34.1 From 90b86ed2d6eb393e6e9070df27b1fe8a40989545 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 30 Aug 2024 14:16:12 +0200 Subject: [PATCH 10/10] s3:smbd: avoid false positives for got_oplock and have_other_lease in delay_for_oplock_fn stat opens should not cause a oplock/lease downgrade if they don't have a lease attached to itself. Note that opens broken to NONE still count if they are non-stat opens... BUG: https://bugzilla.samba.org/show_bug.cgi?id=15649 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15651 Signed-off-by: Stefan Metzmacher Reviewed-by: Ralph Boehme Autobuild-User(master): Stefan Metzmacher Autobuild-Date(master): Thu Oct 10 13:59:18 UTC 2024 on atb-devel-224 (cherry picked from commit dd5b9e08c7a98c54b62d3b097c75faa09cd17da7) --- selftest/knownfail | 1 - .../knownfail.d/smb2.durable-v2-open.bug15649 | 2 -- source3/smbd/open.c | 26 ++++++++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) delete mode 100644 selftest/knownfail.d/smb2.durable-v2-open.bug15649 diff --git a/selftest/knownfail b/selftest/knownfail index 03f8b4669944..31e70a1a9d34 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -218,7 +218,6 @@ ^samba3.smb2.compound.interim2 # wrong return code (STATUS_CANCELLED) ^samba3.smb2.compound.aio.interim2 # wrong return code (STATUS_CANCELLED) ^samba3.smb2.lock.*replay_broken_windows # This tests the windows behaviour -^samba3.smb2.lease.statopen3 ^samba3.smb2.lease.unlink # we currently do not downgrade RH lease to R after unlink ^samba4.smb2.ioctl.compress_notsup.*\(ad_dc_ntvfs\) ^samba3.raw.session.*reauth2 # maybe fix this? diff --git a/selftest/knownfail.d/smb2.durable-v2-open.bug15649 b/selftest/knownfail.d/smb2.durable-v2-open.bug15649 deleted file mode 100644 index 748b6c3150ec..000000000000 --- a/selftest/knownfail.d/smb2.durable-v2-open.bug15649 +++ /dev/null @@ -1,2 +0,0 @@ -^samba3.smb2.durable-v2-open.stat-and-lease -^samba3.smb2.durable-v2-open.nonstat-and-lease diff --git a/source3/smbd/open.c b/source3/smbd/open.c index bc4dcd67e02c..22a39ac35ef6 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -2464,7 +2464,7 @@ struct delay_for_oplock_state { bool first_open_attempt; bool got_handle_lease; bool got_oplock; - bool have_other_lease; + bool disallow_write_lease; uint32_t total_lease_types; bool delay; struct blocker_debug_state *blocker_debug_state; @@ -2572,15 +2572,27 @@ static bool delay_for_oplock_fn( } if (!state->got_oplock && + (e->op_type != NO_OPLOCK) && (e->op_type != LEASE_OPLOCK) && !share_entry_stale_pid(e)) { state->got_oplock = true; } - if (!state->have_other_lease && + /* + * Two things prevent a write lease + * to be granted: + * + * 1. Any oplock or lease (even broken to NONE) + * 2. An open with an access mask other than + * FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES + * or SYNCHRONIZE_ACCESS + */ + if (!state->disallow_write_lease && + (e->op_type != NO_OPLOCK || !is_oplock_stat_open(e->access_mask)) && !is_same_lease(fsp, e, lease) && - !share_entry_stale_pid(e)) { - state->have_other_lease = true; + !share_entry_stale_pid(e)) + { + state->disallow_write_lease = true; } if (e_is_lease && is_lease_stat_open(fsp->access_mask)) { @@ -2814,9 +2826,11 @@ grant: granted &= ~SMB2_LEASE_READ; } - if (state.have_other_lease) { + if (state.disallow_write_lease) { /* - * Can grant only one writer + * Can grant only a write lease + * if there are no other leases + * and no other non-stat opens. */ granted &= ~SMB2_LEASE_WRITE; } -- 2.34.1