]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ksmbd: break conflicting-open leases only as far as needed
authorNamjae Jeon <linkinjeon@kernel.org>
Sun, 21 Jun 2026 10:51:35 +0000 (19:51 +0900)
committerSteve French <stfrench@microsoft.com>
Tue, 23 Jun 2026 01:15:06 +0000 (20:15 -0500)
smb2.lease.oplock and smb2.lease.breaking1 hold a lease and then issue a
single conflicting open on the same file.  The held lease must break one
step to drop write caching (RWH->RH, RW->R) and then stop, so
lease_break_info.count is 1 and the lease keeps its read/handle caching.

ksmbd instead cascaded the break all the way down to none
(e.g. RWH->RH->R->none), so the break count was 2 or 3 and the reported
lease state ended at 0.  Commit "chain pending lease breaks before waking
waiters" forces break_level to SMB2_OPLOCK_LEVEL_NONE for any non-lease
open against a handle-caching lease, which drives oplock_break()'s retry
loop down to none even when only one open is contending.

Drop that break_level override so a conflicting open breaks a lease only
to its own compatible level (level II, i.e. RH/R).

A deeper break is still required when a truncating open is also waiting
behind the same lease break.  smb2.lease.breaking3 keeps a normal open
pending through RWH->RH and an overwrite open pending behind it, and
expects the lease to continue RH->R->none before either open completes.
The overwrite waiter sets open_trunc on the lease while it blocks on the
pending break, so extend the retry loop to chain another break while that
truncating waiter still needs the lease at none.  The per-break open_trunc
snapshot stays cleared, so the cascade steps down (RH->R->none) instead of
collapsing straight to none, and the normal open stays pending until the
lease is fully broken.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/server/oplock.c

index 33e93084c3b5ff673bb2885010b4a01b6b7e5b53..afd492be88fb2fe98c5c663e6e829db0a24856b0 100644 (file)
@@ -1126,13 +1126,22 @@ again:
                if (wait_ack)
                        wait_lease_breaking(brk_opinfo);
                /*
-                * A break caused by a share-mode conflict only drops the
-                * conflicting caching bit and the triggering open still fails
-                * with a sharing violation, so it must stay a single break.
-                * Do not cascade down to req_op_level through the again loop.
+                * A share-mode conflict break only drops the conflicting
+                * caching bit; the triggering open fails with a sharing
+                * violation, so keep it to a single break.
+                *
+                * Otherwise chain another break while the lease is still
+                * incompatible with this open (req_op_level), or while a
+                * truncating waiter that arrived during the break still needs
+                * the lease dropped to none.  open_trunc snapshotted for this
+                * break stays cleared, so the next state is computed from the
+                * lease state and the cascade steps down (e.g. RH->R->none)
+                * instead of collapsing straight to none.
                 */
                if (wait_ack && !err && !share_break &&
-                   lease_break_needed(brk_opinfo, req_op_level, open_trunc))
+                   (lease_break_needed(brk_opinfo, req_op_level, open_trunc) ||
+                    (brk_opinfo->open_trunc &&
+                     lease->state != SMB2_LEASE_NONE_LE)))
                        goto again;
 
                wake_up_oplock_break(brk_opinfo);
@@ -1426,10 +1435,6 @@ int smb_grant_oplock(struct ksmbd_work *work, int req_op_level, u64 pid,
        prev_op_has_lease = prev_opinfo->is_lease;
        if (prev_op_has_lease)
                prev_op_state = prev_opinfo->o_lease->state;
-       if (prev_op_has_lease && !lctx &&
-           prev_op_state & SMB2_LEASE_HANDLE_CACHING_LE)
-               break_level = SMB2_OPLOCK_LEVEL_NONE;
-
        if (share_ret < 0 &&
            prev_opinfo->level == SMB2_OPLOCK_LEVEL_EXCLUSIVE) {
                err = share_ret;