From: Ralph Boehme Date: Thu, 25 Sep 2025 14:49:22 +0000 (+0200) Subject: smbtorture: add test "smb2.persistent-open.reconnect-contended-two" X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5a18f30534f878c4ce7952ec6ebeb7449b0813d8;p=thirdparty%2Fsamba.git smbtorture: add test "smb2.persistent-open.reconnect-contended-two" Same as smb2.persistent-open.reconnect-contended, but with two Persistent Handles sharing the same lease key. Note that some tests are failing against Windows as Windows is strangely failing to reconnect the second handle. This is being discussed with Microsoft: PR: https://lists.samba.org/archive/cifs-protocol/2025-September/004583.html Signed-off-by: Ralph Boehme Reviewed-by: Anoop C S --- diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index b68858ed0c6..50b575a8602 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -6756,6 +6756,605 @@ static bool test_persistent_reconnect_contended_win_broken( tctx, _tree, contend_results_fo_win_broken); } +#define RO (SEC_RIGHTS_FILE_READ) +#define RW (SEC_RIGHTS_FILE_READ|SEC_RIGHTS_FILE_WRITE) +#define RWD (SEC_RIGHTS_FILE_READ|SEC_RIGHTS_FILE_WRITE|SEC_STD_DELETE) + +static struct persistent_reconnect_contend_results contend_results_two_fo[] = { + /* L, SM, brlck, RL, BL, DA, DO, status, async, NL, NG */ + {"", "RWD", false, "", "", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", false, "RH", "RH", RO, false, NT_STATUS_OK, false, "RWH", "RH"}, + {"RW", "RWD", false, "RW", "RW", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", false, "RWH", "RH", RO, false, NT_STATUS_OK, true, "RWH", "RH"}, + {"", "RWD", false, "", "", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", false, "RH", "", RO, true, NT_STATUS_OK, false, "RWH", "RH"}, + {"RW", "RWD", false, "RW", "RW", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", false, "RWH", "", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, true, "RWH", ""}, + {"", "RWD", false, "", "", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", false, "RH", "RH", RW, false, NT_STATUS_OK, false, "RWH", "RH"}, + {"RW", "RWD", false, "RW", "RW", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", false, "RWH", "RH", RW, false, NT_STATUS_OK, true, "RWH", "RH"}, + {"", "RWD", false, "", "", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", false, "RH", "", RW, true, NT_STATUS_OK, false, "RWH", "RH"}, + {"RW", "RWD", false, "RW", "RW", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", false, "RWH", "", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, true, "RWH", ""}, + {"", "RWD", false, "", "", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", false, "RH", "RH", RWD, false, NT_STATUS_OK, false, "RWH", "RH"}, + {"RW", "RWD", false, "RW", "RW", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", false, "RWH", "RH", RWD, false, NT_STATUS_OK, true, "RWH", "RH"}, + {"", "RWD", false, "", "", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", false, "RH", "", RWD, true, NT_STATUS_OK, false, "RWH", "RH"}, + {"RW", "RWD", false, "RW", "RW", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", false, "RWH", "", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, true, "RWH", ""}, + {"", "RWD", true, "", "", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "", "", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", true, "", "", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RW", "RWD", true, "RW", "RW", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", true, "RWH", "RH", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, true, "RWH", ""}, + {"", "RWD", true, "", "", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "", "", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", true, "", "", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RW", "RWD", true, "RW", "RW", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", true, "RWH", "", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, true, "RWH", ""}, + {"", "RWD", true, "", "", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "", "", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", true, "", "", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RW", "RWD", true, "RW", "RW", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", true, "RWH", "RH", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, true, "RWH", ""}, + {"", "RWD", true, "", "", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "", "", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", true, "", "", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RW", "RWD", true, "RW", "RW", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", true, "RWH", "", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, true, "RWH", ""}, + {"", "RWD", true, "", "", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "", "", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", true, "", "", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RW", "RWD", true, "RW", "RW", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", true, "RWH", "RH", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, true, "RWH", ""}, + {"", "RWD", true, "", "", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "", "", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RH", "RWD", true, "", "", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RW", "RWD", true, "RW", "RW", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"RWH", "RWD", true, "RWH", "", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, true, "RWH", ""}, + + {NULL, NULL, false, NULL, NULL, 0, false, NT_STATUS_INTERNAL_ERROR, false, NULL, NULL} +}; + +/* + * On a share with SMB2_SHARE_CAP_SCALEOUT the server doesn't grant W or H + * leases. + */ +static struct persistent_reconnect_contend_results contend_results_two_so[] = { + {"", "RWD", false, "", "", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", false, "", "", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", false, "", "", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", false, "", "", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", false, "", "", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", false, "", "", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", false, "R", "R", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", true, "", "", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "R", "R", RO, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", true, "", "", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "R", "R", RO, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", true, "", "", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "R", "R", RW, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", true, "", "", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "R", "R", RW, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"", "RWD", true, "", "", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "R", "R", RWD, false, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + {"R", "RWD", true, "R", "R", RWD, true, NT_STATUS_FILE_NOT_AVAILABLE, false, "RWH", ""}, + + {NULL, NULL, false, NULL, NULL, 0, false, NT_STATUS_INTERNAL_ERROR, false, NULL, NULL} +}; + +#undef RO +#undef RW +#undef RWD + +static bool test_persistent_reconnect_contended_do_two( + struct torture_context *tctx, + struct smb2_tree *_tree, + struct persistent_reconnect_contend_results *table, + int testnum) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smbcli_options options1 = _tree->session->transport->options; + struct smbcli_options options2 = _tree->session->transport->options; + struct smb2_tree *tree1 = NULL; + struct smb2_tree *tree2 = NULL; + struct smb2_request *req = NULL; + struct smb2_create io; + struct smb2_create io2; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_lease *ls = NULL; + struct smb2_handle h1 = {}; + struct smb2_handle h2 = {}; + struct smb2_handle h3 = {}; + struct smb2_handle rh = {}; + struct smb2_handle rh2 = {}; + struct smb2_lock lck = {0}; + struct smb2_lock_element el[1]; + uint64_t previous_session_id; + uint64_t lease1 = 1; + uint64_t lease2 = 2; + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + char *fname = NULL; + int rc; + bool expect_break = false; + bool expect_acquire = false; + bool expect_block = false; + int expect_epoch = 0; + NTSTATUS status; + bool ret = true; + + torture_reset_lease_break_info(tctx, &lease_break_info); + + fname = talloc_asprintf(mem_ctx, "lease_break-%ld.dat", random()); + torture_assert_not_null_goto(tctx, fname, ret, done, + "talloc_asprintf failed\n"); + + torture_comment(tctx, + "%2d: " + "Lease [%-3s] " + "Sharemode [%-3s] " + "Brl [%s]. " + "Contend access [0x%0"PRIX32"] " + "Overwrite [%s] " + "Result [%18s]. " + "Reconnect [%-3s] " + "Break [%-3s]\n", + testnum, + table->held_lease, + table->share_mode, + table->do_brlck ? "yes" : "no ", + table->desired_access, + table->overwrite ? "yes" : "no ", + nt_errstr(table->status) + 10, + table->reconnect_lease, + table->broken_to_lease); + + options1.client_guid = GUID_random(); + options2.client_guid = GUID_random(); + + ret = torture_smb2_connection_ext(tctx, 0, &options1, &tree1); + torture_assert_goto(tctx, ret, ret, done, "torture_smb2_connection_ext failed\n"); + previous_session_id = smb2cli_session_current_id(tree1->session->smbXcli); + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + if (smb2_util_lease_state(table->held_lease) == SMB2_LEASE_READ && + smb2_util_lease_state(table->reconnect_lease) == SMB2_LEASE_READ) + { + expect_acquire = true; + } + + if (smb2_util_lease_state(table->reconnect_lease) != + smb2_util_lease_state(table->broken_to_lease)) + { + expect_break = true; + } + + if (NT_STATUS_EQUAL(table->status, NT_STATUS_FILE_NOT_AVAILABLE)) { + expect_block = true; + } + + status = torture_setup_simple_file(tctx, tree1, fname); + torture_assert_ntstatus_ok(tctx, status, "setup file failed\n"); + + /* + * Get persistent open + */ + if (table->held_lease[0] != '\0') { + ls = &ls1; + } else { + ls = NULL; + } + smb2_lease_v2_create_share(&io, + ls, + false, + fname, + smb2_util_share_access(table->share_mode), + lease1, + NULL, + smb2_util_lease_state(table->held_lease), + 0); + io.in.durable_open_v2 = true; + io.in.persistent_open = true; + io.in.create_guid = create_guid1; + status = smb2_create(tree1, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = io.out.file.handle; + rh = h1; + + torture_assert_goto( + tctx, io.out.persistent_open == true, + ret, done, "Persistent open not granted\n"); + + if (io.in.lease_request_v2 != NULL) { + torture_assert_int_equal_goto( + tctx, + io.out.oplock_level, + SMB2_OPLOCK_LEVEL_LEASE, + ret, done, + "Bad lease level\n"); + torture_assert_int_equal_goto( + tctx, + io.out.lease_response_v2.lease_state, + smb2_util_lease_state(table->held_lease), + ret, done, + "Bad lease level\n"); + } + + /* + * Get a second persistent open + */ + io.in.create_guid = create_guid2; + + status = smb2_create(tree1, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h2 = io.out.file.handle; + rh2 = h2; + + torture_assert_goto( + tctx, io.out.persistent_open == true, + ret, done, "Persistent open not granted\n"); + + if (io.in.lease_request_v2 != NULL) { + torture_assert_int_equal_goto( + tctx, + io.out.oplock_level, + SMB2_OPLOCK_LEVEL_LEASE, + ret, done, + "Bad lease level\n"); + torture_assert_int_equal_goto( + tctx, + io.out.lease_response_v2.lease_state, + smb2_util_lease_state(table->held_lease), + ret, done, + "Bad lease level\n"); + } + + if (table->do_brlck) { + ZERO_ARRAY(el); + ZERO_STRUCT(lck); + el[0].offset = 0; + el[0].length = 1; + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = el; + lck.in.lock_count = 1; + lck.in.file.handle = h1; + + status = smb2_lock(tree1, &lck); + torture_assert_ntstatus_equal_goto( + tctx, status, NT_STATUS_OK, + ret, done, "smb2_lock failed\n"); + } + + /* + * Now disconnect + */ + TALLOC_FREE(tree1); + ZERO_STRUCT(h1); + ZERO_STRUCT(h2); + smb_msleep(100); + + /* + * Contend durable open with second open from second client + */ + + ret = torture_smb2_connection_ext(tctx, 0, &options2, &tree2); + torture_assert_goto(tctx, ret, ret, done, + "torture_smb2_connection_ext failed\n"); + + smb2_lease_v2_create(&io2, &ls2, false, fname, + lease2, NULL, + smb2_util_lease_state(table->new_lease), 0); + io2.in.desired_access = table->desired_access; + if (table->overwrite) { + io2.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + } + + req = smb2_create_send(tree2, &io2); + torture_assert_not_null_goto(tctx, req, ret, done, + "smb2_create_send failed\n"); + + while (!req->cancel.can_cancel && + (req->state < SMB2_REQUEST_DONE)) + { + rc = tevent_loop_once(req->transport->ev); + torture_assert_goto(tctx, rc == 0, ret, done, + "tevent_loop_once failed\n"); + } + + if (table->async) { + torture_assert_goto(tctx, req->state < SMB2_REQUEST_DONE, + ret, done, + "Expected async interim response\n"); + } else { + torture_assert_goto(tctx, req->state == SMB2_REQUEST_DONE, + ret, done, + "Expected response\n"); + } + + /* + * Now reconnect first client and the persistent handle + */ + ret = torture_smb2_connection_ext(tctx, previous_session_id, + &options1, &tree1); + torture_assert_goto(tctx, ret, ret, done, + "torture_smb2_connection_ext failed\n"); + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + if (table->held_lease[0] != '\0') { + ls = &ls1; + } else { + ls = NULL; + } + smb2_lease_v2_create_share(&io, + ls, + false, + fname, + smb2_util_share_access(table->share_mode), + lease1, + NULL, + smb2_util_lease_state(table->held_lease), + 128); + io.in.durable_handle_v2 = &rh; + io.in.create_guid = create_guid1; + io.in.persistent_open = true; + status = smb2_create(tree1, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = io.out.file.handle; + + if (io.in.lease_request_v2 != NULL) { + torture_assert_int_equal_goto( + tctx, + io.out.oplock_level, + SMB2_OPLOCK_LEVEL_LEASE, + ret, done, + "Bad lease level\n"); + torture_assert_int_equal_goto( + tctx, + io.out.lease_response_v2.lease_state, + smb2_util_lease_state(table->reconnect_lease), + ret, done, + "Bad lease level\n"); + + if (expect_break) { + expect_epoch = 1; + } else if (expect_acquire) { + expect_epoch = 129; + } else if (expect_block) { + expect_epoch = 128; + } else { + expect_epoch = 1; + } + + torture_assert_int_equal_goto( + tctx, + io.out.lease_response_v2.lease_epoch, + expect_epoch, + ret, + done, + "Bad lease epoch\n"); + + if (expect_break) { + CHECK_BREAK_INFO_V2(tree1->session->transport, + table->held_lease, + table->broken_to_lease, + lease1, + 2); + } else { + CHECK_NO_BREAK(tctx); + } + } + + torture_reset_lease_break_info(tctx, &lease_break_info); + + io.in.durable_handle_v2 = &rh2; + io.in.create_guid = create_guid2; + ls1.lease_epoch = expect_epoch + 1; + ls1.lease_state = lease_break_info.lease_break.new_lease_state; + + status = smb2_create(tree1, mem_ctx, &io); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h2 = io.out.file.handle; + + if (io.in.lease_request_v2 != NULL) { + torture_assert_int_equal_goto( + tctx, + io.out.oplock_level, + SMB2_OPLOCK_LEVEL_LEASE, + ret, done, + "Bad lease level\n"); + torture_assert_int_equal_goto( + tctx, + io.out.lease_response_v2.lease_state, + smb2_util_lease_state(table->broken_to_lease), + ret, done, + "Bad lease level\n"); + + if (expect_break) { + expect_epoch = 2; + } else if (expect_acquire) { + expect_epoch = 129; + } else if (expect_block) { + expect_epoch = 128; + } else { + expect_epoch = 1; + } + + torture_assert_int_equal_goto( + tctx, + io.out.lease_response_v2.lease_epoch, + expect_epoch, + ret, + done, + "Bad lease epoch\n"); + } + CHECK_NO_BREAK(tctx); + + status = smb2_create_recv(req, tctx, &io2); + if (!NT_STATUS_IS_OK(table->status)) { + torture_assert_ntstatus_equal_goto( + tctx, status, table->status, + ret, done, "smb2_create failed\n"); + } else { + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h3 = io2.out.file.handle; + + if (io2.in.lease_request_v2 != NULL) { + torture_assert_int_equal_goto( + tctx, + io2.out.oplock_level, + SMB2_OPLOCK_LEVEL_LEASE, + ret, done, + "Bad lease level\n"); + torture_assert_int_equal_goto( + tctx, + io2.out.lease_response_v2.lease_state, + smb2_util_lease_state(table->new_granted), + ret, done, + "Bad lease level\n"); + } + + status = smb2_util_close(tree2, h3); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_close failed\n"); + ZERO_STRUCT(h3); + } + + if ((smb2_util_share_access(table->share_mode) & + NTCREATEX_SHARE_ACCESS_READ) && + table->do_brlck) + { + status = torture_smb2_testfile_access( + tree2, fname, &h3, + SEC_RIGHTS_FILE_READ); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + + ZERO_ARRAY(el); + ZERO_STRUCT(lck); + el[0].offset = 0; + el[0].length = 1; + el[0].flags = SMB2_LOCK_FLAG_SHARED | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + lck.in.locks = el; + lck.in.lock_count = 1; + lck.in.file.handle = h3; + + status = smb2_lock(tree2, &lck); + torture_assert_ntstatus_equal_goto( + tctx, status, NT_STATUS_LOCK_NOT_GRANTED, + ret, done, "smb2_lock failed\n"); + + status = smb2_util_close(tree2, h3); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_close failed\n"); + ZERO_STRUCT(h3); + } + + status = smb2_util_close(tree1, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_close failed\n"); + ZERO_STRUCT(h1); + + status = smb2_util_close(tree1, h2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_close failed\n"); + ZERO_STRUCT(h2); + +done: + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree1, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree1, h2); + } + if (tree1 != NULL) { + smb2_util_unlink(tree1, fname); + } else if (tree2 != NULL) { + smb2_util_unlink(tree2, fname); + } + TALLOC_FREE(tree1); + TALLOC_FREE(tree2); + TALLOC_FREE(mem_ctx); + return ret; +} + +static bool test_persistent_reconnect_contended_two_do_table( + struct torture_context *tctx, + struct smb2_tree *_tree, + struct persistent_reconnect_contend_results *table) +{ + int i; + bool single; + bool ok; + + i = torture_setting_int(tctx, "subtest", 0); + single = torture_setting_bool(tctx, "single", false); + + for (; table[i].held_lease != NULL; i++) { + ok = test_persistent_reconnect_contended_do_two( + tctx, _tree, &table[i], i); + if (!ok) { + return false; + } + if (single) { + return true; + } + } + return true; +} + +static bool test_persistent_reconnect_contended_two( + struct torture_context *tctx, + struct smb2_tree *_tree) +{ + struct persistent_reconnect_contend_results *table = NULL; + uint32_t caps; + uint32_t tcon_caps; + + caps = smb2cli_conn_server_capabilities(_tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + tcon_caps = smb2cli_tcon_capabilities(_tree->smbXcli); + if (!(tcon_caps & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY)) { + torture_skip(tctx, "PH are not supported\n"); + } + + if (tcon_caps & SMB2_SHARE_CAP_SCALEOUT) { + table = contend_results_two_so; + } else { + table = contend_results_two_fo; + } + + return test_persistent_reconnect_contended_two_do_table( + tctx, _tree, table); +} + struct torture_suite *torture_smb2_persistent_open_init(TALLOC_CTX *ctx) { struct torture_suite *suite = @@ -6769,6 +7368,7 @@ struct torture_suite *torture_smb2_persistent_open_init(TALLOC_CTX *ctx) torture_suite_add_1smb2_test(suite, "leaselevels_so", test_persistent_leaselevels_so); torture_suite_add_1smb2_test(suite, "reconnect-contended", test_persistent_reconnect_contended); torture_suite_add_1smb2_test(suite, "reconnect-contended-win-broken", test_persistent_reconnect_contended_win_broken); + torture_suite_add_1smb2_test(suite, "reconnect-contended-two", test_persistent_reconnect_contended_two); return suite; }