diff mbox

[2/3] fs: hide another detail of delegation logic

Message ID 877excde18.fsf@notabene.neil.brown.name (mailing list archive)
State New, archived
Headers show

Commit Message

NeilBrown Sept. 5, 2017, 9:35 p.m. UTC
On Tue, Sep 05 2017, J. Bruce Fields wrote:

> On Mon, Sep 04, 2017 at 02:52:43PM +1000, NeilBrown wrote:
>> On Fri, Sep 01 2017, J. Bruce Fields wrote:
>> 
>> >> 
>> >> nfsd would need to find that delegation, prevent further delegations
>> >> being handed out, and check that there aren't already conflicting
>> >> delegations.  If there are conflicts, recall them.  Once there are no
>> >> conflicting delegations, make the vfs_ request.
>> >
>> > The way that we currently serialize setting, unsetting, and breaking
>> > delegations is by locks on the delegated inodes which aren't taken till
>> > deeper in the vfs code.
>> 
>> Do we?
>> I can see nfs4_set_delegation adding a new delegation for a new client
>> without entering the vfs at all if there is already a lease held.
>
> By "delegations", I meant locks of type FL_DELEG.  But even then I was
> wrong, apologies.
>
> There is an inode_trylock in generic_add_lease that will prevent any new
> delegations from being given while the inode's locked.
>
>> If there isn't a lease already, vfs_setlease() is called, which doesn't
>> its own internal locking of course.  Much the same applies to unsetting
>> delegations.
>> Breaking delegations involves nfsd_break_deleg_cb() which has a comment
>> that it is called with i_lock held.... that seems to be used to
>> be sure that it is safe to a reference to the delegation state id.
>> Is that the only dependency on the vfs locking, or did I miss something?
>> 
>> >
>> > I guess you're suggesting adding a second mechanism to prevent
>> > delegations being given out on the inode.  We could add an atomic
>> > counter taken by each nfsd breaker while it's in progress.  Hrm.
>> 
>> Something like that.
>> We would also need to be able to look up an nfs4_file by inode (why
>> *are* they hashed by file handle??)
>
> Grepping the logs....  That was ca9432178378 "nfsd: Use the filehandle
> to look up the struct nfs4_file instead of inode" which doesn't give a
> full justification.  Later commits suggest it might be about keeping
> nfsv4 state in many-to-one filehandle->inode cases (spec requirement, I
> believe) and preventing the nfs4_file from pinning the inode (not seeing
> immediately why that was an issue).
>
> Anyway, I can't think of a reason why hashing the filehandle's a
> problem.

Thanks for the background.  I didn't see it as a problem exactly,
though I did wonder about different filehandles mapping to the same
nfs4_file (unlikely but possible).  You say that is required and I can
see how that might be.

My perspective was more that I do want to perform a lookup by inode.
When an UNLINK arrives we lookup the dentry and so know the inode.  Then
we want to see if the client holds a delegation.  So we want to find the
nfs4_file given the dentry/inode.  We could, of course, use
export_encode_fh, but that seems a bit round-about.
We could add a second index, but would need to allow that there could be
multiple nfs4_files for a given inode.

>
>> and add some wait queue somewhere
>> so the breaker could wait for a delegation to be returned.
>
> In the nfsd case we're just returning to the client immediately, so
> that's not really necessary, though maybe it could be useful.

Ah yes, so we do.  I inverted the logic in my mind.  That makes it easier.

>
>> My big-picture point is that any complexity created by NFSD's choice to
>> merge delegations to multiple clients into a single vfs-level delegation
>> should be handled by NFSD, and not imposed on the VFS.
>> It certainly makes sense for the VFS to understand that certain
>> operations are being performed by an fl_owner_t, and to allow
>> delegations to that owner to remain.  It doesn't make as much sense for
>> the VFS to understand that there is a finer granularity of ownership
>> than the one that it already supports.
>
> Fair enough, I'll think about that.

Thanks.  Below is a patch that does compile but is probably wrong is
various ways and definitely needs cleanliness work at least.  I provide
it just to be more concrete about my thinking.

NeilBrown

Comments

Bruce Fields Sept. 6, 2017, 4:03 p.m. UTC | #1
On Wed, Sep 06, 2017 at 07:35:47AM +1000, NeilBrown wrote:
> On Tue, Sep 05 2017, J. Bruce Fields wrote:
> 
> > On Mon, Sep 04, 2017 at 02:52:43PM +1000, NeilBrown wrote:
> >> and add some wait queue somewhere
> >> so the breaker could wait for a delegation to be returned.
> >
> > In the nfsd case we're just returning to the client immediately, so
> > that's not really necessary, though maybe it could be useful.
> 
> Ah yes, so we do.  I inverted the logic in my mind.  That makes it easier.

(Minor derail: it might be worth waiting briefly before returning
NFS4ERR_DELAY.

It would be easy enough to implement, the hard part would be testing
whether it helped.  I think the initial client retry time is 100ms
(NFS4_POLL_RETRY_MIN), so it'd have to beat that frequently enough.)

> Thanks.  Below is a patch that does compile but is probably wrong is
> various ways and definitely needs cleanliness work at least.  I provide
> it just to be more concrete about my thinking.

Gah, I hate having to patch every notify_change caller.  But maybe I
should get over that, the resulting logic is simpler.  Anyway, stripping
away all those callers:

Right, the advantage is that this makes checking for conflicts simple
and obvious:

> diff --git a/fs/locks.c b/fs/locks.c
> index afefeb4ad6de..231d93bfbdc1 100644
> --- a/fs/locks.c
> +++ b/fs/locks.c
> @@ -1408,6 +1408,8 @@ static bool leases_conflict(struct file_lock *lease, struct file_lock *breaker)
>  		return false;
>  	if ((breaker->fl_flags & FL_DELEG) && (lease->fl_flags & FL_LEASE))
>  		return false;
> +	if (breaker->fl_owner && breaker->fl_owner == lease->fl_owner)
> +		return false;
>  	return locks_conflict(breaker, lease);
>  }

notify_change, vfs_unlink, etc., all get a new argument:

> + * @owner:	allow delegation to this owner to remain

And, right, we need a way to lookup nfs4_file by inode:

> +static struct nfs4_file *
> +find_deleg_file_by_inode(struct inode *ino)

(ignoring how we do it for now).

>  /* 
>   * Called from nfsd_lookup and encode_dirent. Check if we have crossed 
>   * a mount point.
> @@ -455,7 +458,8 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
>  			.ia_size	= iap->ia_size,
>  		};
>  
> -		host_err = notify_change(dentry, &size_attr, NULL);
> +		host_err = nfsd_conflicting_leases(dentry, rqstp);
> +		host_err = host_err ?: notify_change(dentry, &size_attr, nfsd_deleg_owner, NULL);

And then you recall nfsd delegations and delegations held by
(hypothetical) non-nfsd users separately, OK (also ignoring how).

There are no such users currently, so nfsd could just pass NULL.

--b.
NeilBrown Sept. 7, 2017, 12:43 a.m. UTC | #2
>> +		host_err = nfsd_conflicting_leases(dentry, rqstp);
>> +		host_err = host_err ?: notify_change(dentry, &size_attr, nfsd_deleg_owner, NULL);
>
> And then you recall nfsd delegations and delegations held by
> (hypothetical) non-nfsd users separately, OK (also ignoring how).
>
> There are no such users currently, so nfsd could just pass NULL.

I don't think so.  If we pass NULL (as the owner), when VFS will recall
the one nfsd delegation that we want to preserve. ???

NeilBrown
J. Bruce Fields Sept. 8, 2017, 3:06 p.m. UTC | #3
On Thu, Sep 07, 2017 at 10:43:29AM +1000, NeilBrown wrote:
> 
> >> +		host_err = nfsd_conflicting_leases(dentry, rqstp);
> >> +		host_err = host_err ?: notify_change(dentry, &size_attr, nfsd_deleg_owner, NULL);
> >
> > And then you recall nfsd delegations and delegations held by
> > (hypothetical) non-nfsd users separately, OK (also ignoring how).
> >
> > There are no such users currently, so nfsd could just pass NULL.
> 
> I don't think so.  If we pass NULL (as the owner), when VFS will recall
> the one nfsd delegation that we want to preserve. ???

Oops, right.--b.
diff mbox

Patch

diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c
index d2fb9c8ed205..c823a244f8b2 100644
--- a/drivers/base/devtmpfs.c
+++ b/drivers/base/devtmpfs.c
@@ -216,7 +216,7 @@  static int handle_create(const char *nodename, umode_t mode, kuid_t uid,
 		newattrs.ia_gid = gid;
 		newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID;
 		inode_lock(d_inode(dentry));
-		notify_change(dentry, &newattrs, NULL);
+		notify_change(dentry, &newattrs, NULL, NULL);
 		inode_unlock(d_inode(dentry));
 
 		/* mark as kernel-created inode */
@@ -323,9 +323,9 @@  static int handle_remove(const char *nodename, struct device *dev)
 			newattrs.ia_valid =
 				ATTR_UID|ATTR_GID|ATTR_MODE;
 			inode_lock(d_inode(dentry));
-			notify_change(dentry, &newattrs, NULL);
+			notify_change(dentry, &newattrs, NULL, NULL);
 			inode_unlock(d_inode(dentry));
-			err = vfs_unlink(d_inode(parent.dentry), dentry, NULL);
+			err = vfs_unlink(d_inode(parent.dentry), dentry, NULL, NULL);
 			if (!err || err == -ENOENT)
 				deleted = 1;
 		}
diff --git a/fs/attr.c b/fs/attr.c
index 135304146120..d94e516070af 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -185,6 +185,7 @@  EXPORT_SYMBOL(setattr_copy);
  * notify_change - modify attributes of a filesytem object
  * @dentry:	object affected
  * @iattr:	new attributes
+ * @owner:      allow delegations to this owner to remain
  * @delegated_inode: returns inode, if the inode is delegated
  *
  * The caller must hold the i_mutex on the affected object.
@@ -201,7 +202,7 @@  EXPORT_SYMBOL(setattr_copy);
  * the file open for write, as there can be no conflicting delegation in
  * that case.
  */
-int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **delegated_inode)
+int notify_change(struct dentry * dentry, struct iattr * attr, fl_owner_t owner, struct inode **delegated_inode)
 {
 	struct inode *inode = dentry->d_inode;
 	umode_t mode = inode->i_mode;
@@ -304,7 +305,7 @@  int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de
 	error = security_inode_setattr(dentry, attr);
 	if (error)
 		return error;
-	error = try_break_deleg(inode, delegated_inode);
+	error = try_break_deleg(inode, owner, delegated_inode);
 	if (error)
 		return error;
 
diff --git a/fs/inode.c b/fs/inode.c
index 50370599e371..c28fbb91b863 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -1788,7 +1788,7 @@  static int __remove_privs(struct dentry *dentry, int kill)
 	 * Note we call this on write, so notify_change will not
 	 * encounter any conflicting delegations:
 	 */
-	return notify_change(dentry, &newattrs, NULL);
+	return notify_change(dentry, &newattrs, NULL, NULL);
 }
 
 /*
diff --git a/fs/locks.c b/fs/locks.c
index afefeb4ad6de..231d93bfbdc1 100644
--- a/fs/locks.c
+++ b/fs/locks.c
@@ -1408,6 +1408,8 @@  static bool leases_conflict(struct file_lock *lease, struct file_lock *breaker)
 		return false;
 	if ((breaker->fl_flags & FL_DELEG) && (lease->fl_flags & FL_LEASE))
 		return false;
+	if (breaker->fl_owner && breaker->fl_owner == lease->fl_owner)
+		return false;
 	return locks_conflict(breaker, lease);
 }
 
@@ -1429,6 +1431,7 @@  any_leases_conflict(struct inode *inode, struct file_lock *breaker)
 /**
  *	__break_lease	-	revoke all outstanding leases on file
  *	@inode: the inode of the file to return
+ *      @owner: if non-NULL, ignore leases held by this owner.
  *	@mode: O_RDONLY: break only write leases; O_WRONLY or O_RDWR:
  *	    break all leases
  *	@type: FL_LEASE: break leases and delegations; FL_DELEG: break
@@ -1439,7 +1442,7 @@  any_leases_conflict(struct inode *inode, struct file_lock *breaker)
  *	a call to open() or truncate().  This function can sleep unless you
  *	specified %O_NONBLOCK to your open().
  */
-int __break_lease(struct inode *inode, unsigned int mode, unsigned int type)
+int __break_lease(struct inode *inode, fl_owner_t owner, unsigned int mode, unsigned int type)
 {
 	int error = 0;
 	struct file_lock_context *ctx;
@@ -1452,6 +1455,7 @@  int __break_lease(struct inode *inode, unsigned int mode, unsigned int type)
 	if (IS_ERR(new_fl))
 		return PTR_ERR(new_fl);
 	new_fl->fl_flags = type;
+	new_fl->fl_owner = owner;
 
 	/* typically we will check that ctx is non-NULL before calling */
 	ctx = smp_load_acquire(&inode->i_flctx);
diff --git a/fs/namei.c b/fs/namei.c
index ddb6a7c2b3d4..a1bf2ccdabb5 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -3941,6 +3941,7 @@  SYSCALL_DEFINE1(rmdir, const char __user *, pathname)
  * vfs_unlink - unlink a filesystem object
  * @dir:	parent directory
  * @dentry:	victim
+ * @owner:	allow delegation to this owner to remain.
  * @delegated_inode: returns victim inode, if the inode is delegated.
  *
  * The caller must hold dir->i_mutex.
@@ -3955,7 +3956,7 @@  SYSCALL_DEFINE1(rmdir, const char __user *, pathname)
  * be appropriate for callers that expect the underlying filesystem not
  * to be NFS exported.
  */
-int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode)
+int vfs_unlink(struct inode *dir, struct dentry *dentry, fl_owner_t owner, struct inode **delegated_inode)
 {
 	struct inode *target = dentry->d_inode;
 	int error = may_delete(dir, dentry, 0);
@@ -3972,7 +3973,7 @@  int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegate
 	else {
 		error = security_inode_unlink(dir, dentry);
 		if (!error) {
-			error = try_break_deleg(target, delegated_inode);
+			error = try_break_deleg(target, owner, delegated_inode);
 			if (error)
 				goto out;
 			error = dir->i_op->unlink(dir, dentry);
@@ -4040,7 +4041,7 @@  static long do_unlinkat(int dfd, const char __user *pathname)
 		error = security_path_unlink(&path, dentry);
 		if (error)
 			goto exit2;
-		error = vfs_unlink(path.dentry->d_inode, dentry, &delegated_inode);
+		error = vfs_unlink(path.dentry->d_inode, dentry, NULL, &delegated_inode);
 exit2:
 		dput(dentry);
 	}
@@ -4049,7 +4050,7 @@  static long do_unlinkat(int dfd, const char __user *pathname)
 		iput(inode);	/* truncate the inode here */
 	inode = NULL;
 	if (delegated_inode) {
-		error = break_deleg_wait(&delegated_inode);
+		error = break_deleg_wait(NULL, &delegated_inode);
 		if (!error)
 			goto retry_deleg;
 	}
@@ -4152,6 +4153,7 @@  SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn
  * @old_dentry:	object to be linked
  * @dir:	new parent
  * @new_dentry:	where to create the new link
+ * @owner:	allow delegation to this owner to remain
  * @delegated_inode: returns inode needing a delegation break
  *
  * The caller must hold dir->i_mutex
@@ -4166,7 +4168,8 @@  SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn
  * be appropriate for callers that expect the underlying filesystem not
  * to be NFS exported.
  */
-int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry, struct inode **delegated_inode)
+int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry,
+	     fl_owner_t owner, struct inode **delegated_inode)
 {
 	struct inode *inode = old_dentry->d_inode;
 	unsigned max_links = dir->i_sb->s_max_links;
@@ -4210,7 +4213,7 @@  int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de
 	else if (max_links && inode->i_nlink >= max_links)
 		error = -EMLINK;
 	else {
-		error = try_break_deleg(inode, delegated_inode);
+		error = try_break_deleg(inode, owner, delegated_inode);
 		if (!error)
 			error = dir->i_op->link(old_dentry, dir, new_dentry);
 	}
@@ -4280,11 +4283,11 @@  SYSCALL_DEFINE5(linkat, int, olddfd, const char __user *, oldname,
 	error = security_path_link(old_path.dentry, &new_path, new_dentry);
 	if (error)
 		goto out_dput;
-	error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, &delegated_inode);
+	error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, NULL, &delegated_inode);
 out_dput:
 	done_path_create(&new_path, new_dentry);
 	if (delegated_inode) {
-		error = break_deleg_wait(&delegated_inode);
+		error = break_deleg_wait(NULL, &delegated_inode);
 		if (!error) {
 			path_put(&old_path);
 			goto retry;
@@ -4312,6 +4315,7 @@  SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
  * @old_dentry:	source
  * @new_dir:	parent of destination
  * @new_dentry:	destination
+ * @owner:	allow delegation to this owner to remain
  * @delegated_inode: returns an inode needing a delegation break
  * @flags:	rename flags
  *
@@ -4358,6 +4362,7 @@  SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
  */
 int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 	       struct inode *new_dir, struct dentry *new_dentry,
+	       fl_owner_t owner,
 	       struct inode **delegated_inode, unsigned int flags)
 {
 	int error;
@@ -4435,12 +4440,12 @@  int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
 	if (is_dir && !(flags & RENAME_EXCHANGE) && target)
 		shrink_dcache_parent(new_dentry);
 	if (!is_dir) {
-		error = try_break_deleg(source, delegated_inode);
+		error = try_break_deleg(source, owner, delegated_inode);
 		if (error)
 			goto out;
 	}
 	if (target && !new_is_dir) {
-		error = try_break_deleg(target, delegated_inode);
+		error = try_break_deleg(target, owner, delegated_inode);
 		if (error)
 			goto out;
 	}
@@ -4594,7 +4599,7 @@  SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
 		goto exit5;
 	error = vfs_rename(old_path.dentry->d_inode, old_dentry,
 			   new_path.dentry->d_inode, new_dentry,
-			   &delegated_inode, flags);
+			   NULL, &delegated_inode, flags);
 exit5:
 	dput(new_dentry);
 exit4:
@@ -4602,7 +4607,7 @@  SYSCALL_DEFINE5(renameat2, int, olddfd, const char __user *, oldname,
 exit3:
 	unlock_rename(new_path.dentry, old_path.dentry);
 	if (delegated_inode) {
-		error = break_deleg_wait(&delegated_inode);
+		error = break_deleg_wait(NULL, &delegated_inode);
 		if (!error)
 			goto retry_deleg;
 	}
diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
index 0c04f81aa63b..e713484b93b3 100644
--- a/fs/nfsd/nfs4state.c
+++ b/fs/nfsd/nfs4state.c
@@ -3654,6 +3654,27 @@  find_file_locked(struct knfsd_fh *fh, unsigned int hashval)
 	return NULL;
 }
 
+static struct nfs4_file *
+find_deleg_file_by_inode(struct inode *ino)
+{
+	int i;
+	struct nfs4_file *fp;
+
+	if (!ino)
+		return NULL;
+
+	rcu_read_lock();
+	for (i = 0; i < FILE_HASH_SIZE; i++)
+		hlist_for_each_entry_rcu(fp, &file_hashtbl[i], fi_hash)
+			if (fp->fi_deleg_file && file_inode(fp->fi_deleg_file) == ino)
+				if (atomic_inc_not_zero(&fp->fi_ref)) {
+					rcu_read_unlock();
+					return fp;
+				}
+
+	return NULL;
+}
+
 struct nfs4_file *
 find_file(struct knfsd_fh *fh)
 {
@@ -3825,6 +3846,49 @@  nfsd_break_deleg_cb(struct file_lock *fl)
 	return ret;
 }
 
+static struct nfs4_client *nfsd4_client_from_rqst(struct svc_rqst *rqst)
+{
+	struct nfsd4_compoundres *resp;
+
+	/*
+	 * In case it's possible we could be called from NLM or ACL
+	 * code?:
+	 */
+	if (rqst->rq_prog != NFS_PROGRAM)
+		return NULL;
+	if (rqst->rq_vers != 4)
+		return NULL;
+	resp = rqst->rq_resp;
+	return resp->cstate.clp;
+}
+
+int nfsd_conflicting_leases(struct dentry *dentry, struct svc_rqst *rqstp)
+{
+	struct nfs4_client *cl;
+	struct nfs4_delegation *dl;
+	struct nfs4_file *fi;
+	bool conflict;
+
+	cl = nfsd4_client_from_rqst(rqstp);
+	if (!cl)
+		return 0;
+	fi = find_deleg_file_by_inode(d_inode(dentry));
+	if (!fi)
+		return 0;
+
+	spin_lock(&fi->fi_lock);
+	conflict = false;
+	list_for_each_entry(dl, &fi->fi_delegations, dl_perfile) {
+		if (dl->dl_stid.sc_client != cl) {
+			fi->fi_had_conflict = true;
+			nfsd_break_one_deleg(dl);
+			conflict = true;
+		}
+	}
+	spin_unlock(&fi->fi_lock);
+	return conflict ? -EWOULDBLOCK : 0;
+}
+
 static int
 nfsd_change_deleg_cb(struct file_lock *onlist, int arg,
 		     struct list_head *dispose)
@@ -4137,6 +4201,8 @@  static bool nfsd4_cb_channel_good(struct nfs4_client *clp)
 	return clp->cl_minorversion && clp->cl_cb_state == NFSD4_CB_UNKNOWN;
 }
 
+char nfsd_deleg_owner[1];
+
 static struct file_lock *nfs4_alloc_init_lease(struct nfs4_file *fp, int flag)
 {
 	struct file_lock *fl;
@@ -4148,7 +4214,7 @@  static struct file_lock *nfs4_alloc_init_lease(struct nfs4_file *fp, int flag)
 	fl->fl_flags = FL_DELEG;
 	fl->fl_type = flag == NFS4_OPEN_DELEGATE_READ? F_RDLCK: F_WRLCK;
 	fl->fl_end = OFFSET_MAX;
-	fl->fl_owner = (fl_owner_t)fp;
+	fl->fl_owner = (fl_owner_t)nfsd_deleg_owner;
 	fl->fl_pid = current->tgid;
 	return fl;
 }
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index bc69d40c4e8b..c091633fe441 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -75,6 +75,9 @@  struct raparm_hbucket {
 #define RAPARM_HASH_MASK	(RAPARM_HASH_SIZE-1)
 static struct raparm_hbucket	raparm_hash[RAPARM_HASH_SIZE];
 
+bool nfsd_conflicting_leases(struct dentry *dentry, struct svc_rqst *rqstp);
+extern char nfsd_deleg_owner[1];
+
 /* 
  * Called from nfsd_lookup and encode_dirent. Check if we have crossed 
  * a mount point.
@@ -455,7 +458,8 @@  nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
 			.ia_size	= iap->ia_size,
 		};
 
-		host_err = notify_change(dentry, &size_attr, NULL);
+		host_err = nfsd_conflicting_leases(dentry, rqstp);
+		host_err = host_err ?: notify_change(dentry, &size_attr, nfsd_deleg_owner, NULL);
 		if (host_err)
 			goto out_unlock;
 		iap->ia_valid &= ~ATTR_SIZE;
@@ -470,7 +474,8 @@  nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap,
 	}
 
 	iap->ia_valid |= ATTR_CTIME;
-	host_err = notify_change(dentry, iap, NULL);
+	host_err = nfsd_conflicting_leases(dentry, rqstp);
+	host_err = host_err ?: notify_change(dentry, iap, nfsd_deleg_owner, NULL);
 
 out_unlock:
 	fh_unlock(fhp);
@@ -1590,7 +1595,8 @@  nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
 	err = nfserr_noent;
 	if (d_really_is_negative(dold))
 		goto out_dput;
-	host_err = vfs_link(dold, dirp, dnew, NULL);
+	host_err = nfsd_conflicting_leases(dold, rqstp);
+	host_err = host_err ?: vfs_link(dold, dirp, dnew, nfsd_deleg_owner, NULL);
 	if (!host_err) {
 		err = nfserrno(commit_metadata(ffhp));
 		if (!err)
@@ -1683,7 +1689,9 @@  nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
 	if (ffhp->fh_export->ex_path.dentry != tfhp->fh_export->ex_path.dentry)
 		goto out_dput_new;
 
-	host_err = vfs_rename(fdir, odentry, tdir, ndentry, NULL, 0);
+	host_err = nfsd_conflicting_leases(odentry, rqstp);
+	host_err |= nfsd_conflicting_leases(ndentry, rqstp);
+	host_err = host_err ?: vfs_rename(fdir, odentry, tdir, ndentry, nfsd_deleg_owner, NULL, 0);
 	if (!host_err) {
 		host_err = commit_metadata(tfhp);
 		if (!host_err)
@@ -1752,9 +1760,10 @@  nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
 	if (!type)
 		type = d_inode(rdentry)->i_mode & S_IFMT;
 
-	if (type != S_IFDIR)
-		host_err = vfs_unlink(dirp, rdentry, NULL);
-	else
+	if (type != S_IFDIR) {
+		host_err = nfsd_conflicting_leases(dentry, rqstp);
+		host_err = host_err ?: vfs_unlink(dirp, rdentry, nfsd_deleg_owner, NULL);
+	} else
 		host_err = vfs_rmdir(dirp, rdentry);
 	if (!host_err)
 		host_err = commit_metadata(fhp);
diff --git a/fs/open.c b/fs/open.c
index 35bb784763a4..fad27de55ec0 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -60,7 +60,7 @@  int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs,
 
 	inode_lock(dentry->d_inode);
 	/* Note any delegations or leases have already been broken: */
-	ret = notify_change(dentry, &newattrs, NULL);
+	ret = notify_change(dentry, &newattrs, NULL, NULL);
 	inode_unlock(dentry->d_inode);
 	return ret;
 }
@@ -529,11 +529,11 @@  static int chmod_common(const struct path *path, umode_t mode)
 		goto out_unlock;
 	newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO);
 	newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
-	error = notify_change(path->dentry, &newattrs, &delegated_inode);
+	error = notify_change(path->dentry, &newattrs, NULL, &delegated_inode);
 out_unlock:
 	inode_unlock(inode);
 	if (delegated_inode) {
-		error = break_deleg_wait(&delegated_inode);
+		error = break_deleg_wait(NULL, &delegated_inode);
 		if (!error)
 			goto retry_deleg;
 	}
@@ -609,10 +609,10 @@  static int chown_common(const struct path *path, uid_t user, gid_t group)
 	inode_lock(inode);
 	error = security_path_chown(path, uid, gid);
 	if (!error)
-		error = notify_change(path->dentry, &newattrs, &delegated_inode);
+		error = notify_change(path->dentry, &newattrs, NULL, &delegated_inode);
 	inode_unlock(inode);
 	if (delegated_inode) {
-		error = break_deleg_wait(&delegated_inode);
+		error = break_deleg_wait(NULL, &delegated_inode);
 		if (!error)
 			goto retry_deleg;
 	}
diff --git a/fs/utimes.c b/fs/utimes.c
index 6571d8c848a0..c7b53d3602ce 100644
--- a/fs/utimes.c
+++ b/fs/utimes.c
@@ -87,10 +87,10 @@  static int utimes_common(const struct path *path, struct timespec *times)
 	}
 retry_deleg:
 	inode_lock(inode);
-	error = notify_change(path->dentry, &newattrs, &delegated_inode);
+	error = notify_change(path->dentry, &newattrs, NULL, &delegated_inode);
 	inode_unlock(inode);
 	if (delegated_inode) {
-		error = break_deleg_wait(&delegated_inode);
+		error = break_deleg_wait(NULL, &delegated_inode);
 		if (!error)
 			goto retry_deleg;
 	}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index cc2e0f5a8fd1..6e434c677e4c 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1084,7 +1084,7 @@  extern int vfs_test_lock(struct file *, struct file_lock *);
 extern int vfs_lock_file(struct file *, unsigned int, struct file_lock *, struct file_lock *);
 extern int vfs_cancel_lock(struct file *filp, struct file_lock *fl);
 extern int locks_lock_inode_wait(struct inode *inode, struct file_lock *fl);
-extern int __break_lease(struct inode *inode, unsigned int flags, unsigned int type);
+extern int __break_lease(struct inode *inode, fl_owner_t owner, unsigned int flags, unsigned int type);
 extern void lease_get_mtime(struct inode *, struct timespec *time);
 extern int generic_setlease(struct file *, long, struct file_lock **, void **priv);
 extern int vfs_setlease(struct file *, long, struct file_lock **, void **);
@@ -1195,7 +1195,7 @@  static inline int locks_lock_inode_wait(struct inode *inode, struct file_lock *f
 	return -ENOLCK;
 }
 
-static inline int __break_lease(struct inode *inode, unsigned int mode, unsigned int type)
+static inline int __break_lease(struct inode *inode, fl_owner_t owner, unsigned int mode, unsigned int type)
 {
 	return 0;
 }
@@ -1573,10 +1573,10 @@  extern int vfs_create(struct inode *, struct dentry *, umode_t, bool);
 extern int vfs_mkdir(struct inode *, struct dentry *, umode_t);
 extern int vfs_mknod(struct inode *, struct dentry *, umode_t, dev_t);
 extern int vfs_symlink(struct inode *, struct dentry *, const char *);
-extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **);
+extern int vfs_link(struct dentry *, struct inode *, struct dentry *, fl_owner_t, struct inode **);
 extern int vfs_rmdir(struct inode *, struct dentry *);
-extern int vfs_unlink(struct inode *, struct dentry *, struct inode **);
-extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **, unsigned int);
+extern int vfs_unlink(struct inode *, struct dentry *, fl_owner_t, struct inode **);
+extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, fl_owner_t, struct inode **, unsigned int);
 extern int vfs_whiteout(struct inode *, struct dentry *);
 
 extern struct dentry *vfs_tmpfile(struct dentry *dentry, umode_t mode,
@@ -2260,11 +2260,11 @@  static inline int break_lease(struct inode *inode, unsigned int mode)
 	 */
 	smp_mb();
 	if (inode->i_flctx && !list_empty_careful(&inode->i_flctx->flc_lease))
-		return __break_lease(inode, mode, FL_LEASE);
+		return __break_lease(inode, NULL, mode, FL_LEASE);
 	return 0;
 }
 
-static inline int break_deleg(struct inode *inode, unsigned int mode)
+static inline int break_deleg(struct inode *inode, fl_owner_t owner, unsigned int mode)
 {
 	/*
 	 * Since this check is lockless, we must ensure that any refcounts
@@ -2274,15 +2274,15 @@  static inline int break_deleg(struct inode *inode, unsigned int mode)
 	 */
 	smp_mb();
 	if (inode->i_flctx && !list_empty_careful(&inode->i_flctx->flc_lease))
-		return __break_lease(inode, mode, FL_DELEG);
+		return __break_lease(inode, owner, mode, FL_DELEG);
 	return 0;
 }
 
-static inline int try_break_deleg(struct inode *inode, struct inode **delegated_inode)
+static inline int try_break_deleg(struct inode *inode, fl_owner_t owner, struct inode **delegated_inode)
 {
 	int ret;
 
-	ret = break_deleg(inode, O_WRONLY|O_NONBLOCK);
+	ret = break_deleg(inode, owner, O_WRONLY|O_NONBLOCK);
 	if (ret == -EWOULDBLOCK && delegated_inode) {
 		*delegated_inode = inode;
 		ihold(inode);
@@ -2290,11 +2290,11 @@  static inline int try_break_deleg(struct inode *inode, struct inode **delegated_
 	return ret;
 }
 
-static inline int break_deleg_wait(struct inode **delegated_inode)
+static inline int break_deleg_wait(fl_owner_t owner, struct inode **delegated_inode)
 {
 	int ret;
 
-	ret = break_deleg(*delegated_inode, O_WRONLY);
+	ret = break_deleg(*delegated_inode, owner, O_WRONLY);
 	iput(*delegated_inode);
 	*delegated_inode = NULL;
 	return ret;
@@ -2304,7 +2304,7 @@  static inline int break_layout(struct inode *inode, bool wait)
 {
 	smp_mb();
 	if (inode->i_flctx && !list_empty_careful(&inode->i_flctx->flc_lease))
-		return __break_lease(inode,
+		return __break_lease(inode, NULL,
 				wait ? O_WRONLY : O_WRONLY | O_NONBLOCK,
 				FL_LAYOUT);
 	return 0;
@@ -2316,17 +2316,17 @@  static inline int break_lease(struct inode *inode, unsigned int mode)
 	return 0;
 }
 
-static inline int break_deleg(struct inode *inode, unsigned int mode)
+static inline int break_deleg(struct inode *inode, fl_owner_t owner, unsigned int mode)
 {
 	return 0;
 }
 
-static inline int try_break_deleg(struct inode *inode, struct inode **delegated_inode)
+static inline int try_break_deleg(struct inode *inode, fl_owner_t owner, struct inode **delegated_inode)
 {
 	return 0;
 }
 
-static inline int break_deleg_wait(struct inode **delegated_inode)
+static inline int break_deleg_wait(fl_owner_t owner, struct inode **delegated_inode)
 {
 	BUG();
 	return 0;
@@ -2643,7 +2643,7 @@  extern void emergency_remount(void);
 #ifdef CONFIG_BLOCK
 extern sector_t bmap(struct inode *, sector_t);
 #endif
-extern int notify_change(struct dentry *, struct iattr *, struct inode **);
+extern int notify_change(struct dentry *, struct iattr *, fl_owner_t, struct inode **);
 extern int inode_permission(struct inode *, int);
 extern int __inode_permission(struct inode *, int);
 extern int generic_permission(struct inode *, int);