diff --git a/SOURCES/samba-4.19-redhat.patch b/SOURCES/samba-4.19-redhat.patch index 8f747fb..021c3dc 100644 --- a/SOURCES/samba-4.19-redhat.patch +++ b/SOURCES/samba-4.19-redhat.patch @@ -1,7 +1,7 @@ From 3c29fc78029e1274f931e171c9e04c19ad0182c1 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Thu, 17 Aug 2023 01:05:54 +0300 -Subject: [PATCH 01/63] gp: Support more global trust directories +Subject: [PATCH 01/96] gp: Support more global trust directories In addition to the SUSE global trust directory, add support for RHEL and Debian-based distributions (including Ubuntu). @@ -60,13 +60,13 @@ index 312c8ddf467..1b90ab46e90 100644 # Symlink the certs to global trust dir dst = os.path.join(global_trust_dir, os.path.basename(src)) -- -2.51.0 +2.52.0 From 063606e8ec83a58972df47eb561ab267f8937ba4 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Thu, 17 Aug 2023 01:09:28 +0300 -Subject: [PATCH 02/63] gp: Support update-ca-trust helper +Subject: [PATCH 02/96] gp: Support update-ca-trust helper This is used on RHEL/Fedora instead of update-ca-certificates. They behave similarly so it's enough to change the command name. @@ -104,13 +104,13 @@ index 1b90ab46e90..cefdafa21b2 100644 Popen([update]).wait() # Setup Certificate Auto Enrollment -- -2.51.0 +2.52.0 From 3b548bf280ca59ef12a7af10a9131813067a850a Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 11 Aug 2023 18:46:42 +0300 -Subject: [PATCH 03/63] gp: Change root cert extension suffix +Subject: [PATCH 03/96] gp: Change root cert extension suffix On Ubuntu, certificates must end in '.crt' in order to be considered by the `update-ca-certificates` helper. @@ -138,13 +138,13 @@ index cefdafa21b2..c562722906b 100644 w.write(cert) root_certs.append(dest) -- -2.51.0 +2.52.0 From 7592ed5032836dc43f657f66607a0a4661edcdb4 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 18 Aug 2023 17:06:43 +0300 -Subject: [PATCH 04/63] gp: Test with binary content for certificate data +Subject: [PATCH 04/96] gp: Test with binary content for certificate data This fails all GPO-related tests that call `gpupdate --rsop`. @@ -216,13 +216,13 @@ index 00000000000..0aad59607c2 +^samba.tests.gpo.samba.tests.gpo.GPOTests.test_advanced_gp_cert_auto_enroll_ext +^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext -- -2.51.0 +2.52.0 From 7f7b235bda9e85c5ea330e52e734d1113a884571 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Wed, 16 Aug 2023 12:20:11 +0300 -Subject: [PATCH 05/63] gp: Convert CA certificates to base64 +Subject: [PATCH 05/96] gp: Convert CA certificates to base64 I don't know whether this applies universally, but in our case the contents of `es['cACertificate'][0]` are binary, so cleanly converting @@ -289,13 +289,13 @@ index 0aad59607c2..00000000000 -^samba.tests.gpo.samba.tests.gpo.GPOTests.test_advanced_gp_cert_auto_enroll_ext -^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext -- -2.51.0 +2.52.0 From 49cc74015a603e80048a38fe635cd1ac28938ee4 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 18 Aug 2023 17:16:23 +0300 -Subject: [PATCH 06/63] gp: Test adding new cert templates enforces changes +Subject: [PATCH 06/96] gp: Test adding new cert templates enforces changes Ensure that cepces-submit reporting additional templates and re-applying will enforce the updated policy. @@ -422,13 +422,13 @@ index 00000000000..4edc1dce730 +^samba.tests.gpo.samba.tests.gpo.GPOTests.test_advanced_gp_cert_auto_enroll_ext +^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext -- -2.51.0 +2.52.0 From 4c0906bd79f030e591701234bc54bc749a42d686 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Wed, 16 Aug 2023 12:37:17 +0300 -Subject: [PATCH 07/63] gp: Template changes should invalidate cache +Subject: [PATCH 07/96] gp: Template changes should invalidate cache If certificate templates are added or removed, the autoenroll extension should react to this and reapply the policy. Previously this wasn't @@ -487,13 +487,13 @@ index 4edc1dce730..00000000000 -^samba.tests.gpo.samba.tests.gpo.GPOTests.test_advanced_gp_cert_auto_enroll_ext -^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext -- -2.51.0 +2.52.0 From e61f30dc2518d5a1c239f090baea4a309307f3f8 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 18 Aug 2023 17:26:59 +0300 -Subject: [PATCH 08/63] gp: Test disabled enrollment unapplies policy +Subject: [PATCH 08/96] gp: Test disabled enrollment unapplies policy For this we need to stage a Registry.pol file with certificate autoenrollment enabled, but with checkboxes unticked. @@ -588,13 +588,13 @@ index 00000000000..83bc9f0ac1f @@ -0,0 +1 @@ +^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext -- -2.51.0 +2.52.0 From 7757b9b48546d71e19798d1260da97780caa99c3 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Wed, 16 Aug 2023 12:33:59 +0300 -Subject: [PATCH 09/63] gp: Send list of keys instead of dict to remove +Subject: [PATCH 09/96] gp: Send list of keys instead of dict to remove `cache_get_all_attribute_values` returns a dict whereas we need to pass a list of keys to `remove`. These will be interpolated in the gpdb search. @@ -634,13 +634,13 @@ index 83bc9f0ac1f..00000000000 @@ -1 +0,0 @@ -^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext -- -2.51.0 +2.52.0 From 4e9b2e6409c5764ec0e66cc6c90b08e70f702e7c Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 9 Jan 2024 08:50:01 +0100 -Subject: [PATCH 10/63] python:gp: Print a nice message if cepces-submit can't +Subject: [PATCH 10/96] python:gp: Print a nice message if cepces-submit can't be found BUG: https://bugzilla.samba.org/show_bug.cgi?id=15552 @@ -691,13 +691,13 @@ index 64c35782ae8..08d1a7348cd 100644 def getca(ca, url, trust_dir): -- -2.51.0 +2.52.0 From fb3aefff51c02cf8ba3f8dfeb7d3f971e8d4902a Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Mon, 8 Jan 2024 18:05:08 +0200 -Subject: [PATCH 11/63] gpo: Test certificate policy without NDES +Subject: [PATCH 11/96] gpo: Test certificate policy without NDES As of 8231eaf856b, the NDES feature is no longer required on Windows, as cert auto-enroll can use the certificate from the LDAP request. @@ -895,13 +895,13 @@ index 00000000000..f1e590bc7d8 @@ -0,0 +1 @@ +^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext_without_ndes -- -2.51.0 +2.52.0 From 1a9af36177c7491687c75df151474bb10285f00e Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Thu, 18 Jan 2024 20:23:24 +0200 -Subject: [PATCH 12/63] gpo: Decode base64 root cert before importing +Subject: [PATCH 12/96] gpo: Decode base64 root cert before importing The reasoning behind this is described in the previous commit message, but essentially this should either be wrapped in certificate blocks and @@ -948,13 +948,13 @@ index f1e590bc7d8..00000000000 @@ -1 +0,0 @@ -^samba.tests.gpo.samba.tests.gpo.GPOTests.test_gp_cert_auto_enroll_ext_without_ndes -- -2.51.0 +2.52.0 From f5fc88f9ae255f4dc135580f0fa4a02f5addc390 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 19 Jan 2024 11:36:19 +0200 -Subject: [PATCH 13/63] gpo: Do not get templates list on first run +Subject: [PATCH 13/96] gpo: Do not get templates list on first run This is a visual fix and has no impact on functionality apart from cleaner log messages. @@ -997,13 +997,13 @@ index cd5e54f1110..559c903e1a2 100644 if changed(new_data, old_data) or self.cache_get_apply_state() == GPOSTATE.ENFORCE: self.unapply(guid, attribute, old_val) -- -2.51.0 +2.52.0 From e8a6219181f2af87813b53fd09684650c1aa6f90 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Fri, 5 Jan 2024 08:47:07 -0700 -Subject: [PATCH 14/63] gp: Skip site GP list if no site is found +Subject: [PATCH 14/96] gp: Skip site GP list if no site is found [MS-GPOL] 3.2.5.1.4 Site Search says if the site search returns ERROR_NO_SITENAME, the GP site @@ -1065,13 +1065,13 @@ index 617ef79350c..babd8f90748 100644 # (L)ocal gpo_list.insert(0, gpo.GROUP_POLICY_OBJECT("Local Policy", -- -2.51.0 +2.52.0 From d0d1a890d6f2466691fa4ee663232ee0bd1c3776 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 14:14:30 +0100 -Subject: [PATCH 15/63] python:gp: Avoid path check for cepces-submit +Subject: [PATCH 15/96] python:gp: Avoid path check for cepces-submit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -1111,13 +1111,13 @@ index 559c903e1a2..7325d5132cf 100644 '%s --server=%s --auth=%s' % (cepces_submit, ca['hostname'], auth)], -- -2.51.0 +2.52.0 From 7f6c9a4945635c6eb8ada2255bd0febbf0f4e540 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 14:07:47 +0100 -Subject: [PATCH 16/63] python:gp: Improve logging for certificate enrollment +Subject: [PATCH 16/96] python:gp: Improve logging for certificate enrollment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -1171,13 +1171,13 @@ index 7325d5132cf..a25a9678587 100644 getcert = which('getcert') cepces_submit = find_cepces_submit() -- -2.51.0 +2.52.0 From 5321d5b5bd24d7659743576f2e12a7dc0a93a828 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 15:04:36 +0100 -Subject: [PATCH 17/63] python:gp: Do not print an error, if CA already exists +Subject: [PATCH 17/96] python:gp: Do not print an error, if CA already exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -1217,13 +1217,13 @@ index a25a9678587..0b23cd688db 100644 for template in supported_templates: attrs = fetch_template_attrs(ldb, template) -- -2.51.0 +2.52.0 From 6a7a8a4090b8cdb8e71f4ad590260ceeda253ce2 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 15:05:02 +0100 -Subject: [PATCH 18/63] python:gp: Do not print an error if template already +Subject: [PATCH 18/96] python:gp: Do not print an error if template already exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -1264,13 +1264,13 @@ index 0b23cd688db..db681cb6f69 100644 data['templates'].append(nickname) if update is not None: -- -2.51.0 +2.52.0 From 43dc3d5d833bc1db885eb45402decd3225a7c946 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 15:05:24 +0100 -Subject: [PATCH 19/63] python:gp: Log an error if update fails +Subject: [PATCH 19/96] python:gp: Log an error if update fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -1301,13 +1301,13 @@ index db681cb6f69..c8ad2039dc6 100644 log.warn('certmonger and cepces must be installed for ' + 'certificate auto enrollment to work') -- -2.51.0 +2.52.0 From d8276d6a098d10f405b8f24c4dfb82af4496607c Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 15:46:24 +0100 -Subject: [PATCH 20/63] python:gp: Improve working of log messages to avoid +Subject: [PATCH 20/96] python:gp: Improve working of log messages to avoid confusion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -1354,13 +1354,13 @@ index c8ad2039dc6..2b7f7d22c2b 100644 log.warn('Installing the server certificate only.') der_certificate = base64.b64decode(ca['cACertificate']) -- -2.51.0 +2.52.0 From 585357bf0d8889747a2769c2451ee34766087d95 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 29 Jan 2024 17:46:30 +0100 -Subject: [PATCH 21/63] python:gp: Fix logging with gp +Subject: [PATCH 21/96] python:gp: Fix logging with gp This allows enable INFO level logging with: `samba-gpupdate -d3` @@ -1396,13 +1396,13 @@ index a74a8707d50..c3de32825db 100644 logger.setLevel(logging.CRITICAL) if log_level == 1: -- -2.51.0 +2.52.0 From 14ceb0b5f2f954bbabdaf78b8185fc515e3c8294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Wed, 13 Mar 2024 13:55:41 +0100 -Subject: [PATCH 22/63] docs-xml: Add parameter all_groupmem to idmap_ad +Subject: [PATCH 22/96] docs-xml: Add parameter all_groupmem to idmap_ad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -1438,13 +1438,13 @@ index b364bbfa231..de6d36afe95 100644 This parameter is a list of OUs from which objects will not be mapped via the ad idmap -- -2.51.0 +2.52.0 From ac4184c8c3220263cb6f1a46a012533ed1c4e047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Tue, 12 Mar 2024 13:20:24 +0100 -Subject: [PATCH 23/63] s3:winbindd: Improve performance of lookup_groupmem() +Subject: [PATCH 23/96] s3:winbindd: Improve performance of lookup_groupmem() in idmap_ad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -1521,13 +1521,13 @@ index d7a665abbc6..e625aa6473f 100644 if (!NT_STATUS_IS_OK(status)) { DEBUG(10, ("%s: add_primary_group_members failed: %s\n", -- -2.51.0 +2.52.0 From d0e2002efcc37055b35c351a6b936e6ab89fad32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Mon, 25 Mar 2024 22:38:18 +0100 -Subject: [PATCH 24/63] selftest: Add "winbind expand groups = 1" to +Subject: [PATCH 24/96] selftest: Add "winbind expand groups = 1" to setup_ad_member_idmap_ad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -1555,13 +1555,13 @@ index 44ac4a5901a..606c65f8ab1 100755 my $ret = $self->provision( -- -2.51.0 +2.52.0 From 9625b6aed981aa4e70fe11d9d1acdb54db7591a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Thu, 14 Mar 2024 15:24:21 +0100 -Subject: [PATCH 25/63] tests: Add a test for "all_groups=no" to +Subject: [PATCH 25/96] tests: Add a test for "all_groups=no" to test_idmap_ad.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -1628,13 +1628,13 @@ index 7ae112ada71..1d4bd395ba9 100755 changetype: delete EOF -- -2.51.0 +2.52.0 From e5890e63c35a4a5af29ae16e6dd734c4a3a304cc Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 28 May 2024 13:51:53 +0200 -Subject: [PATCH 26/63] s3:libads: Allow get_kdc_ip_string() to lookup the KDCs +Subject: [PATCH 26/96] s3:libads: Allow get_kdc_ip_string() to lookup the KDCs IP Remove the requirement to provide an IP address. We should look up the @@ -1693,13 +1693,13 @@ index 50f4a6de3c6..ddf97c11973 100644 /* -- -2.51.0 +2.52.0 From 96a1ecd8db249fa03db60259cf76fdef9c1bd749 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 28 May 2024 13:53:51 +0200 -Subject: [PATCH 27/63] s3:libads: Do not fail if we don't get an IP passed +Subject: [PATCH 27/96] s3:libads: Do not fail if we don't get an IP passed down The IP should be optional and we should look it up if not provided. @@ -1727,13 +1727,13 @@ index ddf97c11973..f74d8eb567c 100644 } -- -2.51.0 +2.52.0 From 4934642b7a7d92c6d81ba25ef6e4b66e3805f708 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 28 May 2024 13:54:24 +0200 -Subject: [PATCH 28/63] s3:winbind: Fix idmap_ad creating an invalid local +Subject: [PATCH 28/96] s3:winbind: Fix idmap_ad creating an invalid local krb5.conf In case of a trusted domain, we are providing the realm of the primary @@ -1783,13 +1783,13 @@ index 5c9fe07db95..b8002825161 100644 if (!ok) { DBG_DEBUG("Could not create private krb5.conf\n"); -- -2.51.0 +2.52.0 From cccc902c64c93db317bf4707d0af5e56b2887286 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jul 2024 12:26:55 +0200 -Subject: [PATCH 29/63] s3:notifyd: Use a watcher per db record +Subject: [PATCH 29/96] s3:notifyd: Use a watcher per db record MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -2301,13 +2301,13 @@ index 36c08f47c54..db8e6e1c005 100644 #endif -- -2.51.0 +2.52.0 From b04cb93ee52aac0ce7213d0581d69e852df52d4a Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Mon, 5 Feb 2024 15:03:48 +0100 -Subject: [PATCH 30/63] smbd: simplify handling of failing fstat() after +Subject: [PATCH 30/96] smbd: simplify handling of failing fstat() after unlinking file close_remove_share_mode() already called vfs_stat_fsp(), so we can skip the @@ -2365,13 +2365,13 @@ index 3581c4b9173..93c12e00eb0 100644 } -- -2.51.0 +2.52.0 From 29f0c0fb2f1cb0cfc4c615d31e82048b46a2cb0d Mon Sep 17 00:00:00 2001 From: Noel Power Date: Tue, 20 Feb 2024 09:26:29 +0000 -Subject: [PATCH 31/63] s3/smbd: If we fail to close file_handle ensure we +Subject: [PATCH 31/96] s3/smbd: If we fail to close file_handle ensure we should reset the fd if fsp_flags.fstat_before_close == true then close_file_smb will call @@ -2446,13 +2446,13 @@ index 93c12e00eb0..74be444fef5 100644 /**************************************************************************** -- -2.51.0 +2.52.0 From ed138c4d679e8291de18162e1cac65cc9da33b4d Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Wed, 15 Jan 2025 10:21:19 -0800 -Subject: [PATCH 32/63] auth: Add missing talloc_free() in error code path. +Subject: [PATCH 32/96] auth: Add missing talloc_free() in error code path. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -2483,13 +2483,13 @@ index b914075d85c..196654b36bd 100644 } -- -2.51.0 +2.52.0 From f8a7d7a3e8c3be3c7742c874239766b34c25ef3e Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 16 Jan 2025 16:12:31 -0800 -Subject: [PATCH 33/63] auth: Cleanup exit code paths in kerberos_decode_pac(). +Subject: [PATCH 33/96] auth: Cleanup exit code paths in kerberos_decode_pac(). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @@ -2755,13 +2755,13 @@ index 196654b36bd..abb096bde1b 100644 NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx, -- -2.51.0 +2.52.0 From 9fd06d5c331f5babaf417cc7339d12854a79fe4b Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 15 Feb 2024 17:29:46 +0100 -Subject: [PATCH 34/63] s3:libsmb/dsgetdcname: use +Subject: [PATCH 34/96] s3:libsmb/dsgetdcname: use NETLOGON_NT_VERSION_AVOID_NT4EMUL In 2024 we always want an active directory response... @@ -2792,13 +2792,13 @@ index 280ccd585b0..6fcaa26810c 100644 snprintf(my_acct_name, -- -2.51.0 +2.52.0 From 58e28d056f2df0906ee77ccfb9b56e8a764b38b4 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 7 May 2024 14:53:24 +0000 -Subject: [PATCH 35/63] s3:libsmb: allow store_cldap_reply() to work with a +Subject: [PATCH 35/96] s3:libsmb: allow store_cldap_reply() to work with a ipv6 response BUG: https://bugzilla.samba.org/show_bug.cgi?id=15642 @@ -2850,13 +2850,13 @@ index 6fcaa26810c..da173e7bbb0 100644 ndr_err = ndr_push_struct_blob(&blob, mem_ctx, r, (ndr_push_flags_fn_t)ndr_push_NETLOGON_SAM_LOGON_RESPONSE_EX); -- -2.51.0 +2.52.0 From e4d5269b2359c670acdf0cba81248f148ae68c17 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 11 Oct 2024 13:32:22 +0000 -Subject: [PATCH 36/63] s3:libsmb: let discover_dc_netbios() return +Subject: [PATCH 36/96] s3:libsmb: let discover_dc_netbios() return DOMAIN_CONTROLLER_NOT_FOUND We may get NT_STATUS_NOT_FOUND when the name can't be resolved @@ -2896,13 +2896,13 @@ index da173e7bbb0..8278959dd7d 100644 } -- -2.51.0 +2.52.0 From d90d2b0e985913247f43192cb94eec0efb3e9046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Deschner?= Date: Wed, 2 Jul 2025 21:59:48 +0200 -Subject: [PATCH 37/63] s3-winbindd: Fix internal winbind dsgetdcname calls +Subject: [PATCH 37/96] s3-winbindd: Fix internal winbind dsgetdcname calls w.r.t. domain name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -3080,13 +3080,13 @@ index fe93528787d..eca4116d0c8 100644 + return wbdom->name; +} -- -2.51.0 +2.52.0 From 7da6072ce95bca445368f6d0453247c8f92fcdf2 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 9 May 2025 09:38:41 +0200 -Subject: [PATCH 38/63] s3:winbindd: avoid using any netlogon call to get a dc +Subject: [PATCH 38/96] s3:winbindd: avoid using any netlogon call to get a dc name BUG: https://bugzilla.samba.org/show_bug.cgi?id=15876 @@ -3383,13 +3383,13 @@ index f0fd18a8fa6..47c68257b12 100644 NTSTATUS _wbint_LookupRids(struct pipes_struct *p, struct wbint_LookupRids *r) -- -2.51.0 +2.52.0 From ad54ceadacfbcf0d9c96ad773e50db96003e2c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Wed, 23 Jul 2025 15:09:21 +0200 -Subject: [PATCH 39/63] s3:winbindd: Resolve dc name using CLDAP also for +Subject: [PATCH 39/96] s3:winbindd: Resolve dc name using CLDAP also for ROLE_IPA_DC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -3437,13 +3437,13 @@ index 195259daa43..86dbf68f033 100644 } -- -2.51.0 +2.52.0 From b73efffbb02903427af2c2cc57171d4848ca11f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Mon, 4 Aug 2025 08:35:29 +0200 -Subject: [PATCH 40/63] docs-xml: Make smb.conf 'server role' value consistent +Subject: [PATCH 40/96] docs-xml: Make smb.conf 'server role' value consistent with ROLE_IPA_DC in libparam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -3474,13 +3474,13 @@ index 4ea4e4751ee..40244e125ce 100644 This mode of operation runs Samba in a hybrid mode for IPA domain controller, providing forest trust to Active Directory. -- -2.51.0 +2.52.0 From 832a4e31630fd441f8ab4325439f90d561cb8fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Mon, 4 Aug 2025 23:26:02 +0200 -Subject: [PATCH 41/63] s3:netlogon: IPA DC is the PDC as well - allow +Subject: [PATCH 41/96] s3:netlogon: IPA DC is the PDC as well - allow ROLE_IPA_DC in _netr_DsRGetForestTrustInformation() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -3514,13 +3514,13 @@ index c5a4b0ef30c..7957d3ab34d 100644 return WERR_NERR_NOTPRIMARY; } -- -2.51.0 +2.52.0 From 8d5638581dfc539c8524d7a507e8cc8977e827a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= Date: Mon, 4 Aug 2025 23:28:24 +0200 -Subject: [PATCH 42/63] s3:utils: Allow ROLE_IPA_DC to allow to use Kerberos in +Subject: [PATCH 42/96] s3:utils: Allow ROLE_IPA_DC to allow to use Kerberos in gensec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -3563,13 +3563,13 @@ index cff3c53845f..2968ca47734 100644 CRED_USE_KERBEROS_DESIRED, CRED_SPECIFIED); -- -2.51.0 +2.52.0 From 3ef02a381cdc83549506e159ebc457730c06c547 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 22 Jul 2025 19:22:31 +0200 -Subject: [PATCH 43/63] libads: fix get_kdc_ip_string() +Subject: [PATCH 43/96] libads: fix get_kdc_ip_string() Correctly handle the interaction between optionally passed in DC via pss and DC lookup. @@ -3614,13 +3614,13 @@ index f74d8eb567c..f324321c87b 100644 } -- -2.51.0 +2.52.0 From b0dbc167f85deabff2af5b18bc201e8db0d3b97d Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 22 Jul 2025 19:16:14 +0200 -Subject: [PATCH 44/63] winbindd: use find_domain_from_name_noinit() in +Subject: [PATCH 44/96] winbindd: use find_domain_from_name_noinit() in find_dns_domain_name() Avoid triggering a connection to a DC of a trusted domain. @@ -3648,13 +3648,13 @@ index eca4116d0c8..3a7a9114988 100644 return domain_name; } -- -2.51.0 +2.52.0 From 1961f54ce07f7dc3cfcae5c00b96b39109f08b3a Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 19 Dec 2023 11:11:55 +0100 -Subject: [PATCH 45/63] vfs_default: allow disabling /proc/fds and +Subject: [PATCH 45/96] vfs_default: allow disabling /proc/fds and RESOLVE_NO_SYMLINK at compile time This will be used in CI to have a gitlab runner without all modern Linux @@ -3718,13 +3718,13 @@ index 1d4b9b1a840..8d78831492f 100644 return 0; /* Return >= 0 for success */ } -- -2.51.0 +2.52.0 From 26de62a2a968dd5b73af296251b26112cdd533e5 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 19 Dec 2023 11:12:49 +0100 -Subject: [PATCH 46/63] CI: disable /proc/fds and RESOLVE_NO_SYMLINK in +Subject: [PATCH 46/96] CI: disable /proc/fds and RESOLVE_NO_SYMLINK in samba-no-opath-build runner This is a more sensible combination of missing Linux specific features: @@ -3791,13 +3791,13 @@ index c3a13f5ec6e..67764a0b027 100644 +^samba3.blackbox.virus_scanner.*\(fileserver:local\) +^samba3.blackbox.shadow_copy2.*\(fileserver.*\) -- -2.51.0 +2.52.0 From 2c27aae5a4c8d7368dc142fb2be36919296d2a02 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 2 Jan 2024 12:49:14 +0100 -Subject: [PATCH 47/63] smbd: pass symlink target path to +Subject: [PATCH 47/96] smbd: pass symlink target path to safe_symlink_target_path() Moves processing the symlink error response to the caller @@ -3955,13 +3955,13 @@ index 8693dcf1153..45fb90381e2 100644 symlink_redirects += 1; -- -2.51.0 +2.52.0 From 99d7e841d4e18f760c137530bbed0dea6115311a Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 2 Jan 2024 13:25:25 +0100 -Subject: [PATCH 48/63] smbd: add a directory argument to +Subject: [PATCH 48/96] smbd: add a directory argument to safe_symlink_target_path() Existing caller passes NULL, no change in behaviour. Prepares for @@ -4033,13 +4033,13 @@ index 45fb90381e2..55a49e0ba93 100644 unparsed, &safe_target); -- -2.51.0 +2.52.0 From 5041a6fa5cdfd21bf697249d900ea5c107d355a2 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 2 Jan 2024 14:34:26 +0100 -Subject: [PATCH 49/63] smbd: use safe_symlink_target_path() in +Subject: [PATCH 49/96] smbd: use safe_symlink_target_path() in symlink_target_below_conn() BUG: https://bugzilla.samba.org/show_bug.cgi?id=15549 @@ -4179,13 +4179,13 @@ index 74be444fef5..6582bd60245 100644 discard_const_p(files_struct, dirfsp), smb_fname_rel, -- -2.51.0 +2.52.0 From f2fc99f0c7d441115a486413f345c0226a00b38b Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Mon, 18 Dec 2023 12:35:58 +0100 -Subject: [PATCH 50/63] smbd: use dirfsp and atname in open_directory() +Subject: [PATCH 50/96] smbd: use dirfsp and atname in open_directory() On systems without /proc/fd support this avoid the expensive chdir() logic in non_widelink_open(). open_file_ntcreate() already passes @@ -4224,13 +4224,13 @@ index 6582bd60245..b9849f82396 100644 O_RDONLY | O_DIRECTORY, 0, -- -2.51.0 +2.52.0 From 7d102268ebbebf6fc723a43485a82f72069d00ee Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Fri, 16 Dec 2022 16:35:00 +0100 -Subject: [PATCH 51/63] smbd: Return open_symlink_err from +Subject: [PATCH 51/96] smbd: Return open_symlink_err from filename_convert_dirfsp_nosymlink() Don't lose information returned from openat_pathref_fsp_nosymlink() @@ -4387,13 +4387,13 @@ index 55a49e0ba93..9fd85af992a 100644 return status; } -- -2.51.0 +2.52.0 From edaabc3d53fddd9e2fa6168c8bf01ebfbf229657 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 25 Apr 2024 15:24:57 +0200 -Subject: [PATCH 52/63] s3/lib: add next helper variable in server_id_watch_* +Subject: [PATCH 52/96] s3/lib: add next helper variable in server_id_watch_* BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624 @@ -4467,13 +4467,13 @@ index f0189e0e896..50b35f27b3e 100644 return; } -- -2.51.0 +2.52.0 From c25f1811c2ccaa2d5cc8005597fb9979aa1102ee Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 4 Apr 2024 12:31:05 +0200 -Subject: [PATCH 53/63] s3/lib: add option "serverid watch:debug = yes" to +Subject: [PATCH 53/96] s3/lib: add option "serverid watch:debug = yes" to print kernel stack of hanging process We only do if sys_have_proc_fds() returns true, so it's most likely @@ -4589,13 +4589,13 @@ index 50b35f27b3e..c372ec8c431 100644 subreq = tevent_wakeup_send(state, state->ev, next); if (tevent_req_nomem(subreq, req)) { -- -2.51.0 +2.52.0 From 23dbf8f0317810d65e716a3c9b947c7a6549cb46 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 25 Apr 2024 15:17:08 +0200 -Subject: [PATCH 54/63] s3/lib: add option "serverid watch:debug script" +Subject: [PATCH 54/96] s3/lib: add option "serverid watch:debug script" This takes just PID and NODE:PID on a cluster. @@ -4682,13 +4682,13 @@ index c372ec8c431..8ddf9c6b1c8 100644 DBG_ERR("Process %s hanging for %f seconds?\n", pid, duration); -- -2.51.0 +2.52.0 From 59975168627e4bfbd2e75a611cb8cb13019a7df3 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Fri, 5 Apr 2024 12:15:28 +0200 -Subject: [PATCH 55/63] smbd: log share_mode_watch_recv() errors as errors +Subject: [PATCH 55/96] smbd: log share_mode_watch_recv() errors as errors BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624 @@ -4719,13 +4719,13 @@ index b9849f82396..da129119c7f 100644 * Even if it failed, retry anyway. TODO: We need a way to * tell a re-scheduled open about that error. -- -2.51.0 +2.52.0 From e619b72fe1b9c36963c452c1d102009b28e8e289 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 4 Apr 2024 19:18:19 +0200 -Subject: [PATCH 56/63] smbd: add option "smbd lease break:debug hung procs" +Subject: [PATCH 56/96] smbd: add option "smbd lease break:debug hung procs" By enabling this a process sending a lease break message to another process holding a lease will start watching that process and if that process didn't @@ -4977,13 +4977,13 @@ index da129119c7f..4cc5190f690 100644 } if (!NT_STATUS_IS_OK(status)) { -- -2.51.0 +2.52.0 From e6a0d821ba28839728371ca94bb364dd6865b5dd Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Wed, 20 Mar 2024 14:27:27 +0100 -Subject: [PATCH 57/63] smbd: move trace_state variable behind tv variable +Subject: [PATCH 57/96] smbd: move trace_state variable behind tv variable Next commit adds timestamp variables to trace_state that want to be initialized with the current time, so moving behind tv we can then just reuse tv for that. @@ -5025,13 +5025,13 @@ index fbbe4ef3992..188eaa14839 100644 char *chroot_dir = NULL; int rc; -- -2.51.0 +2.52.0 From 15276d7645255ddddf2a3bf6b7a429e3d40ec9b7 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Wed, 20 Mar 2024 14:28:43 +0100 -Subject: [PATCH 58/63] smbd: add option "smbd:debug events" for tevent +Subject: [PATCH 58/96] smbd: add option "smbd:debug events" for tevent handling duration threshold warnings Can be used to enable printing an error message if tevent event handlers ran @@ -5167,13 +5167,13 @@ index 188eaa14839..dbe91132f7f 100644 tevent_set_trace_callback(ev_ctx, smbd_tevent_trace_callback, -- -2.51.0 +2.52.0 From 4631b9d60a874db10dbdd52406d0094a7dbd1356 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 26 Aug 2024 14:11:02 +0200 -Subject: [PATCH 59/63] vfs_error_inject: add 'error_inject:durable_reconnect = +Subject: [PATCH 59/96] vfs_error_inject: add 'error_inject:durable_reconnect = st_ex_nlink' This allows to simulate durable reconnect failures because the stat @@ -5288,13 +5288,13 @@ index 529504fd8d5..dcf0de0a2d9 100644 static_decl_vfs; -- -2.51.0 +2.52.0 From c8e88652163cc56b1f9fb0926a140c81e6b7ec94 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 26 Aug 2024 14:42:02 +0200 -Subject: [PATCH 60/63] s4:torture/smb2: add +Subject: [PATCH 60/96] s4:torture/smb2: add smb2.durable-v2-regressions.durable_v2_reconnect_bug15624 BUG: https://bugzilla.samba.org/show_bug.cgi?id=15624 @@ -5464,13 +5464,13 @@ index 5b6477e47bc..9cf7f5da78b 100644 torture_suite_add_suite(suite, torture_smb2_lease_init(suite)); torture_suite_add_suite(suite, torture_smb2_compound_init(suite)); -- -2.51.0 +2.52.0 From 56a3aaf95c44052b19b61115686c71d5b7dbab4a Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 26 Aug 2024 14:42:12 +0200 -Subject: [PATCH 61/63] s3:tests: let test_durable_handle_reconnect.sh run +Subject: [PATCH 61/96] s3:tests: let test_durable_handle_reconnect.sh run smb2.durable-v2-regressions.durable_v2_reconnect_bug15624 This demonstrates the dead lock after a durable reconnect failed @@ -5524,13 +5524,13 @@ index 0ab32974824..fd5c156956f 100755 + testok $0 $failed -- -2.51.0 +2.52.0 From d8f01885145ecfce15f2507fdcc625442db1738c Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 9 Apr 2024 14:52:44 +0200 -Subject: [PATCH 62/63] smbd: consolidate DH reconnect failure code +Subject: [PATCH 62/96] smbd: consolidate DH reconnect failure code No change in behaviour, except that we now also call fd_close() if vfs_default_durable_cookie() @@ -5828,13 +5828,13 @@ index b21c223b2e4..50075ddd3f7 100644 + return status; } -- -2.51.0 +2.52.0 From b248ddd3dd7193ba44c9ad86488dd180a25e3774 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 9 Apr 2024 14:53:32 +0200 -Subject: [PATCH 63/63] smbd: remove just created sharemode entry in the error +Subject: [PATCH 63/96] smbd: remove just created sharemode entry in the error codepaths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 @@ -5900,5 +5900,8035 @@ index 50075ddd3f7..98d0d403e30 100644 NTSTATUS close_status; close_status = fd_close(fsp); -- -2.51.0 +2.52.0 + + +From 67ff429e41004899e514d893e80332de79ca2bab Mon Sep 17 00:00:00 2001 +From: Earl Chew +Date: Sun, 17 Dec 2023 08:37:33 -0800 +Subject: [PATCH 64/96] Augment library_flags() to return libraries + +Extend library_flags() to return the libraries provided by +pkg-config --libs. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15623 + +Signed-off-by: Earl Chew +Reviewed-by: Andrew Bartlett +Reviewed-by: Douglas Bagnall +(cherry picked from commit 363c33185779141fdfbda695997d548939a0251f) +--- + buildtools/wafsamba/samba3.py | 8 ++++---- + buildtools/wafsamba/samba_autoconf.py | 22 +++++++++++++--------- + buildtools/wafsamba/samba_deps.py | 5 ++--- + lib/util/charset/wscript_configure | 2 +- + 4 files changed, 20 insertions(+), 17 deletions(-) + +diff --git a/buildtools/wafsamba/samba3.py b/buildtools/wafsamba/samba3.py +index 227ee27705d..ba0783f0d22 100644 +--- a/buildtools/wafsamba/samba3.py ++++ b/buildtools/wafsamba/samba3.py +@@ -45,25 +45,25 @@ def s3_fix_kwargs(bld, kwargs): + '../bin/default/third_party/heimdal/lib/asn1' ] + + if bld.CONFIG_SET('USING_SYSTEM_TDB'): +- (tdb_includes, tdb_ldflags, tdb_cpppath) = library_flags(bld, 'tdb') ++ (tdb_includes, tdb_ldflags, tdb_cpppath, tdb_libs) = library_flags(bld, 'tdb') + extra_includes += tdb_cpppath + else: + extra_includes += [ '../lib/tdb/include' ] + + if bld.CONFIG_SET('USING_SYSTEM_TEVENT'): +- (tevent_includes, tevent_ldflags, tevent_cpppath) = library_flags(bld, 'tevent') ++ (tevent_includes, tevent_ldflags, tevent_cpppath, tevent_libs) = library_flags(bld, 'tevent') + extra_includes += tevent_cpppath + else: + extra_includes += [ '../lib/tevent' ] + + if bld.CONFIG_SET('USING_SYSTEM_TALLOC'): +- (talloc_includes, talloc_ldflags, talloc_cpppath) = library_flags(bld, 'talloc') ++ (talloc_includes, talloc_ldflags, talloc_cpppath, talloc_libs) = library_flags(bld, 'talloc') + extra_includes += talloc_cpppath + else: + extra_includes += [ '../lib/talloc' ] + + if bld.CONFIG_SET('USING_SYSTEM_POPT'): +- (popt_includes, popt_ldflags, popt_cpppath) = library_flags(bld, 'popt') ++ (popt_includes, popt_ldflags, popt_cpppath, popt_libs) = library_flags(bld, 'popt') + extra_includes += popt_cpppath + else: + extra_includes += [ '../lib/popt' ] +diff --git a/buildtools/wafsamba/samba_autoconf.py b/buildtools/wafsamba/samba_autoconf.py +index 34fd5fab2c0..d3b6503c5ca 100644 +--- a/buildtools/wafsamba/samba_autoconf.py ++++ b/buildtools/wafsamba/samba_autoconf.py +@@ -91,7 +91,7 @@ def CHECK_HEADER(conf, h, add_headers=False, lib=None): + conf.env.hlist.append(h) + return True + +- (ccflags, ldflags, cpppath) = library_flags(conf, lib) ++ (ccflags, ldflags, cpppath, libs) = library_flags(conf, lib) + + hdrs = hlist_to_string(conf, headers=h) + if lib is None: +@@ -435,7 +435,7 @@ def CHECK_CODE(conf, code, define, + + uselib = TO_LIST(lib) + +- (ccflags, ldflags, cpppath) = library_flags(conf, uselib) ++ (ccflags, ldflags, cpppath, libs) = library_flags(conf, uselib) + + includes = TO_LIST(includes) + includes.extend(cpppath) +@@ -569,21 +569,24 @@ Build.BuildContext.CONFIG_SET = CONFIG_SET + Build.BuildContext.CONFIG_GET = CONFIG_GET + + +-def library_flags(self, libs): ++def library_flags(self, library): + '''work out flags from pkg_config''' + ccflags = [] + ldflags = [] + cpppath = [] +- for lib in TO_LIST(libs): ++ libs = [] ++ for lib in TO_LIST(library): + # note that we do not add the -I and -L in here, as that is added by the waf + # core. Adding it here would just change the order that it is put on the link line + # which can cause system paths to be added before internal libraries + extra_ccflags = TO_LIST(getattr(self.env, 'CFLAGS_%s' % lib.upper(), [])) + extra_ldflags = TO_LIST(getattr(self.env, 'LDFLAGS_%s' % lib.upper(), [])) + extra_cpppath = TO_LIST(getattr(self.env, 'CPPPATH_%s' % lib.upper(), [])) ++ extra_libs = TO_LIST(getattr(self.env, 'LIB_%s' % lib.upper(), [])) + ccflags.extend(extra_ccflags) + ldflags.extend(extra_ldflags) + cpppath.extend(extra_cpppath) ++ libs.extend(extra_libs) + + extra_cpppath = TO_LIST(getattr(self.env, 'INCLUDES_%s' % lib.upper(), [])) + cpppath.extend(extra_cpppath) +@@ -593,11 +596,12 @@ def library_flags(self, libs): + ccflags = unique_list(ccflags) + ldflags = unique_list(ldflags) + cpppath = unique_list(cpppath) +- return (ccflags, ldflags, cpppath) ++ libs = unique_list(libs) ++ return (ccflags, ldflags, cpppath, libs) + + + @conf +-def CHECK_LIB(conf, libs, mandatory=False, empty_decl=True, set_target=True, shlib=False): ++def CHECK_LIB(conf, library, mandatory=False, empty_decl=True, set_target=True, shlib=False): + '''check if a set of libraries exist as system libraries + + returns the sublist of libs that do exist as a syslib or [] +@@ -611,13 +615,13 @@ int foo() + } + ''' + ret = [] +- liblist = TO_LIST(libs) +- for lib in liblist[:]: ++ liblist = TO_LIST(library) ++ for lib in liblist: + if GET_TARGET_TYPE(conf, lib) == 'SYSLIB': + ret.append(lib) + continue + +- (ccflags, ldflags, cpppath) = library_flags(conf, lib) ++ (ccflags, ldflags, cpppath, libs) = library_flags(conf, lib) + if shlib: + res = conf.check(features='c cshlib', fragment=fragment, lib=lib, uselib_store=lib, cflags=ccflags, ldflags=ldflags, uselib=lib.upper(), mandatory=False) + else: +diff --git a/buildtools/wafsamba/samba_deps.py b/buildtools/wafsamba/samba_deps.py +index 66adf40307e..5b428295b86 100644 +--- a/buildtools/wafsamba/samba_deps.py ++++ b/buildtools/wafsamba/samba_deps.py +@@ -83,9 +83,8 @@ def build_dependencies(self): + self.add_objects = list(self.final_objects) + + # extra link flags from pkg_config +- libs = self.final_syslibs.copy() +- +- (cflags, ldflags, cpppath) = library_flags(self, list(libs)) ++ (cflags, ldflags, cpppath, libs) = library_flags( ++ self, list(self.final_syslibs.copy())) + new_ldflags = getattr(self, 'samba_ldflags', [])[:] + new_ldflags.extend(ldflags) + self.ldflags = new_ldflags +diff --git a/lib/util/charset/wscript_configure b/lib/util/charset/wscript_configure +index 9c27fc664f0..58858f69b31 100644 +--- a/lib/util/charset/wscript_configure ++++ b/lib/util/charset/wscript_configure +@@ -8,7 +8,7 @@ + # managed to link when specifying -liconv a executable even if there is no + # libiconv.so or libiconv.a + +-conf.CHECK_LIB(libs="iconv", shlib=True) ++conf.CHECK_LIB("iconv", shlib=True) + + #HP-UX can use libiconv as an add-on package, which has #define iconv_open libiconv_open + if (conf.CHECK_FUNCS_IN('iconv_open', 'iconv', checklibc=False, headers='iconv.h') or +-- +2.52.0 + + +From a4f79d7fb725fab47bda53b9482c1ee301a8393a Mon Sep 17 00:00:00 2001 +From: Earl Chew +Date: Sat, 16 Dec 2023 17:47:09 -0800 +Subject: [PATCH 65/96] Improve CHECK_LIB interaction with CHECK_PKG + +When checking for shared libraries, only name the target library +if it was not previously discoverd by pkg-config --libs and now +available from uselib_store. This avoids using both sources of +information which results in the library being named twice on +the command line. + +Once the library is confirmed by CHECK_LIB, append the library if +not already present, to avoid dropping libraries that were +previously discovered by CHECK_PKG. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15623 + +Signed-off-by: Earl Chew +Reviewed-by: Andrew Bartlett +Reviewed-by: Douglas Bagnall +(cherry picked from commit 0c983bd0095d4fb20ef8b42f5efb740393073862) +--- + buildtools/wafsamba/samba_autoconf.py | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/buildtools/wafsamba/samba_autoconf.py b/buildtools/wafsamba/samba_autoconf.py +index d3b6503c5ca..b1d2f761095 100644 +--- a/buildtools/wafsamba/samba_autoconf.py ++++ b/buildtools/wafsamba/samba_autoconf.py +@@ -623,7 +623,12 @@ int foo() + + (ccflags, ldflags, cpppath, libs) = library_flags(conf, lib) + if shlib: +- res = conf.check(features='c cshlib', fragment=fragment, lib=lib, uselib_store=lib, cflags=ccflags, ldflags=ldflags, uselib=lib.upper(), mandatory=False) ++ # Avoid repeating the library if it is already named by ++ # pkg-config --libs. ++ kw = {} ++ if lib not in libs: ++ kw['lib'] = lib ++ res = conf.check(features='c cshlib', fragment=fragment, uselib_store=lib, cflags=ccflags, ldflags=ldflags, uselib=lib.upper(), mandatory=False, **kw) + else: + res = conf.check(lib=lib, uselib_store=lib, cflags=ccflags, ldflags=ldflags, uselib=lib.upper(), mandatory=False) + +@@ -637,7 +642,10 @@ int foo() + SET_TARGET_TYPE(conf, lib, 'EMPTY') + else: + conf.define('HAVE_LIB%s' % lib.upper().replace('-','_').replace('.','_'), 1) +- conf.env['LIB_' + lib.upper()] = lib ++ # To avoid losing information from pkg-config, append the library ++ # only it is not already present. ++ if lib not in libs: ++ conf.env.append_value('LIB_' + lib.upper(), lib) + if set_target: + conf.SET_TARGET_TYPE(lib, 'SYSLIB') + ret.append(lib) +-- +2.52.0 + + +From 2b4f5a62eac69e12ecd9a1e3919ea4a8b3d40820 Mon Sep 17 00:00:00 2001 +From: Earl Chew +Date: Sat, 16 Dec 2023 08:48:36 -0800 +Subject: [PATCH 66/96] Combine ICU libraries icu-i18n and icu-uc into a single + dependency + +Rather than probing for icu-i18n, icu-uc, and icudata libraries +separately, only probe for icu-i18n, and icu-uc, as direct dependencies +This avoids overlinking with icudata, and allows the package +to build even when ICU is not installed as a system library. + +RN: Only use icu-i18n and icu-uc to express ICU dependency + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15623 + +Signed-off-by: Earl Chew +Reviewed-by: Andrew Bartlett +Reviewed-by: Douglas Bagnall +(cherry picked from commit 05807488fd340751ee976c5f8a367013ff94843e) +--- + lib/util/charset/iconv.c | 8 ++++---- + lib/util/charset/wscript_build | 3 ++- + lib/util/charset/wscript_configure | 17 +++++++---------- + 3 files changed, 13 insertions(+), 15 deletions(-) + +diff --git a/lib/util/charset/iconv.c b/lib/util/charset/iconv.c +index 30e705ee119..3234f92bc55 100644 +--- a/lib/util/charset/iconv.c ++++ b/lib/util/charset/iconv.c +@@ -26,7 +26,7 @@ + #include "lib/util/charset/charset.h" + #include "lib/util/charset/charset_proto.h" + +-#ifdef HAVE_ICU_I18N ++#ifdef HAVE_ICUI18N + #include + #include + #endif +@@ -168,7 +168,7 @@ static size_t sys_iconv(void *cd, + } + #endif + +-#ifdef HAVE_ICU_I18N ++#ifdef HAVE_ICUI18N + static size_t sys_uconv(void *cd, + const char **inbuf, + size_t *inbytesleft, +@@ -334,7 +334,7 @@ static bool is_utf16(const char *name) + + static int smb_iconv_t_destructor(smb_iconv_t hwd) + { +-#ifdef HAVE_ICU_I18N ++#ifdef HAVE_ICUI18N + /* + * This has to come first, as the cd_direct member won't be an iconv + * handle and must not be passed to iconv_close(). +@@ -418,7 +418,7 @@ _PUBLIC_ smb_iconv_t smb_iconv_open_ex(TALLOC_CTX *mem_ctx, const char *tocode, + } + #endif + +-#ifdef HAVE_ICU_I18N ++#ifdef HAVE_ICUI18N + if (strcasecmp(fromcode, "UTF8-NFD") == 0 && + strcasecmp(tocode, "UTF8-NFC") == 0) + { +diff --git a/lib/util/charset/wscript_build b/lib/util/charset/wscript_build +index c69a17170ad..3af90a0ad57 100644 +--- a/lib/util/charset/wscript_build ++++ b/lib/util/charset/wscript_build +@@ -6,7 +6,8 @@ bld.SAMBA_SUBSYSTEM('ICONV_WRAPPER', + weird.c + charset_macosxfs.c + ''', +- public_deps='iconv replace talloc ' + bld.env['icu-libs']) ++ deps=bld.env['icu-libs'], ++ public_deps='iconv replace talloc') + + bld.SAMBA_SUBSYSTEM('charset', + public_headers='charset.h', +diff --git a/lib/util/charset/wscript_configure b/lib/util/charset/wscript_configure +index 58858f69b31..c49b55a4fd4 100644 +--- a/lib/util/charset/wscript_configure ++++ b/lib/util/charset/wscript_configure +@@ -37,15 +37,12 @@ conf.CHECK_CODE(''' + lib='iconv', + headers='errno.h iconv.h') + +-if conf.CHECK_CFG(package='icu-i18n', ++if conf.CHECK_CFG(package='icu-i18n icu-uc', + args='--cflags --libs', +- msg='Checking for icu-i18n', +- uselib_store='ICU_I18N'): +- for lib in conf.env['LIB_ICU_I18N']: +- conf.CHECK_LIB(lib, shlib=True, mandatory=True) +- conf.env['icu-libs'] = ' '.join(conf.env['LIB_ICU_I18N']) +- if not conf.CHECK_HEADERS('unicode/ustring.h'): +- conf.fatal('Found libicu, but unicode/ustring.h is missing') ++ msg='Checking for icu-i18n icu-uc', ++ uselib_store='ICUI18N'): ++ conf.env['icu-libs'] = 'icui18n' ++ conf.CHECK_LIB(conf.env['icu-libs'], shlib=True, mandatory=True) ++ if not conf.CHECK_HEADERS('unicode/ustring.h', lib='icui18n'): ++ conf.fatal('Found icui18n, but unicode/ustring.h is missing') + conf.DEFINE('HAVE_UTF8_NORMALISATION', 1) +-else: +- conf.env['icu-libs'] = '' +-- +2.52.0 + + +From 8e5968634b263c20ad71c75e839abb217614b567 Mon Sep 17 00:00:00 2001 +From: Earl Chew +Date: Fri, 10 May 2024 19:46:28 -0700 +Subject: [PATCH 67/96] Restore empty string default for conf.env['icu-libs'] + +The reworked ICU libraries configuration code used [] as +default for conf.env['icu-libs']. This breaks dependency analysis +in samba_deps.py because SAMBA_SUBSYSTEM() expects deps to be +a string. + +Signed-off-by: Earl Chew +Reviewed-by: Andrew Bartlett +Reviewed-by: Douglas Bagnall +Reviewed-by: Volker Lendecke + +Autobuild-User(master): Andreas Schneider +Autobuild-Date(master): Tue May 14 14:44:06 UTC 2024 on atb-devel-224 + +(cherry picked from commit 68a1200f66e9008ca0a739b37b48c49453ca9d83) +--- + lib/util/charset/wscript_configure | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/lib/util/charset/wscript_configure b/lib/util/charset/wscript_configure +index c49b55a4fd4..adae44eab5e 100644 +--- a/lib/util/charset/wscript_configure ++++ b/lib/util/charset/wscript_configure +@@ -46,3 +46,5 @@ if conf.CHECK_CFG(package='icu-i18n icu-uc', + if not conf.CHECK_HEADERS('unicode/ustring.h', lib='icui18n'): + conf.fatal('Found icui18n, but unicode/ustring.h is missing') + conf.DEFINE('HAVE_UTF8_NORMALISATION', 1) ++else: ++ conf.env['icu-libs'] = '' +-- +2.52.0 + + +From 88a29be0ed6cf611eb812c0729d2ee61be07a3a3 Mon Sep 17 00:00:00 2001 +From: Earl Chew +Date: Fri, 27 Sep 2024 06:50:31 -0700 +Subject: [PATCH 68/96] Describe implication of upstream ICU-22610 + +Add commentary to link commit 86c7688 (MR !3447) to the upstream +fix for ICU-22610 in case there is subsequent breakage. + +Signed-off-by: Earl Chew +Reviewed-by: Andreas Schneider +Reviewed-by: Andrew Bartlett + +Autobuild-User(master): Andrew Bartlett +Autobuild-Date(master): Fri Nov 8 00:20:38 UTC 2024 on atb-devel-224 + +(cherry picked from commit 1655413f1246147db9b34d4684a64dac49cf5f0c) +--- + lib/util/charset/wscript_configure | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/lib/util/charset/wscript_configure b/lib/util/charset/wscript_configure +index adae44eab5e..451f7f7bca3 100644 +--- a/lib/util/charset/wscript_configure ++++ b/lib/util/charset/wscript_configure +@@ -37,6 +37,10 @@ conf.CHECK_CODE(''' + lib='iconv', + headers='errno.h iconv.h') + ++# Since commit 86c7688 (MR !3447), the required ICU libraries are discovered ++# as a single group. This had the benefit of working around ICU-22610, and also ++# works with the fix that was merged to ICU main in commit 199bc827. ++ + if conf.CHECK_CFG(package='icu-i18n icu-uc', + args='--cflags --libs', + msg='Checking for icu-i18n icu-uc', +-- +2.52.0 + + +From 72c6766af2ac55854b816147a277404d98b1de9a Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Thu, 8 Jan 2026 11:55:18 +0100 +Subject: [PATCH 69/96] smbd: add a directory argument to + safe_symlink_target_path() + +Existing caller passes NULL, no change in behaviour. Prepares for +replacing symlink_target_below_conn() in open.c. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15549 + +Signed-off-by: Ralph Boehme +Reviewed-by: Volker Lendecke +(backported from commit fc80c72d658a41fe4d93b24b793b52c91b350175) + +Backport changes: + - The v4-19 branch has a different safe_symlink_target_path() signature + that takes (name_in, substitute) instead of (dir, target), due to + commit 7d102268ebb being backported out of order. + - Adapted the function to use the new (dir, target) signature matching + proto.h, which already had the correct declaration. + - Updated filename_convert_dirfsp() to call symlink_target_path() first + to compute the target path, then pass dir=NULL to safe_symlink_target_path(). + - This fixes symlink resolution for relative symlinks in subdirectories + (e.g., subdir/link -> ../file) by correctly building the absolute path + as connectpath/dir/target instead of just connectpath/target. +--- + source3/smbd/filename.c | 52 +++++++++++++++++++++++++++-------------- + 1 file changed, 34 insertions(+), 18 deletions(-) + +diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c +index 9fd85af992a..f6e9ed6aae0 100644 +--- a/source3/smbd/filename.c ++++ b/source3/smbd/filename.c +@@ -944,35 +944,40 @@ static char *symlink_target_path( + + NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx, + const char *connectpath, +- const char *name_in, +- const char *substitute, ++ const char *dir, ++ const char *target, + size_t unparsed, + char **_relative) + { +- char *target = NULL; + char *abs_target = NULL; + char *abs_target_canon = NULL; + const char *relative = NULL; + bool in_share; + NTSTATUS status = NT_STATUS_NO_MEMORY; + +- target = symlink_target_path(mem_ctx, +- name_in, +- substitute, +- unparsed); +- if (target == NULL) { +- goto fail; +- } +- +- DBG_DEBUG("connectpath [%s] target [%s] unparsed [%zu]\n", +- connectpath, target, unparsed); ++ DBG_DEBUG("connectpath [%s] dir [%s] target [%s] unparsed [%zu]\n", ++ connectpath, ++ dir != NULL ? dir : "", ++ target, ++ unparsed); + + if (target[0] == '/') { +- abs_target = target; +- } else { +- abs_target = talloc_asprintf(target, ++ abs_target = talloc_strdup(mem_ctx, target); ++ } else if (dir == NULL) { ++ abs_target = talloc_asprintf(mem_ctx, ++ "%s/%s", ++ connectpath, ++ target); ++ } else if (dir[0] == '/') { ++ abs_target = talloc_asprintf(mem_ctx, + "%s/%s", ++ dir, ++ target); ++ } else { ++ abs_target = talloc_asprintf(mem_ctx, ++ "%s/%s/%s", + connectpath, ++ dir, + target); + } + if (abs_target == NULL) { +@@ -1432,6 +1437,7 @@ NTSTATUS filename_convert_dirfsp( + struct open_symlink_err *symlink_err = NULL; + NTSTATUS status; + char *substitute = NULL; ++ char *target = NULL; + char *safe_target = NULL; + size_t symlink_redirects = 0; + +@@ -1470,13 +1476,23 @@ next: + */ + substitute = symlink_err->reparse->substitute_name; + ++ target = symlink_target_path(mem_ctx, ++ name_in, ++ substitute, ++ symlink_err->unparsed); ++ if (target == NULL) { ++ TALLOC_FREE(symlink_err); ++ return NT_STATUS_NO_MEMORY; ++ } ++ + status = safe_symlink_target_path(mem_ctx, + conn->connectpath, +- name_in, +- substitute, ++ NULL, ++ target, + symlink_err->unparsed, + &safe_target); + TALLOC_FREE(symlink_err); ++ TALLOC_FREE(target); + if (!NT_STATUS_IS_OK(status)) { + return status; + } +-- +2.52.0 + + +From 9b8c2d3abe56b53b4ac7dfb6af927a889580ae7f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pavel=20Filipensk=C3=BD?= +Date: Mon, 19 Jan 2026 14:33:52 +0100 +Subject: [PATCH 70/96] s3:libads: Reset ads->config.flags in ads_disconnect() +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This is doing the same thing in ads_disconnect() as commit +a26f535 Clear previous CLDAP ping flags when reusing the ADS_STRUCT +did in ads_current_time() + +In this case we: + +1) found cached ADS_STRUCT which already has ads->config.flags set: + + lookup_groupmem() + ads_cached_connection() + ads_cached_connection_reuse() + +2) started search which immediately timeouts (the cached conn. was dead) + + ads_do_search_retry_internal() + ldap_search_with_timeout() - IO_TIMEOUT + +3) Retry loop finds a new DC and tries to connect + + ads_do_search_retry_internal() + ads_disconnect() + ads_find_dc() + ads_try_connect() + netlogon_pings() + check_cldap_reply_required_flags() + +4) check_cldap_reply_required_flags() fails since ads->config.flags + (stored possibly long time ago) contain: + + NBT_SERVER_CLOSEST 0x00000080 + which is misinterpreted as: + DS_PDC_REQUIRED 0x00000080 + + the newly found DC is not PDC (we asked for DS_ONLY_LDAP_NEEDED) + and since previous DC had NBT_SERVER_CLOSEST we want DS_PDC_REQUIRED + and fail. + +We should anyway avoid mixing independent namespaces NBT_* and DS_* +in the same flag. +Next commit will do that. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15972 + +Signed-off-by: Pavel Filipenský +Reviewed-by: Andreas Schneider +(cherry picked from commit 9f3a35991feb01a8d2c2b69fa0b914bbc637a809) +--- + source3/libads/ldap.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c +index cc00753ff74..625377fa2cc 100644 +--- a/source3/libads/ldap.c ++++ b/source3/libads/ldap.c +@@ -1068,6 +1068,7 @@ void ads_disconnect(ADS_STRUCT *ads) + if (ads->ldap_wrap_data.mem_ctx) { + talloc_free(ads->ldap_wrap_data.mem_ctx); + } ++ ads->config.flags = 0; + ads_zero_ldap(ads); + ZERO_STRUCT(ads->ldap_wrap_data); + } +-- +2.52.0 + + +From 1e1c43cc946f1947835570907064b4ee3aaa3ee7 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 30 Aug 2024 14:22:24 +0200 +Subject: [PATCH 71/96] 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 f56c55811ac..39b6efad567 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.52.0 + + +From 9149916376570a97ee0d94e9ed64751796da5053 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 30 Aug 2024 14:22:24 +0200 +Subject: [PATCH 72/96] 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 7447dd287a4..7eed4327b52 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.52.0 + + +From 986cc4d97eba6d3807170f66f6b3664f395a6e3e Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 30 Aug 2024 17:38:02 +0200 +Subject: [PATCH 73/96] 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 00000000000..3e3bd80ba46 +--- /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 39b6efad567..cc78c0aff66 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.52.0 + + +From cdffe7d7a0b6919fe405fbf4b536a6f33d33363f Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 30 Aug 2024 17:38:02 +0200 +Subject: [PATCH 74/96] 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 3e3bd80ba46..16273fb4ad4 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 7eed4327b52..685ef80c0cc 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.52.0 + + +From d770866bbdd9ba70f896eb3b75bba6baa01fdf33 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 30 Aug 2024 18:10:16 +0200 +Subject: [PATCH 75/96] 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 16273fb4ad4..00000000000 +--- 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 98d0d403e30..b7fa53e7555 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.52.0 + + +From 14ec1065ac307f5c59e292ac8f805104f9c10884 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Wed, 28 Aug 2024 16:48:27 +0200 +Subject: [PATCH 76/96] 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 00000000000..748b6c3150e +--- /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 00000000000..1bb0a70d9a0 +--- /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 685ef80c0cc..e86b1955092 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.52.0 + + +From 7c395c25b4e2d0365ef8c99404e5e9e88f0adf2d Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Wed, 4 Sep 2024 18:18:43 +0200 +Subject: [PATCH 77/96] 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 1bb0a70d9a0..1702a3a6580 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 00000000000..3a6380c6d65 +--- /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 e86b1955092..104796e76ad 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.52.0 + + +From 9b8a74aac964841fce8d032487678513a438274a Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 29 Aug 2024 20:20:23 +0200 +Subject: [PATCH 78/96] 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 b7fa53e7555..bf3cbed0a2c 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.52.0 + + +From 2aa438545f3d31dfb2be6c054e3dcbd2c4707caa Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Thu, 29 Aug 2024 18:43:14 +0200 +Subject: [PATCH 79/96] 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 1702a3a6580..00000000000 +--- 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 3fc7d56562a..4bbccdcd3bd 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.52.0 + + +From 2eca89f51181b452cc86277f45db234e8b00db74 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Fri, 30 Aug 2024 14:16:12 +0200 +Subject: [PATCH 80/96] 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) + +Autobuild-User(v4-21-test): Jule Anger +Autobuild-Date(v4-21-test): Mon Oct 14 11:09:14 UTC 2024 on atb-devel-224 +--- + 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 4e34effbbd1..2da5cbd6dfd 100644 +--- a/selftest/knownfail ++++ b/selftest/knownfail +@@ -219,7 +219,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 748b6c3150e..00000000000 +--- 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 4cc5190f690..2ccccb9eb75 100644 +--- a/source3/smbd/open.c ++++ b/source3/smbd/open.c +@@ -2485,7 +2485,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; +@@ -2593,15 +2593,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)) { +@@ -2835,9 +2847,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.52.0 + + +From 11fc816defc6e5d381c8ce8834dbf03fac4a2378 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Thu, 9 Jan 2025 08:57:17 +0100 +Subject: [PATCH 81/96] dbwrap: check for option "tdb_hash_size:DBNAME.tdb" in + db_open() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(cherry picked from commit 7eb135c42d530a16e80e165d9e8e99d920797f12) +--- + source3/lib/dbwrap/dbwrap_open.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/source3/lib/dbwrap/dbwrap_open.c b/source3/lib/dbwrap/dbwrap_open.c +index 52c8a94aeff..91556f22819 100644 +--- a/source3/lib/dbwrap/dbwrap_open.c ++++ b/source3/lib/dbwrap/dbwrap_open.c +@@ -80,6 +80,11 @@ struct db_context *db_open(TALLOC_CTX *mem_ctx, + base = name; + } + ++ hash_size = lp_parm_int(GLOBAL_SECTION_SNUM, ++ "tdb_hash_size", ++ base, ++ hash_size); ++ + if (tdb_flags & TDB_CLEAR_IF_FIRST) { + bool try_readonly = false; + +-- +2.52.0 + + +From 8d960286aa04b28b2e97fe7bfddb487c3694bef6 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Thu, 9 Jan 2025 12:27:43 +0100 +Subject: [PATCH 82/96] smbtorture: add test "open-brlock-deadlock" + +smbtorture reproducer for bug 15767. As it needs a very specific setup that +can't easily be done in selftest, the test is only executed when manually called +with + + --option=torture:open_brlock_deadlock_timemout=SEC + +To prepare the setup for the test set: + + tdb_hash_size:locking.tdb = 1 + tdb_hash_size:brlock.tdb = 1 + +and remove both tdb from disk which is needed so the TDBs get recreated with the +new hash_size. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(cherry picked from commit 7c60498cee7dca5770d4d1f623c472d585ae9cae) +--- + source4/torture/smb2/lock.c | 283 ++++++++++++++++++++++++++++++++++++ + 1 file changed, 283 insertions(+) + +diff --git a/source4/torture/smb2/lock.c b/source4/torture/smb2/lock.c +index eac0d557fc3..e5cf61a471a 100644 +--- a/source4/torture/smb2/lock.c ++++ b/source4/torture/smb2/lock.c +@@ -3465,6 +3465,288 @@ done: + return ret; + } + ++struct open_brlock_deadlock_state { ++ bool stop; ++ bool ok; ++ struct torture_context *tctx; ++ struct smb2_tree *tree1; ++ struct smb2_tree *tree2; ++ struct smb2_create cr; ++ struct smb2_request *cr_req; ++ struct smb2_close cl; ++ struct smb2_request *cl_req; ++ ++ struct smb2_lock_element el; ++ struct smb2_lock lock; ++ struct smb2_request *lock_req; ++}; ++ ++static void test_open_brlock_deadlock_loop_opened(struct smb2_request *req); ++ ++static void test_open_brlock_deadlock_loop_open( ++ struct open_brlock_deadlock_state *state) ++{ ++ if (state->stop) { ++ return; ++ } ++ ++ state->cr_req = smb2_create_send(state->tree1, &state->cr); ++ torture_assert_goto(state->tctx, state->cr_req != NULL, state->ok, failed, ++ "smb2_create_send failed\n"); ++ ++ state->cr_req->async.fn = test_open_brlock_deadlock_loop_opened; ++ state->cr_req->async.private_data = state; ++ return; ++failed: ++ state->stop = true; ++} ++ ++static void test_open_brlock_deadlock_loop_close( ++ struct open_brlock_deadlock_state *state); ++ ++static void test_open_brlock_deadlock_loop_opened(struct smb2_request *req) ++{ ++ struct open_brlock_deadlock_state *state = req->async.private_data; ++ TALLOC_CTX *frame = talloc_stackframe(); ++ NTSTATUS status; ++ ++ status = smb2_create_recv(req, frame, &state->cr); ++ torture_assert_ntstatus_ok_goto(state->tctx, status, ++ state->ok, failed, __location__); ++ state->cr_req = NULL; ++ ++ TALLOC_FREE(frame); ++ test_open_brlock_deadlock_loop_close(state); ++ return; ++ ++failed: ++ state->stop = true; ++ TALLOC_FREE(frame); ++} ++ ++static void test_open_brlock_deadlock_loop_closed(struct smb2_request *req); ++ ++static void test_open_brlock_deadlock_loop_close( ++ struct open_brlock_deadlock_state *state) ++{ ++ if (state->stop) { ++ return; ++ } ++ ++ state->cl.in.file = state->cr.out.file; ++ state->cl_req = smb2_close_send(state->tree1, &state->cl); ++ torture_assert_goto(state->tctx, state->cl_req != NULL, state->ok, failed, ++ "smb2_create_send failed\n"); ++ ++ state->cl_req->async.fn = test_open_brlock_deadlock_loop_closed; ++ state->cl_req->async.private_data = state; ++ return; ++failed: ++ state->stop = true; ++} ++ ++static void test_open_brlock_deadlock_loop_closed(struct smb2_request *req) ++{ ++ struct open_brlock_deadlock_state *state = req->async.private_data; ++ TALLOC_CTX *frame = talloc_stackframe(); ++ NTSTATUS status; ++ ++ status = smb2_close_recv(req, &state->cl); ++ torture_assert_ntstatus_ok_goto(state->tctx, status, ++ state->ok, failed, __location__); ++ state->cl_req = NULL; ++ ++ TALLOC_FREE(frame); ++ test_open_brlock_deadlock_loop_open(state); ++ return; ++ ++failed: ++ state->stop = true; ++ TALLOC_FREE(frame); ++} ++ ++static void test_open_brlock_deadlock_loop_locked(struct smb2_request *req); ++ ++static void test_open_brlock_deadlock_loop_lock( ++ struct open_brlock_deadlock_state *state) ++{ ++ if (state->stop) { ++ return; ++ } ++ ++ state->el.flags = SMB2_LOCK_FLAG_EXCLUSIVE; ++ ++ state->lock_req = smb2_lock_send(state->tree2, &state->lock); ++ torture_assert_goto(state->tctx, state->lock_req != NULL, ++ state->ok, failed, ++ "smb2_create_send failed\n"); ++ ++ state->lock_req->async.fn = test_open_brlock_deadlock_loop_locked; ++ state->lock_req->async.private_data = state; ++ return; ++failed: ++ state->stop = true; ++} ++ ++static void test_open_brlock_deadlock_loop_unlock( ++ struct open_brlock_deadlock_state *state); ++ ++static void test_open_brlock_deadlock_loop_locked(struct smb2_request *req) ++{ ++ struct open_brlock_deadlock_state *state = req->async.private_data; ++ TALLOC_CTX *frame = talloc_stackframe(); ++ NTSTATUS status; ++ ++ status = smb2_lock_recv(req, &state->lock); ++ torture_assert_ntstatus_ok_goto(state->tctx, status, ++ state->ok, failed, __location__); ++ state->lock_req = NULL; ++ ++ TALLOC_FREE(frame); ++ test_open_brlock_deadlock_loop_unlock(state); ++ return; ++ ++failed: ++ state->stop = true; ++ TALLOC_FREE(frame); ++} ++ ++static void test_open_brlock_deadlock_loop_unlocked(struct smb2_request *req); ++ ++static void test_open_brlock_deadlock_loop_unlock( ++ struct open_brlock_deadlock_state *state) ++{ ++ if (state->stop) { ++ return; ++ } ++ ++ state->el.flags = SMB2_LOCK_FLAG_UNLOCK; ++ ++ state->lock_req = smb2_lock_send(state->tree2, &state->lock); ++ torture_assert_goto(state->tctx, state->lock_req != NULL, ++ state->ok, failed, ++ "smb2_create_send failed\n"); ++ ++ state->lock_req->async.fn = test_open_brlock_deadlock_loop_unlocked; ++ state->lock_req->async.private_data = state; ++ return; ++failed: ++ state->stop = true; ++} ++ ++static void test_open_brlock_deadlock_loop_unlocked(struct smb2_request *req) ++{ ++ struct open_brlock_deadlock_state *state = req->async.private_data; ++ TALLOC_CTX *frame = talloc_stackframe(); ++ NTSTATUS status; ++ ++ status = smb2_lock_recv(req, &state->lock); ++ torture_assert_ntstatus_ok_goto(state->tctx, status, ++ state->ok, failed, __location__); ++ state->lock_req = NULL; ++ ++ TALLOC_FREE(frame); ++ test_open_brlock_deadlock_loop_lock(state); ++ return; ++ ++failed: ++ state->stop = true; ++ TALLOC_FREE(frame); ++} ++ ++ ++static void test_open_brlock_deadlock_timeout(struct tevent_context *ev, ++ struct tevent_timer *te, ++ struct timeval current_time, ++ void *private_data) ++{ ++ struct open_brlock_deadlock_state *state = private_data; ++ state->stop = true; ++} ++ ++static bool test_open_brlock_deadlock(struct torture_context *tctx, ++ struct smb2_tree *tree1, ++ struct smb2_tree *tree2) ++{ ++ int timeout_sec = torture_setting_int(tctx, "open_brlock_deadlock_timemout", 0); ++ const char *fname1 = BASEDIR "\\test_open_brlock_deadlock1.txt"; ++ const char *fname2 = BASEDIR "\\test_open_brlock_deadlock2.txt"; ++ struct open_brlock_deadlock_state state; ++ struct smb2_handle h = {}; ++ uint8_t buf[200]; ++ struct tevent_timer *te = NULL; ++ NTSTATUS status; ++ bool ret = true; ++ ++ if (timeout_sec == 0) { ++ torture_skip_goto(tctx, done, "Test skipped, pass '--option=torture:open_brlock_deadlock_timemout=SEC' to run\n"); ++ } ++ ++ state = (struct open_brlock_deadlock_state) { ++ .tctx = tctx, ++ .tree1 = tree1, ++ .tree2 = tree2, ++ }; ++ ++ ret = smb2_util_setup_dir(tctx, tree1, BASEDIR); ++ torture_assert_goto(tctx, ret, ret, done, ++ "smb2_util_setup_dir failed"); ++ ++ status = torture_smb2_testfile(tree1, fname1, &h); ++ torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ++ "torture_smb2_testfile failed"); ++ status = smb2_util_close(tree1, h); ++ torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ++ "smb2_util_close failed"); ++ ++ status = torture_smb2_testfile(tree2, fname2, &h); ++ torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ++ "torture_smb2_testfile failed"); ++ ++ status = smb2_util_write(tree2, h, buf, 0, ARRAY_SIZE(buf)); ++ torture_assert_ntstatus_ok_goto(tctx, status, ret, done, ++ "smb2_util_write failed"); ++ ++ state.cr = (struct smb2_create) { ++ .in.desired_access = SEC_FILE_READ_DATA, ++ .in.share_access = NTCREATEX_SHARE_ACCESS_MASK, ++ .in.create_disposition = NTCREATEX_DISP_OPEN, ++ .in.fname = fname1, ++ }; ++ ++ state.el = (struct smb2_lock_element) { ++ .length = 1, ++ .offset = 0, ++ }; ++ state.lock = (struct smb2_lock) { ++ .in.locks = &state.el, ++ .in.lock_count = 1, ++ .in.file.handle = h, ++ }; ++ ++ te = tevent_add_timer(tctx->ev, ++ tctx, ++ timeval_current_ofs(timeout_sec, 0), ++ test_open_brlock_deadlock_timeout, ++ &state); ++ torture_assert_goto(tctx, te != NULL, ret, done, __location__); ++ ++ test_open_brlock_deadlock_loop_open(&state); ++ test_open_brlock_deadlock_loop_lock(&state); ++ ++ while (!state.stop) { ++ int rc = tevent_loop_once(tctx->ev); ++ torture_assert_int_equal(tctx, rc, 0, "tevent_loop_once"); ++ } ++ ++done: ++ if (!smb2_util_handle_empty(h)) { ++ smb2_util_close(tree2, h); ++ } ++ smb2_deltree(tree1, BASEDIR); ++ return ret; ++} ++ + /* basic testing of SMB2 locking + */ + struct torture_suite *torture_smb2_lock_init(TALLOC_CTX *ctx) +@@ -3506,6 +3788,7 @@ struct torture_suite *torture_smb2_lock_init(TALLOC_CTX *ctx) + torture_suite_add_1smb2_test(suite, "replay_smb3_specification_multi", + test_replay_smb3_specification_multi); + torture_suite_add_1smb2_test(suite, "ctdb-delrec-deadlock", test_deadlock); ++ torture_suite_add_2smb2_test(suite, "open-brlock-deadlock", test_open_brlock_deadlock); + + suite->description = talloc_strdup(suite, "SMB2-LOCK tests"); + +-- +2.52.0 + + +From 241beffc5dee532a6f9a013d3f2d60e5dc7fb472 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Mon, 6 Jan 2025 15:59:27 +0100 +Subject: [PATCH 83/96] s3/brlock: split out brl_get_locks_readonly_parse() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Pair-Programmed-With: Ralph Boehme +Signed-off-by: Ralph Boehme +Signed-off-by: Stefan Metzmacher +(cherry picked from commit 94e7cbcc32b73e4d56e7209e04d22d4270a6eb5b) +--- + source3/locking/brlock.c | 43 ++++++++++++++++++++++++++-------------- + 1 file changed, 28 insertions(+), 15 deletions(-) + +diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c +index 905da049c58..a1713418d65 100644 +--- a/source3/locking/brlock.c ++++ b/source3/locking/brlock.c +@@ -1859,29 +1859,18 @@ static void brl_get_locks_readonly_parser(TDB_DATA key, TDB_DATA data, + *state->br_lock = br_lck; + } + +-struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp) ++static struct byte_range_lock *brl_get_locks_readonly_parse(TALLOC_CTX *mem_ctx, ++ files_struct *fsp) + { + struct byte_range_lock *br_lock = NULL; + struct brl_get_locks_readonly_state state; + NTSTATUS status; + +- DEBUG(10, ("seqnum=%d, fsp->brlock_seqnum=%d\n", +- dbwrap_get_seqnum(brlock_db), fsp->brlock_seqnum)); +- +- if ((fsp->brlock_rec != NULL) +- && (dbwrap_get_seqnum(brlock_db) == fsp->brlock_seqnum)) { +- /* +- * We have cached the brlock_rec and the database did not +- * change. +- */ +- return fsp->brlock_rec; +- } +- + /* + * Parse the record fresh from the database + */ + +- state.mem_ctx = fsp; ++ state.mem_ctx = mem_ctx; + state.br_lock = &br_lock; + + status = dbwrap_parse_record( +@@ -1894,7 +1883,7 @@ struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp) + /* + * No locks on this file. Return an empty br_lock. + */ +- br_lock = talloc_zero(fsp, struct byte_range_lock); ++ br_lock = talloc_zero(mem_ctx, struct byte_range_lock); + if (br_lock == NULL) { + return NULL; + } +@@ -1912,6 +1901,30 @@ struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp) + br_lock->modified = false; + br_lock->record = NULL; + ++ return br_lock; ++} ++ ++struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp) ++{ ++ struct byte_range_lock *br_lock = NULL; ++ ++ DEBUG(10, ("seqnum=%d, fsp->brlock_seqnum=%d\n", ++ dbwrap_get_seqnum(brlock_db), fsp->brlock_seqnum)); ++ ++ if ((fsp->brlock_rec != NULL) ++ && (dbwrap_get_seqnum(brlock_db) == fsp->brlock_seqnum)) { ++ /* ++ * We have cached the brlock_rec and the database did not ++ * change. ++ */ ++ return fsp->brlock_rec; ++ } ++ ++ br_lock = brl_get_locks_readonly_parse(fsp, fsp); ++ if (br_lock == NULL) { ++ return NULL; ++ } ++ + /* + * Cache the brlock struct, invalidated when the dbwrap_seqnum + * changes. See beginning of this routine. +-- +2.52.0 + + +From e0acfc837633b184e933a2a60cdf294229767f9a Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Mon, 6 Jan 2025 17:07:11 +0100 +Subject: [PATCH 84/96] s3/brlock: add brl_req_set() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Stefan Metzmacher +Reviewed-by: Ralph Boehme +(cherry picked from commit c9c04c7d75dee0c3e6e843b581624a3852042057) +--- + source3/locking/brlock.c | 9 +++++++++ + source3/locking/proto.h | 3 +++ + 2 files changed, 12 insertions(+) + +diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c +index a1713418d65..9629c68c3c8 100644 +--- a/source3/locking/brlock.c ++++ b/source3/locking/brlock.c +@@ -86,6 +86,15 @@ struct files_struct *brl_fsp(struct byte_range_lock *brl) + return brl->fsp; + } + ++ ++void brl_req_set(struct byte_range_lock *br_lck, ++ TALLOC_CTX *req_mem_ctx, ++ const struct GUID *req_guid) ++{ ++ br_lck->req_mem_ctx = req_mem_ctx; ++ br_lck->req_guid = req_guid; ++} ++ + TALLOC_CTX *brl_req_mem_ctx(const struct byte_range_lock *brl) + { + if (brl->req_mem_ctx == NULL) { +diff --git a/source3/locking/proto.h b/source3/locking/proto.h +index 7fc177d7aa6..3413596baed 100644 +--- a/source3/locking/proto.h ++++ b/source3/locking/proto.h +@@ -32,6 +32,9 @@ void brl_shutdown(void); + + unsigned int brl_num_locks(const struct byte_range_lock *brl); + struct files_struct *brl_fsp(struct byte_range_lock *brl); ++void brl_req_set(struct byte_range_lock *br_lck, ++ TALLOC_CTX *req_mem_ctx, ++ const struct GUID *req_guid); + TALLOC_CTX *brl_req_mem_ctx(const struct byte_range_lock *brl); + const struct GUID *brl_req_guid(const struct byte_range_lock *brl); + +-- +2.52.0 + + +From 862bec505a66f8c9958f96411fa2f05db8059ed1 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Sat, 1 Feb 2025 10:37:40 +0100 +Subject: [PATCH 85/96] s3/brlock: add share_mode_do_locked_brl() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Pair-Programmed-With: Stefan Metzmacher +Signed-off-by: Ralph Boehme +Signed-off-by: Stefan Metzmacher +(cherry picked from commit e17fb732c89f8b34de00904383044de3c4f85bd0) +--- + source3/locking/brlock.c | 103 +++++++++++++++++++++++++++++++++++++++ + source3/locking/proto.h | 8 +++ + 2 files changed, 111 insertions(+) + +diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c +index 9629c68c3c8..cf639451e99 100644 +--- a/source3/locking/brlock.c ++++ b/source3/locking/brlock.c +@@ -34,6 +34,7 @@ + #include "serverid.h" + #include "messages.h" + #include "util_tdb.h" ++#include "source3/locking/share_mode_lock.h" + + #undef DBGC_CLASS + #define DBGC_CLASS DBGC_LOCKING +@@ -2018,3 +2019,105 @@ done: + talloc_free(frame); + return ret; + } ++ ++struct share_mode_do_locked_brl_state { ++ share_mode_do_locked_brl_fn_t cb; ++ void *cb_data; ++ struct files_struct *fsp; ++ NTSTATUS status; ++}; ++ ++static void share_mode_do_locked_brl_fn(struct share_mode_lock *lck, ++ void *private_data) ++{ ++ struct share_mode_do_locked_brl_state *state = private_data; ++ struct byte_range_lock *br_lck = NULL; ++ TDB_DATA key = make_tdb_data((uint8_t *)&state->fsp->file_id, ++ sizeof(state->fsp->file_id)); ++ ++ if (lp_locking(state->fsp->conn->params) && ++ state->fsp->fsp_flags.can_lock) ++ { ++ br_lck = brl_get_locks_readonly_parse(talloc_tos(), ++ state->fsp); ++ if (br_lck == NULL) { ++ state->status = NT_STATUS_NO_MEMORY; ++ return; ++ } ++ } ++ ++ state->cb(lck, br_lck, state->cb_data); ++ ++ if (br_lck == NULL || !br_lck->modified) { ++ TALLOC_FREE(br_lck); ++ return; ++ } ++ ++ br_lck->record = dbwrap_fetch_locked(brlock_db, br_lck, key); ++ if (br_lck->record == NULL) { ++ DBG_ERR("Could not lock byte range lock entry for '%s'\n", ++ fsp_str_dbg(state->fsp)); ++ TALLOC_FREE(br_lck); ++ state->status = NT_STATUS_INTERNAL_DB_ERROR; ++ return; ++ } ++ ++ byte_range_lock_flush(br_lck); ++ share_mode_wakeup_waiters(br_lck->fsp->file_id); ++ TALLOC_FREE(br_lck); ++} ++ ++/* ++ * Run cb with a glock'ed locking.tdb record, providing both a share_mode_lock ++ * and a br_lck object. An initial read-only, but upgradable, br_lck object is ++ * fetched from brlock.tdb while holding the glock on the locking.tdb record. ++ * ++ * This function only ever hold one low-level TDB chainlock at a time on either ++ * locking.tdb or brlock.tdb, so it can't run afoul any lock order violations. ++ * ++ * Note that br_lck argument in the callback might be NULL in case lp_locking() ++ * is disabled, the fsp doesn't allow locking or is a directory, so either ++ * the caller or the callback have to check for this. ++ */ ++NTSTATUS share_mode_do_locked_brl(files_struct *fsp, ++ share_mode_do_locked_brl_fn_t cb, ++ void *cb_data) ++{ ++ static bool recursion_guard; ++ TALLOC_CTX *frame = NULL; ++ struct share_mode_do_locked_brl_state state = { ++ .fsp = fsp, ++ .cb = cb, ++ .cb_data = cb_data, ++ }; ++ NTSTATUS status; ++ ++ SMB_ASSERT(!recursion_guard); ++ ++ /* silently return ok on print files as we don't do locking there */ ++ if (fsp->print_file) { ++ return NT_STATUS_OK; ++ } ++ ++ frame = talloc_stackframe(); ++ ++ recursion_guard = true; ++ status = share_mode_do_locked_vfs_allowed( ++ fsp->file_id, ++ share_mode_do_locked_brl_fn, ++ &state); ++ recursion_guard = false; ++ if (!NT_STATUS_IS_OK(status)) { ++ DBG_ERR("share_mode_do_locked_vfs_allowed() failed for %s - %s\n", ++ fsp_str_dbg(fsp), nt_errstr(status)); ++ TALLOC_FREE(frame); ++ return status; ++ } ++ if (!NT_STATUS_IS_OK(state.status)) { ++ TALLOC_FREE(frame); ++ return state.status; ++ } ++ ++ TALLOC_FREE(frame); ++ return NT_STATUS_OK; ++} +diff --git a/source3/locking/proto.h b/source3/locking/proto.h +index 3413596baed..c9d769ba53f 100644 +--- a/source3/locking/proto.h ++++ b/source3/locking/proto.h +@@ -87,6 +87,14 @@ struct byte_range_lock *brl_get_locks_for_locking(TALLOC_CTX *mem_ctx, + files_struct *fsp, + TALLOC_CTX *req_mem_ctx, + const struct GUID *req_guid); ++struct share_mode_lock; ++typedef void (*share_mode_do_locked_brl_fn_t)( ++ struct share_mode_lock *lck, ++ struct byte_range_lock *br_lck, /* br_lck can be NULL */ ++ void *private_data); ++NTSTATUS share_mode_do_locked_brl(files_struct *fsp, ++ share_mode_do_locked_brl_fn_t fn, ++ void *private_data); + struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx, + files_struct *fsp); + struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp); +-- +2.52.0 + + +From d18f6942b66eb7166f8929e9b475d0acbd721cb0 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Wed, 8 Jan 2025 15:43:04 +0100 +Subject: [PATCH 86/96] s3/brlock: don't increment current_lock_count if + do_lock_fn() failed + +Also only assign psmblctx and pblocker_pid if the lock request failed. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(cherry picked from commit 3a0c6e99de4377f44bc29766b6ceb79040caed9f) +--- + source3/locking/locking.c | 20 +++++++++++--------- + 1 file changed, 11 insertions(+), 9 deletions(-) + +diff --git a/source3/locking/locking.c b/source3/locking/locking.c +index 6ee5987ffda..d796e6ffb7b 100644 +--- a/source3/locking/locking.c ++++ b/source3/locking/locking.c +@@ -340,19 +340,21 @@ NTSTATUS do_lock(files_struct *fsp, + nt_errstr(status)); + return status; + } +- +- if (psmblctx != NULL) { +- *psmblctx = state.blocker_smblctx; +- } +- if (pblocker_pid != NULL) { +- *pblocker_pid = state.blocker_pid; ++ if (!NT_STATUS_IS_OK(state.status)) { ++ DBG_DEBUG("do_lock_fn returned %s\n", ++ nt_errstr(state.status)); ++ if (psmblctx != NULL) { ++ *psmblctx = state.blocker_smblctx; ++ } ++ if (pblocker_pid != NULL) { ++ *pblocker_pid = state.blocker_pid; ++ } ++ return state.status; + } + +- DBG_DEBUG("returning status=%s\n", nt_errstr(state.status)); +- + increment_current_lock_count(fsp, lock_flav); + +- return state.status; ++ return NT_STATUS_OK; + } + + /**************************************************************************** +-- +2.52.0 + + +From 4a6c36f1d31556e1abcdbf090f76d4c49b261e92 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Wed, 29 Jan 2025 06:13:29 +0100 +Subject: [PATCH 87/96] s3/locking: add brl_set_modified() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(cherry picked from commit 2772f147c9b13cd2160181c4f7905b54ab765054) +--- + source3/locking/brlock.c | 5 +++++ + source3/locking/proto.h | 1 + + 2 files changed, 6 insertions(+) + +diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c +index cf639451e99..e0d18800cf1 100644 +--- a/source3/locking/brlock.c ++++ b/source3/locking/brlock.c +@@ -2121,3 +2121,8 @@ NTSTATUS share_mode_do_locked_brl(files_struct *fsp, + TALLOC_FREE(frame); + return NT_STATUS_OK; + } ++ ++void brl_set_modified(struct byte_range_lock *br_lck, bool modified) ++{ ++ br_lck->modified = modified; ++} +diff --git a/source3/locking/proto.h b/source3/locking/proto.h +index c9d769ba53f..c74539c8161 100644 +--- a/source3/locking/proto.h ++++ b/source3/locking/proto.h +@@ -99,6 +99,7 @@ struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx, + files_struct *fsp); + struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp); + bool brl_cleanup_disconnected(struct file_id fid, uint64_t open_persistent_id); ++void brl_set_modified(struct byte_range_lock *br_lck, bool modified); + + /* The following definitions come from locking/locking.c */ + +-- +2.52.0 + + +From 3362898470c1efa07bb9e05f6663a42fef53c784 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Wed, 29 Jan 2025 06:13:44 +0100 +Subject: [PATCH 88/96] smbd: use share_mode_do_locked_brl() + +Fix a deadlock that can happen if two clients happen to open and byte-range-lock +two different files whos record in locking.tdb and brlock.tdb happen to sit on +the same hashchain. + +The deadlock was introduced by commit +680c7907325b433856ac1dd916ab63e671fbe4ab. Before, we used share_mode_do_locked() +in do_lock() which meant we acquired a chainlock on locking.tdb before getting a +chainlock on brlock.tdb via brl_get_locks_for_locking(), so the TDB chainlock +order invariant was always uphold. + +The following race between specific client requests lead to the deadlock. + +Client A) issues a byte-range-lock request on a file: + +A1) glock locking.tdb (via _share_mode_do_locked_vfs_allowed()) +A2) chainlock brlock.tdb (via brl_lock()) +A3) attempt to chainlock locking.tdb (via share_mode_g_lock_dump()) +[1] + +Client B) opens a different (!) file: + +B1) glock and chainlock locking.tdb (via _share_mode_entry_prepare_lock()) +B2) attempt to chainlock brlock.tdb (via file_has_brlocks()) +[2] + +The glock from A1 is per record and hence doesn't synchronize with the glock +from B1 as it is for a different file and hence a different record, subsequently +A2 and A3 violate the lock order constraint + +To avoid the chainlock lock order violation in the second client we modify the +br-lock code to not take the brlock.tdb chainlock from step A2 via +br_get_locks() for the whole time we process the request. Instead we just fetch +the br-locks via br_get_locks_readonly(), so when running into +contend_level2_oplocks_begin_default() to check for leases and looking into +locking.tdb we don't hold a brlock.tdb chainlock. + +Or im simpler terms, we only ever take at most one low-level TDB chainlock at a +time: + +Byte-range-lock code calls share_mode_do_locked_brl(..., cb_fn, ...): +1) chainlock locking.tdb +2) glock locking.tdb (via share_mode_do_locked_vfs_allowed()) +3) chainunlock locking.tdb +4) share_mode_do_locked_brl_fn() -> brl_get_locks_readonly_parse(): + a) chainlock brlock.tdb + b) parse record and store in-memory copy + c) chainunlock brlock.tdb +5) run cb_fn() +6) chainlock brlock.tdb: + a) br_lck->record = dbwrap_fetch_locked(brlock_db, ...) + b) store modifed br_lck from 5) via byte_range_lock_flush() +7) chainunlock brlock.tdb +8) chainlock locking.tdb +9) gunlock locking.tdb +10) chainunlock locking.tdb + +All access to brlock.tdb is synchronized correctly via glocks on the locking.tdb +record of the file (step 3)), so operations still appear atomic to clients. + +As a result of using share_mode_do_locked_brl(), the functions do_[un]lock() -> +brl_[un]lock() now loop over the same br_lck object in memory, avoiding +repeatedly fetching and storing the locks per loop. + +[1] +Full SBT: + + #0 0x00007fffa0cecbb0 in __pthread_mutex_lock_full () from /lib64/glibc-hwcaps/power9/libpthread-2.28.so + #1 0x00007fffa0a73cf8 in chain_mutex_lock (m=, m@entry=0x7fff9ae071b0, waitflag=, waitflag@entry=true) at ../../lib/tdb/common/mutex.c:182 + #2 0x00007fffa0a7432c in tdb_mutex_lock (tdb=0x1543ba120, rw=, off=, len=, waitflag=, pret=0x7fffd7df3858) at ../../lib/tdb/common/mutex.c:234 + #3 0x00007fffa0a6812c in fcntl_lock (waitflag=, len=1, off=376608, rw=0, tdb=0x1543ba120) at ../../lib/tdb/common/lock.c:200 + #4 tdb_brlock (tdb=0x1543ba120, rw_type=, offset=, len=1, flags=) at ../../lib/tdb/common/lock.c:200 + #5 0x00007fffa0a68af8 in tdb_nest_lock (flags=, ltype=0, offset=, tdb=0x1543ba120) at ../../lib/tdb/common/lock.c:390 + #6 tdb_nest_lock (tdb=0x1543ba120, offset=, ltype=, flags=) at ../../lib/tdb/common/lock.c:336 + #7 0x00007fffa0a69088 in tdb_lock_list (tdb=0x1543ba120, list=, ltype=, waitflag=) at ../../lib/tdb/common/lock.c:482 + #8 0x00007fffa0a69198 in tdb_lock (tdb=0x1543ba120, list=, ltype=) at ../../lib/tdb/common/lock.c:500 + #9 0x00007fffa0a64b50 in tdb_find_lock_hash (tdb=, tdb@entry=0x1543ba120, key=..., hash=, locktype=, locktype@entry=0, rec=, rec@entry=0x7fffd7df3ab0) at ../../lib/tdb/common/tdb.c:165 + #10 0x00007fffa0a64ed0 in tdb_parse_record (tdb=0x1543ba120, key=..., parser=0x7fffa0e74470 , private_data=0x7fffd7df3b18) at ../../lib/tdb/common/tdb.c:329 + #11 0x00007fffa0e74cbc in db_ctdb_ltdb_parse (db=, private_data=0x7fffd7df3b70, parser=0x7fffa0e76470 , key=...) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:170 + #12 db_ctdb_try_parse_local_record (ctx=ctx@entry=0x1543d4580, key=..., state=state@entry=0x7fffd7df3b70) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:1385 + #13 0x00007fffa0e76024 in db_ctdb_parse_record (db=, key=..., parser=0x7fffa1313910 , private_data=0x7fffd7df3c08) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:1425 + #14 0x00007fffa0884760 in dbwrap_parse_record (db=, key=..., parser=, private_data=) at ../../lib/dbwrap/dbwrap.c:454 + #15 0x00007fffa1313ab4 in dbwrap_watched_parse_record (db=0x1543a7160, key=..., parser=0x7fffa13187d0 , private_data=0x7fffd7df3ce8) at ../../source3/lib/dbwrap/dbwrap_watch.c:783 + #16 0x00007fffa0884760 in dbwrap_parse_record (db=, key=..., parser=, private_data=) at ../../lib/dbwrap/dbwrap.c:454 + #17 0x00007fffa131c004 in g_lock_dump (ctx=, key=..., fn=0x7fffa14f3d70 , private_data=0x7fffd7df3dd8) at ../../source3/lib/g_lock.c:1653 + #18 0x00007fffa14f434c in share_mode_g_lock_dump (key=..., fn=0x7fffa14f3d70 , private_data=0x7fffd7df3dd8) at ../../source3/locking/share_mode_lock.c:96 + #19 0x00007fffa14f8d44 in fsp_update_share_mode_flags (fsp=0x15433c550) at ../../source3/locking/share_mode_lock.c:1181 + #20 file_has_read_lease (fsp=0x15433c550) at ../../source3/locking/share_mode_lock.c:1207 + #21 0x00007fffa15ccc98 in contend_level2_oplocks_begin_default (type=, fsp=0x15433c550) at ../../source3/smbd/smb2_oplock.c:1282 + #22 smbd_contend_level2_oplocks_begin (fsp=0x15433c550, type=) at ../../source3/smbd/smb2_oplock.c:1338 + #23 0x00007fffa0dd0b54 in contend_level2_oplocks_begin (fsp=, type=) at ../../source3/lib/smbd_shim.c:72 + #24 0x00007fffa14ecfd0 in brl_lock_windows_default (br_lck=0x154421330, plock=0x7fffd7df4250) at ../../source3/locking/brlock.c:457 + #25 0x00007fffa150b70c in vfswrap_brl_lock_windows (handle=, br_lck=, plock=) at ../../source3/modules/vfs_default.c:3424 + #26 0x00007fffa1561910 in smb_vfs_call_brl_lock_windows (handle=, br_lck=, plock=) at ../../source3/smbd/vfs.c:2686 + #27 0x00007fff9c0a7350 in smb_time_audit_brl_lock_windows (handle=, br_lck=0x154421330, plock=0x7fffd7df4250) at ../../source3/modules/vfs_time_audit.c:1740 + #28 0x00007fffa1561910 in smb_vfs_call_brl_lock_windows (handle=, br_lck=, plock=) at ../../source3/smbd/vfs.c:2686 + #29 0x00007fffa14ed410 in brl_lock (br_lck=0x154421330, smblctx=3102281601, pid=..., start=0, size=18446744073709551615, lock_type=, lock_flav=WINDOWS_LOCK, blocker_pid=0x7fffd7df4540, psmblctx=0x7fffd7df4558) at ../../source3/locking/brlock.c:1004 + #30 0x00007fffa14e7b18 in do_lock_fn (lck=, private_data=0x7fffd7df4508) at ../../source3/locking/locking.c:271 + #31 0x00007fffa14fcd94 in _share_mode_do_locked_vfs_allowed (id=..., fn=0x7fffa14e7a60 , private_data=0x7fffd7df4508, location=) at ../../source3/locking/share_mode_lock.c:2927 + #32 0x00007fffa14e918c in do_lock (fsp=0x15433c550, req_mem_ctx=, req_guid=, smblctx=, count=18446744073709551615, offset=0, lock_type=, lock_flav=, pblocker_pid=0x7fffd7df46f0, + psmblctx=0x7fffd7df46d8) at ../../source3/locking/locking.c:335 + #33 0x00007fffa155381c in smbd_do_locks_try (fsp=0x15433c550, num_locks=, locks=0x1543bc310, blocker_idx=0x7fffd7df46d6, blocking_pid=0x7fffd7df46f0, blocking_smblctx=0x7fffd7df46d8) at ../../source3/smbd/blocking.c:46 + #34 0x00007fffa159dc90 in smbd_smb2_lock_try (req=req@entry=0x1543bc080) at ../../source3/smbd/smb2_lock.c:590 + #35 0x00007fffa159ee8c in smbd_smb2_lock_send (in_locks=, in_lock_count=1, in_lock_sequence=, fsp=0x15433c550, smb2req=0x1543532e0, ev=0x154328120, mem_ctx=0x1543532e0) at ../../source3/smbd/smb2_lock.c:488 + #36 smbd_smb2_request_process_lock (req=0x1543532e0) at ../../source3/smbd/smb2_lock.c:150 + #37 0x00007fffa158a368 in smbd_smb2_request_dispatch (req=0x1543532e0) at ../../source3/smbd/smb2_server.c:3515 + #38 0x00007fffa158c540 in smbd_smb2_io_handler (fde_flags=, xconn=0x154313f30) at ../../source3/smbd/smb2_server.c:5112 + #39 smbd_smb2_connection_handler (ev=, fde=, flags=, private_data=) at ../../source3/smbd/smb2_server.c:5150 + #40 0x00007fffa1198b2c in tevent_common_invoke_fd_handler (fde=0x1543670f0, flags=, removed=0x0) at ../../lib/tevent/tevent_fd.c:158 + #41 0x00007fffa11a2b9c in epoll_event_loop (tvalp=0x7fffd7df4b28, epoll_ev=0x1543b4e80) at ../../lib/tevent/tevent_epoll.c:730 + #42 epoll_event_loop_once (ev=, location=) at ../../lib/tevent/tevent_epoll.c:946 + #43 0x00007fffa11a0090 in std_event_loop_once (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent_standard.c:110 + #44 0x00007fffa119744c in _tevent_loop_once (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent.c:823 + #45 0x00007fffa1197884 in tevent_common_loop_wait (ev=, location=) at ../../lib/tevent/tevent.c:950 + #46 0x00007fffa119ffc0 in std_event_loop_wait (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent_standard.c:141 + #47 0x00007fffa1197978 in _tevent_loop_wait (ev=, location=) at ../../lib/tevent/tevent.c:971 + #48 0x00007fffa15737fc in smbd_process (ev_ctx=0x154328120, msg_ctx=, sock_fd=, interactive=) at ../../source3/smbd/smb2_process.c:2158 + #49 0x000000011db5c554 in smbd_accept_connection (ev=0x154328120, fde=, flags=, private_data=) at ../../source3/smbd/server.c:1150 + #50 0x00007fffa1198b2c in tevent_common_invoke_fd_handler (fde=0x1543ac2d0, flags=, removed=0x0) at ../../lib/tevent/tevent_fd.c:158 + #51 0x00007fffa11a2b9c in epoll_event_loop (tvalp=0x7fffd7df4f98, epoll_ev=0x154328350) at ../../lib/tevent/tevent_epoll.c:730 + #52 epoll_event_loop_once (ev=, location=) at ../../lib/tevent/tevent_epoll.c:946 + #53 0x00007fffa11a0090 in std_event_loop_once (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent_standard.c:110 + #54 0x00007fffa119744c in _tevent_loop_once (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent.c:823 + #55 0x00007fffa1197884 in tevent_common_loop_wait (ev=, location=) at ../../lib/tevent/tevent.c:950 + #56 0x00007fffa119ffc0 in std_event_loop_wait (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent_standard.c:141 + #57 0x00007fffa1197978 in _tevent_loop_wait (ev=, location=) at ../../lib/tevent/tevent.c:971 + #58 0x000000011db58c54 in smbd_parent_loop (parent=, ev_ctx=0x154328120) at ../../source3/smbd/server.c:1499 + #59 main (argc=, argv=) at ../../source3/smbd/server.c:2258 + +[2] +Full SBT: + + #0 0x00007fffa0cecbb0 in __pthread_mutex_lock_full () from /lib64/glibc-hwcaps/power9/libpthread-2.28.so + #1 0x00007fffa0a73cf8 in chain_mutex_lock (m=, m@entry=0x7fff9b3a71b0, waitflag=, waitflag@entry=true) at ../../lib/tdb/common/mutex.c:182 + #2 0x00007fffa0a7432c in tdb_mutex_lock (tdb=0x1543c6900, rw=, off=, len=, waitflag=, pret=0x7fffd7df2e28) at ../../lib/tdb/common/mutex.c:234 + #3 0x00007fffa0a6812c in fcntl_lock (waitflag=, len=1, off=376608, rw=0, tdb=0x1543c6900) at ../../lib/tdb/common/lock.c:200 + #4 tdb_brlock (tdb=0x1543c6900, rw_type=, offset=, len=1, flags=) at ../../lib/tdb/common/lock.c:200 + #5 0x00007fffa0a68af8 in tdb_nest_lock (flags=, ltype=0, offset=, tdb=0x1543c6900) at ../../lib/tdb/common/lock.c:390 + #6 tdb_nest_lock (tdb=0x1543c6900, offset=, ltype=, flags=) at ../../lib/tdb/common/lock.c:336 + #7 0x00007fffa0a69088 in tdb_lock_list (tdb=0x1543c6900, list=, ltype=, waitflag=) at ../../lib/tdb/common/lock.c:482 + #8 0x00007fffa0a69198 in tdb_lock (tdb=0x1543c6900, list=, ltype=) at ../../lib/tdb/common/lock.c:500 + #9 0x00007fffa0a64b50 in tdb_find_lock_hash (tdb=, tdb@entry=0x1543c6900, key=..., hash=, locktype=, locktype@entry=0, rec=, rec@entry=0x7fffd7df3080) at ../../lib/tdb/common/tdb.c:165 + #10 0x00007fffa0a64ed0 in tdb_parse_record (tdb=0x1543c6900, key=..., parser=0x7fffa0e74470 , private_data=0x7fffd7df30e8) at ../../lib/tdb/common/tdb.c:329 + #11 0x00007fffa0e74cbc in db_ctdb_ltdb_parse (db=, private_data=0x7fffd7df3140, parser=0x7fffa0e76470 , key=...) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:170 + #12 db_ctdb_try_parse_local_record (ctx=ctx@entry=0x154328fc0, key=..., state=state@entry=0x7fffd7df3140) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:1385 + #13 0x00007fffa0e76024 in db_ctdb_parse_record (db=, key=..., parser=0x7fffa14ec820 , private_data=0x7fffd7df3218) at ../../source3/lib/dbwrap/dbwrap_ctdb.c:1425 + #14 0x00007fffa0884760 in dbwrap_parse_record (db=, key=..., parser=, private_data=) at ../../lib/dbwrap/dbwrap.c:454 + #15 0x00007fffa14ef5bc in brl_get_locks_readonly (fsp=0x1543d01e0) at ../../source3/locking/brlock.c:1884 + #16 0x00007fffa1546968 in file_has_brlocks (fsp=0x1543d01e0) at ../../source3/smbd/open.c:2232 + #17 delay_for_oplock (pgranted=, poplock_type=, first_open_attempt=, create_disposition=1, have_sharing_violation=false, lck=0x7fffd7df3ce8, lease=0x0, oplock_request=0, fsp=0x1543d01e0) at ../../source3/smbd/open.c:2749 + #18 handle_share_mode_lease (pgranted=, poplock_type=, first_open_attempt=, lease=0x0, oplock_request=0, share_access=7, access_mask=131201, create_disposition=1, lck=0x7fffd7df3ce8, fsp=0x1543d01e0) at ../../source3/smbd/open.c:2865 + #19 check_and_store_share_mode (first_open_attempt=, lease=0x0, oplock_request=0, share_access=7, access_mask=131201, create_disposition=1, lck=0x7fffd7df3ce8, req=0x154414800, fsp=0x1543d01e0) at ../../source3/smbd/open.c:3333 + #20 open_ntcreate_lock_add_entry (lck=0x7fffd7df3ce8, keep_locked=0x7fffd7df3ad0, private_data=0x7fffd7df3cc8) at ../../source3/smbd/open.c:3688 + #21 0x00007fffa14f6248 in share_mode_entry_prepare_lock_fn (glck=0x7fffd7df35b8, cb_private=0x7fffd7df3a88) at ../../source3/locking/share_mode_lock.c:2978 + #22 0x00007fffa1317680 in g_lock_lock_cb_run_and_store (cb_state=cb_state@entry=0x7fffd7df35b8) at ../../source3/lib/g_lock.c:597 + #23 0x00007fffa1319df8 in g_lock_lock_simple_fn (rec=0x7fffd7df3798, value=..., private_data=0x7fffd7df39a0) at ../../source3/lib/g_lock.c:1212 + #24 0x00007fffa13160e0 in dbwrap_watched_do_locked_fn (backend_rec=, backend_value=..., private_data=0x7fffd7df3768) at ../../source3/lib/dbwrap/dbwrap_watch.c:458 + #25 0x00007fffa0884e48 in dbwrap_do_locked (db=, key=..., fn=0x7fffa1316080 , private_data=0x7fffd7df3768) at ../../lib/dbwrap/dbwrap.c:602 + #26 0x00007fffa1315274 in dbwrap_watched_do_locked (db=0x1543a7160, key=..., fn=0x7fffa1319ca0 , private_data=0x7fffd7df39a0) at ../../source3/lib/dbwrap/dbwrap_watch.c:480 + #27 0x00007fffa0884d60 in dbwrap_do_locked (db=, key=..., fn=, private_data=) at ../../lib/dbwrap/dbwrap.c:582 + #28 0x00007fffa131b458 in g_lock_lock (ctx=0x1543cc630, key=..., type=, timeout=..., cb_fn=0x7fffa14f6190 , cb_private=0x7fffd7df3a88) at ../../source3/lib/g_lock.c:1267 + #29 0x00007fffa14fd060 in _share_mode_entry_prepare_lock (prepare_state=0x7fffd7df3cc8, id=..., servicepath=, smb_fname=, old_write_time=, fn=, private_data=0x7fffd7df3cc8, location=0x7fffa165b880 "../../source3/smbd/open.c:4292") at ../../source3/locking/share_mode_lock.c:3033 + #30 0x00007fffa15491e0 in open_file_ntcreate (conn=conn@entry=0x154382050, req=req@entry=0x154414800, access_mask=, access_mask@entry=131201, share_access=share_access@entry=7, create_disposition=create_disposition@entry=1, create_options=create_options@entry=0, new_dos_attributes=, new_dos_attributes@entry=128, oplock_request=oplock_request@entry=0, lease=, lease@entry=0x0, private_flags=, private_flags@entry=0, parent_dir_fname=, smb_fname_atname=, pinfo=, pinfo@entry=0x7fffd7df3f1c, fsp=, fsp@entry=0x1543d01e0) at ../../source3/smbd/open.c:4286 + #31 0x00007fffa154b94c in create_file_unixpath (conn=conn@entry=0x154382050, req=req@entry=0x154414800, dirfsp=dirfsp@entry=0x15439a7f0, smb_fname=smb_fname@entry=0x154416300, access_mask=access_mask@entry=131201, share_access=share_access@entry=7, create_disposition=create_disposition@entry=1, create_options=create_options@entry=0, file_attributes=file_attributes@entry=128, oplock_request=, oplock_request@entry=0, lease=, lease@entry=0x0, allocation_size=allocation_size@entry=0, private_flags=private_flags@entry=0, sd=sd@entry=0x0, ea_list=ea_list@entry=0x0, result=result@entry=0x7fffd7df4168, pinfo=pinfo@entry=0x7fffd7df4160) at ../../source3/smbd/open.c:6290 + #32 0x00007fffa154dfac in create_file_default (conn=0x154382050, req=0x154414800, dirfsp=0x15439a7f0, smb_fname=0x154416300, access_mask=, share_access=, create_disposition=, create_options=, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/smbd/open.c:6609 + #33 0x00007fffa150972c in vfswrap_create_file (handle=, req=, dirfsp=, smb_fname=, access_mask=, share_access=, create_disposition=, create_options=, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/modules/vfs_default.c:776 + #34 0x00007fffa1559cbc in smb_vfs_call_create_file (handle=, req=, dirfsp=, smb_fname=, access_mask=, share_access=, create_disposition=, create_options=, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/smbd/vfs.c:1560 + #35 0x00007fff9c0a9ec4 in smb_time_audit_create_file (handle=0x154426820, req=0x154414800, dirfsp=0x15439a7f0, fname=0x154416300, access_mask=, share_access=, create_disposition=, create_options=, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result_fsp=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/modules/vfs_time_audit.c:634 + #36 0x00007fffa1559cbc in smb_vfs_call_create_file (handle=, req=, dirfsp=, smb_fname=, access_mask=, share_access=, create_disposition=, create_options=, file_attributes=128, oplock_request=0, lease=0x0, allocation_size=0, private_flags=0, sd=0x0, ea_list=0x0, result=0x1544144e8, pinfo=0x1544144fc, in_context_blobs=0x7fffd7df4798, out_context_blobs=0x154414710) at ../../source3/smbd/vfs.c:1560 + #37 0x00007fffa1597aa8 in smbd_smb2_create_send (in_context_blobs=..., in_name=0x154413ca0, in_create_options=, in_create_disposition=, in_share_access=, in_file_attributes=, in_desired_access=, in_impersonation_level=, in_oplock_level=, smb2req=0x154413770, ev=0x154328120, mem_ctx=0x154413770) at ../../source3/smbd/smb2_create.c:1115 + #38 smbd_smb2_request_process_create (smb2req=0x154413770) at ../../source3/smbd/smb2_create.c:291 + #39 0x00007fffa158a628 in smbd_smb2_request_dispatch (req=0x154413770) at ../../source3/smbd/smb2_server.c:3485 + #40 0x00007fffa158c540 in smbd_smb2_io_handler (fde_flags=, xconn=0x154313f30) at ../../source3/smbd/smb2_server.c:5112 + #41 smbd_smb2_connection_handler (ev=, fde=, flags=, private_data=) at ../../source3/smbd/smb2_server.c:5150 + #42 0x00007fffa1198b2c in tevent_common_invoke_fd_handler (fde=0x15435add0, flags=, removed=0x0) at ../../lib/tevent/tevent_fd.c:158 + #43 0x00007fffa11a2b9c in epoll_event_loop (tvalp=0x7fffd7df4b28, epoll_ev=0x1543b4e80) at ../../lib/tevent/tevent_epoll.c:730 + #44 epoll_event_loop_once (ev=, location=) at ../../lib/tevent/tevent_epoll.c:946 + #45 0x00007fffa11a0090 in std_event_loop_once (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent_standard.c:110 + #46 0x00007fffa119744c in _tevent_loop_once (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent.c:823 + #47 0x00007fffa1197884 in tevent_common_loop_wait (ev=, location=) at ../../lib/tevent/tevent.c:950 + #48 0x00007fffa119ffc0 in std_event_loop_wait (ev=0x154328120, location=0x7fffa1668db8 "../../source3/smbd/smb2_process.c:2158") at ../../lib/tevent/tevent_standard.c:141 + #49 0x00007fffa1197978 in _tevent_loop_wait (ev=, location=) at ../../lib/tevent/tevent.c:971 + #50 0x00007fffa15737fc in smbd_process (ev_ctx=0x154328120, msg_ctx=, sock_fd=, interactive=) at ../../source3/smbd/smb2_process.c:2158 + #51 0x000000011db5c554 in smbd_accept_connection (ev=0x154328120, fde=, flags=, private_data=) at ../../source3/smbd/server.c:1150 + #52 0x00007fffa1198b2c in tevent_common_invoke_fd_handler (fde=0x1543ac2d0, flags=, removed=0x0) at ../../lib/tevent/tevent_fd.c:158 + #53 0x00007fffa11a2b9c in epoll_event_loop (tvalp=0x7fffd7df4f98, epoll_ev=0x154328350) at ../../lib/tevent/tevent_epoll.c:730 + #54 epoll_event_loop_once (ev=, location=) at ../../lib/tevent/tevent_epoll.c:946 + #55 0x00007fffa11a0090 in std_event_loop_once (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent_standard.c:110 + #56 0x00007fffa119744c in _tevent_loop_once (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent.c:823 + #57 0x00007fffa1197884 in tevent_common_loop_wait (ev=, location=) at ../../lib/tevent/tevent.c:950 + #58 0x00007fffa119ffc0 in std_event_loop_wait (ev=0x154328120, location=0x11db60b50 "../../source3/smbd/server.c:1499") at ../../lib/tevent/tevent_standard.c:141 + #59 0x00007fffa1197978 in _tevent_loop_wait (ev=, location=) at ../../lib/tevent/tevent.c:971 + #60 0x000000011db58c54 in smbd_parent_loop (parent=, ev_ctx=0x154328120) at ../../source3/smbd/server.c:1499 + #61 main (argc=, argv=) at ../../source3/smbd/server.c:2258 + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Pair-Programmed-With: Stefan Metzmacher +Signed-off-by: Ralph Boehme +Signed-off-by: Stefan Metzmacher +(backported from commit 2eef298ff4c5baf15c7d29c65fb021dbed5b0a93) +[slow@samba.org: changed argument of share_mode_watch_send()] +[slow@samba.org: small context change in vfs_fruit] +--- + source3/locking/locking.c | 109 +++++-------------- + source3/locking/proto.h | 4 +- + source3/modules/vfs_fruit.c | 91 +++++++++++----- + source3/smbd/blocking.c | 202 +++++++++++++++++++----------------- + source3/smbd/proto.h | 19 ++-- + source3/smbd/smb2_lock.c | 77 +++++++++----- + source3/smbd/smb2_reply.c | 53 ++++++---- + 7 files changed, 293 insertions(+), 262 deletions(-) + +diff --git a/source3/locking/locking.c b/source3/locking/locking.c +index d796e6ffb7b..25b3bdcf7f7 100644 +--- a/source3/locking/locking.c ++++ b/source3/locking/locking.c +@@ -237,52 +237,7 @@ static void decrement_current_lock_count(files_struct *fsp, + Utility function called by locking requests. + ****************************************************************************/ + +-struct do_lock_state { +- struct files_struct *fsp; +- TALLOC_CTX *req_mem_ctx; +- const struct GUID *req_guid; +- uint64_t smblctx; +- uint64_t count; +- uint64_t offset; +- enum brl_type lock_type; +- enum brl_flavour lock_flav; +- +- struct server_id blocker_pid; +- uint64_t blocker_smblctx; +- NTSTATUS status; +-}; +- +-static void do_lock_fn( +- struct share_mode_lock *lck, +- void *private_data) +-{ +- struct do_lock_state *state = private_data; +- struct byte_range_lock *br_lck = NULL; +- +- br_lck = brl_get_locks_for_locking(talloc_tos(), +- state->fsp, +- state->req_mem_ctx, +- state->req_guid); +- if (br_lck == NULL) { +- state->status = NT_STATUS_NO_MEMORY; +- return; +- } +- +- state->status = brl_lock( +- br_lck, +- state->smblctx, +- messaging_server_id(state->fsp->conn->sconn->msg_ctx), +- state->offset, +- state->count, +- state->lock_type, +- state->lock_flav, +- &state->blocker_pid, +- &state->blocker_smblctx); +- +- TALLOC_FREE(br_lck); +-} +- +-NTSTATUS do_lock(files_struct *fsp, ++NTSTATUS do_lock(struct byte_range_lock *br_lck, + TALLOC_CTX *req_mem_ctx, + const struct GUID *req_guid, + uint64_t smblctx, +@@ -293,22 +248,13 @@ NTSTATUS do_lock(files_struct *fsp, + struct server_id *pblocker_pid, + uint64_t *psmblctx) + { +- struct do_lock_state state = { +- .fsp = fsp, +- .req_mem_ctx = req_mem_ctx, +- .req_guid = req_guid, +- .smblctx = smblctx, +- .count = count, +- .offset = offset, +- .lock_type = lock_type, +- .lock_flav = lock_flav, +- }; ++ files_struct *fsp = brl_fsp(br_lck); ++ struct server_id blocker_pid; ++ uint64_t blocker_smblctx; + NTSTATUS status; + +- /* silently return ok on print files as we don't do locking there */ +- if (fsp->print_file) { +- return NT_STATUS_OK; +- } ++ SMB_ASSERT(req_mem_ctx != NULL); ++ SMB_ASSERT(req_guid != NULL); + + if (!fsp->fsp_flags.can_lock) { + if (fsp->fsp_flags.is_directory) { +@@ -332,25 +278,27 @@ NTSTATUS do_lock(files_struct *fsp, + fsp_fnum_dbg(fsp), + fsp_str_dbg(fsp)); + +- status = share_mode_do_locked_vfs_allowed(fsp->file_id, +- do_lock_fn, +- &state); +- if (!NT_STATUS_IS_OK(status)) { +- DBG_DEBUG("share_mode_do_locked returned %s\n", +- nt_errstr(status)); +- return status; +- } +- if (!NT_STATUS_IS_OK(state.status)) { +- DBG_DEBUG("do_lock_fn returned %s\n", +- nt_errstr(state.status)); ++ brl_req_set(br_lck, req_mem_ctx, req_guid); ++ status = brl_lock(br_lck, ++ smblctx, ++ messaging_server_id(fsp->conn->sconn->msg_ctx), ++ offset, ++ count, ++ lock_type, ++ lock_flav, ++ &blocker_pid, ++ &blocker_smblctx); ++ brl_req_set(br_lck, NULL, NULL); ++ if (!NT_STATUS_IS_OK(status)) { ++ DBG_DEBUG("brl_lock failed: %s\n", nt_errstr(status)); + if (psmblctx != NULL) { +- *psmblctx = state.blocker_smblctx; ++ *psmblctx = blocker_smblctx; + } + if (pblocker_pid != NULL) { +- *pblocker_pid = state.blocker_pid; ++ *pblocker_pid = blocker_pid; + } +- return state.status; +- } ++ return status; ++ } + + increment_current_lock_count(fsp, lock_flav); + +@@ -361,14 +309,14 @@ NTSTATUS do_lock(files_struct *fsp, + Utility function called by unlocking requests. + ****************************************************************************/ + +-NTSTATUS do_unlock(files_struct *fsp, ++NTSTATUS do_unlock(struct byte_range_lock *br_lck, + uint64_t smblctx, + uint64_t count, + uint64_t offset, + enum brl_flavour lock_flav) + { ++ files_struct *fsp = brl_fsp(br_lck); + bool ok = False; +- struct byte_range_lock *br_lck = NULL; + + if (!fsp->fsp_flags.can_lock) { + return fsp->fsp_flags.is_directory ? +@@ -387,11 +335,6 @@ NTSTATUS do_unlock(files_struct *fsp, + fsp_fnum_dbg(fsp), + fsp_str_dbg(fsp)); + +- br_lck = brl_get_locks(talloc_tos(), fsp); +- if (!br_lck) { +- return NT_STATUS_NO_MEMORY; +- } +- + ok = brl_unlock(br_lck, + smblctx, + messaging_server_id(fsp->conn->sconn->msg_ctx), +@@ -399,8 +342,6 @@ NTSTATUS do_unlock(files_struct *fsp, + count, + lock_flav); + +- TALLOC_FREE(br_lck); +- + if (!ok) { + DEBUG(10,("do_unlock: returning ERRlock.\n" )); + return NT_STATUS_RANGE_NOT_LOCKED; +diff --git a/source3/locking/proto.h b/source3/locking/proto.h +index c74539c8161..e332abf34ec 100644 +--- a/source3/locking/proto.h ++++ b/source3/locking/proto.h +@@ -120,7 +120,7 @@ NTSTATUS query_lock(files_struct *fsp, + uint64_t *poffset, + enum brl_type *plock_type, + enum brl_flavour lock_flav); +-NTSTATUS do_lock(files_struct *fsp, ++NTSTATUS do_lock(struct byte_range_lock *br_lck, + TALLOC_CTX *req_mem_ctx, + const struct GUID *req_guid, + uint64_t smblctx, +@@ -130,7 +130,7 @@ NTSTATUS do_lock(files_struct *fsp, + enum brl_flavour lock_flav, + struct server_id *pblocker_pid, + uint64_t *psmblctx); +-NTSTATUS do_unlock(files_struct *fsp, ++NTSTATUS do_unlock(struct byte_range_lock *br_lck, + uint64_t smblctx, + uint64_t count, + uint64_t offset, +diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c +index 49a1723864e..caaf1f73856 100644 +--- a/source3/modules/vfs_fruit.c ++++ b/source3/modules/vfs_fruit.c +@@ -622,11 +622,21 @@ static bool test_netatalk_lock(files_struct *fsp, off_t in_offset) + return false; + } + +-static NTSTATUS fruit_check_access(vfs_handle_struct *handle, +- files_struct *fsp, +- uint32_t access_mask, +- uint32_t share_mode) ++struct check_access_state { ++ NTSTATUS status; ++ files_struct *fsp; ++ uint32_t access_mask; ++ uint32_t share_mode; ++}; ++ ++static void fruit_check_access(struct share_mode_lock *lck, ++ struct byte_range_lock *br_lck, ++ void *private_data) + { ++ struct check_access_state *state = private_data; ++ files_struct *fsp = state->fsp; ++ uint32_t access_mask = state->access_mask; ++ uint32_t share_mode = state->share_mode; + NTSTATUS status = NT_STATUS_OK; + off_t off; + bool share_for_read = (share_mode & FILE_SHARE_READ); +@@ -640,6 +650,14 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + /* FIXME: hardcoded data fork, add resource fork */ + enum apple_fork fork_type = APPLE_FORK_DATA; + ++ /* ++ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so ++ * br_lck has to be there! ++ */ ++ SMB_ASSERT(br_lck != NULL); ++ ++ state->status = NT_STATUS_OK; ++ + DBG_DEBUG("fruit_check_access: %s, am: %s/%s, sm: 0x%x\n", + fsp_str_dbg(fsp), + access_mask & FILE_READ_DATA ? "READ" :"-", +@@ -647,7 +665,7 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + share_mode); + + if (fsp_get_io_fd(fsp) == -1) { +- return NT_STATUS_OK; ++ return; + } + + /* Read NetATalk opens and deny modes on the file. */ +@@ -670,22 +688,26 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + /* If there are any conflicts - sharing violation. */ + if ((access_mask & FILE_READ_DATA) && + netatalk_already_open_with_deny_read) { +- return NT_STATUS_SHARING_VIOLATION; ++ state->status = NT_STATUS_SHARING_VIOLATION; ++ return; + } + + if (!share_for_read && + netatalk_already_open_for_reading) { +- return NT_STATUS_SHARING_VIOLATION; ++ state->status = NT_STATUS_SHARING_VIOLATION; ++ return; + } + + if ((access_mask & FILE_WRITE_DATA) && + netatalk_already_open_with_deny_write) { +- return NT_STATUS_SHARING_VIOLATION; ++ state->status = NT_STATUS_SHARING_VIOLATION; ++ return; + } + + if (!share_for_write && + netatalk_already_open_for_writing) { +- return NT_STATUS_SHARING_VIOLATION; ++ state->status = NT_STATUS_SHARING_VIOLATION; ++ return; + } + + if (!(access_mask & FILE_READ_DATA)) { +@@ -693,15 +715,16 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + * Nothing we can do here, we need read access + * to set locks. + */ +- return NT_STATUS_OK; ++ return; + } + + /* Set NetAtalk locks matching our access */ + if (access_mask & FILE_READ_DATA) { + off = access_to_netatalk_brl(fork_type, FILE_READ_DATA); + req_guid.time_hi_and_version = __LINE__; ++ + status = do_lock( +- fsp, ++ br_lck, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, +@@ -711,17 +734,18 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + POSIX_LOCK, + NULL, + NULL); +- + if (!NT_STATUS_IS_OK(status)) { +- return status; ++ state->status = status; ++ return; + } + } + + if (!share_for_read) { + off = denymode_to_netatalk_brl(fork_type, DENY_READ); + req_guid.time_hi_and_version = __LINE__; ++ + status = do_lock( +- fsp, ++ br_lck, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, +@@ -731,17 +755,18 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + POSIX_LOCK, + NULL, + NULL); +- + if (!NT_STATUS_IS_OK(status)) { +- return status; ++ state->status = status; ++ return; + } + } + + if (access_mask & FILE_WRITE_DATA) { + off = access_to_netatalk_brl(fork_type, FILE_WRITE_DATA); + req_guid.time_hi_and_version = __LINE__; ++ + status = do_lock( +- fsp, ++ br_lck, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, +@@ -751,17 +776,18 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + POSIX_LOCK, + NULL, + NULL); +- + if (!NT_STATUS_IS_OK(status)) { +- return status; ++ state->status = status; ++ return; + } + } + + if (!share_for_write) { + off = denymode_to_netatalk_brl(fork_type, DENY_WRITE); + req_guid.time_hi_and_version = __LINE__; ++ + status = do_lock( +- fsp, ++ br_lck, + talloc_tos(), + &req_guid, + fsp->op->global->open_persistent_id, +@@ -771,13 +797,11 @@ static NTSTATUS fruit_check_access(vfs_handle_struct *handle, + POSIX_LOCK, + NULL, + NULL); +- + if (!NT_STATUS_IS_OK(status)) { +- return status; ++ state->status = status; ++ return; + } + } +- +- return NT_STATUS_OK; + } + + static NTSTATUS check_aapl(vfs_handle_struct *handle, +@@ -4346,16 +4370,27 @@ static NTSTATUS fruit_create_file(vfs_handle_struct *handle, + } + + if ((config->locking == FRUIT_LOCKING_NETATALK) && ++ lp_locking(fsp->conn->params) && ++ fsp->fsp_flags.can_lock && + (fsp->op != NULL) && + !fsp->fsp_flags.is_pathref) + { +- status = fruit_check_access( +- handle, *result, +- access_mask, +- share_access); ++ struct check_access_state state = (struct check_access_state) { ++ .fsp = fsp, ++ .access_mask = access_mask, ++ .share_mode = share_access, ++ }; ++ ++ status = share_mode_do_locked_brl(fsp, ++ fruit_check_access, ++ &state); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } ++ if (!NT_STATUS_IS_OK(state.status)) { ++ status = state.status; ++ goto fail; ++ } + } + + return status; +diff --git a/source3/smbd/blocking.c b/source3/smbd/blocking.c +index 8b41288bfbf..c9d9aff98b7 100644 +--- a/source3/smbd/blocking.c ++++ b/source3/smbd/blocking.c +@@ -29,31 +29,27 @@ + #undef DBGC_CLASS + #define DBGC_CLASS DBGC_LOCKING + +-NTSTATUS smbd_do_locks_try( +- struct files_struct *fsp, +- uint16_t num_locks, +- struct smbd_lock_element *locks, +- uint16_t *blocker_idx, +- struct server_id *blocking_pid, +- uint64_t *blocking_smblctx) ++NTSTATUS smbd_do_locks_try(struct byte_range_lock *br_lck, ++ struct smbd_do_locks_state *state) + { +- NTSTATUS status = NT_STATUS_OK; ++ bool unlock_ok; + uint16_t i; ++ NTSTATUS status = NT_STATUS_OK; + +- for (i=0; inum_locks; i++) { ++ struct smbd_lock_element *e = &state->locks[i]; + + status = do_lock( +- fsp, +- locks, /* req_mem_ctx */ ++ br_lck, ++ state->locks, /* req_mem_ctx */ + &e->req_guid, + e->smblctx, + e->count, + e->offset, + e->brltype, + e->lock_flav, +- blocking_pid, +- blocking_smblctx); ++ &state->blocking_pid, ++ &state->blocking_smblctx); + if (!NT_STATUS_IS_OK(status)) { + break; + } +@@ -63,18 +59,35 @@ NTSTATUS smbd_do_locks_try( + return NT_STATUS_OK; + } + +- *blocker_idx = i; ++ state->blocker_idx = i; ++ unlock_ok = true; + + /* + * Undo the locks we successfully got + */ + for (i = i-1; i != UINT16_MAX; i--) { +- struct smbd_lock_element *e = &locks[i]; +- do_unlock(fsp, +- e->smblctx, +- e->count, +- e->offset, +- e->lock_flav); ++ struct smbd_lock_element *e = &state->locks[i]; ++ NTSTATUS ulstatus; ++ ++ ulstatus = do_unlock(br_lck, ++ e->smblctx, ++ e->count, ++ e->offset, ++ e->lock_flav); ++ if (!NT_STATUS_IS_OK(ulstatus)) { ++ DBG_DEBUG("Failed to undo lock flavour %s lock " ++ "type %s start=%"PRIu64" len=%"PRIu64" " ++ "requested for file [%s]\n", ++ lock_flav_name(e->lock_flav), ++ lock_type_name(e->brltype), ++ e->offset, ++ e->count, ++ fsp_str_dbg(brl_fsp(br_lck))); ++ unlock_ok = false; ++ } ++ } ++ if (unlock_ok) { ++ brl_set_modified(br_lck, false); + } + + return status; +@@ -118,13 +131,6 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req); + static void smbd_smb1_do_locks_retry(struct tevent_req *subreq); + static void smbd_smb1_blocked_locks_cleanup( + struct tevent_req *req, enum tevent_req_state req_state); +-static NTSTATUS smbd_smb1_do_locks_check( +- struct files_struct *fsp, +- uint16_t num_locks, +- struct smbd_lock_element *locks, +- uint16_t *blocker_idx, +- struct server_id *blocking_pid, +- uint64_t *blocking_smblctx); + + static void smbd_smb1_do_locks_setup_timeout( + struct smbd_smb1_do_locks_state *state, +@@ -378,18 +384,35 @@ static NTSTATUS smbd_smb1_do_locks_check_blocked( + return NT_STATUS_OK; + } + +-static NTSTATUS smbd_smb1_do_locks_check( +- struct files_struct *fsp, +- uint16_t num_locks, +- struct smbd_lock_element *locks, +- uint16_t *blocker_idx, +- struct server_id *blocking_pid, +- uint64_t *blocking_smblctx) ++static void smbd_smb1_do_locks_try_fn(struct share_mode_lock *lck, ++ struct byte_range_lock *br_lck, ++ void *private_data) + { ++ struct tevent_req *req = talloc_get_type_abort( ++ private_data, struct tevent_req); ++ struct smbd_smb1_do_locks_state *state = tevent_req_data( ++ req, struct smbd_smb1_do_locks_state); ++ struct smbd_do_locks_state brl_state; ++ struct files_struct *fsp = state->fsp; + struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs; + size_t num_blocked = talloc_array_length(blocked); +- NTSTATUS status; ++ struct timeval endtime = { 0 }; ++ struct tevent_req *subreq = NULL; + size_t bi; ++ NTSTATUS status; ++ bool ok; ++ bool expired; ++ ++ /* ++ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so ++ * br_lck has to be there! ++ */ ++ SMB_ASSERT(br_lck != NULL); ++ ++ brl_state = (struct smbd_do_locks_state) { ++ .num_locks = state->num_locks, ++ .locks = state->locks, ++ }; + + /* + * We check the pending/blocked requests +@@ -404,8 +427,8 @@ static NTSTATUS smbd_smb1_do_locks_check( + tevent_req_data(blocked[bi], + struct smbd_smb1_do_locks_state); + +- if (blocked_state->locks == locks) { +- SMB_ASSERT(blocked_state->num_locks == num_locks); ++ if (blocked_state->locks == state->locks) { ++ SMB_ASSERT(blocked_state->num_locks == state->num_locks); + + /* + * We found ourself... +@@ -416,61 +439,24 @@ static NTSTATUS smbd_smb1_do_locks_check( + status = smbd_smb1_do_locks_check_blocked( + blocked_state->num_locks, + blocked_state->locks, +- num_locks, +- locks, +- blocker_idx, +- blocking_smblctx); ++ state->num_locks, ++ state->locks, ++ &brl_state.blocker_idx, ++ &brl_state.blocking_smblctx); + if (!NT_STATUS_IS_OK(status)) { +- *blocking_pid = messaging_server_id( +- fsp->conn->sconn->msg_ctx); +- return status; ++ brl_state.blocking_pid = messaging_server_id( ++ fsp->conn->sconn->msg_ctx); ++ goto check_retry; + } + } + +- status = smbd_do_locks_try( +- fsp, +- num_locks, +- locks, +- blocker_idx, +- blocking_pid, +- blocking_smblctx); +- if (!NT_STATUS_IS_OK(status)) { +- return status; +- } +- +- return NT_STATUS_OK; +-} +- +-static void smbd_smb1_do_locks_try(struct tevent_req *req) +-{ +- struct smbd_smb1_do_locks_state *state = tevent_req_data( +- req, struct smbd_smb1_do_locks_state); +- struct files_struct *fsp = state->fsp; +- struct share_mode_lock *lck; +- struct timeval endtime = { 0 }; +- struct server_id blocking_pid = { 0 }; +- uint64_t blocking_smblctx = 0; +- struct tevent_req *subreq = NULL; +- NTSTATUS status; +- bool ok; +- bool expired; +- +- lck = get_existing_share_mode_lock(state, fsp->file_id); +- if (tevent_req_nomem(lck, req)) { +- DBG_DEBUG("Could not get share mode lock\n"); +- return; +- } +- +- status = smbd_smb1_do_locks_check( +- fsp, +- state->num_locks, +- state->locks, +- &state->blocker, +- &blocking_pid, +- &blocking_smblctx); ++ status = smbd_do_locks_try(br_lck, &brl_state); + if (NT_STATUS_IS_OK(status)) { + goto done; + } ++ ++ state->blocker = brl_state.blocker_idx; ++ + if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) { + /* + * We got NT_STATUS_RETRY, +@@ -494,8 +480,8 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req) + * locking.tdb may cause retries. + */ + +- if (blocking_smblctx != UINT64_MAX) { +- SMB_ASSERT(blocking_smblctx == 0); ++ if (brl_state.blocking_smblctx != UINT64_MAX) { ++ SMB_ASSERT(brl_state.blocking_smblctx == 0); + goto setup_retry; + } + +@@ -516,6 +502,8 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req) + endtime = timeval_current_ofs_msec(state->retry_msecs); + goto setup_retry; + } ++ ++check_retry: + if (!ERROR_WAS_LOCK_DENIED(status)) { + goto done; + } +@@ -529,7 +517,7 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req) + smbd_smb1_do_locks_setup_timeout(state, &state->locks[state->blocker]); + DBG_DEBUG("timeout=%"PRIu32", blocking_smblctx=%"PRIu64"\n", + state->timeout, +- blocking_smblctx); ++ brl_state.blocking_smblctx); + + /* + * The client specified timeout expired +@@ -554,7 +542,7 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req) + + endtime = state->endtime; + +- if (blocking_smblctx == UINT64_MAX) { ++ if (brl_state.blocking_smblctx == UINT64_MAX) { + struct timeval tmp; + + smbd_smb1_do_locks_update_polling_msecs(state); +@@ -568,11 +556,11 @@ static void smbd_smb1_do_locks_try(struct tevent_req *req) + + setup_retry: + subreq = share_mode_watch_send( +- state, state->ev, lck, blocking_pid); ++ state, state->ev, lck, brl_state.blocking_pid); + if (tevent_req_nomem(subreq, req)) { ++ status = NT_STATUS_NO_MEMORY; + goto done; + } +- TALLOC_FREE(lck); + tevent_req_set_callback(subreq, smbd_smb1_do_locks_retry, req); + + if (timeval_is_zero(&endtime)) { +@@ -586,10 +574,38 @@ setup_retry: + } + return; + done: +- TALLOC_FREE(lck); + smbd_smb1_brl_finish_by_req(req, status); + } + ++static void smbd_smb1_do_locks_try(struct tevent_req *req) ++{ ++ struct smbd_smb1_do_locks_state *state = tevent_req_data( ++ req, struct smbd_smb1_do_locks_state); ++ NTSTATUS status; ++ ++ if (!state->fsp->fsp_flags.can_lock) { ++ if (state->fsp->fsp_flags.is_directory) { ++ return smbd_smb1_brl_finish_by_req(req, ++ NT_STATUS_INVALID_DEVICE_REQUEST); ++ } ++ return smbd_smb1_brl_finish_by_req(req, ++ NT_STATUS_INVALID_HANDLE); ++ } ++ ++ if (!lp_locking(state->fsp->conn->params)) { ++ return smbd_smb1_brl_finish_by_req(req, NT_STATUS_OK); ++ } ++ ++ status = share_mode_do_locked_brl(state->fsp, ++ smbd_smb1_do_locks_try_fn, ++ req); ++ if (!NT_STATUS_IS_OK(status)) { ++ smbd_smb1_brl_finish_by_req(req, status); ++ return; ++ } ++ return; ++} ++ + static void smbd_smb1_do_locks_retry(struct tevent_req *subreq) + { + struct tevent_req *req = tevent_req_callback_data( +diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h +index 78e1b48be09..b8bb8eb293c 100644 +--- a/source3/smbd/proto.h ++++ b/source3/smbd/proto.h +@@ -86,13 +86,18 @@ ssize_t pwrite_fsync_recv(struct tevent_req *req, int *perr); + + /* The following definitions come from smbd/blocking.c */ + +-NTSTATUS smbd_do_locks_try( +- struct files_struct *fsp, +- uint16_t num_locks, +- struct smbd_lock_element *locks, +- uint16_t *blocker_idx, +- struct server_id *blocking_pid, +- uint64_t *blocking_smblctx); ++struct smbd_do_locks_state { ++ uint16_t num_locks; ++ struct smbd_lock_element *locks; ++ NTSTATUS status; ++ uint16_t blocker_idx; ++ struct server_id blocking_pid; ++ uint64_t blocking_smblctx; ++}; ++ ++NTSTATUS smbd_do_locks_try(struct byte_range_lock *br_lck, ++ struct smbd_do_locks_state *state); ++ + struct tevent_req *smbd_smb1_do_locks_send( + TALLOC_CTX *mem_ctx, + struct tevent_context *ev, +diff --git a/source3/smbd/smb2_lock.c b/source3/smbd/smb2_lock.c +index c9d810f71ba..14f912bf2da 100644 +--- a/source3/smbd/smb2_lock.c ++++ b/source3/smbd/smb2_lock.c +@@ -569,33 +569,32 @@ static void smbd_smb2_lock_update_polling_msecs( + state->polling_msecs += v_min; + } + +-static void smbd_smb2_lock_try(struct tevent_req *req) ++static void smbd_do_locks_try_fn(struct share_mode_lock *lck, ++ struct byte_range_lock *br_lck, ++ void *private_data) + { ++ struct tevent_req *req = talloc_get_type_abort( ++ private_data, struct tevent_req); + struct smbd_smb2_lock_state *state = tevent_req_data( + req, struct smbd_smb2_lock_state); +- struct share_mode_lock *lck = NULL; +- uint16_t blocker_idx; +- struct server_id blocking_pid = { 0 }; +- uint64_t blocking_smblctx; +- NTSTATUS status; ++ struct smbd_do_locks_state brl_state; + struct tevent_req *subreq = NULL; + struct timeval endtime = { 0 }; ++ NTSTATUS status; + +- lck = get_existing_share_mode_lock( +- talloc_tos(), state->fsp->file_id); +- if (tevent_req_nomem(lck, req)) { +- return; +- } ++ /* ++ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so ++ * br_lck has to be there! ++ */ ++ SMB_ASSERT(br_lck != NULL); + +- status = smbd_do_locks_try( +- state->fsp, +- state->lock_count, +- state->locks, +- &blocker_idx, +- &blocking_pid, +- &blocking_smblctx); ++ brl_state = (struct smbd_do_locks_state) { ++ .num_locks = state->lock_count, ++ .locks = state->locks, ++ }; ++ ++ status = smbd_do_locks_try(br_lck, &brl_state); + if (NT_STATUS_IS_OK(status)) { +- TALLOC_FREE(lck); + tevent_req_done(req); + return; + } +@@ -622,8 +621,8 @@ static void smbd_smb2_lock_try(struct tevent_req *req) + * locking.tdb may cause retries. + */ + +- if (blocking_smblctx != UINT64_MAX) { +- SMB_ASSERT(blocking_smblctx == 0); ++ if (brl_state.blocking_smblctx != UINT64_MAX) { ++ SMB_ASSERT(brl_state.blocking_smblctx == 0); + goto setup_retry; + } + +@@ -658,7 +657,6 @@ static void smbd_smb2_lock_try(struct tevent_req *req) + status = NT_STATUS_LOCK_NOT_GRANTED; + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_LOCK_NOT_GRANTED)) { +- TALLOC_FREE(lck); + tevent_req_nterror(req, status); + return; + } +@@ -670,12 +668,11 @@ static void smbd_smb2_lock_try(struct tevent_req *req) + state->retry_msecs = 0; + + if (!state->blocking) { +- TALLOC_FREE(lck); + tevent_req_nterror(req, status); + return; + } + +- if (blocking_smblctx == UINT64_MAX) { ++ if (brl_state.blocking_smblctx == UINT64_MAX) { + smbd_smb2_lock_update_polling_msecs(state); + + DBG_DEBUG("Blocked on a posix lock. Retry in %"PRIu32" msecs\n", +@@ -688,8 +685,7 @@ setup_retry: + DBG_DEBUG("Watching share mode lock\n"); + + subreq = share_mode_watch_send( +- state, state->ev, lck, blocking_pid); +- TALLOC_FREE(lck); ++ state, state->ev, lck, brl_state.blocking_pid); + if (tevent_req_nomem(subreq, req)) { + return; + } +@@ -708,6 +704,35 @@ setup_retry: + } + } + ++static void smbd_smb2_lock_try(struct tevent_req *req) ++{ ++ struct smbd_smb2_lock_state *state = tevent_req_data( ++ req, struct smbd_smb2_lock_state); ++ NTSTATUS status; ++ ++ if (!state->fsp->fsp_flags.can_lock) { ++ if (state->fsp->fsp_flags.is_directory) { ++ tevent_req_nterror(req, ++ NT_STATUS_INVALID_DEVICE_REQUEST); ++ return; ++ } ++ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE); ++ return; ++ } ++ ++ if (!lp_locking(state->fsp->conn->params)) { ++ return tevent_req_done(req); ++ } ++ ++ status = share_mode_do_locked_brl(state->fsp, ++ smbd_do_locks_try_fn, ++ req); ++ if (!NT_STATUS_IS_OK(status)) { ++ tevent_req_nterror(req, status); ++ return; ++ } ++} ++ + static void smbd_smb2_lock_retry(struct tevent_req *subreq) + { + struct tevent_req *req = tevent_req_callback_data( +diff --git a/source3/smbd/smb2_reply.c b/source3/smbd/smb2_reply.c +index dfcd05d2cae..c782a624d7f 100644 +--- a/source3/smbd/smb2_reply.c ++++ b/source3/smbd/smb2_reply.c +@@ -2113,23 +2113,22 @@ uint64_t get_lock_offset(const uint8_t *data, int data_offset, + return offset; + } + +-struct smbd_do_unlocking_state { +- struct files_struct *fsp; +- uint16_t num_ulocks; +- struct smbd_lock_element *ulocks; +- NTSTATUS status; +-}; +- +-static void smbd_do_unlocking_fn( +- struct share_mode_lock *lck, +- void *private_data) ++static void smbd_do_unlocking_fn(struct share_mode_lock *lck, ++ struct byte_range_lock *br_lck, ++ void *private_data) + { +- struct smbd_do_unlocking_state *state = private_data; +- struct files_struct *fsp = state->fsp; ++ struct smbd_do_locks_state *state = private_data; ++ struct files_struct *fsp = brl_fsp(br_lck); + uint16_t i; + +- for (i = 0; i < state->num_ulocks; i++) { +- struct smbd_lock_element *e = &state->ulocks[i]; ++ /* ++ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so ++ * br_lck has to be there! ++ */ ++ SMB_ASSERT(br_lck != NULL); ++ ++ for (i = 0; i < state->num_locks; i++) { ++ struct smbd_lock_element *e = &state->locks[i]; + + DBG_DEBUG("unlock start=%"PRIu64", len=%"PRIu64" for " + "pid %"PRIu64", file %s\n", +@@ -2145,7 +2144,7 @@ static void smbd_do_unlocking_fn( + } + + state->status = do_unlock( +- fsp, e->smblctx, e->count, e->offset, e->lock_flav); ++ br_lck, e->smblctx, e->count, e->offset, e->lock_flav); + + DBG_DEBUG("do_unlock returned %s\n", + nt_errstr(state->status)); +@@ -2163,20 +2162,30 @@ NTSTATUS smbd_do_unlocking(struct smb_request *req, + uint16_t num_ulocks, + struct smbd_lock_element *ulocks) + { +- struct smbd_do_unlocking_state state = { +- .fsp = fsp, +- .num_ulocks = num_ulocks, +- .ulocks = ulocks, ++ struct smbd_do_locks_state state = { ++ .num_locks = num_ulocks, ++ .locks = ulocks, + }; + NTSTATUS status; + + DBG_NOTICE("%s num_ulocks=%"PRIu16"\n", fsp_fnum_dbg(fsp), num_ulocks); + +- status = share_mode_do_locked_vfs_allowed( +- fsp->file_id, smbd_do_unlocking_fn, &state); ++ if (!fsp->fsp_flags.can_lock) { ++ if (fsp->fsp_flags.is_directory) { ++ return NT_STATUS_INVALID_DEVICE_REQUEST; ++ } ++ return NT_STATUS_INVALID_HANDLE; ++ } ++ ++ if (!lp_locking(fsp->conn->params)) { ++ return NT_STATUS_OK; ++ } + ++ status = share_mode_do_locked_brl(fsp, ++ smbd_do_unlocking_fn, ++ &state); + if (!NT_STATUS_IS_OK(status)) { +- DBG_DEBUG("share_mode_do_locked_vfs_allowed failed: %s\n", ++ DBG_DEBUG("share_mode_do_locked_brl failed: %s\n", + nt_errstr(status)); + return status; + } +-- +2.52.0 + + +From e067ece27012e10a5a49cf0f757fcfb37bc97450 Mon Sep 17 00:00:00 2001 +From: Stefan Metzmacher +Date: Wed, 8 Jan 2025 12:51:37 +0100 +Subject: [PATCH 89/96] s3/brlock: remove brl_get_locks_for_locking() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Pair-Programmed-With: Ralph Boehme +Signed-off-by: Ralph Boehme +Signed-off-by: Stefan Metzmacher +(cherry picked from commit 0c4c430c50e15d591a0d871a5f3e59e8be0d0a83) +--- + source3/locking/brlock.c | 19 ------------------- + source3/locking/proto.h | 4 ---- + 2 files changed, 23 deletions(-) + +diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c +index e0d18800cf1..592792c5303 100644 +--- a/source3/locking/brlock.c ++++ b/source3/locking/brlock.c +@@ -1824,25 +1824,6 @@ struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx, files_struct *fsp) + return br_lck; + } + +-struct byte_range_lock *brl_get_locks_for_locking(TALLOC_CTX *mem_ctx, +- files_struct *fsp, +- TALLOC_CTX *req_mem_ctx, +- const struct GUID *req_guid) +-{ +- struct byte_range_lock *br_lck = NULL; +- +- br_lck = brl_get_locks(mem_ctx, fsp); +- if (br_lck == NULL) { +- return NULL; +- } +- SMB_ASSERT(req_mem_ctx != NULL); +- br_lck->req_mem_ctx = req_mem_ctx; +- SMB_ASSERT(req_guid != NULL); +- br_lck->req_guid = req_guid; +- +- return br_lck; +-} +- + struct brl_get_locks_readonly_state { + TALLOC_CTX *mem_ctx; + struct byte_range_lock **br_lock; +diff --git a/source3/locking/proto.h b/source3/locking/proto.h +index e332abf34ec..44b43c1b1e2 100644 +--- a/source3/locking/proto.h ++++ b/source3/locking/proto.h +@@ -83,10 +83,6 @@ int brl_forall(void (*fn)(struct file_id id, struct server_id pid, + br_off start, br_off size, + void *private_data), + void *private_data); +-struct byte_range_lock *brl_get_locks_for_locking(TALLOC_CTX *mem_ctx, +- files_struct *fsp, +- TALLOC_CTX *req_mem_ctx, +- const struct GUID *req_guid); + struct share_mode_lock; + typedef void (*share_mode_do_locked_brl_fn_t)( + struct share_mode_lock *lck, +-- +2.52.0 + + +From 79331346ad6305470c67710e7a6189442ca491f1 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Mon, 27 Jan 2025 15:22:26 +0100 +Subject: [PATCH 90/96] smbd: call locking_close_file() while still holding a + glock on the locking.tdb record + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(cherry picked from commit 4d680b6c17ee7674b9686aec2b69038f89e1989a) +--- + source3/smbd/close.c | 26 +++++++++++++++++++------- + 1 file changed, 19 insertions(+), 7 deletions(-) + +diff --git a/source3/smbd/close.c b/source3/smbd/close.c +index e16cb2d3485..f36e699c6ea 100644 +--- a/source3/smbd/close.c ++++ b/source3/smbd/close.c +@@ -303,6 +303,17 @@ static void close_share_mode_lock_prepare(struct share_mode_lock *lck, + */ + *keep_locked = false; + ++ if (fsp->current_lock_count > 0) { ++ /* ++ * Remove the byte-range locks under the glock ++ */ ++ *keep_locked = true; ++ } ++ ++ if (fh_get_refcount(fsp->fh) > 1) { ++ return; ++ } ++ + if (fsp->oplock_type != NO_OPLOCK) { + ok = remove_share_oplock(lck, fsp); + if (!ok) { +@@ -453,6 +464,12 @@ static NTSTATUS close_remove_share_mode(files_struct *fsp, + return status; + } + ++ locking_close_file(fsp, close_type); ++ ++ if (fh_get_refcount(fsp->fh) > 1) { ++ goto done; ++ } ++ + /* Remove the oplock before potentially deleting the file. */ + if (fsp->oplock_type != NO_OPLOCK) { + release_file_oplock(fsp); +@@ -901,13 +918,8 @@ static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp, + the same handle we only have one share mode. Ensure we only remove + the share mode on the last close. */ + +- if (fh_get_refcount(fsp->fh) == 1) { +- /* Should we return on error here... ? */ +- tmp = close_remove_share_mode(fsp, close_type); +- status = ntstatus_keeperror(status, tmp); +- } +- +- locking_close_file(fsp, close_type); ++ tmp = close_remove_share_mode(fsp, close_type); ++ status = ntstatus_keeperror(status, tmp); + + /* + * Ensure pending modtime is set before closing underlying fd. +-- +2.52.0 + + +From 02708477b64206765976cb03e70c95a4b4daf894 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Thu, 30 Jan 2025 17:35:26 +0100 +Subject: [PATCH 91/96] s3/locking: prepare brl_locktest() for upgradable + read-only locks + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(cherry picked from commit 8f9387ceb5c94c7db92ab342e33c64b858c301b1) +--- + source3/locking/brlock.c | 5 +++-- + source3/locking/locking.c | 4 ++-- + source3/locking/proto.h | 3 ++- + 3 files changed, 7 insertions(+), 5 deletions(-) + +diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c +index 592792c5303..08500c4d1ac 100644 +--- a/source3/locking/brlock.c ++++ b/source3/locking/brlock.c +@@ -1283,7 +1283,8 @@ bool brl_unlock(struct byte_range_lock *br_lck, + ****************************************************************************/ + + bool brl_locktest(struct byte_range_lock *br_lck, +- const struct lock_struct *rw_probe) ++ const struct lock_struct *rw_probe, ++ bool upgradable) + { + bool ret = True; + unsigned int i; +@@ -1296,7 +1297,7 @@ bool brl_locktest(struct byte_range_lock *br_lck, + * Our own locks don't conflict. + */ + if (brl_conflict_other(&locks[i], rw_probe)) { +- if (br_lck->record == NULL) { ++ if (!upgradable) { + /* readonly */ + return false; + } +diff --git a/source3/locking/locking.c b/source3/locking/locking.c +index 25b3bdcf7f7..d8e70a479ad 100644 +--- a/source3/locking/locking.c ++++ b/source3/locking/locking.c +@@ -143,7 +143,7 @@ bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock) + if (!br_lck) { + return true; + } +- ret = brl_locktest(br_lck, plock); ++ ret = brl_locktest(br_lck, plock, false); + + if (!ret) { + /* +@@ -154,7 +154,7 @@ bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock) + if (br_lck == NULL) { + return true; + } +- ret = brl_locktest(br_lck, plock); ++ ret = brl_locktest(br_lck, plock, true); + TALLOC_FREE(br_lck); + } + +diff --git a/source3/locking/proto.h b/source3/locking/proto.h +index 44b43c1b1e2..44808171f1a 100644 +--- a/source3/locking/proto.h ++++ b/source3/locking/proto.h +@@ -66,7 +66,8 @@ bool brl_unlock(struct byte_range_lock *br_lck, + bool brl_unlock_windows_default(struct byte_range_lock *br_lck, + const struct lock_struct *plock); + bool brl_locktest(struct byte_range_lock *br_lck, +- const struct lock_struct *rw_probe); ++ const struct lock_struct *rw_probe, ++ bool upgradable); + NTSTATUS brl_lockquery(struct byte_range_lock *br_lck, + uint64_t *psmblctx, + struct server_id pid, +-- +2.52.0 + + +From 0bf638a66c95a1f8304cb3efa284f2303445f2c0 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Wed, 2 Apr 2025 12:43:15 +0200 +Subject: [PATCH 92/96] smbd: check can_lock in strict_lock_check_default() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Pair-Programmed-With: Stefan Metzmacher +Signed-off-by: Ralph Boehme +Signed-off-by: Stefan Metzmacher +(cherry picked from commit 678f28c1af7c160ffdcb0e4baa0a7d4b9906f2e5) +--- + source3/locking/locking.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/source3/locking/locking.c b/source3/locking/locking.c +index d8e70a479ad..9bb0696946e 100644 +--- a/source3/locking/locking.c ++++ b/source3/locking/locking.c +@@ -115,7 +115,10 @@ bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock) + return True; + } + +- if (!lp_locking(fsp->conn->params) || !strict_locking) { ++ if (!lp_locking(fsp->conn->params) || ++ !strict_locking || ++ !fsp->fsp_flags.can_lock) ++ { + return True; + } + +-- +2.52.0 + + +From 936a9736ba92cf9827591a2d8279ec7953717323 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Thu, 30 Jan 2025 07:40:32 +0100 +Subject: [PATCH 93/96] smbd: use share_mode_do_locked_brl() in + strict_lock_check_default() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(cherry picked from commit 56bb20c87a733ab8f7efedd881ea0ecaf51b2ba8) +--- + source3/locking/locking.c | 43 +++++++++++++++++++++++++++++++++------ + 1 file changed, 37 insertions(+), 6 deletions(-) + +diff --git a/source3/locking/locking.c b/source3/locking/locking.c +index 9bb0696946e..400b4722379 100644 +--- a/source3/locking/locking.c ++++ b/source3/locking/locking.c +@@ -105,10 +105,32 @@ void init_strict_lock_struct(files_struct *fsp, + plock->lock_flav = lp_posix_cifsu_locktype(fsp); + } + ++struct strict_lock_check_state { ++ bool ret; ++ files_struct *fsp; ++ struct lock_struct *plock; ++}; ++ ++static void strict_lock_check_default_fn(struct share_mode_lock *lck, ++ struct byte_range_lock *br_lck, ++ void *private_data) ++{ ++ struct strict_lock_check_state *state = private_data; ++ ++ /* ++ * The caller has checked fsp->fsp_flags.can_lock and lp_locking so ++ * br_lck has to be there! ++ */ ++ SMB_ASSERT(br_lck != NULL); ++ ++ state->ret = brl_locktest(br_lck, state->plock, true); ++} ++ + bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock) + { + struct byte_range_lock *br_lck; + int strict_locking = lp_strict_locking(fsp->conn->params); ++ NTSTATUS status; + bool ret = False; + + if (plock->size == 0) { +@@ -147,18 +169,27 @@ bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock) + return true; + } + ret = brl_locktest(br_lck, plock, false); +- + if (!ret) { + /* + * We got a lock conflict. Retry with rw locks to enable + * autocleanup. This is the slow path anyway. + */ +- br_lck = brl_get_locks(talloc_tos(), fsp); +- if (br_lck == NULL) { +- return true; ++ ++ struct strict_lock_check_state state = ++ (struct strict_lock_check_state) { ++ .fsp = fsp, ++ .plock = plock, ++ }; ++ ++ status = share_mode_do_locked_brl(fsp, ++ strict_lock_check_default_fn, ++ &state); ++ if (!NT_STATUS_IS_OK(status)) { ++ DBG_ERR("share_mode_do_locked_brl [%s] failed: %s\n", ++ fsp_str_dbg(fsp), nt_errstr(status)); ++ state.ret = false; + } +- ret = brl_locktest(br_lck, plock, true); +- TALLOC_FREE(br_lck); ++ ret = state.ret; + } + + DEBUG(10, ("strict_lock_default: flavour = %s brl start=%ju " +-- +2.52.0 + + +From 66794d5d5c2b930d6afde8103484333f53b2e68c Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Tue, 28 Jan 2025 11:19:05 +0100 +Subject: [PATCH 94/96] smbd: use share_mode_do_locked_brl() in + vfs_default_durable_disconnect() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(backported from commit 393379fc9c726eb781fd1bfb3a70ea2802739aff) +[slow@samba.org: conflict due to removed delayed write time handling] +--- + source3/locking/brlock.c | 14 +---- + source3/locking/proto.h | 9 ++- + source3/smbd/durable.c | 117 ++++++++++++++++++++++++--------------- + 3 files changed, 82 insertions(+), 58 deletions(-) + +diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c +index 08500c4d1ac..b4c8501b4de 100644 +--- a/source3/locking/brlock.c ++++ b/source3/locking/brlock.c +@@ -1459,14 +1459,14 @@ void brl_close_fnum(struct byte_range_lock *br_lck) + } + } + +-bool brl_mark_disconnected(struct files_struct *fsp) ++bool brl_mark_disconnected(struct files_struct *fsp, ++ struct byte_range_lock *br_lck) + { + uint32_t tid = fsp->conn->cnum; + uint64_t smblctx; + uint64_t fnum = fsp->fnum; + unsigned int i; + struct server_id self = messaging_server_id(fsp->conn->sconn->msg_ctx); +- struct byte_range_lock *br_lck = NULL; + + if (fsp->op == NULL) { + return false; +@@ -1482,11 +1482,6 @@ bool brl_mark_disconnected(struct files_struct *fsp) + return true; + } + +- br_lck = brl_get_locks(talloc_tos(), fsp); +- if (br_lck == NULL) { +- return false; +- } +- + for (i=0; i < br_lck->num_locks; i++) { + struct lock_struct *lock = &br_lck->lock_data[i]; + +@@ -1496,22 +1491,18 @@ bool brl_mark_disconnected(struct files_struct *fsp) + */ + + if (lock->context.smblctx != smblctx) { +- TALLOC_FREE(br_lck); + return false; + } + + if (lock->context.tid != tid) { +- TALLOC_FREE(br_lck); + return false; + } + + if (!server_id_equal(&lock->context.pid, &self)) { +- TALLOC_FREE(br_lck); + return false; + } + + if (lock->fnum != fnum) { +- TALLOC_FREE(br_lck); + return false; + } + +@@ -1521,7 +1512,6 @@ bool brl_mark_disconnected(struct files_struct *fsp) + } + + br_lck->modified = true; +- TALLOC_FREE(br_lck); + return true; + } + +diff --git a/source3/locking/proto.h b/source3/locking/proto.h +index 44808171f1a..d3b4e02bc26 100644 +--- a/source3/locking/proto.h ++++ b/source3/locking/proto.h +@@ -75,7 +75,14 @@ NTSTATUS brl_lockquery(struct byte_range_lock *br_lck, + br_off *psize, + enum brl_type *plock_type, + enum brl_flavour lock_flav); +-bool brl_mark_disconnected(struct files_struct *fsp); ++ ++struct brl_connectstate { ++ bool ok; ++ struct files_struct *fsp; ++}; ++ ++bool brl_mark_disconnected(struct files_struct *fsp, ++ struct byte_range_lock *br_lck); + bool brl_reconnect_disconnected(struct files_struct *fsp); + void brl_close_fnum(struct byte_range_lock *br_lck); + int brl_forall(void (*fn)(struct file_id id, struct server_id pid, +diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c +index bf3cbed0a2c..1506d37208a 100644 +--- a/source3/smbd/durable.c ++++ b/source3/smbd/durable.c +@@ -133,6 +133,63 @@ NTSTATUS vfs_default_durable_cookie(struct files_struct *fsp, + return NT_STATUS_OK; + } + ++struct durable_disconnect_state { ++ NTSTATUS status; ++ struct files_struct *fsp; ++}; ++ ++static void default_durable_disconnect_fn(struct share_mode_lock *lck, ++ struct byte_range_lock *br_lck, ++ void *private_data) ++{ ++ struct durable_disconnect_state *state = private_data; ++ struct files_struct *fsp = state->fsp; ++ struct smb_file_time ft; ++ bool ok; ++ ++ /* Ensure any pending write time updates are done. */ ++ if (fsp->update_write_time_event) { ++ fsp_flush_write_time_update(fsp); ++ } ++ ++ ++ init_smb_file_time(&ft); ++ ++ if (fsp->fsp_flags.write_time_forced) { ++ NTTIME mtime = share_mode_changed_write_time(lck); ++ ft.mtime = nt_time_to_full_timespec(mtime); ++ } else if (fsp->fsp_flags.update_write_time_on_close) { ++ if (is_omit_timespec(&fsp->close_write_time)) { ++ ft.mtime = timespec_current(); ++ } else { ++ ft.mtime = fsp->close_write_time; ++ } ++ } ++ ++ if (!is_omit_timespec(&ft.mtime)) { ++ round_timespec(fsp->conn->ts_res, &ft.mtime); ++ file_ntimes(fsp->conn, fsp, &ft); ++ } ++ ++ ok = mark_share_mode_disconnected(lck, fsp); ++ if (!ok) { ++ state->status = NT_STATUS_UNSUCCESSFUL; ++ return; ++ } ++ ++ if (br_lck == NULL) { ++ state->status = NT_STATUS_OK; ++ return; ++ } ++ ++ ok = brl_mark_disconnected(fsp, br_lck); ++ if (!ok) { ++ state->status = NT_STATUS_UNSUCCESSFUL; ++ return; ++ } ++ state->status = NT_STATUS_OK; ++} ++ + NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp, + const DATA_BLOB old_cookie, + TALLOC_CTX *mem_ctx, +@@ -143,8 +200,7 @@ NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp, + enum ndr_err_code ndr_err; + struct vfs_default_durable_cookie cookie; + DATA_BLOB new_cookie_blob = data_blob_null; +- struct share_mode_lock *lck; +- bool ok; ++ struct durable_disconnect_state state; + + *new_cookie = data_blob_null; + +@@ -198,52 +254,23 @@ NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp, + return NT_STATUS_NOT_SUPPORTED; + } + +- /* Ensure any pending write time updates are done. */ +- if (fsp->update_write_time_event) { +- fsp_flush_write_time_update(fsp); +- } ++ state = (struct durable_disconnect_state) { ++ .fsp = fsp, ++ }; + +- /* +- * The above checks are done in mark_share_mode_disconnected() too +- * but we want to avoid getting the lock if possible +- */ +- lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id); +- if (lck != NULL) { +- struct smb_file_time ft; +- +- init_smb_file_time(&ft); +- +- if (fsp->fsp_flags.write_time_forced) { +- NTTIME mtime = share_mode_changed_write_time(lck); +- ft.mtime = nt_time_to_full_timespec(mtime); +- } else if (fsp->fsp_flags.update_write_time_on_close) { +- if (is_omit_timespec(&fsp->close_write_time)) { +- ft.mtime = timespec_current(); +- } else { +- ft.mtime = fsp->close_write_time; +- } +- } +- +- if (!is_omit_timespec(&ft.mtime)) { +- round_timespec(conn->ts_res, &ft.mtime); +- file_ntimes(conn, fsp, &ft); +- } +- +- ok = mark_share_mode_disconnected(lck, fsp); +- if (!ok) { +- TALLOC_FREE(lck); +- } +- } +- if (lck != NULL) { +- ok = brl_mark_disconnected(fsp); +- if (!ok) { +- TALLOC_FREE(lck); +- } ++ status = share_mode_do_locked_brl(fsp, ++ default_durable_disconnect_fn, ++ &state); ++ if (!NT_STATUS_IS_OK(status)) { ++ DBG_ERR("share_mode_do_locked_brl [%s] failed: %s\n", ++ fsp_str_dbg(fsp), nt_errstr(status)); ++ return status; + } +- if (lck == NULL) { +- return NT_STATUS_NOT_SUPPORTED; ++ if (!NT_STATUS_IS_OK(state.status)) { ++ DBG_ERR("default_durable_disconnect_fn [%s] failed: %s\n", ++ fsp_str_dbg(fsp), nt_errstr(state.status)); ++ return state.status; + } +- TALLOC_FREE(lck); + + status = vfs_stat_fsp(fsp); + if (!NT_STATUS_IS_OK(status)) { +-- +2.52.0 + + +From 70a466aad41c97cdedb01d0a501d3a34c6fe1eb3 Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Wed, 2 Apr 2025 14:52:03 +0200 +Subject: [PATCH 95/96] smbd: use share_mode_do_locked_brl() in + vfs_default_durable_reconnect() + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(backported from commit dc03a06ffcc79d0818ae4a36fe3f2df705144138) +[slow@samba.org: conflict due to removed delayed write time handling] +[slow@samba.org: conflict due to filename_convert_dirfsp_rel()] +--- + source3/locking/brlock.c | 15 +- + source3/locking/proto.h | 3 +- + source3/smbd/durable.c | 439 ++++++++++++++++++++------------------- + 3 files changed, 234 insertions(+), 223 deletions(-) + +diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c +index b4c8501b4de..117b3edf05f 100644 +--- a/source3/locking/brlock.c ++++ b/source3/locking/brlock.c +@@ -1515,14 +1515,14 @@ bool brl_mark_disconnected(struct files_struct *fsp, + return true; + } + +-bool brl_reconnect_disconnected(struct files_struct *fsp) ++bool brl_reconnect_disconnected(struct files_struct *fsp, ++ struct byte_range_lock *br_lck) + { + uint32_t tid = fsp->conn->cnum; + uint64_t smblctx; + uint64_t fnum = fsp->fnum; + unsigned int i; + struct server_id self = messaging_server_id(fsp->conn->sconn->msg_ctx); +- struct byte_range_lock *br_lck = NULL; + + if (fsp->op == NULL) { + return false; +@@ -1540,13 +1540,7 @@ bool brl_reconnect_disconnected(struct files_struct *fsp) + * them instead. + */ + +- br_lck = brl_get_locks(talloc_tos(), fsp); +- if (br_lck == NULL) { +- return false; +- } +- + if (br_lck->num_locks == 0) { +- TALLOC_FREE(br_lck); + return true; + } + +@@ -1559,22 +1553,18 @@ bool brl_reconnect_disconnected(struct files_struct *fsp) + */ + + if (lock->context.smblctx != smblctx) { +- TALLOC_FREE(br_lck); + return false; + } + + if (lock->context.tid != TID_FIELD_INVALID) { +- TALLOC_FREE(br_lck); + return false; + } + + if (!server_id_is_disconnected(&lock->context.pid)) { +- TALLOC_FREE(br_lck); + return false; + } + + if (lock->fnum != FNUM_FIELD_INVALID) { +- TALLOC_FREE(br_lck); + return false; + } + +@@ -1585,7 +1575,6 @@ bool brl_reconnect_disconnected(struct files_struct *fsp) + + fsp->current_lock_count = br_lck->num_locks; + br_lck->modified = true; +- TALLOC_FREE(br_lck); + return true; + } + +diff --git a/source3/locking/proto.h b/source3/locking/proto.h +index d3b4e02bc26..37d382833fc 100644 +--- a/source3/locking/proto.h ++++ b/source3/locking/proto.h +@@ -83,7 +83,8 @@ struct brl_connectstate { + + bool brl_mark_disconnected(struct files_struct *fsp, + struct byte_range_lock *br_lck); +-bool brl_reconnect_disconnected(struct files_struct *fsp); ++bool brl_reconnect_disconnected(struct files_struct *fsp, ++ struct byte_range_lock *br_lck); + void brl_close_fnum(struct byte_range_lock *br_lck); + int brl_forall(void (*fn)(struct file_id id, struct server_id pid, + enum brl_type lock_type, +diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c +index 1506d37208a..34218d1c596 100644 +--- a/source3/smbd/durable.c ++++ b/source3/smbd/durable.c +@@ -563,121 +563,42 @@ static bool durable_reconnect_fn( + return false; /* Look at potential other entries */ + } + +-NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, +- struct smb_request *smb1req, +- struct smbXsrv_open *op, +- const DATA_BLOB old_cookie, +- TALLOC_CTX *mem_ctx, +- files_struct **result, +- DATA_BLOB *new_cookie) ++struct vfs_default_durable_reconnect_state { ++ NTSTATUS status; ++ TALLOC_CTX *mem_ctx; ++ struct smb_request *smb1req; ++ struct smbXsrv_open *op; ++ struct vfs_default_durable_cookie cookie; ++ struct files_struct *fsp; ++ DATA_BLOB new_cookie_blob; ++}; ++ ++static void vfs_default_durable_reconnect_fn(struct share_mode_lock *lck, ++ struct byte_range_lock *br_lck, ++ void *private_data) + { ++ struct vfs_default_durable_reconnect_state *state = private_data; + const struct loadparm_substitution *lp_sub = + loadparm_s3_global_substitution(); +- struct share_mode_lock *lck; ++ struct files_struct *fsp = state->fsp; + 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; +- int ret; ++ struct durable_reconnect_state rstate = { .op = state->op, .e = &e, }; + struct vfs_open_how how = { .flags = 0, }; + struct file_id file_id; +- struct smb_filename *smb_fname = NULL; +- enum ndr_err_code ndr_err; +- struct vfs_default_durable_cookie cookie; +- DATA_BLOB new_cookie_blob = data_blob_null; + bool have_share_mode_entry = false; +- +- *result = NULL; +- *new_cookie = data_blob_null; +- +- if (!lp_durable_handles(SNUM(conn))) { +- return NT_STATUS_NOT_SUPPORTED; +- } +- +- /* +- * the checks for kernel oplocks +- * and similar things are done +- * in the vfs_default_durable_cookie() +- * call below. +- */ +- +- ndr_err = ndr_pull_struct_blob_all( +- &old_cookie, +- talloc_tos(), +- &cookie, +- (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie); +- if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { +- status = ndr_map_error2ntstatus(ndr_err); +- return status; +- } +- +- if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) { +- return NT_STATUS_INVALID_PARAMETER; +- } +- +- if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) { +- return NT_STATUS_INVALID_PARAMETER; +- } +- +- if (!cookie.allow_reconnect) { +- return NT_STATUS_OBJECT_NAME_NOT_FOUND; +- } +- +- if (strcmp(cookie.servicepath, conn->connectpath) != 0) { +- return NT_STATUS_OBJECT_NAME_NOT_FOUND; +- } +- +- /* Create an smb_filename with stream_name == NULL. */ +- smb_fname = synthetic_smb_fname(talloc_tos(), +- cookie.base_name, +- NULL, +- NULL, +- 0, +- 0); +- if (smb_fname == NULL) { +- return NT_STATUS_NO_MEMORY; +- } +- +- ret = SMB_VFS_LSTAT(conn, smb_fname); +- if (ret == -1) { +- status = map_nt_error_from_unix_common(errno); +- DEBUG(1, ("Unable to lstat stream: %s => %s\n", +- smb_fname_str_dbg(smb_fname), +- nt_errstr(status))); +- return status; +- } +- +- if (!S_ISREG(smb_fname->st.st_ex_mode)) { +- return NT_STATUS_OBJECT_NAME_NOT_FOUND; +- } +- +- file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st); +- if (!file_id_equal(&cookie.id, &file_id)) { +- return NT_STATUS_OBJECT_NAME_NOT_FOUND; +- } +- +- /* +- * 1. check entry in locking.tdb +- */ +- +- lck = get_existing_share_mode_lock(mem_ctx, file_id); +- if (lck == NULL) { +- DEBUG(5, ("vfs_default_durable_reconnect: share-mode lock " +- "not obtained from db\n")); +- return NT_STATUS_OBJECT_NAME_NOT_FOUND; +- } ++ int ret; ++ bool ok; + + 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; ++ state->status = NT_STATUS_INTERNAL_DB_ERROR; + goto fail; + } + + if (e.pid.pid == 0) { + DBG_WARNING("Did not find a unique valid share mode entry\n"); +- status = NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + +@@ -685,69 +606,36 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, + DEBUG(5, ("vfs_default_durable_reconnect: denying durable " + "reconnect for handle that was not marked " + "disconnected (e.g. smbd or cluster node died)\n")); +- status = NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + +- if (e.share_file_id != op->global->open_persistent_id) { ++ if (e.share_file_id != state->op->global->open_persistent_id) { + DBG_INFO("denying durable " + "share_file_id changed %"PRIu64" != %"PRIu64" " + "(e.g. another client had opened the file)\n", + e.share_file_id, +- op->global->open_persistent_id); +- status = NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ state->op->global->open_persistent_id); ++ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + + if ((e.access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) && +- !CAN_WRITE(conn)) ++ !CAN_WRITE(fsp->conn)) + { + DEBUG(5, ("vfs_default_durable_reconnect: denying durable " + "share[%s] is not writeable anymore\n", +- lp_servicename(talloc_tos(), lp_sub, SNUM(conn)))); +- status = NT_STATUS_OBJECT_NAME_NOT_FOUND; +- goto fail; +- } +- +- /* +- * 2. proceed with opening file +- */ +- +- status = fsp_new(conn, conn, &fsp); +- if (!NT_STATUS_IS_OK(status)) { +- DEBUG(0, ("vfs_default_durable_reconnect: failed to create " +- "new fsp: %s\n", nt_errstr(status))); ++ lp_servicename(talloc_tos(), lp_sub, SNUM(fsp->conn)))); ++ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + + fh_set_private_options(fsp->fh, e.private_options); +- fsp->file_id = file_id; +- fsp->file_pid = smb1req->smbpid; +- fsp->vuid = smb1req->vuid; + fsp->open_time = e.time; + fsp->access_mask = e.access_mask; + fsp->fsp_flags.can_read = ((fsp->access_mask & FILE_READ_DATA) != 0); + fsp->fsp_flags.can_write = ((fsp->access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) != 0); +- fsp->fnum = op->local_id; +- fsp_set_gen_id(fsp); + +- /* +- * TODO: +- * Do we need to store the modified flag in the DB? +- */ +- fsp->fsp_flags.modified = false; +- /* +- * no durables for directories +- */ +- fsp->fsp_flags.is_directory = false; +- /* +- * For normal files, can_lock == !is_directory +- */ +- fsp->fsp_flags.can_lock = true; +- /* +- * We do not support aio write behind for smb2 +- */ +- fsp->fsp_flags.aio_write_behind = false; + fsp->oplock_type = e.op_type; + + if (fsp->oplock_type == LEASE_OPLOCK) { +@@ -760,21 +648,21 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, + */ + if (!GUID_equal(fsp_client_guid(fsp), + &e.client_guid)) { +- status = NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + +- status = leases_db_get( ++ state->status = leases_db_get( + &e.client_guid, + &e.lease_key, +- &file_id, ++ &fsp->file_id, + ¤t_state, /* current_state */ + NULL, /* breaking */ + NULL, /* breaking_to_requested */ + NULL, /* breaking_to_required */ + &lease_version, /* lease_version */ + &epoch); /* epoch */ +- if (!NT_STATUS_IS_OK(status)) { ++ if (!NT_STATUS_IS_OK(state->status)) { + goto fail; + } + +@@ -785,53 +673,46 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, + lease_version, + epoch); + if (fsp->lease == NULL) { +- status = NT_STATUS_NO_MEMORY; ++ state->status = NT_STATUS_NO_MEMORY; + goto fail; + } + } + +- fsp->initial_allocation_size = cookie.initial_allocation_size; +- fh_set_position_information(fsp->fh, cookie.position_information); ++ fsp->initial_allocation_size = state->cookie.initial_allocation_size; ++ fh_set_position_information(fsp->fh, state->cookie.position_information); + fsp->fsp_flags.update_write_time_triggered = +- cookie.update_write_time_triggered; ++ state->cookie.update_write_time_triggered; + fsp->fsp_flags.update_write_time_on_close = +- cookie.update_write_time_on_close; +- fsp->fsp_flags.write_time_forced = cookie.write_time_forced; ++ state->cookie.update_write_time_on_close; ++ fsp->fsp_flags.write_time_forced = state->cookie.write_time_forced; + fsp->close_write_time = nt_time_to_full_timespec( +- cookie.close_write_time); ++ state->cookie.close_write_time); + +- status = fsp_set_smb_fname(fsp, smb_fname); +- if (!NT_STATUS_IS_OK(status)) { +- DEBUG(0, ("vfs_default_durable_reconnect: " +- "fsp_set_smb_fname failed: %s\n", +- nt_errstr(status))); +- goto fail; +- } +- +- op->compat = fsp; +- fsp->op = op; ++ state->op->compat = fsp; ++ fsp->op = state->op; + + ok = reset_share_mode_entry( + lck, + e.pid, + e.share_file_id, +- messaging_server_id(conn->sconn->msg_ctx), +- smb1req->mid, ++ messaging_server_id(fsp->conn->sconn->msg_ctx), ++ state->smb1req->mid, + fh_get_gen_id(fsp->fh)); + if (!ok) { + DBG_DEBUG("Could not set new share_mode_entry values\n"); +- status = NT_STATUS_INTERNAL_ERROR; ++ state->status = NT_STATUS_INTERNAL_ERROR; + goto fail; + } + have_share_mode_entry = true; + +- ok = brl_reconnect_disconnected(fsp); +- if (!ok) { +- status = NT_STATUS_INTERNAL_ERROR; +- DEBUG(1, ("vfs_default_durable_reconnect: " +- "failed to reopen brlocks: %s\n", +- nt_errstr(status))); +- goto fail; ++ if (br_lck != NULL) { ++ ok = brl_reconnect_disconnected(fsp, br_lck); ++ if (!ok) { ++ state->status = NT_STATUS_INTERNAL_ERROR; ++ DBG_ERR("failed to reopen brlocks: %s\n", ++ nt_errstr(state->status)); ++ goto fail; ++ } + } + + /* +@@ -845,10 +726,9 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, + how.flags = O_RDONLY; + } + +- status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how); +- if (!NT_STATUS_IS_OK(status)) { +- DEBUG(1, ("vfs_default_durable_reconnect: failed to open " +- "file: %s\n", nt_errstr(status))); ++ state->status = fd_openat(fsp->conn->cwd_fsp, fsp->fsp_name, fsp, &how); ++ if (!NT_STATUS_IS_OK(state->status)) { ++ DBG_ERR("failed to open file: %s\n", nt_errstr(state->status)); + goto fail; + } + +@@ -863,70 +743,66 @@ NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, + + ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st); + if (ret == -1) { +- status = map_nt_error_from_unix_common(errno); +- DEBUG(1, ("Unable to fstat stream: %s => %s\n", +- smb_fname_str_dbg(smb_fname), +- nt_errstr(status))); ++ state->status = map_nt_error_from_unix_common(errno); ++ DBG_ERR("Unable to fstat stream: %s => %s\n", ++ fsp_str_dbg(fsp), ++ nt_errstr(state->status)); + goto fail; + } + + if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) { +- status = NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + +- file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st); +- if (!file_id_equal(&cookie.id, &file_id)) { +- status = NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ file_id = vfs_file_id_from_sbuf(fsp->conn, &fsp->fsp_name->st); ++ if (!file_id_equal(&state->cookie.id, &file_id)) { ++ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + + (void)fdos_mode(fsp); + +- ok = vfs_default_durable_reconnect_check_stat(&cookie.stat_info, ++ ok = vfs_default_durable_reconnect_check_stat(&state->cookie.stat_info, + &fsp->fsp_name->st, + fsp_str_dbg(fsp)); + if (!ok) { +- status = NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + goto fail; + } + +- status = set_file_oplock(fsp); +- if (!NT_STATUS_IS_OK(status)) { ++ state->status = set_file_oplock(fsp); ++ if (!NT_STATUS_IS_OK(state->status)) { + goto fail; + } + +- status = vfs_default_durable_cookie(fsp, mem_ctx, &new_cookie_blob); +- if (!NT_STATUS_IS_OK(status)) { +- DEBUG(1, ("vfs_default_durable_reconnect: " +- "vfs_default_durable_cookie - %s\n", +- nt_errstr(status))); ++ state->status = vfs_default_durable_cookie(fsp, ++ state->mem_ctx, ++ &state->new_cookie_blob); ++ if (!NT_STATUS_IS_OK(state->status)) { ++ DBG_ERR("vfs_default_durable_cookie - %s\n", ++ nt_errstr(state->status)); + goto fail; + } + +- smb1req->chain_fsp = fsp; +- smb1req->smb2req->compat_chain_fsp = fsp; +- +- DEBUG(10, ("vfs_default_durable_reconnect: opened file '%s'\n", +- fsp_str_dbg(fsp))); ++ state->smb1req->chain_fsp = fsp; ++ state->smb1req->smb2req->compat_chain_fsp = fsp; + +- TALLOC_FREE(lck); ++ DBG_DEBUG("opened file '%s'\n", fsp_str_dbg(fsp)); + + fsp->fsp_flags.is_fsa = true; + +- *result = fsp; +- *new_cookie = new_cookie_blob; +- +- return NT_STATUS_OK; ++ state->status = NT_STATUS_OK; ++ return; + + fail: +- if (fsp != NULL && have_share_mode_entry) { ++ if (have_share_mode_entry) { + /* + * Something is screwed up, delete the sharemode entry. + */ + del_share_mode(lck, fsp); + } +- if (fsp != NULL && fsp_get_pathref_fd(fsp) != -1) { ++ if (fsp_get_pathref_fd(fsp) != -1) { + NTSTATUS close_status; + close_status = fd_close(fsp); + if (!NT_STATUS_IS_OK(close_status)) { +@@ -934,11 +810,156 @@ fail: + nt_errstr(close_status)); + } + } +- TALLOC_FREE(lck); +- if (fsp != NULL) { +- op->compat = NULL; +- fsp->op = NULL; +- file_free(smb1req, fsp); ++ state->op->compat = NULL; ++ fsp->op = NULL; ++} ++ ++NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn, ++ struct smb_request *smb1req, ++ struct smbXsrv_open *op, ++ const DATA_BLOB old_cookie, ++ TALLOC_CTX *mem_ctx, ++ files_struct **result, ++ DATA_BLOB *new_cookie) ++{ ++ struct vfs_default_durable_reconnect_state state; ++ struct smb_filename *smb_fname = NULL; ++ struct file_id file_id; ++ NTSTATUS status; ++ enum ndr_err_code ndr_err; ++ int ret; ++ ++ *result = NULL; ++ *new_cookie = data_blob_null; ++ ++ if (!lp_durable_handles(SNUM(conn))) { ++ return NT_STATUS_NOT_SUPPORTED; ++ } ++ ++ state = (struct vfs_default_durable_reconnect_state) { ++ .mem_ctx = mem_ctx, ++ .smb1req = smb1req, ++ .op = op, ++ }; ++ ++ /* ++ * the checks for kernel oplocks ++ * and similar things are done ++ * in the vfs_default_durable_cookie() ++ * call below. ++ */ ++ ++ ndr_err = ndr_pull_struct_blob_all( ++ &old_cookie, ++ talloc_tos(), ++ &state.cookie, ++ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie); ++ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { ++ status = ndr_map_error2ntstatus(ndr_err); ++ return status; + } +- return status; ++ ++ if (strcmp(state.cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) { ++ return NT_STATUS_INVALID_PARAMETER; ++ } ++ ++ if (state.cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) { ++ return NT_STATUS_INVALID_PARAMETER; ++ } ++ ++ if (!state.cookie.allow_reconnect) { ++ return NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ } ++ ++ if (strcmp(state.cookie.servicepath, conn->connectpath) != 0) { ++ return NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ } ++ ++ /* Create an smb_filename with stream_name == NULL. */ ++ smb_fname = synthetic_smb_fname(talloc_tos(), ++ state.cookie.base_name, ++ NULL, ++ NULL, ++ 0, ++ 0); ++ if (smb_fname == NULL) { ++ return NT_STATUS_NO_MEMORY; ++ } ++ ++ ret = SMB_VFS_LSTAT(conn, smb_fname); ++ if (ret == -1) { ++ status = map_nt_error_from_unix_common(errno); ++ DEBUG(1, ("Unable to lstat stream: %s => %s\n", ++ smb_fname_str_dbg(smb_fname), ++ nt_errstr(status))); ++ return status; ++ } ++ if (!S_ISREG(smb_fname->st.st_ex_mode)) { ++ return NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ } ++ ++ file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st); ++ if (!file_id_equal(&state.cookie.id, &file_id)) { ++ return NT_STATUS_OBJECT_NAME_NOT_FOUND; ++ } ++ ++ status = fsp_new(conn, conn, &state.fsp); ++ if (!NT_STATUS_IS_OK(status)) { ++ DBG_ERR("failed to create new fsp: %s\n", ++ nt_errstr(status)); ++ return status; ++ } ++ state.fsp->file_id = file_id; ++ state.fsp->file_pid = smb1req->smbpid; ++ state.fsp->vuid = smb1req->vuid; ++ state.fsp->fnum = op->local_id; ++ fsp_set_gen_id(state.fsp); ++ ++ status = fsp_set_smb_fname(state.fsp, smb_fname); ++ if (!NT_STATUS_IS_OK(status)) { ++ DBG_ERR("fsp_set_smb_fname failed: %s\n", ++ nt_errstr(status)); ++ file_free(smb1req, state.fsp); ++ return status; ++ } ++ ++ /* ++ * TODO: ++ * Do we need to store the modified flag in the DB? ++ */ ++ state.fsp->fsp_flags.modified = false; ++ /* ++ * no durables for directories ++ */ ++ state.fsp->fsp_flags.is_directory = false; ++ /* ++ * For normal files, can_lock == !is_directory ++ */ ++ state.fsp->fsp_flags.can_lock = true; ++ /* ++ * We do not support aio write behind for smb2 ++ */ ++ state.fsp->fsp_flags.aio_write_behind = false; ++ ++ status = share_mode_do_locked_brl(state.fsp, ++ vfs_default_durable_reconnect_fn, ++ &state); ++ if (!NT_STATUS_IS_OK(status)) { ++ DBG_ERR("share_mode_do_locked_brl [%s] failed: %s\n", ++ smb_fname_str_dbg(smb_fname), nt_errstr(status)); ++ file_free(smb1req, state.fsp); ++ return status; ++ } ++ if (!NT_STATUS_IS_OK(state.status)) { ++ DBG_ERR("default_durable_reconnect_fn [%s] failed: %s\n", ++ smb_fname_str_dbg(smb_fname), ++ nt_errstr(state.status)); ++ file_free(smb1req, state.fsp); ++ return state.status; ++ } ++ ++ *result = state.fsp; ++ *new_cookie = state.new_cookie_blob; ++ ++ return NT_STATUS_OK; + } +-- +2.52.0 + + +From 05df077f6fd9402f2513a9c9a9d55cfbb40971cf Mon Sep 17 00:00:00 2001 +From: Ralph Boehme +Date: Tue, 28 Jan 2025 14:48:39 +0100 +Subject: [PATCH 96/96] s3:rpc_server/srvsvc: use brl_get_locks_readonly() + instead of brl_get_locks() + +No need to keep the record locked longer then needed. + +BUG: https://bugzilla.samba.org/show_bug.cgi?id=15767 + +Signed-off-by: Ralph Boehme +Reviewed-by: Stefan Metzmacher +(cherry picked from commit c36cc2b6720a2cfe54ce52a500dc499418e27e34) +--- + source3/rpc_server/srvsvc/srv_srvsvc_nt.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c +index d6e7bed5949..c07eefdfaad 100644 +--- a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c ++++ b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c +@@ -191,17 +191,23 @@ static WERROR net_enum_files(TALLOC_CTX *ctx, + /* need to count the number of locks on a file */ + + for (i=0; i<(*ctr3)->count; i++) { +- struct files_struct fsp = { .file_id = f_enum_cnt.fids[i], }; ++ struct files_struct *fsp = NULL; + struct byte_range_lock *brl = NULL; + +- brl = brl_get_locks(ctx, &fsp); ++ fsp = talloc_zero(talloc_tos(), struct files_struct); ++ if (fsp == NULL) { ++ return WERR_NOT_ENOUGH_MEMORY; ++ } ++ fsp->file_id = f_enum_cnt.fids[i]; ++ ++ brl = brl_get_locks_readonly(fsp); + if (brl == NULL) { + continue; + } + + (*ctr3)->array[i].num_locks = brl_num_locks(brl); +- + TALLOC_FREE(brl); ++ TALLOC_FREE(fsp); + } + + return WERR_OK; +-- +2.52.0 diff --git a/SPECS/samba.spec b/SPECS/samba.spec index ba2e55f..4562f8f 100644 --- a/SPECS/samba.spec +++ b/SPECS/samba.spec @@ -147,7 +147,7 @@ %define samba_requires_eq() %(LC_ALL="C" echo '%*' | xargs -r rpm -q --qf 'Requires: %%{name} = %%{epoch}:%%{version}\\n' | sed -e 's/ (none):/ /' -e 's/ 0:/ /' | grep -v "is not") %global samba_version 4.19.4 -%global baserelease 12 +%global baserelease 15 # This should be rc1 or %%nil %global pre_release %nil @@ -4479,6 +4479,15 @@ fi %endif %changelog +* Tue Jan 27 2026 Pavel Filipenský - 4.19.4-15 +- resolves: RHEL-132396 - Fix deadlock between two smbd processes + +* Tue Jan 27 2026 Pavel Filipenský - 4.19.4-14 + - resolves: RHEL-144390 - Fix winbind group resolution + +* Thu Jan 08 2026 Andreas Schneider - 4.19.4-13 +- resolves: RHEL-131616 - Fix regression with relative symlinks in a share + * Thu Oct 09 2025 Andreas Schneider - 4.19.4-12 - resolves: RHEL-119843 - Fix stale sharemode entries which can cause deadlocks