diff --git a/samba-4.19-redhat.patch b/samba-4.19-redhat.patch index d6a9282..021c3dc 100644 --- a/samba-4.19-redhat.patch +++ b/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/70] 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). @@ -66,7 +66,7 @@ index 312c8ddf467..1b90ab46e90 100644 From 063606e8ec83a58972df47eb561ab267f8937ba4 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Thu, 17 Aug 2023 01:09:28 +0300 -Subject: [PATCH 02/70] 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. @@ -110,7 +110,7 @@ index 1b90ab46e90..cefdafa21b2 100644 From 3b548bf280ca59ef12a7af10a9131813067a850a Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 11 Aug 2023 18:46:42 +0300 -Subject: [PATCH 03/70] 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. @@ -144,7 +144,7 @@ index cefdafa21b2..c562722906b 100644 From 7592ed5032836dc43f657f66607a0a4661edcdb4 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 18 Aug 2023 17:06:43 +0300 -Subject: [PATCH 04/70] 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`. @@ -222,7 +222,7 @@ index 00000000000..0aad59607c2 From 7f7b235bda9e85c5ea330e52e734d1113a884571 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Wed, 16 Aug 2023 12:20:11 +0300 -Subject: [PATCH 05/70] 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 @@ -295,7 +295,7 @@ index 0aad59607c2..00000000000 From 49cc74015a603e80048a38fe635cd1ac28938ee4 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 18 Aug 2023 17:16:23 +0300 -Subject: [PATCH 06/70] 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. @@ -428,7 +428,7 @@ index 00000000000..4edc1dce730 From 4c0906bd79f030e591701234bc54bc749a42d686 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Wed, 16 Aug 2023 12:37:17 +0300 -Subject: [PATCH 07/70] 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 @@ -493,7 +493,7 @@ index 4edc1dce730..00000000000 From e61f30dc2518d5a1c239f090baea4a309307f3f8 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 18 Aug 2023 17:26:59 +0300 -Subject: [PATCH 08/70] 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. @@ -594,7 +594,7 @@ index 00000000000..83bc9f0ac1f From 7757b9b48546d71e19798d1260da97780caa99c3 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Wed, 16 Aug 2023 12:33:59 +0300 -Subject: [PATCH 09/70] 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. @@ -640,7 +640,7 @@ index 83bc9f0ac1f..00000000000 From 4e9b2e6409c5764ec0e66cc6c90b08e70f702e7c Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 9 Jan 2024 08:50:01 +0100 -Subject: [PATCH 10/70] 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 @@ -697,7 +697,7 @@ index 64c35782ae8..08d1a7348cd 100644 From fb3aefff51c02cf8ba3f8dfeb7d3f971e8d4902a Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Mon, 8 Jan 2024 18:05:08 +0200 -Subject: [PATCH 11/70] 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. @@ -901,7 +901,7 @@ index 00000000000..f1e590bc7d8 From 1a9af36177c7491687c75df151474bb10285f00e Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Thu, 18 Jan 2024 20:23:24 +0200 -Subject: [PATCH 12/70] 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 @@ -954,7 +954,7 @@ index f1e590bc7d8..00000000000 From f5fc88f9ae255f4dc135580f0fa4a02f5addc390 Mon Sep 17 00:00:00 2001 From: Gabriel Nagy Date: Fri, 19 Jan 2024 11:36:19 +0200 -Subject: [PATCH 13/70] 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. @@ -1003,7 +1003,7 @@ index cd5e54f1110..559c903e1a2 100644 From e8a6219181f2af87813b53fd09684650c1aa6f90 Mon Sep 17 00:00:00 2001 From: David Mulder Date: Fri, 5 Jan 2024 08:47:07 -0700 -Subject: [PATCH 14/70] 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 @@ -1071,7 +1071,7 @@ index 617ef79350c..babd8f90748 100644 From d0d1a890d6f2466691fa4ee663232ee0bd1c3776 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 14:14:30 +0100 -Subject: [PATCH 15/70] 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 @@ -1117,7 +1117,7 @@ index 559c903e1a2..7325d5132cf 100644 From 7f6c9a4945635c6eb8ada2255bd0febbf0f4e540 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 14:07:47 +0100 -Subject: [PATCH 16/70] 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 @@ -1177,7 +1177,7 @@ index 7325d5132cf..a25a9678587 100644 From 5321d5b5bd24d7659743576f2e12a7dc0a93a828 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 15:04:36 +0100 -Subject: [PATCH 17/70] 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 @@ -1223,7 +1223,7 @@ index a25a9678587..0b23cd688db 100644 From 6a7a8a4090b8cdb8e71f4ad590260ceeda253ce2 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 15:05:02 +0100 -Subject: [PATCH 18/70] 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 @@ -1270,7 +1270,7 @@ index 0b23cd688db..db681cb6f69 100644 From 43dc3d5d833bc1db885eb45402decd3225a7c946 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 15:05:24 +0100 -Subject: [PATCH 19/70] 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 @@ -1307,7 +1307,7 @@ index db681cb6f69..c8ad2039dc6 100644 From d8276d6a098d10f405b8f24c4dfb82af4496607c Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jan 2024 15:46:24 +0100 -Subject: [PATCH 20/70] 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 @@ -1360,7 +1360,7 @@ index c8ad2039dc6..2b7f7d22c2b 100644 From 585357bf0d8889747a2769c2451ee34766087d95 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 29 Jan 2024 17:46:30 +0100 -Subject: [PATCH 21/70] 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` @@ -1402,7 +1402,7 @@ index a74a8707d50..c3de32825db 100644 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/70] 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 @@ -1444,7 +1444,7 @@ index b364bbfa231..de6d36afe95 100644 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/70] 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 @@ -1527,7 +1527,7 @@ index d7a665abbc6..e625aa6473f 100644 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/70] 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 @@ -1561,7 +1561,7 @@ index 44ac4a5901a..606c65f8ab1 100755 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/70] 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 @@ -1634,7 +1634,7 @@ index 7ae112ada71..1d4bd395ba9 100755 From e5890e63c35a4a5af29ae16e6dd734c4a3a304cc Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 28 May 2024 13:51:53 +0200 -Subject: [PATCH 26/70] 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 @@ -1699,7 +1699,7 @@ index 50f4a6de3c6..ddf97c11973 100644 From 96a1ecd8db249fa03db60259cf76fdef9c1bd749 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 28 May 2024 13:53:51 +0200 -Subject: [PATCH 27/70] 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. @@ -1733,7 +1733,7 @@ index ddf97c11973..f74d8eb567c 100644 From 4934642b7a7d92c6d81ba25ef6e4b66e3805f708 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Tue, 28 May 2024 13:54:24 +0200 -Subject: [PATCH 28/70] 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 @@ -1789,7 +1789,7 @@ index 5c9fe07db95..b8002825161 100644 From cccc902c64c93db317bf4707d0af5e56b2887286 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Mon, 22 Jul 2024 12:26:55 +0200 -Subject: [PATCH 29/70] 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 @@ -2307,7 +2307,7 @@ index 36c08f47c54..db8e6e1c005 100644 From b04cb93ee52aac0ce7213d0581d69e852df52d4a Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Mon, 5 Feb 2024 15:03:48 +0100 -Subject: [PATCH 30/70] 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 @@ -2371,7 +2371,7 @@ index 3581c4b9173..93c12e00eb0 100644 From 29f0c0fb2f1cb0cfc4c615d31e82048b46a2cb0d Mon Sep 17 00:00:00 2001 From: Noel Power Date: Tue, 20 Feb 2024 09:26:29 +0000 -Subject: [PATCH 31/70] 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 @@ -2452,7 +2452,7 @@ index 93c12e00eb0..74be444fef5 100644 From ed138c4d679e8291de18162e1cac65cc9da33b4d Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Wed, 15 Jan 2025 10:21:19 -0800 -Subject: [PATCH 32/70] 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 @@ -2489,7 +2489,7 @@ index b914075d85c..196654b36bd 100644 From f8a7d7a3e8c3be3c7742c874239766b34c25ef3e Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 16 Jan 2025 16:12:31 -0800 -Subject: [PATCH 33/70] 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 @@ -2761,7 +2761,7 @@ index 196654b36bd..abb096bde1b 100644 From 9fd06d5c331f5babaf417cc7339d12854a79fe4b Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Thu, 15 Feb 2024 17:29:46 +0100 -Subject: [PATCH 34/70] 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... @@ -2798,7 +2798,7 @@ index 280ccd585b0..6fcaa26810c 100644 From 58e28d056f2df0906ee77ccfb9b56e8a764b38b4 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Tue, 7 May 2024 14:53:24 +0000 -Subject: [PATCH 35/70] 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 @@ -2856,7 +2856,7 @@ index 6fcaa26810c..da173e7bbb0 100644 From e4d5269b2359c670acdf0cba81248f148ae68c17 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 11 Oct 2024 13:32:22 +0000 -Subject: [PATCH 36/70] 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 @@ -2902,7 +2902,7 @@ index da173e7bbb0..8278959dd7d 100644 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/70] 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 @@ -3086,7 +3086,7 @@ index fe93528787d..eca4116d0c8 100644 From 7da6072ce95bca445368f6d0453247c8f92fcdf2 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Fri, 9 May 2025 09:38:41 +0200 -Subject: [PATCH 38/70] 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 @@ -3389,7 +3389,7 @@ index f0fd18a8fa6..47c68257b12 100644 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/70] 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 @@ -3443,7 +3443,7 @@ index 195259daa43..86dbf68f033 100644 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/70] 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 @@ -3480,7 +3480,7 @@ index 4ea4e4751ee..40244e125ce 100644 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/70] 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 @@ -3520,7 +3520,7 @@ index c5a4b0ef30c..7957d3ab34d 100644 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/70] 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 @@ -3569,7 +3569,7 @@ index cff3c53845f..2968ca47734 100644 From 3ef02a381cdc83549506e159ebc457730c06c547 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 22 Jul 2025 19:22:31 +0200 -Subject: [PATCH 43/70] 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. @@ -3620,7 +3620,7 @@ index f74d8eb567c..f324321c87b 100644 From b0dbc167f85deabff2af5b18bc201e8db0d3b97d Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 22 Jul 2025 19:16:14 +0200 -Subject: [PATCH 44/70] 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. @@ -3654,7 +3654,7 @@ index eca4116d0c8..3a7a9114988 100644 From 1961f54ce07f7dc3cfcae5c00b96b39109f08b3a Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 19 Dec 2023 11:11:55 +0100 -Subject: [PATCH 45/70] 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 @@ -3724,7 +3724,7 @@ index 1d4b9b1a840..8d78831492f 100644 From 26de62a2a968dd5b73af296251b26112cdd533e5 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 19 Dec 2023 11:12:49 +0100 -Subject: [PATCH 46/70] 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: @@ -3797,7 +3797,7 @@ index c3a13f5ec6e..67764a0b027 100644 From 2c27aae5a4c8d7368dc142fb2be36919296d2a02 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 2 Jan 2024 12:49:14 +0100 -Subject: [PATCH 47/70] 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 @@ -3961,7 +3961,7 @@ index 8693dcf1153..45fb90381e2 100644 From 99d7e841d4e18f760c137530bbed0dea6115311a Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 2 Jan 2024 13:25:25 +0100 -Subject: [PATCH 48/70] 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 @@ -4039,7 +4039,7 @@ index 45fb90381e2..55a49e0ba93 100644 From 5041a6fa5cdfd21bf697249d900ea5c107d355a2 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 2 Jan 2024 14:34:26 +0100 -Subject: [PATCH 49/70] 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 @@ -4185,7 +4185,7 @@ index 74be444fef5..6582bd60245 100644 From f2fc99f0c7d441115a486413f345c0226a00b38b Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Mon, 18 Dec 2023 12:35:58 +0100 -Subject: [PATCH 50/70] 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 @@ -4230,7 +4230,7 @@ index 6582bd60245..b9849f82396 100644 From 7d102268ebbebf6fc723a43485a82f72069d00ee Mon Sep 17 00:00:00 2001 From: Volker Lendecke Date: Fri, 16 Dec 2022 16:35:00 +0100 -Subject: [PATCH 51/70] 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() @@ -4393,7 +4393,7 @@ index 55a49e0ba93..9fd85af992a 100644 From edaabc3d53fddd9e2fa6168c8bf01ebfbf229657 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 25 Apr 2024 15:24:57 +0200 -Subject: [PATCH 52/70] 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 @@ -4473,7 +4473,7 @@ index f0189e0e896..50b35f27b3e 100644 From c25f1811c2ccaa2d5cc8005597fb9979aa1102ee Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 4 Apr 2024 12:31:05 +0200 -Subject: [PATCH 53/70] 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 @@ -4595,7 +4595,7 @@ index 50b35f27b3e..c372ec8c431 100644 From 23dbf8f0317810d65e716a3c9b947c7a6549cb46 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 25 Apr 2024 15:17:08 +0200 -Subject: [PATCH 54/70] 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. @@ -4688,7 +4688,7 @@ index c372ec8c431..8ddf9c6b1c8 100644 From 59975168627e4bfbd2e75a611cb8cb13019a7df3 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Fri, 5 Apr 2024 12:15:28 +0200 -Subject: [PATCH 55/70] 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 @@ -4725,7 +4725,7 @@ index b9849f82396..da129119c7f 100644 From e619b72fe1b9c36963c452c1d102009b28e8e289 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 4 Apr 2024 19:18:19 +0200 -Subject: [PATCH 56/70] 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 @@ -4983,7 +4983,7 @@ index da129119c7f..4cc5190f690 100644 From e6a0d821ba28839728371ca94bb364dd6865b5dd Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Wed, 20 Mar 2024 14:27:27 +0100 -Subject: [PATCH 57/70] 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. @@ -5031,7 +5031,7 @@ index fbbe4ef3992..188eaa14839 100644 From 15276d7645255ddddf2a3bf6b7a429e3d40ec9b7 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Wed, 20 Mar 2024 14:28:43 +0100 -Subject: [PATCH 58/70] 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 @@ -5173,7 +5173,7 @@ index 188eaa14839..dbe91132f7f 100644 From 4631b9d60a874db10dbdd52406d0094a7dbd1356 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 26 Aug 2024 14:11:02 +0200 -Subject: [PATCH 59/70] 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 @@ -5294,7 +5294,7 @@ index 529504fd8d5..dcf0de0a2d9 100644 From c8e88652163cc56b1f9fb0926a140c81e6b7ec94 Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 26 Aug 2024 14:42:02 +0200 -Subject: [PATCH 60/70] 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 @@ -5470,7 +5470,7 @@ index 5b6477e47bc..9cf7f5da78b 100644 From 56a3aaf95c44052b19b61115686c71d5b7dbab4a Mon Sep 17 00:00:00 2001 From: Stefan Metzmacher Date: Mon, 26 Aug 2024 14:42:12 +0200 -Subject: [PATCH 61/70] 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 @@ -5530,7 +5530,7 @@ index 0ab32974824..fd5c156956f 100755 From d8f01885145ecfce15f2507fdcc625442db1738c Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 9 Apr 2024 14:52:44 +0200 -Subject: [PATCH 62/70] 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() @@ -5834,7 +5834,7 @@ index b21c223b2e4..50075ddd3f7 100644 From b248ddd3dd7193ba44c9ad86488dd180a25e3774 Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Tue, 9 Apr 2024 14:53:32 +0200 -Subject: [PATCH 63/70] 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 @@ -5906,7 +5906,7 @@ index 50075ddd3f7..98d0d403e30 100644 From 67ff429e41004899e514d893e80332de79ca2bab Mon Sep 17 00:00:00 2001 From: Earl Chew Date: Sun, 17 Dec 2023 08:37:33 -0800 -Subject: [PATCH 64/70] Augment library_flags() to return libraries +Subject: [PATCH 64/96] Augment library_flags() to return libraries Extend library_flags() to return the libraries provided by pkg-config --libs. @@ -6075,7 +6075,7 @@ index 9c27fc664f0..58858f69b31 100644 From a4f79d7fb725fab47bda53b9482c1ee301a8393a Mon Sep 17 00:00:00 2001 From: Earl Chew Date: Sat, 16 Dec 2023 17:47:09 -0800 -Subject: [PATCH 65/70] Improve CHECK_LIB interaction with CHECK_PKG +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 @@ -6134,7 +6134,7 @@ index d3b6503c5ca..b1d2f761095 100644 From 2b4f5a62eac69e12ecd9a1e3919ea4a8b3d40820 Mon Sep 17 00:00:00 2001 From: Earl Chew Date: Sat, 16 Dec 2023 08:48:36 -0800 -Subject: [PATCH 66/70] Combine ICU libraries icu-i18n and icu-uc into a single +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 @@ -6244,7 +6244,7 @@ index 58858f69b31..c49b55a4fd4 100644 From 8e5968634b263c20ad71c75e839abb217614b567 Mon Sep 17 00:00:00 2001 From: Earl Chew Date: Fri, 10 May 2024 19:46:28 -0700 -Subject: [PATCH 67/70] Restore empty string default for conf.env['icu-libs'] +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 @@ -6281,7 +6281,7 @@ index c49b55a4fd4..adae44eab5e 100644 From 88a29be0ed6cf611eb812c0729d2ee61be07a3a3 Mon Sep 17 00:00:00 2001 From: Earl Chew Date: Fri, 27 Sep 2024 06:50:31 -0700 -Subject: [PATCH 68/70] Describe implication of upstream ICU-22610 +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. @@ -6320,7 +6320,7 @@ index adae44eab5e..451f7f7bca3 100644 From 72c6766af2ac55854b816147a277404d98b1de9a Mon Sep 17 00:00:00 2001 From: Ralph Boehme Date: Thu, 8 Jan 2026 11:55:18 +0100 -Subject: [PATCH 69/70] smbd: add a directory argument to +Subject: [PATCH 69/96] smbd: add a directory argument to safe_symlink_target_path() Existing caller passes NULL, no change in behaviour. Prepares for @@ -6449,7 +6449,7 @@ index 9fd85af992a..f6e9ed6aae0 100644 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/70] s3:libads: Reset ads->config.flags in ads_disconnect() +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 @@ -6519,3 +6519,7416 @@ index cc00753ff74..625377fa2cc 100644 -- 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/samba.spec b/samba.spec index 29c2ed5..4562f8f 100644 --- a/samba.spec +++ b/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 14 +%global baserelease 15 # This should be rc1 or %%nil %global pre_release %nil @@ -4479,6 +4479,9 @@ 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