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); _enter(",,%s", dirname);
/* search the current directory for the element name */ /* search the current directory for the element name */
inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
retry: retry:
ret = cachefiles_inject_read_error(); ret = cachefiles_inject_read_error();
if (ret == 0) if (ret == 0)
subdir = lookup_one(&nop_mnt_idmap, &QSTR(dirname), dir); subdir = start_creating(&nop_mnt_idmap, dir, &QSTR(dirname));
else else
subdir = ERR_PTR(ret); subdir = ERR_PTR(ret);
trace_cachefiles_lookup(NULL, dir, subdir); trace_cachefiles_lookup(NULL, dir, subdir);
@ -141,7 +140,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
trace_cachefiles_mkdir(dir, subdir); trace_cachefiles_mkdir(dir, subdir);
if (unlikely(d_unhashed(subdir) || d_is_negative(subdir))) { if (unlikely(d_unhashed(subdir) || d_is_negative(subdir))) {
dput(subdir); end_creating(subdir, dir);
goto retry; goto retry;
} }
ASSERT(d_backing_inode(subdir)); 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 */ /* Tell rmdir() it's not allowed to delete the subdir */
inode_lock(d_inode(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))) { if (!__cachefiles_mark_inode_in_use(NULL, d_inode(subdir))) {
pr_notice("cachefiles: Inode already in use: %pd (B=%lx)\n", 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); return ERR_PTR(-EBUSY);
mkdir_error: mkdir_error:
inode_unlock(d_inode(dir)); end_creating(subdir, dir);
if (!IS_ERR(subdir))
dput(subdir);
pr_err("mkdir %s failed with error %d\n", dirname, ret); pr_err("mkdir %s failed with error %d\n", dirname, ret);
return ERR_PTR(ret); return ERR_PTR(ret);
lookup_error: lookup_error:
inode_unlock(d_inode(dir));
ret = PTR_ERR(subdir); ret = PTR_ERR(subdir);
pr_err("Lookup %s failed with error %d\n", dirname, ret); pr_err("Lookup %s failed with error %d\n", dirname, ret);
return ERR_PTR(ret); return ERR_PTR(ret);
@ -679,36 +676,41 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
_enter(",%pD", object->file); _enter(",%pD", object->file);
inode_lock_nested(d_inode(fan), I_MUTEX_PARENT);
ret = cachefiles_inject_read_error(); ret = cachefiles_inject_read_error();
if (ret == 0) 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 else
dentry = ERR_PTR(ret); dentry = ERR_PTR(ret);
if (IS_ERR(dentry)) { if (IS_ERR(dentry)) {
trace_cachefiles_vfs_error(object, d_inode(fan), PTR_ERR(dentry), trace_cachefiles_vfs_error(object, d_inode(fan), PTR_ERR(dentry),
cachefiles_trace_lookup_error); cachefiles_trace_lookup_error);
_debug("lookup fail %ld", PTR_ERR(dentry)); _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, ret = cachefiles_unlink(volume->cache, object, fan, dentry,
FSCACHE_OBJECT_IS_STALE); FSCACHE_OBJECT_IS_STALE);
if (ret < 0) if (ret < 0)
goto out_dput; goto out_end;
end_creating(dentry, fan);
dput(dentry);
ret = cachefiles_inject_read_error(); ret = cachefiles_inject_read_error();
if (ret == 0) 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 else
dentry = ERR_PTR(ret); dentry = ERR_PTR(ret);
if (IS_ERR(dentry)) { if (IS_ERR(dentry)) {
trace_cachefiles_vfs_error(object, d_inode(fan), PTR_ERR(dentry), trace_cachefiles_vfs_error(object, d_inode(fan), PTR_ERR(dentry),
cachefiles_trace_lookup_error); cachefiles_trace_lookup_error);
_debug("lookup fail %ld", PTR_ERR(dentry)); _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; success = true;
} }
out_dput: out_end:
dput(dentry); end_creating(dentry, fan);
out_unlock: out:
inode_unlock(d_inode(fan));
_leave(" = %u", success); _leave(" = %u", success);
return success; return success;
} }

View File

@ -3221,6 +3221,33 @@ struct dentry *lookup_noperm_positive_unlocked(struct qstr *name,
} }
EXPORT_SYMBOL(lookup_noperm_positive_unlocked); 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 #ifdef CONFIG_UNIX98_PTYS
int path_pts(struct path *path) 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) void end_creating_path(const struct path *path, struct dentry *dentry)
{ {
if (IS_ERR(dentry)) end_creating(dentry, path->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);
mnt_drop_write(path->mnt); mnt_drop_write(path->mnt);
path_put(path); path_put(path);
} }

View File

@ -281,14 +281,11 @@ nfsd3_create_file(struct svc_rqst *rqstp, struct svc_fh *fhp,
if (host_err) if (host_err)
return nfserrno(host_err); return nfserrno(host_err);
inode_lock_nested(inode, I_MUTEX_PARENT); child = start_creating(&nop_mnt_idmap, parent,
&QSTR_LEN(argp->name, argp->len));
child = lookup_one(&nop_mnt_idmap,
&QSTR_LEN(argp->name, argp->len),
parent);
if (IS_ERR(child)) { if (IS_ERR(child)) {
status = nfserrno(PTR_ERR(child)); status = nfserrno(PTR_ERR(child));
goto out; goto out_write;
} }
if (d_really_is_negative(child)) { 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); status = nfsd_create_setattr(rqstp, fhp, resfhp, &attrs);
out: out:
inode_unlock(inode); end_creating(child, parent);
if (child && !IS_ERR(child)) out_write:
dput(child);
fh_drop_write(fhp); fh_drop_write(fhp);
return status; 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)) if (is_create_with_attrs(open))
nfsd4_acl_to_attr(NF4REG, open->op_acl, &attrs); nfsd4_acl_to_attr(NF4REG, open->op_acl, &attrs);
inode_lock_nested(inode, I_MUTEX_PARENT); child = start_creating(&nop_mnt_idmap, parent,
&QSTR_LEN(open->op_fname, open->op_fnamelen));
child = lookup_one(&nop_mnt_idmap,
&QSTR_LEN(open->op_fname, open->op_fnamelen),
parent);
if (IS_ERR(child)) { if (IS_ERR(child)) {
status = nfserrno(PTR_ERR(child)); status = nfserrno(PTR_ERR(child));
goto out; goto out_write;
} }
if (d_really_is_negative(child)) { 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) if (attrs.na_aclerr)
open->op_bmval[0] &= ~FATTR4_WORD0_ACL; open->op_bmval[0] &= ~FATTR4_WORD0_ACL;
out: out:
inode_unlock(inode); end_creating(child, parent);
nfsd_attrs_free(&attrs); nfsd_attrs_free(&attrs);
if (child && !IS_ERR(child)) out_write:
dput(child);
fh_drop_write(fhp); fh_drop_write(fhp);
return status; return status;
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -59,15 +59,21 @@ int ovl_cleanup(struct ovl_fs *ofs, struct dentry *workdir,
return 0; 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); static atomic_t temp_id = ATOMIC_INIT(0);
/* counter is allowed to wrap, since temp dentries are ephemeral */ /* 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)); temp = ovl_lookup_upper(ofs, name, workdir, strlen(name));
if (!IS_ERR(temp) && temp->d_inode) { if (!IS_ERR(temp) && temp->d_inode) {
pr_err("workdir/%s already exists\n", name); 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; 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) static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
{ {
int err; int err;
struct dentry *whiteout; struct dentry *whiteout, *link;
struct dentry *workdir = ofs->workdir; struct dentry *workdir = ofs->workdir;
struct inode *wdir = workdir->d_inode; struct inode *wdir = workdir->d_inode;
guard(mutex)(&ofs->whiteout_lock); guard(mutex)(&ofs->whiteout_lock);
if (!ofs->whiteout) { if (!ofs->whiteout) {
inode_lock_nested(wdir, I_MUTEX_PARENT); whiteout = ovl_start_creating_temp(ofs, workdir);
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);
if (IS_ERR(whiteout)) if (IS_ERR(whiteout))
return 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) { if (!ofs->no_shared_whiteout) {
inode_lock_nested(wdir, I_MUTEX_PARENT); link = ovl_start_creating_temp(ofs, workdir);
whiteout = ovl_lookup_temp(ofs, workdir); if (IS_ERR(link))
if (!IS_ERR(whiteout)) { return link;
err = ovl_do_link(ofs, ofs->whiteout, wdir, whiteout); err = ovl_do_link(ofs, ofs->whiteout, wdir, link);
if (err) { if (!err)
dput(whiteout); whiteout = dget(link);
whiteout = ERR_PTR(err); end_creating(link, workdir);
} if (!err)
} return whiteout;;
inode_unlock(wdir);
if (!IS_ERR(whiteout)) if (err != -EMLINK) {
return whiteout; pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%u)\n",
if (PTR_ERR(whiteout) != -EMLINK) {
pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%lu)\n",
ofs->whiteout->d_inode->i_nlink, ofs->whiteout->d_inode->i_nlink,
PTR_ERR(whiteout)); err);
ofs->no_shared_whiteout = true; 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 ovl_cattr *attr)
{ {
struct dentry *ret; struct dentry *ret;
inode_lock_nested(workdir->d_inode, I_MUTEX_PARENT); ret = ovl_start_creating_temp(ofs, workdir);
ret = ovl_create_real(ofs, workdir, if (IS_ERR(ret))
ovl_lookup_temp(ofs, workdir), attr); return ret;
inode_unlock(workdir->d_inode); ret = ovl_create_real(ofs, workdir, ret, attr);
if (!IS_ERR(ret))
dget(ret);
end_creating(ret, workdir);
return ret; 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 ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
struct inode *udir = upperdir->d_inode;
struct dentry *newdentry; struct dentry *newdentry;
int err; int err;
inode_lock_nested(udir, I_MUTEX_PARENT); newdentry = ovl_start_creating_upper(ofs, upperdir,
newdentry = ovl_create_real(ofs, upperdir, &QSTR_LEN(dentry->d_name.name,
ovl_lookup_upper(ofs, dentry->d_name.name, dentry->d_name.len));
upperdir, dentry->d_name.len),
attr);
inode_unlock(udir);
if (IS_ERR(newdentry)) if (IS_ERR(newdentry))
return PTR_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) && if (ovl_type_merge(dentry->d_parent) && d_is_dir(newdentry) &&
!ovl_allow_offline_changes(ofs)) { !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); &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) static inline bool ovl_open_flags_need_copy_up(int flags)
{ {
if (!flags) if (!flags)

View File

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

View File

@ -88,6 +88,39 @@ struct dentry *lookup_one_positive_killable(struct mnt_idmap *idmap,
struct qstr *name, struct qstr *name,
struct dentry *base); 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_one(struct path *);
extern int follow_down(struct path *path, unsigned int flags); extern int follow_down(struct path *path, unsigned int flags);
extern int follow_up(struct path *); extern int follow_up(struct path *);