Re: [PATCH v5 05/14] VFS/nfsd/cachefiles/ovl: introduce start_removing() and end_removing()

Jeff Layton <[email protected]>
Newsgroups org.kernel.vger.ecryptfs,dev.linux.lists.netfs,org.kernel.vger.linux-cifs,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-nfs,org.kernel.vger.linux-security-module,org.kernel.vger.linux-unionfs,org.kernel.vger.linux-xfs,org.kernel.vger.selinux
Message-ID <[email protected]>
On Thu, 2025-11-06 at 11:50 +1100, NeilBrown wrote:
> From: NeilBrown <[email protected]>
> 
> start_removing() is similar to start_creating() but will only return a
> positive dentry with the expectation that it will be removed.  This is
> used by nfsd, cachefiles, and overlayfs.  They are changed to also use
> end_removing() to terminate the action begun by start_removing().  This
> is a simple alias for end_dirop().
> 
> Apart from changes to the error paths, as we no longer need to unlock on
> a lookup error, an effect on callers is that they don't need to test if
> the found dentry is positive or negative - they can be sure it is
> positive.
> 
> Reviewed-by: Amir Goldstein <[email protected]>
> Signed-off-by: NeilBrown <[email protected]>
> ---
>  fs/cachefiles/namei.c    | 32 ++++++++++++++------------------
>  fs/namei.c               | 27 +++++++++++++++++++++++++++
>  fs/nfsd/nfs4recover.c    | 18 +++++-------------
>  fs/nfsd/vfs.c            | 26 ++++++++++----------------
>  fs/overlayfs/dir.c       | 15 +++++++--------
>  fs/overlayfs/overlayfs.h |  8 ++++++++
>  include/linux/namei.h    | 18 ++++++++++++++++++
>  7 files changed, 89 insertions(+), 55 deletions(-)
> 
> diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
> index 0a136eb434da..c7f0c6ab9b88 100644
> --- a/fs/cachefiles/namei.c
> +++ b/fs/cachefiles/namei.c
> @@ -260,6 +260,7 @@ static int cachefiles_unlink(struct cachefiles_cache *cache,
>   * - File backed objects are unlinked
>   * - Directory backed objects are stuffed into the graveyard for userspace to
>   *   delete
> + * On entry dir must be locked.  It will be unlocked on exit.
>   */
>  int cachefiles_bury_object(struct cachefiles_cache *cache,
>  			   struct cachefiles_object *object,
> @@ -274,28 +275,30 @@ int cachefiles_bury_object(struct cachefiles_cache *cache,
>  
>  	_enter(",'%pd','%pd'", dir, rep);
>  
> +	/* end_removing() will dput() @rep but we need to keep
> +	 * a ref, so take one now.  This also stops the dentry
> +	 * being negated when unlinked which we need.
> +	 */
> +	dget(rep);
> +
>  	if (rep->d_parent != dir) {
> -		inode_unlock(d_inode(dir));
> +		end_removing(rep);
>  		_leave(" = -ESTALE");
>  		return -ESTALE;
>  	}
>  
>  	/* non-directories can just be unlinked */
>  	if (!d_is_dir(rep)) {
> -		dget(rep); /* Stop the dentry being negated if it's only pinned
> -			    * by a file struct.
> -			    */
>  		ret = cachefiles_unlink(cache, object, dir, rep, why);
> -		dput(rep);
> +		end_removing(rep);
>  
> -		inode_unlock(d_inode(dir));
>  		_leave(" = %d", ret);
>  		return ret;
>  	}
>  
>  	/* directories have to be moved to the graveyard */
>  	_debug("move stale object to graveyard");
> -	inode_unlock(d_inode(dir));
> +	end_removing(rep);
>  
>  try_again:
>  	/* first step is to make up a grave dentry in the graveyard */
> @@ -749,26 +752,20 @@ static struct dentry *cachefiles_lookup_for_cull(struct cachefiles_cache *cache,
>  	struct dentry *victim;
>  	int ret = -ENOENT;
>  
> -	inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
> +	victim = start_removing(&nop_mnt_idmap, dir, &QSTR(filename));
>  
> -	victim = lookup_one(&nop_mnt_idmap, &QSTR(filename), dir);
>  	if (IS_ERR(victim))
>  		goto lookup_error;
> -	if (d_is_negative(victim))
> -		goto lookup_put;
>  	if (d_inode(victim)->i_flags & S_KERNEL_FILE)
>  		goto lookup_busy;
>  	return victim;
>  
>  lookup_busy:
>  	ret = -EBUSY;
> -lookup_put:
> -	inode_unlock(d_inode(dir));
> -	dput(victim);
> +	end_removing(victim);
>  	return ERR_PTR(ret);
>  
>  lookup_error:
> -	inode_unlock(d_inode(dir));
>  	ret = PTR_ERR(victim);
>  	if (ret == -ENOENT)
>  		return ERR_PTR(-ESTALE); /* Probably got retired by the netfs */
> @@ -816,18 +813,17 @@ int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
>  
>  	ret = cachefiles_bury_object(cache, NULL, dir, victim,
>  				     FSCACHE_OBJECT_WAS_CULLED);
> +	dput(victim);
>  	if (ret < 0)
>  		goto error;
>  
>  	fscache_count_culled();
> -	dput(victim);
>  	_leave(" = 0");
>  	return 0;
>  
>  error_unlock:
> -	inode_unlock(d_inode(dir));
> +	end_removing(victim);
>  error:
> -	dput(victim);
>  	if (ret == -ENOENT)
>  		return -ESTALE; /* Probably got retired by the netfs */
>  
> diff --git a/fs/namei.c b/fs/namei.c
> index 8873ad0f05b0..38dda29552f6 100644
> --- a/fs/namei.c
> +++ b/fs/namei.c
> @@ -3248,6 +3248,33 @@ struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
>  }
>  EXPORT_SYMBOL(start_creating);
>  
> +/**
> + * start_removing - prepare to remove a given name with permission checking
> + * @idmap:  idmap of the mount
> + * @parent: directory in which to find the name
> + * @name:   the name to be removed
> + *
> + * Locks are taken and a lookup in performed prior to removing
> + * an object from a directory.  Permission checking (MAY_EXEC) is performed
> + * against @idmap.
> + *
> + * If the name doesn't exist, an error is returned.
> + *
> + * end_removing() should be called when removal is complete, or aborted.
> + *
> + * Returns: a positive dentry, or an error.
> + */
> +struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
> +			      struct qstr *name)
> +{
> +	int err = lookup_one_common(idmap, name, parent);
> +
> +	if (err)
> +		return ERR_PTR(err);
> +	return start_dirop(parent, name, 0);
> +}
> +EXPORT_SYMBOL(start_removing);
> +
>  #ifdef CONFIG_UNIX98_PTYS
>  int path_pts(struct path *path)
>  {
> diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c
> index c247a7c3291c..3eefaa2202e3 100644
> --- a/fs/nfsd/nfs4recover.c
> +++ b/fs/nfsd/nfs4recover.c
> @@ -324,20 +324,12 @@ nfsd4_unlink_clid_dir(char *name, struct nfsd_net *nn)
>  	dprintk("NFSD: nfsd4_unlink_clid_dir. name %s\n", name);
>  
>  	dir = nn->rec_file->f_path.dentry;
> -	inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
> -	dentry = lookup_one(&nop_mnt_idmap, &QSTR(name), dir);
> -	if (IS_ERR(dentry)) {
> -		status = PTR_ERR(dentry);
> -		goto out_unlock;
> -	}
> -	status = -ENOENT;
> -	if (d_really_is_negative(dentry))
> -		goto out;
> +	dentry = start_removing(&nop_mnt_idmap, dir, &QSTR(name));
> +	if (IS_ERR(dentry))
> +		return PTR_ERR(dentry);
> +
>  	status = vfs_rmdir(&nop_mnt_idmap, d_inode(dir), dentry);
> -out:
> -	dput(dentry);
> -out_unlock:
> -	inode_unlock(d_inode(dir));
> +	end_removing(dentry);
>  	return status;
>  }
>  
> diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
> index 24e501abad0e..6291c371caa7 100644
> --- a/fs/nfsd/vfs.c
> +++ b/fs/nfsd/vfs.c
> @@ -2044,7 +2044,7 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
>  {
>  	struct dentry	*dentry, *rdentry;
>  	struct inode	*dirp;
> -	struct inode	*rinode;
> +	struct inode	*rinode = NULL;
>  	__be32		err;
>  	int		host_err;
>  
> @@ -2063,24 +2063,21 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
>  
>  	dentry = fhp->fh_dentry;
>  	dirp = d_inode(dentry);
> -	inode_lock_nested(dirp, I_MUTEX_PARENT);
>  
> -	rdentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(fname, flen), dentry);
> +	rdentry = start_removing(&nop_mnt_idmap, dentry, &QSTR_LEN(fname, flen));
> +
>  	host_err = PTR_ERR(rdentry);
>  	if (IS_ERR(rdentry))
> -		goto out_unlock;
> +		goto out_drop_write;
>  
> -	if (d_really_is_negative(rdentry)) {
> -		dput(rdentry);
> -		host_err = -ENOENT;
> -		goto out_unlock;
> -	}
> -	rinode = d_inode(rdentry);
>  	err = fh_fill_pre_attrs(fhp);
>  	if (err != nfs_ok)
>  		goto out_unlock;
>  
> +	rinode = d_inode(rdentry);
> +	/* Prevent truncation until after locks dropped */
>  	ihold(rinode);
> +
>  	if (!type)
>  		type = d_inode(rdentry)->i_mode & S_IFMT;
>  
> @@ -2102,10 +2099,10 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
>  	}
>  	fh_fill_post_attrs(fhp);
>  
> -	inode_unlock(dirp);
> -	if (!host_err)
> +out_unlock:
> +	end_removing(rdentry);
> +	if (!err && !host_err)
>  		host_err = commit_metadata(fhp);
> -	dput(rdentry);
>  	iput(rinode);    /* truncate the inode here */
>  
>  out_drop_write:
> @@ -2123,9 +2120,6 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
>  	}
>  out:
>  	return err != nfs_ok ? err : nfserrno(host_err);
> -out_unlock:
> -	inode_unlock(dirp);
> -	goto out_drop_write;
>  }
>  
>  /*
> diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c
> index b9160fefbd00..20682afdbd20 100644
> --- a/fs/overlayfs/dir.c
> +++ b/fs/overlayfs/dir.c
> @@ -866,17 +866,17 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
>  			goto out;
>  	}
>  
> -	inode_lock_nested(dir, I_MUTEX_PARENT);
> -	upper = ovl_lookup_upper(ofs, dentry->d_name.name, upperdir,
> -				 dentry->d_name.len);
> +	upper = ovl_start_removing_upper(ofs, upperdir,
> +					 &QSTR_LEN(dentry->d_name.name,
> +						   dentry->d_name.len));
>  	err = PTR_ERR(upper);
>  	if (IS_ERR(upper))
> -		goto out_unlock;
> +		goto out_dput;
>  
>  	err = -ESTALE;
>  	if ((opaquedir && upper != opaquedir) ||
>  	    (!opaquedir && !ovl_matches_upper(dentry, upper)))
> -		goto out_dput_upper;
> +		goto out_unlock;
>  
>  	if (is_dir)
>  		err = ovl_do_rmdir(ofs, dir, upper);
> @@ -892,10 +892,9 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
>  	 */
>  	if (!err)
>  		d_drop(dentry);
> -out_dput_upper:
> -	dput(upper);
>  out_unlock:
> -	inode_unlock(dir);
> +	end_removing(upper);
> +out_dput:
>  	dput(opaquedir);
>  out:
>  	return err;
> diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
> index beeba96cfcb2..49ad65f829dc 100644
> --- a/fs/overlayfs/overlayfs.h
> +++ b/fs/overlayfs/overlayfs.h
> @@ -423,6 +423,14 @@ static inline struct dentry *ovl_start_creating_upper(struct ovl_fs *ofs,
>  			      parent, name);
>  }
>  
> +static inline struct dentry *ovl_start_removing_upper(struct ovl_fs *ofs,
> +						      struct dentry *parent,
> +						      struct qstr *name)
> +{
> +	return start_removing(ovl_upper_mnt_idmap(ofs),
> +			      parent, name);
> +}
> +
>  static inline bool ovl_open_flags_need_copy_up(int flags)
>  {
>  	if (!flags)
> diff --git a/include/linux/namei.h b/include/linux/namei.h
> index 37b72f4a64f0..6d1069f93ebf 100644
> --- a/include/linux/namei.h
> +++ b/include/linux/namei.h
> @@ -91,6 +91,8 @@ struct dentry *lookup_one_positive_killable(struct mnt_idmap *idmap,
>  
>  struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
>  			      struct qstr *name);
> +struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
> +			      struct qstr *name);
>  
>  /**
>   * end_creating - finish action started with start_creating
> @@ -122,6 +124,22 @@ static inline void end_creating(struct dentry *child, struct dentry *parent)
>  		end_dirop(child);
>  }
>  
> +/**
> + * end_removing - finish action started with start_removing
> + * @child:  dentry returned by start_removing()
> + * @parent: dentry given to start_removing()
> + *
> + * Unlock and release the child.
> + *
> + * This is identical to end_dirop().  It can be passed the result of
> + * start_removing() whether that was successful or not, but it not needed
> + * if start_removing() failed.
> + */
> +static inline void end_removing(struct dentry *child)
> +{
> +	end_dirop(child);
> +}
> +
>  extern int follow_down_one(struct path *);
>  extern int follow_down(struct path *path, unsigned int flags);
>  extern int follow_up(struct path *);

This looks fine to me (particularly the knfsd parts), but doesn't ksmbd
need to be similarly converted?

Reviewed-by: Jeff Layton <[email protected]>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.