* breaking the delegation in this case.
*/
if (!(ia_valid & ATTR_DELEG)) {
- error = try_break_deleg(inode, delegated_inode);
+ error = try_break_deleg(inode, 0, delegated_inode);
if (error)
return error;
}
}
static bool
-any_leases_conflict(struct inode *inode, struct file_lease *breaker)
+ignore_dir_deleg_break(struct file_lease *fl, unsigned int flags)
{
- struct file_lock_context *ctx = inode->i_flctx;
- struct file_lock_core *flc;
+ 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 unsigned int
+break_lease_flags_to_type(unsigned int flags)
+{
+ if (flags & LEASE_BREAK_LEASE)
+ return FL_LEASE;
+ else if (flags & LEASE_BREAK_DELEG)
+ return FL_DELEG;
+ else if (flags & LEASE_BREAK_LAYOUT)
+ return FL_LAYOUT;
+ else
+ return 0;
+
+}
+
+static struct file_lease *
+first_visible_lease(struct inode *inode, struct file_lease *new_fl, unsigned int flags)
+{
+ struct file_lock_context *ctx = locks_inode_context(inode);
+ struct file_lease *fl;
lockdep_assert_held(&ctx->flc_lock);
- list_for_each_entry(flc, &ctx->flc_lease, flc_list) {
- if (leases_conflict(flc, &breaker->c))
- return true;
+ list_for_each_entry(fl, &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;
+ return fl;
}
- return false;
+ return NULL;
}
+
/**
- * __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)
{
bool want_write = !(flags & LEASE_BREAK_OPEN_RDONLY);
int error = 0;
- if (flags & LEASE_BREAK_LEASE)
- type = FL_LEASE;
- else if (flags & LEASE_BREAK_DELEG)
- type = FL_DELEG;
- else if (flags & LEASE_BREAK_LAYOUT)
- type = FL_LAYOUT;
- else
+ type = break_lease_flags_to_type(flags);
+ if (!type)
return -EINVAL;
new_fl = lease_alloc(NULL, type, want_write ? F_WRLCK : F_RDLCK);
time_out_leases(inode, &dispose);
- if (!any_leases_conflict(inode, new_fl))
+ if (!first_visible_lease(inode, new_fl, flags))
goto out;
break_time = 0;
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;
locks_delete_lock_ctx(&fl->c, &dispose);
}
- if (list_empty(&ctx->flc_lease))
+ fl = first_visible_lease(inode, new_fl, flags);
+ if (!fl)
goto out;
if (flags & LEASE_BREAK_NONBLOCK) {
}
restart:
- fl = list_first_entry(&ctx->flc_lease, struct file_lease, c.flc_list);
break_time = fl->fl_break_time;
if (break_time != 0) {
if (time_after(jiffies, break_time)) {
*/
if (error == 0)
time_out_leases(inode, &dispose);
- if (any_leases_conflict(inode, new_fl))
+ fl = first_visible_lease(inode, new_fl, flags);
+ if (fl)
goto restart;
error = 0;
}
error = security_inode_create(dir, dentry, mode);
if (error)
return error;
- error = try_break_deleg(dir, di);
+ error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);
if (error)
return error;
error = dir->i_op->create(idmap, dir, dentry, mode, true);
/* Negative dentry, just create the file */
if (!dentry->d_inode && (open_flag & O_CREAT)) {
/* but break the directory lease first! */
- error = try_break_deleg(dir_inode, delegated_inode);
+ error = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, delegated_inode);
if (error)
goto out_dput;
if (error)
return error;
- error = try_break_deleg(dir, delegated_inode);
+ error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, delegated_inode);
if (error)
return error;
if (max_links && dir->i_nlink >= max_links)
goto err;
- error = try_break_deleg(dir, delegated_inode);
+ error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, delegated_inode);
if (error)
goto err;
if (error)
goto out;
- error = try_break_deleg(dir, delegated_inode);
+ error = try_break_deleg(dir, LEASE_BREAK_DIR_DELETE, delegated_inode);
if (error)
goto out;
else {
error = security_inode_unlink(dir, dentry);
if (!error) {
- error = try_break_deleg(dir, delegated_inode);
+ error = try_break_deleg(dir, LEASE_BREAK_DIR_DELETE, delegated_inode);
if (error)
goto out;
- error = try_break_deleg(target, delegated_inode);
+ error = try_break_deleg(target, 0, delegated_inode);
if (error)
goto out;
error = dir->i_op->unlink(dir, dentry);
if (error)
return error;
- error = try_break_deleg(dir, delegated_inode);
+ error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, delegated_inode);
if (error)
return error;
else if (max_links && inode->i_nlink >= max_links)
error = -EMLINK;
else {
- error = try_break_deleg(dir, delegated_inode);
+ error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, delegated_inode);
if (!error)
- error = try_break_deleg(inode, delegated_inode);
+ error = try_break_deleg(inode, 0, delegated_inode);
if (!error)
error = dir->i_op->link(old_dentry, dir, new_dentry);
}
old_dir->i_nlink >= max_links)
goto out;
}
- error = try_break_deleg(old_dir, delegated_inode);
+ error = try_break_deleg(old_dir,
+ old_dir == new_dir ? LEASE_BREAK_DIR_RENAME :
+ LEASE_BREAK_DIR_DELETE,
+ delegated_inode);
if (error)
goto out;
if (new_dir != old_dir) {
- error = try_break_deleg(new_dir, delegated_inode);
+ error = try_break_deleg(new_dir, LEASE_BREAK_DIR_CREATE, delegated_inode);
if (error)
goto out;
}
if (!is_dir) {
- error = try_break_deleg(source, delegated_inode);
+ error = try_break_deleg(source, 0, delegated_inode);
if (error)
goto out;
}
if (target && !new_is_dir) {
- error = try_break_deleg(target, delegated_inode);
+ error = try_break_deleg(target, 0, delegated_inode);
if (error)
goto out;
}
if (error)
goto out_inode_unlock;
- error = try_break_deleg(inode, &delegated_inode);
+ error = try_break_deleg(inode, 0, &delegated_inode);
if (error)
goto out_inode_unlock;
if (error)
goto out_inode_unlock;
- error = try_break_deleg(inode, &delegated_inode);
+ error = try_break_deleg(inode, 0, &delegated_inode);
if (error)
goto out_inode_unlock;
if (error)
goto out;
- error = try_break_deleg(inode, delegated_inode);
+ error = try_break_deleg(inode, 0, delegated_inode);
if (error)
goto out;
if (error)
goto out;
- error = try_break_deleg(inode, delegated_inode);
+ error = try_break_deleg(inode, 0, delegated_inode);
if (error)
goto out;
#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_DELEG BIT(2) /* NFSv4 delegation */
+#define FL_ACCESS BIT(3) /* not trying to lock, just looking */
+#define FL_EXISTS BIT(4) /* when unlocking, test for existence */
+#define FL_LEASE BIT(5) /* file lease */
+#define FL_CLOSE BIT(6) /* unlock on close */
+#define FL_SLEEP BIT(7) /* A blocking lock */
+#define FL_DOWNGRADE_PENDING BIT(8) /* Lease is being downgraded */
+#define FL_UNLOCK_PENDING BIT(9) /* Lease is being broken */
+#define FL_OFDLCK BIT(10) /* POSIX lock "owned" by struct file */
+#define FL_LAYOUT BIT(11) /* outstanding pNFS layout */
+#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)
#define LEASE_BREAK_LAYOUT BIT(2) // break layouts only
#define LEASE_BREAK_NONBLOCK BIT(3) // non-blocking break
#define LEASE_BREAK_OPEN_RDONLY BIT(4) // readonly open event
+#define LEASE_BREAK_DIR_CREATE BIT(5) // dir deleg create event
+#define LEASE_BREAK_DIR_DELETE BIT(6) // dir deleg delete event
+#define LEASE_BREAK_DIR_RENAME BIT(7) // dir deleg rename event
+
int __break_lease(struct inode *inode, unsigned int flags);
void lease_get_mtime(struct inode *, struct timespec64 *time);
return di->di_inode;
}
-static inline int try_break_deleg(struct inode *inode,
+/**
+ * try_break_deleg - do a non-blocking delegation break
+ * @inode: inode that should have its delegations broken
+ * @flags: extra LEASE_BREAK_* flags to pass to break_deleg()
+ * @di: returns pointer to delegated inode (may be NULL)
+ *
+ * Break delegations in a non-blocking fashion. If there are
+ * outstanding delegations and @di is set, then an extra reference
+ * will be taken on @inode and @di->di_inode will be populated so
+ * that it may be waited upon.
+ *
+ * Returns 0 if there is no need to wait or an error. If -EWOULDBLOCK
+ * is returned, then @di will be populated (if non-NULL).
+ */
+static inline int try_break_deleg(struct inode *inode, unsigned int flags,
struct delegated_inode *di)
{
int ret;
- ret = break_deleg(inode, LEASE_BREAK_NONBLOCK);
+ ret = break_deleg(inode, flags | LEASE_BREAK_NONBLOCK);
if (ret == -EWOULDBLOCK && di) {
di->di_inode = inode;
ihold(inode);
return 0;
}
-static inline int try_break_deleg(struct inode *inode,
+static inline int try_break_deleg(struct inode *inode, unsigned int flags,
struct delegated_inode *delegated_inode)
{
return 0;
{ FL_DOWNGRADE_PENDING, "FL_DOWNGRADE_PENDING" }, \
{ FL_UNLOCK_PENDING, "FL_UNLOCK_PENDING" }, \
{ FL_OFDLCK, "FL_OFDLCK" }, \
- { FL_RECLAIM, "FL_RECLAIM"})
+ { FL_RECLAIM, "FL_RECLAIM" }, \
+ { FL_IGN_DIR_CREATE, "FL_IGN_DIR_CREATE" }, \
+ { FL_IGN_DIR_DELETE, "FL_IGN_DIR_DELETE" }, \
+ { FL_IGN_DIR_RENAME, "FL_IGN_DIR_RENAME" })
#define show_fl_type(val) \
__print_symbolic(val, \