[PATCH RFC v2 16/28] filelock: add support for ignoring deleg breaks for dir change events

Jeff Layton
2025-06-02T14:02:56Z

If a NFS client requests a directory delegation with a notification
bitmask covering directory change events, the server shouldn't recall
the delegation. Instead the client will be notified of the change after
the fact.

Add a support for ignoring lease breaks on directory changes.

Signed-off-by: Jeff Layton <jlayton@kernel.org>
---
fs/locks.c | 56 ++++++++++++++++++++++++++++++++++++++++--------
include/linux/filelock.h | 29 ++++++++++++++-----------
2 files changed, 63 insertions(+), 22 deletions(-)

diff --git a/fs/locks.c b/fs/locks.c
index 6e46176d1e00962904f03c151500e593f410e4c6..95270a1fab4a1792a6fcad738cc9d937d99ad2af 100644
--- a/fs/locks.c
+++ b/fs/locks.c
@@ -1526,15 +1526,52 @@ any_leases_conflict(struct inode *inode, struct file_lease *breaker)
return false;
}

+static bool
+ignore_dir_deleg_break(struct file_lease *fl, unsigned int flags)
+{
+ if ((flags & LEASE_BREAK_DIR_CREATE) && (fl->c.flc_flags & FL_IGN_DIR_CREATE))
+ return true;
+ if ((flags & LEASE_BREAK_DIR_DELETE) && (fl->c.flc_flags & FL_IGN_DIR_DELETE))
+ return true;
+ if ((flags & LEASE_BREAK_DIR_RENAME) && (fl->c.flc_flags & FL_IGN_DIR_RENAME))
+ return true;
+
+ return false;
+}
+
+static bool
+visible_leases_remaining(struct inode *inode, unsigned int flags)
+{
+ struct file_lock_context *ctx = locks_inode_context(inode);
+ struct file_lease *fl;
+
+ lockdep_assert_held(&ctx->flc_lock);
+
+ if (list_empty(&ctx->flc_lease))
+ return false;
+
+ if (!S_ISDIR(inode->i_mode))
+ return true;
+
+ list_for_each_entry(fl, &ctx->flc_lease, c.flc_list) {
+ if (!ignore_dir_deleg_break(fl, flags))
+ return true;
+ }
+ return false;
+}
+
/**
- * __break_lease - revoke all outstanding leases on file
- * @inode: the inode of the file to return
- * @flags: LEASE_BREAK_* flags
+ * __break_lease - revoke all outstanding leases on file
+ * @inode: the inode of the file to return
+ * @flags: LEASE_BREAK_* flags
*
- * break_lease (inlined for speed) has checked there already is at least
- * some kind of lock (maybe a lease) on this file. Leases are broken on
- * a call to open() or truncate(). This function can block waiting for the
- * lease break unless you specify LEASE_BREAK_NONBLOCK.
+ * break_lease (inlined for speed) has checked there already is at least
+ * some kind of lock (maybe a lease) on this file. Leases and Delegations
+ * are broken on a call to open() or truncate(). Delegations are also
+ * broken on any event that would change the ctime. Directory delegations
+ * are broken whenever the directory changes (unless the delegation is set
+ * up to ignore the event). This function can block waiting for the lease
+ * break unless you specify LEASE_BREAK_NONBLOCK.
*/
int __break_lease(struct inode *inode, unsigned int flags)
{
@@ -1545,7 +1582,6 @@ int __break_lease(struct inode *inode, unsigned int flags)
bool want_write = !(flags & LEASE_BREAK_OPEN_RDONLY);
int error = 0;

-
new_fl = lease_alloc(NULL, want_write ? F_WRLCK : F_RDLCK);
if (IS_ERR(new_fl))
return PTR_ERR(new_fl);
@@ -1584,6 +1620,8 @@ int __break_lease(struct inode *inode, unsigned int flags)
list_for_each_entry_safe(fl, tmp, &ctx->flc_lease, c.flc_list) {
if (!leases_conflict(&fl->c, &new_fl->c))
continue;
+ if (S_ISDIR(inode->i_mode) && ignore_dir_deleg_break(fl, flags))
+ continue;
if (want_write) {
if (fl->c.flc_flags & FL_UNLOCK_PENDING)
continue;
@@ -1599,7 +1637,7 @@ int __break_lease(struct inode *inode, unsigned int flags)
locks_delete_lock_ctx(&fl->c, &dispose);
}

- if (list_empty(&ctx->flc_lease))
+ if (!visible_leases_remaining(inode, flags))
goto out;

if (flags & LEASE_BREAK_NONBLOCK) {
diff --git a/include/linux/filelock.h b/include/linux/filelock.h
index f2b2d1e1d1ab08671895c3bfe398e5bba02353d8..32b30c14f5fd52727b1a18957e9dbc930c922941 100644
--- a/include/linux/filelock.h
+++ b/include/linux/filelock.h
@@ -4,19 +4,22 @@

#include <linux/fs.h>

-#define FL_POSIX 1
-#define FL_FLOCK 2
-#define FL_DELEG 4 /* NFSv4 delegation */
-#define FL_ACCESS 8 /* not trying to lock, just looking */
-#define FL_EXISTS 16 /* when unlocking, test for existence */
-#define FL_LEASE 32 /* lease held on this file */
-#define FL_CLOSE 64 /* unlock on close */
-#define FL_SLEEP 128 /* A blocking lock */
-#define FL_DOWNGRADE_PENDING 256 /* Lease is being downgraded */
-#define FL_UNLOCK_PENDING 512 /* Lease is being broken */
-#define FL_OFDLCK 1024 /* lock is "owned" by struct file */
-#define FL_LAYOUT 2048 /* outstanding pNFS layout */
-#define FL_RECLAIM 4096 /* reclaiming from a reboot server */
+#define FL_POSIX BIT(0) /* POSIX lock */
+#define FL_FLOCK BIT(1) /* BSD lock */
+#define FL_LEASE BIT(2) /* file lease */
+#define FL_DELEG BIT(3) /* NFSv4 delegation */
+#define FL_LAYOUT BIT(4) /* outstanding pNFS layout */
+#define FL_ACCESS BIT(5) /* not trying to lock, just looking */
+#define FL_EXISTS BIT(6) /* when unlocking, test for existence */
+#define FL_CLOSE BIT(7) /* unlock on close */
+#define FL_SLEEP BIT(8) /* A blocking lock */
+#define FL_DOWNGRADE_PENDING BIT(9) /* Lease is being downgraded */
+#define FL_UNLOCK_PENDING BIT(10) /* Lease is being broken */
+#define FL_OFDLCK BIT(11) /* POSIX lock "owned" by struct file */
+#define FL_RECLAIM BIT(12) /* reclaiming from a reboot server */
+#define FL_IGN_DIR_CREATE BIT(13) /* ignore DIR_CREATE events */
+#define FL_IGN_DIR_DELETE BIT(14) /* ignore DIR_DELETE events */
+#define FL_IGN_DIR_RENAME BIT(15) /* ignore DIR_RENAME events */

#define FL_CLOSE_POSIX (FL_POSIX | FL_CLOSE)

--
2.49.0