VFS/nfsd/cachefiles/ovl: add start_creating() and end_creating()

start_creating() is similar to simple_start_creating() but is not so
simple.
It takes a qstr for the name, includes permission checking, and does NOT
report an error if the name already exists, returning a positive dentry
instead.

This is currently used by nfsd, cachefiles, and overlayfs.

end_creating() is called after the dentry has been used.
end_creating() drops the reference to the dentry as it is generally no
longer needed.  This is exactly the first section of end_creating_path()
so that function is changed to call the new end_creating()

These calls help encapsulate locking rules so that directory locking can
be changed.

Occasionally this change means that the parent lock is held for a
shorter period of time, for example in cachefiles_commit_tmpfile().
As this function now unlocks after an unlink and before the following
lookup, it is possible that the lookup could again find a positive
dentry, so a while loop is introduced there.

In overlayfs the ovl_lookup_temp() function has ovl_tempname()
split out to be used in ovl_start_creating_temp().  The other use
of ovl_lookup_temp() is preparing for a rename.  When rename handling
is updated, ovl_lookup_temp() will be removed.

Reviewed-by: Jeff Layton <jlayton@kernel.org>
Reviewed-by: Amir Goldstein <amir73il@gmail.com>
Signed-off-by: NeilBrown <neil@brown.name>
Link: https://patch.msgid.link/20251113002050.676694-5-neilb@ownmail.net
Tested-by: syzbot@syzkaller.appspotmail.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
NeilBrown 2025-11-13 11:18:27 +11:00 committed by Christian Brauner
parent 3661a78874
commit 7ab96df840
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
12 changed files with 216 additions and 161 deletions

View File

@ -93,12 +93,11 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
_enter(",,%s", dirname);
/* search the current directory for the element name */
inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
retry:
ret = cachefiles_inject_read_error();
if (ret == 0)
subdir = lookup_one(&nop_mnt_idmap, &QSTR(dirname), dir);
subdir = start_creating(&nop_mnt_idmap, dir, &QSTR(dirname));
else
subdir = ERR_PTR(ret);
trace_cachefiles_lookup(NULL, dir, subdir);
@ -141,7 +140,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
trace_cachefiles_mkdir(dir, subdir);
if (unlikely(d_unhashed(subdir) || d_is_negative(subdir))) {
dput(subdir);
end_creating(subdir, dir);
goto retry;
}
ASSERT(d_backing_inode(subdir));
@ -154,7 +153,8 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
/* Tell rmdir() it's not allowed to delete the subdir */
inode_lock(d_inode(subdir));
inode_unlock(d_inode(dir));
dget(subdir);
end_creating(subdir, dir);
if (!__cachefiles_mark_inode_in_use(NULL, d_inode(subdir))) {
pr_notice("cachefiles: Inode already in use: %pd (B=%lx)\n",
@ -196,14 +196,11 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
return ERR_PTR(-EBUSY);
mkdir_error:
inode_unlock(d_inode(dir));
if (!IS_ERR(subdir))
dput(subdir);
end_creating(subdir, dir);
pr_err("mkdir %s failed with error %d\n", dirname, ret);
return ERR_PTR(ret);
lookup_error:
inode_unlock(d_inode(dir));
ret = PTR_ERR(subdir);
pr_err("Lookup %s failed with error %d\n", dirname, ret);
return ERR_PTR(ret);
@ -679,36 +676,41 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
_enter(",%pD", object->file);
inode_lock_nested(d_inode(fan), I_MUTEX_PARENT);
ret = cachefiles_inject_read_error();
if (ret == 0)
dentry = lookup_one(&nop_mnt_idmap, &QSTR(object->d_name), fan);
dentry = start_creating(&nop_mnt_idmap, fan, &QSTR(object->d_name));
else
dentry = ERR_PTR(ret);
if (IS_ERR(dentry)) {
trace_cachefiles_vfs_error(object, d_inode(fan), PTR_ERR(dentry),
cachefiles_trace_lookup_error);
_debug("lookup fail %ld", PTR_ERR(dentry));
goto out_unlock;
goto out;
}
if (!d_is_negative(dentry)) {
/*
* This loop will only execute more than once if some other thread
* races to create the object we are trying to create.
*/
while (!d_is_negative(dentry)) {
ret = cachefiles_unlink(volume->cache, object, fan, dentry,
FSCACHE_OBJECT_IS_STALE);
if (ret < 0)
goto out_dput;
goto out_end;
end_creating(dentry, fan);
dput(dentry);
ret = cachefiles_inject_read_error();
if (ret == 0)
dentry = lookup_one(&nop_mnt_idmap, &QSTR(object->d_name), fan);
dentry = start_creating(&nop_mnt_idmap, fan,
&QSTR(object->d_name));
else
dentry = ERR_PTR(ret);
if (IS_ERR(dentry)) {
trace_cachefiles_vfs_error(object, d_inode(fan), PTR_ERR(dentry),
cachefiles_trace_lookup_error);
_debug("lookup fail %ld", PTR_ERR(dentry));
goto out_unlock;
goto out;
}
}
@ -729,10 +731,9 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
success = true;
}
out_dput:
dput(dentry);
out_unlock:
inode_unlock(d_inode(fan));
out_end:
end_creating(dentry, fan);
out:
_leave(" = %u", success);
return success;
}

View File

@ -3221,6 +3221,33 @@ struct dentry *lookup_noperm_positive_unlocked(struct qstr *name,
}
EXPORT_SYMBOL(lookup_noperm_positive_unlocked);
/**
* start_creating - prepare to create a given name with permission checking
* @idmap: idmap of the mount
* @parent: directory in which to prepare to create the name
* @name: the name to be created
*
* Locks are taken and a lookup is performed prior to creating
* an object in a directory. Permission checking (MAY_EXEC) is performed
* against @idmap.
*
* If the name already exists, a positive dentry is returned, so
* behaviour is similar to O_CREAT without O_EXCL, which doesn't fail
* with -EEXIST.
*
* Returns: a negative or positive dentry, or an error.
*/
struct dentry *start_creating(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, LOOKUP_CREATE);
}
EXPORT_SYMBOL(start_creating);
#ifdef CONFIG_UNIX98_PTYS
int path_pts(struct path *path)
{
@ -4306,13 +4333,7 @@ EXPORT_SYMBOL(start_creating_path);
*/
void end_creating_path(const struct path *path, struct dentry *dentry)
{
if (IS_ERR(dentry))
/* The parent is still locked despite the error from
* vfs_mkdir() - must unlock it.
*/
inode_unlock(path->dentry->d_inode);
else
end_dirop(dentry);
end_creating(dentry, path->dentry);
mnt_drop_write(path->mnt);
path_put(path);
}

View File

@ -281,14 +281,11 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (host_err)
return nfserrno(host_err);
inode_lock_nested(inode, I_MUTEX_PARENT);
child = lookup_one(&nop_mnt_idmap,
&QSTR_LEN(argp->name, argp->len),
parent);
child = start_creating(&nop_mnt_idmap, parent,
&QSTR_LEN(argp->name, argp->len));
if (IS_ERR(child)) {
status = nfserrno(PTR_ERR(child));
goto out;
goto out_write;
}
if (d_really_is_negative(child)) {
@ -367,9 +364,8 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
status = nfsd_create_setattr(rqstp, fhp, resfhp, &attrs);
out:
inode_unlock(inode);
if (child && !IS_ERR(child))
dput(child);
end_creating(child, parent);
out_write:
fh_drop_write(fhp);
return status;
}

View File

@ -264,14 +264,11 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (is_create_with_attrs(open))
nfsd4_acl_to_attr(NF4REG, open->op_acl, &attrs);
inode_lock_nested(inode, I_MUTEX_PARENT);
child = lookup_one(&nop_mnt_idmap,
&QSTR_LEN(open->op_fname, open->op_fnamelen),
parent);
child = start_creating(&nop_mnt_idmap, parent,
&QSTR_LEN(open->op_fname, open->op_fnamelen));
if (IS_ERR(child)) {
status = nfserrno(PTR_ERR(child));
goto out;
goto out_write;
}
if (d_really_is_negative(child)) {
@ -379,10 +376,9 @@ nfsd4_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (attrs.na_aclerr)
open->op_bmval[0] &= ~FATTR4_WORD0_ACL;
out:
inode_unlock(inode);
end_creating(child, parent);
nfsd_attrs_free(&attrs);
if (child && !IS_ERR(child))
dput(child);
out_write:
fh_drop_write(fhp);
return status;
}

View File

@ -195,13 +195,11 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
goto out_creds;
dir = nn->rec_file->f_path.dentry;
/* lock the parent */
inode_lock(d_inode(dir));
dentry = lookup_one(&nop_mnt_idmap, &QSTR(dname), dir);
dentry = start_creating(&nop_mnt_idmap, dir, &QSTR(dname));
if (IS_ERR(dentry)) {
status = PTR_ERR(dentry);
goto out_unlock;
goto out;
}
if (d_really_is_positive(dentry))
/*
@ -212,15 +210,13 @@ nfsd4_create_clid_dir(struct nfs4_client *clp)
* In the 4.0 case, we should never get here; but we may
* as well be forgiving and just succeed silently.
*/
goto out_put;
goto out_end;
dentry = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), dentry, S_IRWXU);
if (IS_ERR(dentry))
status = PTR_ERR(dentry);
out_put:
if (!status)
dput(dentry);
out_unlock:
inode_unlock(d_inode(dir));
out_end:
end_creating(dentry, dir);
out:
if (status == 0) {
if (nn->in_grace)
__nfsd4_create_reclaim_record_grace(clp, dname,

View File

@ -306,18 +306,16 @@ nfsd_proc_create(struct svc_rqst *rqstp)
goto done;
}
inode_lock_nested(dirfhp->fh_dentry->d_inode, I_MUTEX_PARENT);
dchild = lookup_one(&nop_mnt_idmap, &QSTR_LEN(argp->name, argp->len),
dirfhp->fh_dentry);
dchild = start_creating(&nop_mnt_idmap, dirfhp->fh_dentry,
&QSTR_LEN(argp->name, argp->len));
if (IS_ERR(dchild)) {
resp->status = nfserrno(PTR_ERR(dchild));
goto out_unlock;
goto out_write;
}
fh_init(newfhp, NFS_FHSIZE);
resp->status = fh_compose(newfhp, dirfhp->fh_export, dchild, dirfhp);
if (!resp->status && d_really_is_negative(dchild))
resp->status = nfserr_noent;
dput(dchild);
if (resp->status) {
if (resp->status != nfserr_noent)
goto out_unlock;
@ -423,7 +421,8 @@ nfsd_proc_create(struct svc_rqst *rqstp)
}
out_unlock:
inode_unlock(dirfhp->fh_dentry->d_inode);
end_creating(dchild, dirfhp->fh_dentry);
out_write:
fh_drop_write(dirfhp);
done:
fh_put(dirfhp);

View File

@ -1521,7 +1521,7 @@ nfsd_check_ignore_resizing(struct iattr *iap)
iap->ia_valid &= ~ATTR_SIZE;
}
/* The parent directory should already be locked: */
/* The parent directory should already be locked - we will unlock */
__be32
nfsd_create_locked(struct svc_rqst *rqstp, struct svc_fh *fhp,
struct nfsd_attrs *attrs,
@ -1587,8 +1587,9 @@ nfsd_create_locked(struct svc_rqst *rqstp, struct svc_fh *fhp,
err = nfsd_create_setattr(rqstp, fhp, resfhp, attrs);
out:
if (!IS_ERR(dchild))
dput(dchild);
if (!err)
fh_fill_post_attrs(fhp);
end_creating(dchild, dentry);
return err;
out_nfserr:
@ -1626,28 +1627,26 @@ nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (host_err)
return nfserrno(host_err);
inode_lock_nested(dentry->d_inode, I_MUTEX_PARENT);
dchild = lookup_one(&nop_mnt_idmap, &QSTR_LEN(fname, flen), dentry);
dchild = start_creating(&nop_mnt_idmap, dentry, &QSTR_LEN(fname, flen));
host_err = PTR_ERR(dchild);
if (IS_ERR(dchild)) {
err = nfserrno(host_err);
goto out_unlock;
}
if (IS_ERR(dchild))
return nfserrno(host_err);
err = fh_compose(resfhp, fhp->fh_export, dchild, fhp);
/*
* We unconditionally drop our ref to dchild as fh_compose will have
* already grabbed its own ref for it.
*/
dput(dchild);
if (err)
goto out_unlock;
err = fh_fill_pre_attrs(fhp);
if (err != nfs_ok)
goto out_unlock;
err = nfsd_create_locked(rqstp, fhp, attrs, type, rdev, resfhp);
fh_fill_post_attrs(fhp);
return err;
out_unlock:
inode_unlock(dentry->d_inode);
end_creating(dchild, dentry);
return err;
}
@ -1733,11 +1732,9 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
}
dentry = fhp->fh_dentry;
inode_lock_nested(dentry->d_inode, I_MUTEX_PARENT);
dnew = lookup_one(&nop_mnt_idmap, &QSTR_LEN(fname, flen), dentry);
dnew = start_creating(&nop_mnt_idmap, dentry, &QSTR_LEN(fname, flen));
if (IS_ERR(dnew)) {
err = nfserrno(PTR_ERR(dnew));
inode_unlock(dentry->d_inode);
goto out_drop_write;
}
err = fh_fill_pre_attrs(fhp);
@ -1750,11 +1747,11 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp,
nfsd_create_setattr(rqstp, fhp, resfhp, attrs);
fh_fill_post_attrs(fhp);
out_unlock:
inode_unlock(dentry->d_inode);
end_creating(dnew, dentry);
if (!err)
err = nfserrno(commit_metadata(fhp));
dput(dnew);
if (err==0) err = cerr;
if (!err)
err = cerr;
out_drop_write:
fh_drop_write(fhp);
out:
@ -1809,32 +1806,31 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
ddir = ffhp->fh_dentry;
dirp = d_inode(ddir);
inode_lock_nested(dirp, I_MUTEX_PARENT);
dnew = start_creating(&nop_mnt_idmap, ddir, &QSTR_LEN(name, len));
dnew = lookup_one(&nop_mnt_idmap, &QSTR_LEN(name, len), ddir);
if (IS_ERR(dnew)) {
host_err = PTR_ERR(dnew);
goto out_unlock;
goto out_drop_write;
}
dold = tfhp->fh_dentry;
err = nfserr_noent;
if (d_really_is_negative(dold))
goto out_dput;
goto out_unlock;
err = fh_fill_pre_attrs(ffhp);
if (err != nfs_ok)
goto out_dput;
goto out_unlock;
host_err = vfs_link(dold, &nop_mnt_idmap, dirp, dnew, NULL);
fh_fill_post_attrs(ffhp);
inode_unlock(dirp);
out_unlock:
end_creating(dnew, ddir);
if (!host_err) {
host_err = commit_metadata(ffhp);
if (!host_err)
host_err = commit_metadata(tfhp);
}
dput(dnew);
out_drop_write:
fh_drop_write(tfhp);
if (host_err == -EBUSY) {
@ -1849,12 +1845,6 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp,
}
out:
return err != nfs_ok ? err : nfserrno(host_err);
out_dput:
dput(dnew);
out_unlock:
inode_unlock(dirp);
goto out_drop_write;
}
static void

View File

@ -613,9 +613,9 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)
if (err)
goto out;
inode_lock_nested(udir, I_MUTEX_PARENT);
upper = ovl_lookup_upper(ofs, c->dentry->d_name.name, upperdir,
c->dentry->d_name.len);
upper = ovl_start_creating_upper(ofs, upperdir,
&QSTR_LEN(c->dentry->d_name.name,
c->dentry->d_name.len));
err = PTR_ERR(upper);
if (!IS_ERR(upper)) {
err = ovl_do_link(ofs, ovl_dentry_upper(c->dentry), udir, upper);
@ -626,9 +626,8 @@ static int ovl_link_up(struct ovl_copy_up_ctx *c)
ovl_dentry_set_upper_alias(c->dentry);
ovl_dentry_update_reval(c->dentry, upper);
}
dput(upper);
end_creating(upper, upperdir);
}
inode_unlock(udir);
if (err)
goto out;
@ -894,16 +893,14 @@ static int ovl_copy_up_tmpfile(struct ovl_copy_up_ctx *c)
if (err)
goto out;
inode_lock_nested(udir, I_MUTEX_PARENT);
upper = ovl_lookup_upper(ofs, c->destname.name, c->destdir,
c->destname.len);
upper = ovl_start_creating_upper(ofs, c->destdir,
&QSTR_LEN(c->destname.name,
c->destname.len));
err = PTR_ERR(upper);
if (!IS_ERR(upper)) {
err = ovl_do_link(ofs, temp, udir, upper);
dput(upper);
end_creating(upper, c->destdir);
}
inode_unlock(udir);
if (err)
goto out;

View File

@ -59,15 +59,21 @@ int ovl_cleanup(struct ovl_fs *ofs, struct dentry *workdir,
return 0;
}
struct dentry *ovl_lookup_temp(struct ovl_fs *ofs, struct dentry *workdir)
#define OVL_TEMPNAME_SIZE 20
static void ovl_tempname(char name[OVL_TEMPNAME_SIZE])
{
struct dentry *temp;
char name[20];
static atomic_t temp_id = ATOMIC_INIT(0);
/* counter is allowed to wrap, since temp dentries are ephemeral */
snprintf(name, sizeof(name), "#%x", atomic_inc_return(&temp_id));
snprintf(name, OVL_TEMPNAME_SIZE, "#%x", atomic_inc_return(&temp_id));
}
struct dentry *ovl_lookup_temp(struct ovl_fs *ofs, struct dentry *workdir)
{
struct dentry *temp;
char name[OVL_TEMPNAME_SIZE];
ovl_tempname(name);
temp = ovl_lookup_upper(ofs, name, workdir, strlen(name));
if (!IS_ERR(temp) && temp->d_inode) {
pr_err("workdir/%s already exists\n", name);
@ -78,48 +84,52 @@ struct dentry *ovl_lookup_temp(struct ovl_fs *ofs, struct dentry *workdir)
return temp;
}
static struct dentry *ovl_start_creating_temp(struct ovl_fs *ofs,
struct dentry *workdir)
{
char name[OVL_TEMPNAME_SIZE];
ovl_tempname(name);
return start_creating(ovl_upper_mnt_idmap(ofs), workdir,
&QSTR(name));
}
static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
{
int err;
struct dentry *whiteout;
struct dentry *whiteout, *link;
struct dentry *workdir = ofs->workdir;
struct inode *wdir = workdir->d_inode;
guard(mutex)(&ofs->whiteout_lock);
if (!ofs->whiteout) {
inode_lock_nested(wdir, I_MUTEX_PARENT);
whiteout = ovl_lookup_temp(ofs, workdir);
if (!IS_ERR(whiteout)) {
err = ovl_do_whiteout(ofs, wdir, whiteout);
if (err) {
dput(whiteout);
whiteout = ERR_PTR(err);
}
}
inode_unlock(wdir);
whiteout = ovl_start_creating_temp(ofs, workdir);
if (IS_ERR(whiteout))
return whiteout;
ofs->whiteout = whiteout;
err = ovl_do_whiteout(ofs, wdir, whiteout);
if (!err)
ofs->whiteout = dget(whiteout);
end_creating(whiteout, workdir);
if (err)
return ERR_PTR(err);
}
if (!ofs->no_shared_whiteout) {
inode_lock_nested(wdir, I_MUTEX_PARENT);
whiteout = ovl_lookup_temp(ofs, workdir);
if (!IS_ERR(whiteout)) {
err = ovl_do_link(ofs, ofs->whiteout, wdir, whiteout);
if (err) {
dput(whiteout);
whiteout = ERR_PTR(err);
}
}
inode_unlock(wdir);
if (!IS_ERR(whiteout))
return whiteout;
if (PTR_ERR(whiteout) != -EMLINK) {
pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%lu)\n",
link = ovl_start_creating_temp(ofs, workdir);
if (IS_ERR(link))
return link;
err = ovl_do_link(ofs, ofs->whiteout, wdir, link);
if (!err)
whiteout = dget(link);
end_creating(link, workdir);
if (!err)
return whiteout;;
if (err != -EMLINK) {
pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%u)\n",
ofs->whiteout->d_inode->i_nlink,
PTR_ERR(whiteout));
err);
ofs->no_shared_whiteout = true;
}
}
@ -252,10 +262,13 @@ struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
struct ovl_cattr *attr)
{
struct dentry *ret;
inode_lock_nested(workdir->d_inode, I_MUTEX_PARENT);
ret = ovl_create_real(ofs, workdir,
ovl_lookup_temp(ofs, workdir), attr);
inode_unlock(workdir->d_inode);
ret = ovl_start_creating_temp(ofs, workdir);
if (IS_ERR(ret))
return ret;
ret = ovl_create_real(ofs, workdir, ret, attr);
if (!IS_ERR(ret))
dget(ret);
end_creating(ret, workdir);
return ret;
}
@ -354,18 +367,21 @@ static int ovl_create_upper(struct dentry *dentry, struct inode *inode,
{
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
struct inode *udir = upperdir->d_inode;
struct dentry *newdentry;
int err;
inode_lock_nested(udir, I_MUTEX_PARENT);
newdentry = ovl_create_real(ofs, upperdir,
ovl_lookup_upper(ofs, dentry->d_name.name,
upperdir, dentry->d_name.len),
attr);
inode_unlock(udir);
newdentry = ovl_start_creating_upper(ofs, upperdir,
&QSTR_LEN(dentry->d_name.name,
dentry->d_name.len));
if (IS_ERR(newdentry))
return PTR_ERR(newdentry);
newdentry = ovl_create_real(ofs, upperdir, newdentry, attr);
if (IS_ERR(newdentry)) {
end_creating(newdentry, upperdir);
return PTR_ERR(newdentry);
}
dget(newdentry);
end_creating(newdentry, upperdir);
if (ovl_type_merge(dentry->d_parent) && d_is_dir(newdentry) &&
!ovl_allow_offline_changes(ofs)) {

View File

@ -415,6 +415,14 @@ static inline struct dentry *ovl_lookup_upper_unlocked(struct ovl_fs *ofs,
&QSTR_LEN(name, len), base);
}
static inline struct dentry *ovl_start_creating_upper(struct ovl_fs *ofs,
struct dentry *parent,
struct qstr *name)
{
return start_creating(ovl_upper_mnt_idmap(ofs),
parent, name);
}
static inline bool ovl_open_flags_need_copy_up(int flags)
{
if (!flags)

View File

@ -310,8 +310,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
bool retried = false;
retry:
inode_lock_nested(dir, I_MUTEX_PARENT);
work = ovl_lookup_upper(ofs, name, ofs->workbasedir, strlen(name));
work = ovl_start_creating_upper(ofs, ofs->workbasedir, &QSTR(name));
if (!IS_ERR(work)) {
struct iattr attr = {
@ -320,14 +319,13 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
};
if (work->d_inode) {
err = -EEXIST;
inode_unlock(dir);
if (retried)
goto out_dput;
dget(work);
end_creating(work, ofs->workbasedir);
if (persist)
return work;
err = -EEXIST;
if (retried)
goto out_dput;
retried = true;
err = ovl_workdir_cleanup(ofs, ofs->workbasedir, mnt, work, 0);
dput(work);
@ -338,7 +336,9 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
}
work = ovl_do_mkdir(ofs, dir, work, attr.ia_mode);
inode_unlock(dir);
if (!IS_ERR(work))
dget(work);
end_creating(work, ofs->workbasedir);
err = PTR_ERR(work);
if (IS_ERR(work))
goto out_err;
@ -376,7 +376,6 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
if (err)
goto out_dput;
} else {
inode_unlock(dir);
err = PTR_ERR(work);
goto out_err;
}
@ -626,14 +625,17 @@ static struct dentry *ovl_lookup_or_create(struct ovl_fs *ofs,
struct dentry *parent,
const char *name, umode_t mode)
{
size_t len = strlen(name);
struct dentry *child;
inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
child = ovl_lookup_upper(ofs, name, parent, len);
if (!IS_ERR(child) && !child->d_inode)
child = ovl_create_real(ofs, parent, child, OVL_CATTR(mode));
inode_unlock(parent->d_inode);
child = ovl_start_creating_upper(ofs, parent, &QSTR(name));
if (!IS_ERR(child)) {
if (!child->d_inode)
child = ovl_create_real(ofs, parent, child,
OVL_CATTR(mode));
if (!IS_ERR(child))
dget(child);
end_creating(child, parent);
}
dput(parent);
return child;

View File

@ -88,6 +88,39 @@ struct dentry *lookup_one_positive_killable(struct mnt_idmap *idmap,
struct qstr *name,
struct dentry *base);
struct dentry *start_creating(struct mnt_idmap *idmap, struct dentry *parent,
struct qstr *name);
/**
* end_creating - finish action started with start_creating
* @child: dentry returned by start_creating() or vfs_mkdir()
* @parent: dentry given to start_creating(),
*
* Unlock and release the child.
*
* Unlike end_dirop() this can only be called if start_creating() succeeded.
* It handles @child being and error as vfs_mkdir() might have converted the
* dentry to an error - in that case the parent still needs to be unlocked.
*
* If vfs_mkdir() was called then the value returned from that function
* should be given for @child rather than the original dentry, as vfs_mkdir()
* may have provided a new dentry. Even if vfs_mkdir() returns an error
* it must be given to end_creating().
*
* If vfs_mkdir() was not called, then @child will be a valid dentry and
* @parent will be ignored.
*/
static inline void end_creating(struct dentry *child, struct dentry *parent)
{
if (IS_ERR(child))
/* The parent is still locked despite the error from
* vfs_mkdir() - must unlock it.
*/
inode_unlock(parent->d_inode);
else
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 *);