Skip to content

Commit bd6ede8

Browse files
neilbrownbrauner
authored andcommitted
VFS/nfsd/cachefiles/ovl: introduce start_removing() and end_removing()
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 <amir73il@gmail.com> Reviewed-by: Jeff Layton <jlayton@kernel.org> Signed-off-by: NeilBrown <neil@brown.name> Link: https://patch.msgid.link/20251113002050.676694-6-neilb@ownmail.net Tested-by: syzbot@syzkaller.appspotmail.com Signed-off-by: Christian Brauner <brauner@kernel.org>
1 parent 7ab96df commit bd6ede8

7 files changed

Lines changed: 89 additions & 55 deletions

File tree

fs/cachefiles/namei.c

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ static int cachefiles_unlink(struct cachefiles_cache *cache,
260260
* - File backed objects are unlinked
261261
* - Directory backed objects are stuffed into the graveyard for userspace to
262262
* delete
263+
* On entry dir must be locked. It will be unlocked on exit.
263264
*/
264265
int cachefiles_bury_object(struct cachefiles_cache *cache,
265266
struct cachefiles_object *object,
@@ -274,28 +275,30 @@ int cachefiles_bury_object(struct cachefiles_cache *cache,
274275

275276
_enter(",'%pd','%pd'", dir, rep);
276277

278+
/* end_removing() will dput() @rep but we need to keep
279+
* a ref, so take one now. This also stops the dentry
280+
* being negated when unlinked which we need.
281+
*/
282+
dget(rep);
283+
277284
if (rep->d_parent != dir) {
278-
inode_unlock(d_inode(dir));
285+
end_removing(rep);
279286
_leave(" = -ESTALE");
280287
return -ESTALE;
281288
}
282289

283290
/* non-directories can just be unlinked */
284291
if (!d_is_dir(rep)) {
285-
dget(rep); /* Stop the dentry being negated if it's only pinned
286-
* by a file struct.
287-
*/
288292
ret = cachefiles_unlink(cache, object, dir, rep, why);
289-
dput(rep);
293+
end_removing(rep);
290294

291-
inode_unlock(d_inode(dir));
292295
_leave(" = %d", ret);
293296
return ret;
294297
}
295298

296299
/* directories have to be moved to the graveyard */
297300
_debug("move stale object to graveyard");
298-
inode_unlock(d_inode(dir));
301+
end_removing(rep);
299302

300303
try_again:
301304
/* 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,
749752
struct dentry *victim;
750753
int ret = -ENOENT;
751754

752-
inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
755+
victim = start_removing(&nop_mnt_idmap, dir, &QSTR(filename));
753756

754-
victim = lookup_one(&nop_mnt_idmap, &QSTR(filename), dir);
755757
if (IS_ERR(victim))
756758
goto lookup_error;
757-
if (d_is_negative(victim))
758-
goto lookup_put;
759759
if (d_inode(victim)->i_flags & S_KERNEL_FILE)
760760
goto lookup_busy;
761761
return victim;
762762

763763
lookup_busy:
764764
ret = -EBUSY;
765-
lookup_put:
766-
inode_unlock(d_inode(dir));
767-
dput(victim);
765+
end_removing(victim);
768766
return ERR_PTR(ret);
769767

770768
lookup_error:
771-
inode_unlock(d_inode(dir));
772769
ret = PTR_ERR(victim);
773770
if (ret == -ENOENT)
774771
return ERR_PTR(-ESTALE); /* Probably got retired by the netfs */
@@ -816,18 +813,17 @@ int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
816813

817814
ret = cachefiles_bury_object(cache, NULL, dir, victim,
818815
FSCACHE_OBJECT_WAS_CULLED);
816+
dput(victim);
819817
if (ret < 0)
820818
goto error;
821819

822820
fscache_count_culled();
823-
dput(victim);
824821
_leave(" = 0");
825822
return 0;
826823

827824
error_unlock:
828-
inode_unlock(d_inode(dir));
825+
end_removing(victim);
829826
error:
830-
dput(victim);
831827
if (ret == -ENOENT)
832828
return -ESTALE; /* Probably got retired by the netfs */
833829

fs/namei.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3248,6 +3248,33 @@ struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
32483248
}
32493249
EXPORT_SYMBOL(start_creating);
32503250

3251+
/**
3252+
* start_removing - prepare to remove a given name with permission checking
3253+
* @idmap: idmap of the mount
3254+
* @parent: directory in which to find the name
3255+
* @name: the name to be removed
3256+
*
3257+
* Locks are taken and a lookup in performed prior to removing
3258+
* an object from a directory. Permission checking (MAY_EXEC) is performed
3259+
* against @idmap.
3260+
*
3261+
* If the name doesn't exist, an error is returned.
3262+
*
3263+
* end_removing() should be called when removal is complete, or aborted.
3264+
*
3265+
* Returns: a positive dentry, or an error.
3266+
*/
3267+
struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
3268+
struct qstr *name)
3269+
{
3270+
int err = lookup_one_common(idmap, name, parent);
3271+
3272+
if (err)
3273+
return ERR_PTR(err);
3274+
return start_dirop(parent, name, 0);
3275+
}
3276+
EXPORT_SYMBOL(start_removing);
3277+
32513278
#ifdef CONFIG_UNIX98_PTYS
32523279
int path_pts(struct path *path)
32533280
{

fs/nfsd/nfs4recover.c

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -324,20 +324,12 @@ nfsd4_unlink_clid_dir(char *name, struct nfsd_net *nn)
324324
dprintk("NFSD: nfsd4_unlink_clid_dir. name %s\n", name);
325325

326326
dir = nn->rec_file->f_path.dentry;
327-
inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
328-
dentry = lookup_one(&nop_mnt_idmap, &QSTR(name), dir);
329-
if (IS_ERR(dentry)) {
330-
status = PTR_ERR(dentry);
331-
goto out_unlock;
332-
}
333-
status = -ENOENT;
334-
if (d_really_is_negative(dentry))
335-
goto out;
327+
dentry = start_removing(&nop_mnt_idmap, dir, &QSTR(name));
328+
if (IS_ERR(dentry))
329+
return PTR_ERR(dentry);
330+
336331
status = vfs_rmdir(&nop_mnt_idmap, d_inode(dir), dentry);
337-
out:
338-
dput(dentry);
339-
out_unlock:
340-
inode_unlock(d_inode(dir));
332+
end_removing(dentry);
341333
return status;
342334
}
343335

fs/nfsd/vfs.c

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2044,7 +2044,7 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
20442044
{
20452045
struct dentry *dentry, *rdentry;
20462046
struct inode *dirp;
2047-
struct inode *rinode;
2047+
struct inode *rinode = NULL;
20482048
__be32 err;
20492049
int host_err;
20502050

@@ -2063,24 +2063,21 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
20632063

20642064
dentry = fhp->fh_dentry;
20652065
dirp = d_inode(dentry);
2066-
inode_lock_nested(dirp, I_MUTEX_PARENT);
20672066

2068-
rdentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(fname, flen), dentry);
2067+
rdentry = start_removing(&nop_mnt_idmap, dentry, &QSTR_LEN(fname, flen));
2068+
20692069
host_err = PTR_ERR(rdentry);
20702070
if (IS_ERR(rdentry))
2071-
goto out_unlock;
2071+
goto out_drop_write;
20722072

2073-
if (d_really_is_negative(rdentry)) {
2074-
dput(rdentry);
2075-
host_err = -ENOENT;
2076-
goto out_unlock;
2077-
}
2078-
rinode = d_inode(rdentry);
20792073
err = fh_fill_pre_attrs(fhp);
20802074
if (err != nfs_ok)
20812075
goto out_unlock;
20822076

2077+
rinode = d_inode(rdentry);
2078+
/* Prevent truncation until after locks dropped */
20832079
ihold(rinode);
2080+
20842081
if (!type)
20852082
type = d_inode(rdentry)->i_mode & S_IFMT;
20862083

@@ -2102,10 +2099,10 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
21022099
}
21032100
fh_fill_post_attrs(fhp);
21042101

2105-
inode_unlock(dirp);
2106-
if (!host_err)
2102+
out_unlock:
2103+
end_removing(rdentry);
2104+
if (!err && !host_err)
21072105
host_err = commit_metadata(fhp);
2108-
dput(rdentry);
21092106
iput(rinode); /* truncate the inode here */
21102107

21112108
out_drop_write:
@@ -2123,9 +2120,6 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
21232120
}
21242121
out:
21252122
return err != nfs_ok ? err : nfserrno(host_err);
2126-
out_unlock:
2127-
inode_unlock(dirp);
2128-
goto out_drop_write;
21292123
}
21302124

21312125
/*

fs/overlayfs/dir.c

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -866,17 +866,17 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
866866
goto out;
867867
}
868868

869-
inode_lock_nested(dir, I_MUTEX_PARENT);
870-
upper = ovl_lookup_upper(ofs, dentry->d_name.name, upperdir,
871-
dentry->d_name.len);
869+
upper = ovl_start_removing_upper(ofs, upperdir,
870+
&QSTR_LEN(dentry->d_name.name,
871+
dentry->d_name.len));
872872
err = PTR_ERR(upper);
873873
if (IS_ERR(upper))
874-
goto out_unlock;
874+
goto out_dput;
875875

876876
err = -ESTALE;
877877
if ((opaquedir && upper != opaquedir) ||
878878
(!opaquedir && !ovl_matches_upper(dentry, upper)))
879-
goto out_dput_upper;
879+
goto out_unlock;
880880

881881
if (is_dir)
882882
err = ovl_do_rmdir(ofs, dir, upper);
@@ -892,10 +892,9 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
892892
*/
893893
if (!err)
894894
d_drop(dentry);
895-
out_dput_upper:
896-
dput(upper);
897895
out_unlock:
898-
inode_unlock(dir);
896+
end_removing(upper);
897+
out_dput:
899898
dput(opaquedir);
900899
out:
901900
return err;

fs/overlayfs/overlayfs.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,14 @@ static inline struct dentry *ovl_start_creating_upper(struct ovl_fs *ofs,
423423
parent, name);
424424
}
425425

426+
static inline struct dentry *ovl_start_removing_upper(struct ovl_fs *ofs,
427+
struct dentry *parent,
428+
struct qstr *name)
429+
{
430+
return start_removing(ovl_upper_mnt_idmap(ofs),
431+
parent, name);
432+
}
433+
426434
static inline bool ovl_open_flags_need_copy_up(int flags)
427435
{
428436
if (!flags)

include/linux/namei.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ struct dentry *lookup_one_positive_killable(struct mnt_idmap *idmap,
9090

9191
struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
9292
struct qstr *name);
93+
struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
94+
struct qstr *name);
9395

9496
/**
9597
* end_creating - finish action started with start_creating
@@ -121,6 +123,22 @@ static inline void end_creating(struct dentry *child, struct dentry *parent)
121123
end_dirop(child);
122124
}
123125

126+
/**
127+
* end_removing - finish action started with start_removing
128+
* @child: dentry returned by start_removing()
129+
* @parent: dentry given to start_removing()
130+
*
131+
* Unlock and release the child.
132+
*
133+
* This is identical to end_dirop(). It can be passed the result of
134+
* start_removing() whether that was successful or not, but it not needed
135+
* if start_removing() failed.
136+
*/
137+
static inline void end_removing(struct dentry *child)
138+
{
139+
end_dirop(child);
140+
}
141+
124142
extern int follow_down_one(struct path *);
125143
extern int follow_down(struct path *path, unsigned int flags);
126144
extern int follow_up(struct path *);

0 commit comments

Comments
 (0)