From: Namjae Jeon Date: Sun, 21 Jun 2026 10:51:35 +0000 (+0900) Subject: ksmbd: break conflicting-open leases only as far as needed X-Git-Tag: v7.2-rc1~23^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=889d2e38943ad9ab253dfd7520e6d92867825e7d;p=thirdparty%2Fkernel%2Flinux.git ksmbd: break conflicting-open leases only as far as needed 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 Signed-off-by: Steve French --- diff --git a/fs/smb/server/oplock.c b/fs/smb/server/oplock.c index 33e93084c3b5f..afd492be88fb2 100644 --- a/fs/smb/server/oplock.c +++ b/fs/smb/server/oplock.c @@ -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;