vfs-6.19-rc1.directory.locking

-----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCaSmOZwAKCRCRxhvAZXjc
 op9tAQCJ//STOkvYHfqgsdRD+cW9MRg/gPzfVZgnV1FTyf8sMgEA0IsY5zCZB9eh
 9FdD0E57P8PlWRwWZ+LktnWBzRAUqwI=
 =MOVR
 -----END PGP SIGNATURE-----

Merge tag 'vfs-6.19-rc1.directory.locking' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull directory locking updates from Christian Brauner:
 "This contains the work to add centralized APIs for directory locking
  operations.

  This series is part of a larger effort to change directory operation
  locking to allow multiple concurrent operations in a directory. The
  ultimate goal is to lock the target dentry(s) rather than the whole
  parent directory.

  To help with changing the locking protocol, this series centralizes
  locking and lookup in new helper functions. The helpers establish a
  pattern where it is the dentry that is being locked and unlocked
  (currently the lock is held on dentry->d_parent->d_inode, but that can
  change in the future).

  This also changes vfs_mkdir() to unlock the parent on failure, as well
  as dput()ing the dentry. This allows end_creating() to only require
  the target dentry (which may be IS_ERR() after vfs_mkdir()), not the
  parent"

* tag 'vfs-6.19-rc1.directory.locking' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  nfsd: fix end_creating() conversion
  VFS: introduce end_creating_keep()
  VFS: change vfs_mkdir() to unlock on failure.
  ecryptfs: use new start_creating/start_removing APIs
  Add start_renaming_two_dentries()
  VFS/ovl/smb: introduce start_renaming_dentry()
  VFS/nfsd/ovl: introduce start_renaming() and end_renaming()
  VFS: add start_creating_killable() and start_removing_killable()
  VFS: introduce start_removing_dentry()
  smb/server: use end_removing_noperm for for target of smb2_create_link()
  VFS: introduce start_creating_noperm() and start_removing_noperm()
  VFS/nfsd/cachefiles/ovl: introduce start_removing() and end_removing()
  VFS/nfsd/cachefiles/ovl: add start_creating() and end_creating()
  VFS: tidy up do_unlinkat()
  VFS: introduce start_dirop() and end_dirop()
  debugfs: rename end_creating() to debugfs_end_creating()
This commit is contained in:
Linus Torvalds 2025-12-01 16:13:46 -08:00
commit a8058f8442
31 changed files with 1304 additions and 840 deletions

View File

@ -1309,3 +1309,16 @@ a different length, use
vfs_parse_fs_qstr(fc, key, &QSTR_LEN(value, len)) vfs_parse_fs_qstr(fc, key, &QSTR_LEN(value, len))
instead. instead.
---
**mandatory**
vfs_mkdir() now returns a dentry - the one returned by ->mkdir(). If
that dentry is different from the dentry passed in, including if it is
an IS_ERR() dentry pointer, the original dentry is dput().
When vfs_mkdir() returns an error, and so both dputs() the original
dentry and doesn't provide a replacement, it also unlocks the parent.
Consequently the return value from vfs_mkdir() can be passed to
end_creating() and the parent will be unlocked precisely when necessary.

View File

@ -904,14 +904,9 @@ static noinline int btrfs_mksubvol(struct dentry *parent,
struct fscrypt_str name_str = FSTR_INIT((char *)qname->name, qname->len); struct fscrypt_str name_str = FSTR_INIT((char *)qname->name, qname->len);
int ret; int ret;
ret = down_write_killable_nested(&dir->i_rwsem, I_MUTEX_PARENT); dentry = start_creating_killable(idmap, parent, qname);
if (ret == -EINTR)
return ret;
dentry = lookup_one(idmap, qname, parent);
ret = PTR_ERR(dentry);
if (IS_ERR(dentry)) if (IS_ERR(dentry))
goto out_unlock; return PTR_ERR(dentry);
ret = btrfs_may_create(idmap, dir, dentry); ret = btrfs_may_create(idmap, dir, dentry);
if (ret) if (ret)
@ -940,9 +935,7 @@ static noinline int btrfs_mksubvol(struct dentry *parent,
out_up_read: out_up_read:
up_read(&fs_info->subvol_sem); up_read(&fs_info->subvol_sem);
out_dput: out_dput:
dput(dentry); end_creating(dentry);
out_unlock:
btrfs_inode_unlock(BTRFS_I(dir), 0);
return ret; return ret;
} }
@ -2417,18 +2410,10 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
goto free_subvol_name; goto free_subvol_name;
} }
ret = down_write_killable_nested(&dir->i_rwsem, I_MUTEX_PARENT); dentry = start_removing_killable(idmap, parent, &QSTR(subvol_name));
if (ret == -EINTR)
goto free_subvol_name;
dentry = lookup_one(idmap, &QSTR(subvol_name), parent);
if (IS_ERR(dentry)) { if (IS_ERR(dentry)) {
ret = PTR_ERR(dentry); ret = PTR_ERR(dentry);
goto out_unlock_dir; goto out_end_removing;
}
if (d_really_is_negative(dentry)) {
ret = -ENOENT;
goto out_dput;
} }
inode = d_inode(dentry); inode = d_inode(dentry);
@ -2449,7 +2434,7 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
*/ */
ret = -EPERM; ret = -EPERM;
if (!btrfs_test_opt(fs_info, USER_SUBVOL_RM_ALLOWED)) if (!btrfs_test_opt(fs_info, USER_SUBVOL_RM_ALLOWED))
goto out_dput; goto out_end_removing;
/* /*
* Do not allow deletion if the parent dir is the same * Do not allow deletion if the parent dir is the same
@ -2460,21 +2445,21 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
*/ */
ret = -EINVAL; ret = -EINVAL;
if (root == dest) if (root == dest)
goto out_dput; goto out_end_removing;
ret = inode_permission(idmap, inode, MAY_WRITE | MAY_EXEC); ret = inode_permission(idmap, inode, MAY_WRITE | MAY_EXEC);
if (ret) if (ret)
goto out_dput; goto out_end_removing;
} }
/* check if subvolume may be deleted by a user */ /* check if subvolume may be deleted by a user */
ret = btrfs_may_delete(idmap, dir, dentry, 1); ret = btrfs_may_delete(idmap, dir, dentry, 1);
if (ret) if (ret)
goto out_dput; goto out_end_removing;
if (btrfs_ino(BTRFS_I(inode)) != BTRFS_FIRST_FREE_OBJECTID) { if (btrfs_ino(BTRFS_I(inode)) != BTRFS_FIRST_FREE_OBJECTID) {
ret = -EINVAL; ret = -EINVAL;
goto out_dput; goto out_end_removing;
} }
btrfs_inode_lock(BTRFS_I(inode), 0); btrfs_inode_lock(BTRFS_I(inode), 0);
@ -2483,10 +2468,8 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file,
if (!ret) if (!ret)
d_delete_notify(dir, dentry); d_delete_notify(dir, dentry);
out_dput: out_end_removing:
dput(dentry); end_removing(dentry);
out_unlock_dir:
btrfs_inode_unlock(BTRFS_I(dir), 0);
free_subvol_name: free_subvol_name:
kfree(subvol_name_ptr); kfree(subvol_name_ptr);
free_parent: free_parent:

View File

@ -9,6 +9,7 @@
#include <linux/mount.h> #include <linux/mount.h>
#include <linux/xattr.h> #include <linux/xattr.h>
#include <linux/file.h> #include <linux/file.h>
#include <linux/namei.h>
#include <linux/falloc.h> #include <linux/falloc.h>
#include <trace/events/fscache.h> #include <trace/events/fscache.h>
#include "internal.h" #include "internal.h"
@ -428,11 +429,13 @@ static bool cachefiles_invalidate_cookie(struct fscache_cookie *cookie)
if (!old_tmpfile) { if (!old_tmpfile) {
struct cachefiles_volume *volume = object->volume; struct cachefiles_volume *volume = object->volume;
struct dentry *fan = volume->fanout[(u8)cookie->key_hash]; struct dentry *fan = volume->fanout[(u8)cookie->key_hash];
struct dentry *obj;
inode_lock_nested(d_inode(fan), I_MUTEX_PARENT); obj = start_removing_dentry(fan, old_file->f_path.dentry);
cachefiles_bury_object(volume->cache, object, fan, if (!IS_ERR(obj))
old_file->f_path.dentry, cachefiles_bury_object(volume->cache, object,
FSCACHE_OBJECT_INVALIDATED); fan, obj,
FSCACHE_OBJECT_INVALIDATED);
} }
fput(old_file); fput(old_file);
} }

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);
@ -129,10 +128,12 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache,
if (ret < 0) if (ret < 0)
goto mkdir_error; goto mkdir_error;
ret = cachefiles_inject_write_error(); ret = cachefiles_inject_write_error();
if (ret == 0) if (ret == 0) {
subdir = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), subdir, 0700, NULL); subdir = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), subdir, 0700, NULL);
else } else {
end_creating(subdir);
subdir = ERR_PTR(ret); subdir = ERR_PTR(ret);
}
if (IS_ERR(subdir)) { if (IS_ERR(subdir)) {
trace_cachefiles_vfs_error(NULL, d_inode(dir), ret, trace_cachefiles_vfs_error(NULL, d_inode(dir), ret,
cachefiles_trace_mkdir_error); cachefiles_trace_mkdir_error);
@ -141,7 +142,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);
goto retry; goto retry;
} }
ASSERT(d_backing_inode(subdir)); ASSERT(d_backing_inode(subdir));
@ -154,7 +155,7 @@ 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)); end_creating_keep(subdir);
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 +197,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);
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);
@ -263,6 +261,8 @@ static int cachefiles_unlink(struct cachefiles_cache *cache,
* - File backed objects are unlinked * - File backed objects are unlinked
* - Directory backed objects are stuffed into the graveyard for userspace to * - Directory backed objects are stuffed into the graveyard for userspace to
* delete * delete
* On entry dir must be locked. It will be unlocked on exit.
* On entry there must be at least 2 refs on rep, one will be dropped on exit.
*/ */
int cachefiles_bury_object(struct cachefiles_cache *cache, int cachefiles_bury_object(struct cachefiles_cache *cache,
struct cachefiles_object *object, struct cachefiles_object *object,
@ -278,27 +278,23 @@ int cachefiles_bury_object(struct cachefiles_cache *cache,
_enter(",'%pd','%pd'", dir, rep); _enter(",'%pd','%pd'", dir, rep);
if (rep->d_parent != dir) { if (rep->d_parent != dir) {
inode_unlock(d_inode(dir)); end_removing(rep);
_leave(" = -ESTALE"); _leave(" = -ESTALE");
return -ESTALE; return -ESTALE;
} }
/* non-directories can just be unlinked */ /* non-directories can just be unlinked */
if (!d_is_dir(rep)) { 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); ret = cachefiles_unlink(cache, object, dir, rep, why);
dput(rep); end_removing(rep);
inode_unlock(d_inode(dir));
_leave(" = %d", ret); _leave(" = %d", ret);
return ret; return ret;
} }
/* directories have to be moved to the graveyard */ /* directories have to be moved to the graveyard */
_debug("move stale object to graveyard"); _debug("move stale object to graveyard");
inode_unlock(d_inode(dir)); end_removing(rep);
try_again: try_again:
/* first step is to make up a grave dentry in the graveyard */ /* first step is to make up a grave dentry in the graveyard */
@ -425,13 +421,12 @@ int cachefiles_delete_object(struct cachefiles_object *object,
_enter(",OBJ%x{%pD}", object->debug_id, object->file); _enter(",OBJ%x{%pD}", object->debug_id, object->file);
/* Stop the dentry being negated if it's only pinned by a file struct. */ dentry = start_removing_dentry(fan, dentry);
dget(dentry); if (IS_ERR(dentry))
ret = PTR_ERR(dentry);
inode_lock_nested(d_backing_inode(fan), I_MUTEX_PARENT); else
ret = cachefiles_unlink(volume->cache, object, fan, dentry, why); ret = cachefiles_unlink(volume->cache, object, fan, dentry, why);
inode_unlock(d_backing_inode(fan)); end_removing(dentry);
dput(dentry);
return ret; return ret;
} }
@ -644,9 +639,13 @@ bool cachefiles_look_up_object(struct cachefiles_object *object)
if (!d_is_reg(dentry)) { if (!d_is_reg(dentry)) {
pr_err("%pd is not a file\n", dentry); pr_err("%pd is not a file\n", dentry);
inode_lock_nested(d_inode(fan), I_MUTEX_PARENT); struct dentry *de = start_removing_dentry(fan, dentry);
ret = cachefiles_bury_object(volume->cache, object, fan, dentry, if (IS_ERR(de))
FSCACHE_OBJECT_IS_WEIRD); ret = PTR_ERR(de);
else
ret = cachefiles_bury_object(volume->cache, object,
fan, de,
FSCACHE_OBJECT_IS_WEIRD);
dput(dentry); dput(dentry);
if (ret < 0) if (ret < 0)
return false; return false;
@ -679,36 +678,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);
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 +733,9 @@ bool cachefiles_commit_tmpfile(struct cachefiles_cache *cache,
success = true; success = true;
} }
out_dput: out_end:
dput(dentry); end_creating(dentry);
out_unlock: out:
inode_unlock(d_inode(fan));
_leave(" = %u", success); _leave(" = %u", success);
return success; return success;
} }
@ -748,26 +751,20 @@ static struct dentry *cachefiles_lookup_for_cull(struct cachefiles_cache *cache,
struct dentry *victim; struct dentry *victim;
int ret = -ENOENT; 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)) if (IS_ERR(victim))
goto lookup_error; goto lookup_error;
if (d_is_negative(victim))
goto lookup_put;
if (d_inode(victim)->i_flags & S_KERNEL_FILE) if (d_inode(victim)->i_flags & S_KERNEL_FILE)
goto lookup_busy; goto lookup_busy;
return victim; return victim;
lookup_busy: lookup_busy:
ret = -EBUSY; ret = -EBUSY;
lookup_put: end_removing(victim);
inode_unlock(d_inode(dir));
dput(victim);
return ERR_PTR(ret); return ERR_PTR(ret);
lookup_error: lookup_error:
inode_unlock(d_inode(dir));
ret = PTR_ERR(victim); ret = PTR_ERR(victim);
if (ret == -ENOENT) if (ret == -ENOENT)
return ERR_PTR(-ESTALE); /* Probably got retired by the netfs */ return ERR_PTR(-ESTALE); /* Probably got retired by the netfs */
@ -815,18 +812,17 @@ int cachefiles_cull(struct cachefiles_cache *cache, struct dentry *dir,
ret = cachefiles_bury_object(cache, NULL, dir, victim, ret = cachefiles_bury_object(cache, NULL, dir, victim,
FSCACHE_OBJECT_WAS_CULLED); FSCACHE_OBJECT_WAS_CULLED);
dput(victim);
if (ret < 0) if (ret < 0)
goto error; goto error;
fscache_count_culled(); fscache_count_culled();
dput(victim);
_leave(" = 0"); _leave(" = 0");
return 0; return 0;
error_unlock: error_unlock:
inode_unlock(d_inode(dir)); end_removing(victim);
error: error:
dput(victim);
if (ret == -ENOENT) if (ret == -ENOENT)
return -ESTALE; /* Probably got retired by the netfs */ return -ESTALE; /* Probably got retired by the netfs */

View File

@ -7,6 +7,7 @@
#include <linux/fs.h> #include <linux/fs.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/namei.h>
#include "internal.h" #include "internal.h"
#include <trace/events/fscache.h> #include <trace/events/fscache.h>
@ -58,9 +59,11 @@ void cachefiles_acquire_volume(struct fscache_volume *vcookie)
if (ret < 0) { if (ret < 0) {
if (ret != -ESTALE) if (ret != -ESTALE)
goto error_dir; goto error_dir;
inode_lock_nested(d_inode(cache->store), I_MUTEX_PARENT); vdentry = start_removing_dentry(cache->store, vdentry);
cachefiles_bury_object(cache, NULL, cache->store, vdentry, if (!IS_ERR(vdentry))
FSCACHE_VOLUME_IS_WEIRD); cachefiles_bury_object(cache, NULL, cache->store,
vdentry,
FSCACHE_VOLUME_IS_WEIRD);
cachefiles_put_directory(volume->dentry); cachefiles_put_directory(volume->dentry);
cond_resched(); cond_resched();
goto retry; goto retry;

View File

@ -403,7 +403,7 @@ static struct dentry *debugfs_start_creating(const char *name,
return dentry; return dentry;
} }
static struct dentry *failed_creating(struct dentry *dentry) static struct dentry *debugfs_failed_creating(struct dentry *dentry)
{ {
inode_unlock(d_inode(dentry->d_parent)); inode_unlock(d_inode(dentry->d_parent));
dput(dentry); dput(dentry);
@ -411,7 +411,7 @@ static struct dentry *failed_creating(struct dentry *dentry)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
} }
static struct dentry *end_creating(struct dentry *dentry) static struct dentry *debugfs_end_creating(struct dentry *dentry)
{ {
inode_unlock(d_inode(dentry->d_parent)); inode_unlock(d_inode(dentry->d_parent));
return dentry; return dentry;
@ -435,7 +435,7 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
return dentry; return dentry;
if (!(debugfs_allow & DEBUGFS_ALLOW_API)) { if (!(debugfs_allow & DEBUGFS_ALLOW_API)) {
failed_creating(dentry); debugfs_failed_creating(dentry);
return ERR_PTR(-EPERM); return ERR_PTR(-EPERM);
} }
@ -443,7 +443,7 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
if (unlikely(!inode)) { if (unlikely(!inode)) {
pr_err("out of free dentries, can not create file '%s'\n", pr_err("out of free dentries, can not create file '%s'\n",
name); name);
return failed_creating(dentry); return debugfs_failed_creating(dentry);
} }
inode->i_mode = mode; inode->i_mode = mode;
@ -458,7 +458,7 @@ static struct dentry *__debugfs_create_file(const char *name, umode_t mode,
d_instantiate(dentry, inode); d_instantiate(dentry, inode);
fsnotify_create(d_inode(dentry->d_parent), dentry); fsnotify_create(d_inode(dentry->d_parent), dentry);
return end_creating(dentry); return debugfs_end_creating(dentry);
} }
struct dentry *debugfs_create_file_full(const char *name, umode_t mode, struct dentry *debugfs_create_file_full(const char *name, umode_t mode,
@ -585,7 +585,7 @@ struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)
return dentry; return dentry;
if (!(debugfs_allow & DEBUGFS_ALLOW_API)) { if (!(debugfs_allow & DEBUGFS_ALLOW_API)) {
failed_creating(dentry); debugfs_failed_creating(dentry);
return ERR_PTR(-EPERM); return ERR_PTR(-EPERM);
} }
@ -593,7 +593,7 @@ struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)
if (unlikely(!inode)) { if (unlikely(!inode)) {
pr_err("out of free dentries, can not create directory '%s'\n", pr_err("out of free dentries, can not create directory '%s'\n",
name); name);
return failed_creating(dentry); return debugfs_failed_creating(dentry);
} }
inode->i_mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO; inode->i_mode = S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO;
@ -605,7 +605,7 @@ struct dentry *debugfs_create_dir(const char *name, struct dentry *parent)
d_instantiate(dentry, inode); d_instantiate(dentry, inode);
inc_nlink(d_inode(dentry->d_parent)); inc_nlink(d_inode(dentry->d_parent));
fsnotify_mkdir(d_inode(dentry->d_parent), dentry); fsnotify_mkdir(d_inode(dentry->d_parent), dentry);
return end_creating(dentry); return debugfs_end_creating(dentry);
} }
EXPORT_SYMBOL_GPL(debugfs_create_dir); EXPORT_SYMBOL_GPL(debugfs_create_dir);
@ -632,7 +632,7 @@ struct dentry *debugfs_create_automount(const char *name,
return dentry; return dentry;
if (!(debugfs_allow & DEBUGFS_ALLOW_API)) { if (!(debugfs_allow & DEBUGFS_ALLOW_API)) {
failed_creating(dentry); debugfs_failed_creating(dentry);
return ERR_PTR(-EPERM); return ERR_PTR(-EPERM);
} }
@ -640,7 +640,7 @@ struct dentry *debugfs_create_automount(const char *name,
if (unlikely(!inode)) { if (unlikely(!inode)) {
pr_err("out of free dentries, can not create automount '%s'\n", pr_err("out of free dentries, can not create automount '%s'\n",
name); name);
return failed_creating(dentry); return debugfs_failed_creating(dentry);
} }
make_empty_dir_inode(inode); make_empty_dir_inode(inode);
@ -652,7 +652,7 @@ struct dentry *debugfs_create_automount(const char *name,
d_instantiate(dentry, inode); d_instantiate(dentry, inode);
inc_nlink(d_inode(dentry->d_parent)); inc_nlink(d_inode(dentry->d_parent));
fsnotify_mkdir(d_inode(dentry->d_parent), dentry); fsnotify_mkdir(d_inode(dentry->d_parent), dentry);
return end_creating(dentry); return debugfs_end_creating(dentry);
} }
EXPORT_SYMBOL(debugfs_create_automount); EXPORT_SYMBOL(debugfs_create_automount);
@ -699,13 +699,13 @@ struct dentry *debugfs_create_symlink(const char *name, struct dentry *parent,
pr_err("out of free dentries, can not create symlink '%s'\n", pr_err("out of free dentries, can not create symlink '%s'\n",
name); name);
kfree(link); kfree(link);
return failed_creating(dentry); return debugfs_failed_creating(dentry);
} }
inode->i_mode = S_IFLNK | S_IRWXUGO; inode->i_mode = S_IFLNK | S_IRWXUGO;
inode->i_op = &debugfs_symlink_inode_operations; inode->i_op = &debugfs_symlink_inode_operations;
inode->i_link = link; inode->i_link = link;
d_instantiate(dentry, inode); d_instantiate(dentry, inode);
return end_creating(dentry); return debugfs_end_creating(dentry);
} }
EXPORT_SYMBOL_GPL(debugfs_create_symlink); EXPORT_SYMBOL_GPL(debugfs_create_symlink);
@ -842,7 +842,8 @@ int __printf(2, 3) debugfs_change_name(struct dentry *dentry, const char *fmt, .
int error = 0; int error = 0;
const char *new_name; const char *new_name;
struct name_snapshot old_name; struct name_snapshot old_name;
struct dentry *parent, *target; struct dentry *target;
struct renamedata rd = {};
struct inode *dir; struct inode *dir;
va_list ap; va_list ap;
@ -855,36 +856,31 @@ int __printf(2, 3) debugfs_change_name(struct dentry *dentry, const char *fmt, .
if (!new_name) if (!new_name)
return -ENOMEM; return -ENOMEM;
parent = dget_parent(dentry); rd.old_parent = dget_parent(dentry);
dir = d_inode(parent); rd.new_parent = rd.old_parent;
inode_lock(dir); rd.flags = RENAME_NOREPLACE;
target = lookup_noperm_unlocked(&QSTR(new_name), rd.new_parent);
if (IS_ERR(target))
return PTR_ERR(target);
error = start_renaming_two_dentries(&rd, dentry, target);
if (error) {
if (error == -EEXIST && target == dentry)
/* it isn't an error to rename a thing to itself */
error = 0;
goto out;
}
dir = d_inode(rd.old_parent);
take_dentry_name_snapshot(&old_name, dentry); take_dentry_name_snapshot(&old_name, dentry);
simple_rename_timestamp(dir, dentry, dir, rd.new_dentry);
if (WARN_ON_ONCE(dentry->d_parent != parent)) { d_move(dentry, rd.new_dentry);
error = -EINVAL;
goto out;
}
if (strcmp(old_name.name.name, new_name) == 0)
goto out;
target = lookup_noperm(&QSTR(new_name), parent);
if (IS_ERR(target)) {
error = PTR_ERR(target);
goto out;
}
if (d_really_is_positive(target)) {
dput(target);
error = -EINVAL;
goto out;
}
simple_rename_timestamp(dir, dentry, dir, target);
d_move(dentry, target);
dput(target);
fsnotify_move(dir, dir, &old_name.name, d_is_dir(dentry), NULL, dentry); fsnotify_move(dir, dir, &old_name.name, d_is_dir(dentry), NULL, dentry);
out:
release_dentry_name_snapshot(&old_name); release_dentry_name_snapshot(&old_name);
inode_unlock(dir); end_renaming(&rd);
dput(parent); out:
dput(rd.old_parent);
dput(target);
kfree_const(new_name); kfree_const(new_name);
return error; return error;
} }

View File

@ -24,18 +24,26 @@
#include <linux/unaligned.h> #include <linux/unaligned.h>
#include "ecryptfs_kernel.h" #include "ecryptfs_kernel.h"
static int lock_parent(struct dentry *dentry, static struct dentry *ecryptfs_start_creating_dentry(struct dentry *dentry)
struct dentry **lower_dentry,
struct inode **lower_dir)
{ {
struct dentry *lower_dir_dentry; struct dentry *parent = dget_parent(dentry);
struct dentry *ret;
lower_dir_dentry = ecryptfs_dentry_to_lower(dentry->d_parent); ret = start_creating_dentry(ecryptfs_dentry_to_lower(parent),
*lower_dir = d_inode(lower_dir_dentry); ecryptfs_dentry_to_lower(dentry));
*lower_dentry = ecryptfs_dentry_to_lower(dentry); dput(parent);
return ret;
}
inode_lock_nested(*lower_dir, I_MUTEX_PARENT); static struct dentry *ecryptfs_start_removing_dentry(struct dentry *dentry)
return (*lower_dentry)->d_parent == lower_dir_dentry ? 0 : -EINVAL; {
struct dentry *parent = dget_parent(dentry);
struct dentry *ret;
ret = start_removing_dentry(ecryptfs_dentry_to_lower(parent),
ecryptfs_dentry_to_lower(dentry));
dput(parent);
return ret;
} }
static int ecryptfs_inode_test(struct inode *inode, void *lower_inode) static int ecryptfs_inode_test(struct inode *inode, void *lower_inode)
@ -141,15 +149,12 @@ static int ecryptfs_do_unlink(struct inode *dir, struct dentry *dentry,
struct inode *lower_dir; struct inode *lower_dir;
int rc; int rc;
rc = lock_parent(dentry, &lower_dentry, &lower_dir); lower_dentry = ecryptfs_start_removing_dentry(dentry);
dget(lower_dentry); // don't even try to make the lower negative if (IS_ERR(lower_dentry))
if (!rc) { return PTR_ERR(lower_dentry);
if (d_unhashed(lower_dentry))
rc = -EINVAL; lower_dir = lower_dentry->d_parent->d_inode;
else rc = vfs_unlink(&nop_mnt_idmap, lower_dir, lower_dentry, NULL);
rc = vfs_unlink(&nop_mnt_idmap, lower_dir, lower_dentry,
NULL);
}
if (rc) { if (rc) {
printk(KERN_ERR "Error in vfs_unlink; rc = [%d]\n", rc); printk(KERN_ERR "Error in vfs_unlink; rc = [%d]\n", rc);
goto out_unlock; goto out_unlock;
@ -158,8 +163,7 @@ static int ecryptfs_do_unlink(struct inode *dir, struct dentry *dentry,
set_nlink(inode, ecryptfs_inode_to_lower(inode)->i_nlink); set_nlink(inode, ecryptfs_inode_to_lower(inode)->i_nlink);
inode_set_ctime_to_ts(inode, inode_get_ctime(dir)); inode_set_ctime_to_ts(inode, inode_get_ctime(dir));
out_unlock: out_unlock:
dput(lower_dentry); end_removing(lower_dentry);
inode_unlock(lower_dir);
if (!rc) if (!rc)
d_drop(dentry); d_drop(dentry);
return rc; return rc;
@ -186,9 +190,11 @@ ecryptfs_do_create(struct inode *directory_inode,
struct inode *lower_dir; struct inode *lower_dir;
struct inode *inode; struct inode *inode;
rc = lock_parent(ecryptfs_dentry, &lower_dentry, &lower_dir); lower_dentry = ecryptfs_start_creating_dentry(ecryptfs_dentry);
if (!rc) if (IS_ERR(lower_dentry))
rc = vfs_create(&nop_mnt_idmap, lower_dentry, mode, NULL); return ERR_CAST(lower_dentry);
lower_dir = lower_dentry->d_parent->d_inode;
rc = vfs_create(&nop_mnt_idmap, lower_dentry, mode, NULL);
if (rc) { if (rc) {
printk(KERN_ERR "%s: Failure to create dentry in lower fs; " printk(KERN_ERR "%s: Failure to create dentry in lower fs; "
"rc = [%d]\n", __func__, rc); "rc = [%d]\n", __func__, rc);
@ -204,7 +210,7 @@ ecryptfs_do_create(struct inode *directory_inode,
fsstack_copy_attr_times(directory_inode, lower_dir); fsstack_copy_attr_times(directory_inode, lower_dir);
fsstack_copy_inode_size(directory_inode, lower_dir); fsstack_copy_inode_size(directory_inode, lower_dir);
out_lock: out_lock:
inode_unlock(lower_dir); end_creating(lower_dentry);
return inode; return inode;
} }
@ -432,10 +438,12 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
file_size_save = i_size_read(d_inode(old_dentry)); file_size_save = i_size_read(d_inode(old_dentry));
lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry); lower_old_dentry = ecryptfs_dentry_to_lower(old_dentry);
rc = lock_parent(new_dentry, &lower_new_dentry, &lower_dir); lower_new_dentry = ecryptfs_start_creating_dentry(new_dentry);
if (!rc) if (IS_ERR(lower_new_dentry))
rc = vfs_link(lower_old_dentry, &nop_mnt_idmap, lower_dir, return PTR_ERR(lower_new_dentry);
lower_new_dentry, NULL); lower_dir = lower_new_dentry->d_parent->d_inode;
rc = vfs_link(lower_old_dentry, &nop_mnt_idmap, lower_dir,
lower_new_dentry, NULL);
if (rc || d_really_is_negative(lower_new_dentry)) if (rc || d_really_is_negative(lower_new_dentry))
goto out_lock; goto out_lock;
rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb); rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb);
@ -447,7 +455,7 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir,
ecryptfs_inode_to_lower(d_inode(old_dentry))->i_nlink); ecryptfs_inode_to_lower(d_inode(old_dentry))->i_nlink);
i_size_write(d_inode(new_dentry), file_size_save); i_size_write(d_inode(new_dentry), file_size_save);
out_lock: out_lock:
inode_unlock(lower_dir); end_creating(lower_new_dentry);
return rc; return rc;
} }
@ -467,9 +475,11 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap,
size_t encoded_symlen; size_t encoded_symlen;
struct ecryptfs_mount_crypt_stat *mount_crypt_stat = NULL; struct ecryptfs_mount_crypt_stat *mount_crypt_stat = NULL;
rc = lock_parent(dentry, &lower_dentry, &lower_dir); lower_dentry = ecryptfs_start_creating_dentry(dentry);
if (rc) if (IS_ERR(lower_dentry))
goto out_lock; return PTR_ERR(lower_dentry);
lower_dir = lower_dentry->d_parent->d_inode;
mount_crypt_stat = &ecryptfs_superblock_to_private( mount_crypt_stat = &ecryptfs_superblock_to_private(
dir->i_sb)->mount_crypt_stat; dir->i_sb)->mount_crypt_stat;
rc = ecryptfs_encrypt_and_encode_filename(&encoded_symname, rc = ecryptfs_encrypt_and_encode_filename(&encoded_symname,
@ -489,7 +499,7 @@ static int ecryptfs_symlink(struct mnt_idmap *idmap,
fsstack_copy_attr_times(dir, lower_dir); fsstack_copy_attr_times(dir, lower_dir);
fsstack_copy_inode_size(dir, lower_dir); fsstack_copy_inode_size(dir, lower_dir);
out_lock: out_lock:
inode_unlock(lower_dir); end_creating(lower_dentry);
if (d_really_is_negative(dentry)) if (d_really_is_negative(dentry))
d_drop(dentry); d_drop(dentry);
return rc; return rc;
@ -500,12 +510,14 @@ static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
{ {
int rc; int rc;
struct dentry *lower_dentry; struct dentry *lower_dentry;
struct dentry *lower_dir_dentry;
struct inode *lower_dir; struct inode *lower_dir;
rc = lock_parent(dentry, &lower_dentry, &lower_dir); lower_dentry = ecryptfs_start_creating_dentry(dentry);
if (rc) if (IS_ERR(lower_dentry))
goto out; return lower_dentry;
lower_dir_dentry = dget(lower_dentry->d_parent);
lower_dir = lower_dir_dentry->d_inode;
lower_dentry = vfs_mkdir(&nop_mnt_idmap, lower_dir, lower_dentry = vfs_mkdir(&nop_mnt_idmap, lower_dir,
lower_dentry, mode, NULL); lower_dentry, mode, NULL);
rc = PTR_ERR(lower_dentry); rc = PTR_ERR(lower_dentry);
@ -521,7 +533,7 @@ static struct dentry *ecryptfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
fsstack_copy_inode_size(dir, lower_dir); fsstack_copy_inode_size(dir, lower_dir);
set_nlink(dir, lower_dir->i_nlink); set_nlink(dir, lower_dir->i_nlink);
out: out:
inode_unlock(lower_dir); end_creating(lower_dentry);
if (d_really_is_negative(dentry)) if (d_really_is_negative(dentry))
d_drop(dentry); d_drop(dentry);
return ERR_PTR(rc); return ERR_PTR(rc);
@ -533,21 +545,18 @@ static int ecryptfs_rmdir(struct inode *dir, struct dentry *dentry)
struct inode *lower_dir; struct inode *lower_dir;
int rc; int rc;
rc = lock_parent(dentry, &lower_dentry, &lower_dir); lower_dentry = ecryptfs_start_removing_dentry(dentry);
dget(lower_dentry); // don't even try to make the lower negative if (IS_ERR(lower_dentry))
if (!rc) { return PTR_ERR(lower_dentry);
if (d_unhashed(lower_dentry)) lower_dir = lower_dentry->d_parent->d_inode;
rc = -EINVAL;
else rc = vfs_rmdir(&nop_mnt_idmap, lower_dir, lower_dentry, NULL);
rc = vfs_rmdir(&nop_mnt_idmap, lower_dir, lower_dentry, NULL);
}
if (!rc) { if (!rc) {
clear_nlink(d_inode(dentry)); clear_nlink(d_inode(dentry));
fsstack_copy_attr_times(dir, lower_dir); fsstack_copy_attr_times(dir, lower_dir);
set_nlink(dir, lower_dir->i_nlink); set_nlink(dir, lower_dir->i_nlink);
} }
dput(lower_dentry); end_removing(lower_dentry);
inode_unlock(lower_dir);
if (!rc) if (!rc)
d_drop(dentry); d_drop(dentry);
return rc; return rc;
@ -561,10 +570,12 @@ ecryptfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
struct dentry *lower_dentry; struct dentry *lower_dentry;
struct inode *lower_dir; struct inode *lower_dir;
rc = lock_parent(dentry, &lower_dentry, &lower_dir); lower_dentry = ecryptfs_start_creating_dentry(dentry);
if (!rc) if (IS_ERR(lower_dentry))
rc = vfs_mknod(&nop_mnt_idmap, lower_dir, return PTR_ERR(lower_dentry);
lower_dentry, mode, dev, NULL); lower_dir = lower_dentry->d_parent->d_inode;
rc = vfs_mknod(&nop_mnt_idmap, lower_dir, lower_dentry, mode, dev, NULL);
if (rc || d_really_is_negative(lower_dentry)) if (rc || d_really_is_negative(lower_dentry))
goto out; goto out;
rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb); rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb);
@ -573,7 +584,7 @@ ecryptfs_mknod(struct mnt_idmap *idmap, struct inode *dir,
fsstack_copy_attr_times(dir, lower_dir); fsstack_copy_attr_times(dir, lower_dir);
fsstack_copy_inode_size(dir, lower_dir); fsstack_copy_inode_size(dir, lower_dir);
out: out:
inode_unlock(lower_dir); end_removing(lower_dentry);
if (d_really_is_negative(dentry)) if (d_really_is_negative(dentry))
d_drop(dentry); d_drop(dentry);
return rc; return rc;
@ -589,7 +600,6 @@ ecryptfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
struct dentry *lower_new_dentry; struct dentry *lower_new_dentry;
struct dentry *lower_old_dir_dentry; struct dentry *lower_old_dir_dentry;
struct dentry *lower_new_dir_dentry; struct dentry *lower_new_dir_dentry;
struct dentry *trap;
struct inode *target_inode; struct inode *target_inode;
struct renamedata rd = {}; struct renamedata rd = {};
@ -604,31 +614,13 @@ ecryptfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
target_inode = d_inode(new_dentry); target_inode = d_inode(new_dentry);
trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry); rd.mnt_idmap = &nop_mnt_idmap;
if (IS_ERR(trap)) rd.old_parent = lower_old_dir_dentry;
return PTR_ERR(trap); rd.new_parent = lower_new_dir_dentry;
dget(lower_new_dentry); rc = start_renaming_two_dentries(&rd, lower_old_dentry, lower_new_dentry);
rc = -EINVAL; if (rc)
if (lower_old_dentry->d_parent != lower_old_dir_dentry) return rc;
goto out_lock;
if (lower_new_dentry->d_parent != lower_new_dir_dentry)
goto out_lock;
if (d_unhashed(lower_old_dentry) || d_unhashed(lower_new_dentry))
goto out_lock;
/* source should not be ancestor of target */
if (trap == lower_old_dentry)
goto out_lock;
/* target should not be ancestor of source */
if (trap == lower_new_dentry) {
rc = -ENOTEMPTY;
goto out_lock;
}
rd.mnt_idmap = &nop_mnt_idmap;
rd.old_parent = lower_old_dir_dentry;
rd.old_dentry = lower_old_dentry;
rd.new_parent = lower_new_dir_dentry;
rd.new_dentry = lower_new_dentry;
rc = vfs_rename(&rd); rc = vfs_rename(&rd);
if (rc) if (rc)
goto out_lock; goto out_lock;
@ -639,8 +631,7 @@ ecryptfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
if (new_dir != old_dir) if (new_dir != old_dir)
fsstack_copy_attr_all(old_dir, d_inode(lower_old_dir_dentry)); fsstack_copy_attr_all(old_dir, d_inode(lower_old_dir_dentry));
out_lock: out_lock:
dput(lower_new_dentry); end_renaming(&rd);
unlock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
return rc; return rc;
} }

View File

@ -1397,27 +1397,25 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
if (!parent) if (!parent)
return -ENOENT; return -ENOENT;
inode_lock_nested(parent, I_MUTEX_PARENT);
if (!S_ISDIR(parent->i_mode)) if (!S_ISDIR(parent->i_mode))
goto unlock; goto put_parent;
err = -ENOENT; err = -ENOENT;
dir = d_find_alias(parent); dir = d_find_alias(parent);
if (!dir) if (!dir)
goto unlock; goto put_parent;
name->hash = full_name_hash(dir, name->name, name->len); entry = start_removing_noperm(dir, name);
entry = d_lookup(dir, name);
dput(dir); dput(dir);
if (!entry) if (IS_ERR(entry))
goto unlock; goto put_parent;
fuse_dir_changed(parent); fuse_dir_changed(parent);
if (!(flags & FUSE_EXPIRE_ONLY)) if (!(flags & FUSE_EXPIRE_ONLY))
d_invalidate(entry); d_invalidate(entry);
fuse_invalidate_entry_cache(entry); fuse_invalidate_entry_cache(entry);
if (child_nodeid != 0 && d_really_is_positive(entry)) { if (child_nodeid != 0) {
inode_lock(d_inode(entry)); inode_lock(d_inode(entry));
if (get_node_id(d_inode(entry)) != child_nodeid) { if (get_node_id(d_inode(entry)) != child_nodeid) {
err = -ENOENT; err = -ENOENT;
@ -1445,10 +1443,9 @@ int fuse_reverse_inval_entry(struct fuse_conn *fc, u64 parent_nodeid,
} else { } else {
err = 0; err = 0;
} }
dput(entry);
unlock: end_removing(entry);
inode_unlock(parent); put_parent:
iput(parent); iput(parent);
return err; return err;
} }

View File

@ -67,6 +67,9 @@ int vfs_tmpfile(struct mnt_idmap *idmap,
const struct path *parentpath, const struct path *parentpath,
struct file *file, umode_t mode); struct file *file, umode_t mode);
struct dentry *d_hash_and_lookup(struct dentry *, struct qstr *); struct dentry *d_hash_and_lookup(struct dentry *, struct qstr *);
struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
unsigned int lookup_flags);
int lookup_noperm_common(struct qstr *qname, struct dentry *base);
/* /*
* namespace.c * namespace.c

View File

@ -2290,27 +2290,25 @@ void stashed_dentry_prune(struct dentry *dentry)
cmpxchg(stashed, dentry, NULL); cmpxchg(stashed, dentry, NULL);
} }
/* parent must be held exclusive */ /**
* simple_start_creating - prepare to create a given name
* @parent: directory in which to prepare to create the name
* @name: the name to be created
*
* Required lock is taken and a lookup in performed prior to creating an
* object in a directory. No permission checking is performed.
*
* Returns: a negative dentry on which vfs_create() or similar may
* be attempted, or an error.
*/
struct dentry *simple_start_creating(struct dentry *parent, const char *name) struct dentry *simple_start_creating(struct dentry *parent, const char *name)
{ {
struct dentry *dentry; struct qstr qname = QSTR(name);
struct inode *dir = d_inode(parent); int err;
inode_lock(dir); err = lookup_noperm_common(&qname, parent);
if (unlikely(IS_DEADDIR(dir))) { if (err)
inode_unlock(dir); return ERR_PTR(err);
return ERR_PTR(-ENOENT); return start_dirop(parent, &qname, LOOKUP_CREATE | LOOKUP_EXCL);
}
dentry = lookup_noperm(&QSTR(name), parent);
if (IS_ERR(dentry)) {
inode_unlock(dir);
return dentry;
}
if (dentry->d_inode) {
dput(dentry);
inode_unlock(dir);
return ERR_PTR(-EEXIST);
}
return dentry;
} }
EXPORT_SYMBOL(simple_start_creating); EXPORT_SYMBOL(simple_start_creating);

View File

@ -2835,6 +2835,62 @@ static int filename_parentat(int dfd, struct filename *name,
return __filename_parentat(dfd, name, flags, parent, last, type, NULL); return __filename_parentat(dfd, name, flags, parent, last, type, NULL);
} }
/**
* start_dirop - begin a create or remove dirop, performing locking and lookup
* @parent: the dentry of the parent in which the operation will occur
* @name: a qstr holding the name within that parent
* @lookup_flags: intent and other lookup flags.
*
* The lookup is performed and necessary locks are taken so that, on success,
* the returned dentry can be operated on safely.
* The qstr must already have the hash value calculated.
*
* Returns: a locked dentry, or an error.
*
*/
static struct dentry *__start_dirop(struct dentry *parent, struct qstr *name,
unsigned int lookup_flags,
unsigned int state)
{
struct dentry *dentry;
struct inode *dir = d_inode(parent);
if (state == TASK_KILLABLE) {
int ret = down_write_killable_nested(&dir->i_rwsem,
I_MUTEX_PARENT);
if (ret)
return ERR_PTR(ret);
} else {
inode_lock_nested(dir, I_MUTEX_PARENT);
}
dentry = lookup_one_qstr_excl(name, parent, lookup_flags);
if (IS_ERR(dentry))
inode_unlock(dir);
return dentry;
}
struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
unsigned int lookup_flags)
{
return __start_dirop(parent, name, lookup_flags, TASK_NORMAL);
}
/**
* end_dirop - signal completion of a dirop
* @de: the dentry which was returned by start_dirop or similar.
*
* If the de is an error, nothing happens. Otherwise any lock taken to
* protect the dentry is dropped and the dentry itself is release (dput()).
*/
void end_dirop(struct dentry *de)
{
if (!IS_ERR(de)) {
inode_unlock(de->d_parent->d_inode);
dput(de);
}
}
EXPORT_SYMBOL(end_dirop);
/* does lookup, returns the object with parent locked */ /* does lookup, returns the object with parent locked */
static struct dentry *__start_removing_path(int dfd, struct filename *name, static struct dentry *__start_removing_path(int dfd, struct filename *name,
struct path *path) struct path *path)
@ -2851,10 +2907,9 @@ static struct dentry *__start_removing_path(int dfd, struct filename *name,
return ERR_PTR(-EINVAL); return ERR_PTR(-EINVAL);
/* don't fail immediately if it's r/o, at least try to report other errors */ /* don't fail immediately if it's r/o, at least try to report other errors */
error = mnt_want_write(parent_path.mnt); error = mnt_want_write(parent_path.mnt);
inode_lock_nested(parent_path.dentry->d_inode, I_MUTEX_PARENT); d = start_dirop(parent_path.dentry, &last, 0);
d = lookup_one_qstr_excl(&last, parent_path.dentry, 0);
if (IS_ERR(d)) if (IS_ERR(d))
goto unlock; goto drop;
if (error) if (error)
goto fail; goto fail;
path->dentry = no_free_ptr(parent_path.dentry); path->dentry = no_free_ptr(parent_path.dentry);
@ -2862,10 +2917,9 @@ static struct dentry *__start_removing_path(int dfd, struct filename *name,
return d; return d;
fail: fail:
dput(d); end_dirop(d);
d = ERR_PTR(error); d = ERR_PTR(error);
unlock: drop:
inode_unlock(parent_path.dentry->d_inode);
if (!error) if (!error)
mnt_drop_write(parent_path.mnt); mnt_drop_write(parent_path.mnt);
return d; return d;
@ -2980,7 +3034,7 @@ int vfs_path_lookup(struct dentry *dentry, struct vfsmount *mnt,
} }
EXPORT_SYMBOL(vfs_path_lookup); EXPORT_SYMBOL(vfs_path_lookup);
static int lookup_noperm_common(struct qstr *qname, struct dentry *base) int lookup_noperm_common(struct qstr *qname, struct dentry *base)
{ {
const char *name = qname->name; const char *name = qname->name;
u32 len = qname->len; u32 len = qname->len;
@ -3251,6 +3305,234 @@ 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);
/**
* 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);
/**
* start_creating_killable - 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 in 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.
*
* If a signal is received or was already pending, the function aborts
* with -EINTR;
*
* Returns: a negative or positive dentry, or an error.
*/
struct dentry *start_creating_killable(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, TASK_KILLABLE);
}
EXPORT_SYMBOL(start_creating_killable);
/**
* start_removing_killable - 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.
*
* If a signal is received or was already pending, the function aborts
* with -EINTR;
*
* Returns: a positive dentry, or an error.
*/
struct dentry *start_removing_killable(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, TASK_KILLABLE);
}
EXPORT_SYMBOL(start_removing_killable);
/**
* start_creating_noperm - prepare to create a given name without permission checking
* @parent: directory in which to prepare to create the name
* @name: the name to be created
*
* Locks are taken and a lookup in performed prior to creating
* an object in a directory.
*
* If the name already exists, a positive dentry is returned.
*
* Returns: a negative or positive dentry, or an error.
*/
struct dentry *start_creating_noperm(struct dentry *parent,
struct qstr *name)
{
int err = lookup_noperm_common(name, parent);
if (err)
return ERR_PTR(err);
return start_dirop(parent, name, LOOKUP_CREATE);
}
EXPORT_SYMBOL(start_creating_noperm);
/**
* start_removing_noperm - prepare to remove a given name without permission checking
* @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.
*
* 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_noperm(struct dentry *parent,
struct qstr *name)
{
int err = lookup_noperm_common(name, parent);
if (err)
return ERR_PTR(err);
return start_dirop(parent, name, 0);
}
EXPORT_SYMBOL(start_removing_noperm);
/**
* start_creating_dentry - prepare to create a given dentry
* @parent: directory from which dentry should be removed
* @child: the dentry to be removed
*
* A lock is taken to protect the dentry again other dirops and
* the validity of the dentry is checked: correct parent and still hashed.
*
* If the dentry is valid and negative a reference is taken and
* returned. If not an error is returned.
*
* end_creating() should be called when creation is complete, or aborted.
*
* Returns: the valid dentry, or an error.
*/
struct dentry *start_creating_dentry(struct dentry *parent,
struct dentry *child)
{
inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
if (unlikely(IS_DEADDIR(parent->d_inode) ||
child->d_parent != parent ||
d_unhashed(child))) {
inode_unlock(parent->d_inode);
return ERR_PTR(-EINVAL);
}
if (d_is_positive(child)) {
inode_unlock(parent->d_inode);
return ERR_PTR(-EEXIST);
}
return dget(child);
}
EXPORT_SYMBOL(start_creating_dentry);
/**
* start_removing_dentry - prepare to remove a given dentry
* @parent: directory from which dentry should be removed
* @child: the dentry to be removed
*
* A lock is taken to protect the dentry again other dirops and
* the validity of the dentry is checked: correct parent and still hashed.
*
* If the dentry is valid and positive, a reference is taken and
* returned. If not an error is returned.
*
* end_removing() should be called when removal is complete, or aborted.
*
* Returns: the valid dentry, or an error.
*/
struct dentry *start_removing_dentry(struct dentry *parent,
struct dentry *child)
{
inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
if (unlikely(IS_DEADDIR(parent->d_inode) ||
child->d_parent != parent ||
d_unhashed(child))) {
inode_unlock(parent->d_inode);
return ERR_PTR(-EINVAL);
}
if (d_is_negative(child)) {
inode_unlock(parent->d_inode);
return ERR_PTR(-ENOENT);
}
return dget(child);
}
EXPORT_SYMBOL(start_removing_dentry);
#ifdef CONFIG_UNIX98_PTYS #ifdef CONFIG_UNIX98_PTYS
int path_pts(struct path *path) int path_pts(struct path *path)
{ {
@ -3488,6 +3770,290 @@ void unlock_rename(struct dentry *p1, struct dentry *p2)
} }
EXPORT_SYMBOL(unlock_rename); EXPORT_SYMBOL(unlock_rename);
/**
* __start_renaming - lookup and lock names for rename
* @rd: rename data containing parents and flags, and
* for receiving found dentries
* @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
* LOOKUP_NO_SYMLINKS etc).
* @old_last: name of object in @rd.old_parent
* @new_last: name of object in @rd.new_parent
*
* Look up two names and ensure locks are in place for
* rename.
*
* On success the found dentries are stored in @rd.old_dentry,
* @rd.new_dentry and an extra ref is taken on @rd.old_parent.
* These references and the lock are dropped by end_renaming().
*
* The passed in qstrs must have the hash calculated, and no permission
* checking is performed.
*
* Returns: zero or an error.
*/
static int
__start_renaming(struct renamedata *rd, int lookup_flags,
struct qstr *old_last, struct qstr *new_last)
{
struct dentry *trap;
struct dentry *d1, *d2;
int target_flags = LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
int err;
if (rd->flags & RENAME_EXCHANGE)
target_flags = 0;
if (rd->flags & RENAME_NOREPLACE)
target_flags |= LOOKUP_EXCL;
trap = lock_rename(rd->old_parent, rd->new_parent);
if (IS_ERR(trap))
return PTR_ERR(trap);
d1 = lookup_one_qstr_excl(old_last, rd->old_parent,
lookup_flags);
err = PTR_ERR(d1);
if (IS_ERR(d1))
goto out_unlock;
d2 = lookup_one_qstr_excl(new_last, rd->new_parent,
lookup_flags | target_flags);
err = PTR_ERR(d2);
if (IS_ERR(d2))
goto out_dput_d1;
if (d1 == trap) {
/* source is an ancestor of target */
err = -EINVAL;
goto out_dput_d2;
}
if (d2 == trap) {
/* target is an ancestor of source */
if (rd->flags & RENAME_EXCHANGE)
err = -EINVAL;
else
err = -ENOTEMPTY;
goto out_dput_d2;
}
rd->old_dentry = d1;
rd->new_dentry = d2;
dget(rd->old_parent);
return 0;
out_dput_d2:
dput(d2);
out_dput_d1:
dput(d1);
out_unlock:
unlock_rename(rd->old_parent, rd->new_parent);
return err;
}
/**
* start_renaming - lookup and lock names for rename with permission checking
* @rd: rename data containing parents and flags, and
* for receiving found dentries
* @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
* LOOKUP_NO_SYMLINKS etc).
* @old_last: name of object in @rd.old_parent
* @new_last: name of object in @rd.new_parent
*
* Look up two names and ensure locks are in place for
* rename.
*
* On success the found dentries are stored in @rd.old_dentry,
* @rd.new_dentry. Also the refcount on @rd->old_parent is increased.
* These references and the lock are dropped by end_renaming().
*
* The passed in qstrs need not have the hash calculated, and basic
* eXecute permission checking is performed against @rd.mnt_idmap.
*
* Returns: zero or an error.
*/
int start_renaming(struct renamedata *rd, int lookup_flags,
struct qstr *old_last, struct qstr *new_last)
{
int err;
err = lookup_one_common(rd->mnt_idmap, old_last, rd->old_parent);
if (err)
return err;
err = lookup_one_common(rd->mnt_idmap, new_last, rd->new_parent);
if (err)
return err;
return __start_renaming(rd, lookup_flags, old_last, new_last);
}
EXPORT_SYMBOL(start_renaming);
static int
__start_renaming_dentry(struct renamedata *rd, int lookup_flags,
struct dentry *old_dentry, struct qstr *new_last)
{
struct dentry *trap;
struct dentry *d2;
int target_flags = LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
int err;
if (rd->flags & RENAME_EXCHANGE)
target_flags = 0;
if (rd->flags & RENAME_NOREPLACE)
target_flags |= LOOKUP_EXCL;
/* Already have the dentry - need to be sure to lock the correct parent */
trap = lock_rename_child(old_dentry, rd->new_parent);
if (IS_ERR(trap))
return PTR_ERR(trap);
if (d_unhashed(old_dentry) ||
(rd->old_parent && rd->old_parent != old_dentry->d_parent)) {
/* dentry was removed, or moved and explicit parent requested */
err = -EINVAL;
goto out_unlock;
}
d2 = lookup_one_qstr_excl(new_last, rd->new_parent,
lookup_flags | target_flags);
err = PTR_ERR(d2);
if (IS_ERR(d2))
goto out_unlock;
if (old_dentry == trap) {
/* source is an ancestor of target */
err = -EINVAL;
goto out_dput_d2;
}
if (d2 == trap) {
/* target is an ancestor of source */
if (rd->flags & RENAME_EXCHANGE)
err = -EINVAL;
else
err = -ENOTEMPTY;
goto out_dput_d2;
}
rd->old_dentry = dget(old_dentry);
rd->new_dentry = d2;
rd->old_parent = dget(old_dentry->d_parent);
return 0;
out_dput_d2:
dput(d2);
out_unlock:
unlock_rename(old_dentry->d_parent, rd->new_parent);
return err;
}
/**
* start_renaming_dentry - lookup and lock name for rename with permission checking
* @rd: rename data containing parents and flags, and
* for receiving found dentries
* @lookup_flags: extra flags to pass to ->lookup (e.g. LOOKUP_REVAL,
* LOOKUP_NO_SYMLINKS etc).
* @old_dentry: dentry of name to move
* @new_last: name of target in @rd.new_parent
*
* Look up target name and ensure locks are in place for
* rename.
*
* On success the found dentry is stored in @rd.new_dentry and
* @rd.old_parent is confirmed to be the parent of @old_dentry. If it
* was originally %NULL, it is set. In either case a reference is taken
* so that end_renaming() can have a stable reference to unlock.
*
* References and the lock can be dropped with end_renaming()
*
* The passed in qstr need not have the hash calculated, and basic
* eXecute permission checking is performed against @rd.mnt_idmap.
*
* Returns: zero or an error.
*/
int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
struct dentry *old_dentry, struct qstr *new_last)
{
int err;
err = lookup_one_common(rd->mnt_idmap, new_last, rd->new_parent);
if (err)
return err;
return __start_renaming_dentry(rd, lookup_flags, old_dentry, new_last);
}
EXPORT_SYMBOL(start_renaming_dentry);
/**
* start_renaming_two_dentries - Lock to dentries in given parents for rename
* @rd: rename data containing parent
* @old_dentry: dentry of name to move
* @new_dentry: dentry to move to
*
* Ensure locks are in place for rename and check parentage is still correct.
*
* On success the two dentries are stored in @rd.old_dentry and
* @rd.new_dentry and @rd.old_parent and @rd.new_parent are confirmed to
* be the parents of the dentries.
*
* References and the lock can be dropped with end_renaming()
*
* Returns: zero or an error.
*/
int
start_renaming_two_dentries(struct renamedata *rd,
struct dentry *old_dentry, struct dentry *new_dentry)
{
struct dentry *trap;
int err;
/* Already have the dentry - need to be sure to lock the correct parent */
trap = lock_rename_child(old_dentry, rd->new_parent);
if (IS_ERR(trap))
return PTR_ERR(trap);
err = -EINVAL;
if (d_unhashed(old_dentry) ||
(rd->old_parent && rd->old_parent != old_dentry->d_parent))
/* old_dentry was removed, or moved and explicit parent requested */
goto out_unlock;
if (d_unhashed(new_dentry) ||
rd->new_parent != new_dentry->d_parent)
/* new_dentry was removed or moved */
goto out_unlock;
if (old_dentry == trap)
/* source is an ancestor of target */
goto out_unlock;
if (new_dentry == trap) {
/* target is an ancestor of source */
if (rd->flags & RENAME_EXCHANGE)
err = -EINVAL;
else
err = -ENOTEMPTY;
goto out_unlock;
}
err = -EEXIST;
if (d_is_positive(new_dentry) && (rd->flags & RENAME_NOREPLACE))
goto out_unlock;
rd->old_dentry = dget(old_dentry);
rd->new_dentry = dget(new_dentry);
rd->old_parent = dget(old_dentry->d_parent);
return 0;
out_unlock:
unlock_rename(old_dentry->d_parent, rd->new_parent);
return err;
}
EXPORT_SYMBOL(start_renaming_two_dentries);
void end_renaming(struct renamedata *rd)
{
unlock_rename(rd->old_parent, rd->new_parent);
dput(rd->old_dentry);
dput(rd->new_dentry);
dput(rd->old_parent);
}
EXPORT_SYMBOL(end_renaming);
/** /**
* vfs_prepare_mode - prepare the mode to be used for a new inode * vfs_prepare_mode - prepare the mode to be used for a new inode
* @idmap: idmap of the mount the inode was found from * @idmap: idmap of the mount the inode was found from
@ -4310,21 +4876,18 @@ static struct dentry *filename_create(int dfd, struct filename *name,
*/ */
if (last.name[last.len] && !want_dir) if (last.name[last.len] && !want_dir)
create_flags &= ~LOOKUP_CREATE; create_flags &= ~LOOKUP_CREATE;
inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT); dentry = start_dirop(path->dentry, &last, reval_flag | create_flags);
dentry = lookup_one_qstr_excl(&last, path->dentry,
reval_flag | create_flags);
if (IS_ERR(dentry)) if (IS_ERR(dentry))
goto unlock; goto out_drop_write;
if (unlikely(error)) if (unlikely(error))
goto fail; goto fail;
return dentry; return dentry;
fail: fail:
dput(dentry); end_dirop(dentry);
dentry = ERR_PTR(error); dentry = ERR_PTR(error);
unlock: out_drop_write:
inode_unlock(path->dentry->d_inode);
if (!error) if (!error)
mnt_drop_write(path->mnt); mnt_drop_write(path->mnt);
out: out:
@ -4343,11 +4906,20 @@ struct dentry *start_creating_path(int dfd, const char *pathname,
} }
EXPORT_SYMBOL(start_creating_path); EXPORT_SYMBOL(start_creating_path);
/**
* end_creating_path - finish a code section started by start_creating_path()
* @path: the path instantiated by start_creating_path()
* @dentry: the dentry returned by start_creating_path()
*
* end_creating_path() will unlock and locks taken by start_creating_path()
* and drop an references that were taken. It should only be called
* if start_creating_path() returned a non-error.
* If vfs_mkdir() was called and it returned an error, that error *should*
* be passed to end_creating_path() together with the 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);
dput(dentry);
inode_unlock(path->dentry->d_inode);
mnt_drop_write(path->mnt); mnt_drop_write(path->mnt);
path_put(path); path_put(path);
} }
@ -4567,7 +5139,7 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
return dentry; return dentry;
err: err:
dput(dentry); end_creating(dentry);
return ERR_PTR(error); return ERR_PTR(error);
} }
EXPORT_SYMBOL(vfs_mkdir); EXPORT_SYMBOL(vfs_mkdir);
@ -4709,8 +5281,7 @@ int do_rmdir(int dfd, struct filename *name)
if (error) if (error)
goto exit2; goto exit2;
inode_lock_nested(path.dentry->d_inode, I_MUTEX_PARENT); dentry = start_dirop(path.dentry, &last, lookup_flags);
dentry = lookup_one_qstr_excl(&last, path.dentry, lookup_flags);
error = PTR_ERR(dentry); error = PTR_ERR(dentry);
if (IS_ERR(dentry)) if (IS_ERR(dentry))
goto exit3; goto exit3;
@ -4720,9 +5291,8 @@ int do_rmdir(int dfd, struct filename *name)
error = vfs_rmdir(mnt_idmap(path.mnt), path.dentry->d_inode, error = vfs_rmdir(mnt_idmap(path.mnt), path.dentry->d_inode,
dentry, &delegated_inode); dentry, &delegated_inode);
exit4: exit4:
dput(dentry); end_dirop(dentry);
exit3: exit3:
inode_unlock(path.dentry->d_inode);
mnt_drop_write(path.mnt); mnt_drop_write(path.mnt);
exit2: exit2:
path_put(&path); path_put(&path);
@ -4831,67 +5401,62 @@ int do_unlinkat(int dfd, struct filename *name)
struct path path; struct path path;
struct qstr last; struct qstr last;
int type; int type;
struct inode *inode = NULL; struct inode *inode;
struct delegated_inode delegated_inode = { }; struct delegated_inode delegated_inode = { };
unsigned int lookup_flags = 0; unsigned int lookup_flags = 0;
retry: retry:
error = filename_parentat(dfd, name, lookup_flags, &path, &last, &type); error = filename_parentat(dfd, name, lookup_flags, &path, &last, &type);
if (error) if (error)
goto exit1; goto exit_putname;
error = -EISDIR; error = -EISDIR;
if (type != LAST_NORM) if (type != LAST_NORM)
goto exit2; goto exit_path_put;
error = mnt_want_write(path.mnt); error = mnt_want_write(path.mnt);
if (error) if (error)
goto exit2; goto exit_path_put;
retry_deleg: retry_deleg:
inode_lock_nested(path.dentry->d_inode, I_MUTEX_PARENT); dentry = start_dirop(path.dentry, &last, lookup_flags);
dentry = lookup_one_qstr_excl(&last, path.dentry, lookup_flags);
error = PTR_ERR(dentry); error = PTR_ERR(dentry);
if (!IS_ERR(dentry)) { if (IS_ERR(dentry))
goto exit_drop_write;
/* Why not before? Because we want correct error value */ /* Why not before? Because we want correct error value */
if (last.name[last.len]) if (unlikely(last.name[last.len])) {
goto slashes; if (d_is_dir(dentry))
inode = dentry->d_inode; error = -EISDIR;
ihold(inode); else
error = security_path_unlink(&path, dentry); error = -ENOTDIR;
if (error) end_dirop(dentry);
goto exit3; goto exit_drop_write;
error = vfs_unlink(mnt_idmap(path.mnt), path.dentry->d_inode,
dentry, &delegated_inode);
exit3:
dput(dentry);
} }
inode_unlock(path.dentry->d_inode); inode = dentry->d_inode;
if (inode) ihold(inode);
iput(inode); /* truncate the inode here */ error = security_path_unlink(&path, dentry);
inode = NULL; if (error)
goto exit_end_dirop;
error = vfs_unlink(mnt_idmap(path.mnt), path.dentry->d_inode,
dentry, &delegated_inode);
exit_end_dirop:
end_dirop(dentry);
iput(inode); /* truncate the inode here */
if (is_delegated(&delegated_inode)) { if (is_delegated(&delegated_inode)) {
error = break_deleg_wait(&delegated_inode); error = break_deleg_wait(&delegated_inode);
if (!error) if (!error)
goto retry_deleg; goto retry_deleg;
} }
exit_drop_write:
mnt_drop_write(path.mnt); mnt_drop_write(path.mnt);
exit2: exit_path_put:
path_put(&path); path_put(&path);
if (retry_estale(error, lookup_flags)) { if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL; lookup_flags |= LOOKUP_REVAL;
inode = NULL;
goto retry; goto retry;
} }
exit1: exit_putname:
putname(name); putname(name);
return error; return error;
slashes:
if (d_is_dir(dentry))
error = -EISDIR;
else
error = -ENOTDIR;
goto exit3;
} }
SYSCALL_DEFINE3(unlinkat, int, dfd, const char __user *, pathname, int, flag) SYSCALL_DEFINE3(unlinkat, int, dfd, const char __user *, pathname, int, flag)
@ -5404,14 +5969,11 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
struct filename *to, unsigned int flags) struct filename *to, unsigned int flags)
{ {
struct renamedata rd; struct renamedata rd;
struct dentry *old_dentry, *new_dentry;
struct dentry *trap;
struct path old_path, new_path; struct path old_path, new_path;
struct qstr old_last, new_last; struct qstr old_last, new_last;
int old_type, new_type; int old_type, new_type;
struct delegated_inode delegated_inode = { }; struct delegated_inode delegated_inode = { };
unsigned int lookup_flags = 0, target_flags = unsigned int lookup_flags = 0;
LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
bool should_retry = false; bool should_retry = false;
int error = -EINVAL; int error = -EINVAL;
@ -5422,11 +5984,6 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
(flags & RENAME_EXCHANGE)) (flags & RENAME_EXCHANGE))
goto put_names; goto put_names;
if (flags & RENAME_EXCHANGE)
target_flags = 0;
if (flags & RENAME_NOREPLACE)
target_flags |= LOOKUP_EXCL;
retry: retry:
error = filename_parentat(olddfd, from, lookup_flags, &old_path, error = filename_parentat(olddfd, from, lookup_flags, &old_path,
&old_last, &old_type); &old_last, &old_type);
@ -5456,66 +6013,40 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
goto exit2; goto exit2;
retry_deleg: retry_deleg:
trap = lock_rename(new_path.dentry, old_path.dentry); rd.old_parent = old_path.dentry;
if (IS_ERR(trap)) { rd.mnt_idmap = mnt_idmap(old_path.mnt);
error = PTR_ERR(trap); rd.new_parent = new_path.dentry;
goto exit_lock_rename; rd.delegated_inode = &delegated_inode;
} rd.flags = flags;
error = __start_renaming(&rd, lookup_flags, &old_last, &new_last);
if (error)
goto exit_lock_rename;
old_dentry = lookup_one_qstr_excl(&old_last, old_path.dentry,
lookup_flags);
error = PTR_ERR(old_dentry);
if (IS_ERR(old_dentry))
goto exit3;
new_dentry = lookup_one_qstr_excl(&new_last, new_path.dentry,
lookup_flags | target_flags);
error = PTR_ERR(new_dentry);
if (IS_ERR(new_dentry))
goto exit4;
if (flags & RENAME_EXCHANGE) { if (flags & RENAME_EXCHANGE) {
if (!d_is_dir(new_dentry)) { if (!d_is_dir(rd.new_dentry)) {
error = -ENOTDIR; error = -ENOTDIR;
if (new_last.name[new_last.len]) if (new_last.name[new_last.len])
goto exit5; goto exit_unlock;
} }
} }
/* unless the source is a directory trailing slashes give -ENOTDIR */ /* unless the source is a directory trailing slashes give -ENOTDIR */
if (!d_is_dir(old_dentry)) { if (!d_is_dir(rd.old_dentry)) {
error = -ENOTDIR; error = -ENOTDIR;
if (old_last.name[old_last.len]) if (old_last.name[old_last.len])
goto exit5; goto exit_unlock;
if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len]) if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len])
goto exit5; goto exit_unlock;
} }
/* source should not be ancestor of target */
error = -EINVAL;
if (old_dentry == trap)
goto exit5;
/* target should not be an ancestor of source */
if (!(flags & RENAME_EXCHANGE))
error = -ENOTEMPTY;
if (new_dentry == trap)
goto exit5;
error = security_path_rename(&old_path, old_dentry, error = security_path_rename(&old_path, rd.old_dentry,
&new_path, new_dentry, flags); &new_path, rd.new_dentry, flags);
if (error) if (error)
goto exit5; goto exit_unlock;
rd.old_parent = old_path.dentry;
rd.old_dentry = old_dentry;
rd.mnt_idmap = mnt_idmap(old_path.mnt);
rd.new_parent = new_path.dentry;
rd.new_dentry = new_dentry;
rd.delegated_inode = &delegated_inode;
rd.flags = flags;
error = vfs_rename(&rd); error = vfs_rename(&rd);
exit5: exit_unlock:
dput(new_dentry); end_renaming(&rd);
exit4:
dput(old_dentry);
exit3:
unlock_rename(new_path.dentry, old_path.dentry);
exit_lock_rename: exit_lock_rename:
if (is_delegated(&delegated_inode)) { if (is_delegated(&delegated_inode)) {
error = break_deleg_wait(&delegated_inode); error = break_deleg_wait(&delegated_inode);

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);
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);
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, 0700, NULL); dentry = vfs_mkdir(&nop_mnt_idmap, d_inode(dir), dentry, 0700, NULL);
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);
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,
@ -328,20 +324,12 @@ nfsd4_unlink_clid_dir(char *name, struct nfsd_net *nn)
dprintk("NFSD: nfsd4_unlink_clid_dir. name %s\n", name); dprintk("NFSD: nfsd4_unlink_clid_dir. name %s\n", name);
dir = nn->rec_file->f_path.dentry; dir = nn->rec_file->f_path.dentry;
inode_lock_nested(d_inode(dir), I_MUTEX_PARENT); dentry = start_removing(&nop_mnt_idmap, dir, &QSTR(name));
dentry = lookup_one(&nop_mnt_idmap, &QSTR(name), dir); if (IS_ERR(dentry))
if (IS_ERR(dentry)) { return PTR_ERR(dentry);
status = PTR_ERR(dentry);
goto out_unlock;
}
status = -ENOENT;
if (d_really_is_negative(dentry))
goto out;
status = vfs_rmdir(&nop_mnt_idmap, d_inode(dir), dentry, NULL); status = vfs_rmdir(&nop_mnt_idmap, d_inode(dir), dentry, NULL);
out: end_removing(dentry);
dput(dentry);
out_unlock:
inode_unlock(d_inode(dir));
return status; return status;
} }

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;
@ -409,6 +407,9 @@ nfsd_proc_create(struct svc_rqst *rqstp)
/* File doesn't exist. Create it and set attrs */ /* File doesn't exist. Create it and set attrs */
resp->status = nfsd_create_locked(rqstp, dirfhp, &attrs, type, resp->status = nfsd_create_locked(rqstp, dirfhp, &attrs, type,
rdev, newfhp); rdev, newfhp);
/* nfsd_create_locked() unlocked the parent */
dput(dchild);
goto out_write;
} else if (type == S_IFREG) { } else if (type == S_IFREG) {
dprintk("nfsd: existing %s, valid=%x, size=%ld\n", dprintk("nfsd: existing %s, valid=%x, size=%ld\n",
argp->name, attr->ia_valid, (long) attr->ia_size); argp->name, attr->ia_valid, (long) attr->ia_size);
@ -423,7 +424,8 @@ nfsd_proc_create(struct svc_rqst *rqstp)
} }
out_unlock: out_unlock:
inode_unlock(dirfhp->fh_dentry->d_inode); end_creating(dchild);
out_write:
fh_drop_write(dirfhp); fh_drop_write(dirfhp);
done: done:
fh_put(dirfhp); fh_put(dirfhp);

View File

@ -1522,7 +1522,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);
return err; return err;
out_nfserr: out_nfserr:
@ -1626,28 +1627,24 @@ 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
* 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); /* nfsd_create_locked() unlocked the parent */
dput(dchild);
return err;
out_unlock: out_unlock:
inode_unlock(dentry->d_inode); end_creating(dchild);
return err; return err;
} }
@ -1733,11 +1730,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 +1745,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);
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 +1804,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);
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 +1843,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
@ -1895,11 +1883,12 @@ __be32
nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen, nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
struct svc_fh *tfhp, char *tname, int tlen) struct svc_fh *tfhp, char *tname, int tlen)
{ {
struct dentry *fdentry, *tdentry, *odentry, *ndentry, *trap; struct dentry *fdentry, *tdentry;
int type = S_IFDIR; int type = S_IFDIR;
struct renamedata rd = {};
__be32 err; __be32 err;
int host_err; int host_err;
bool close_cached = false; struct dentry *close_cached;
trace_nfsd_vfs_rename(rqstp, ffhp, tfhp, fname, flen, tname, tlen); trace_nfsd_vfs_rename(rqstp, ffhp, tfhp, fname, flen, tname, tlen);
@ -1925,15 +1914,22 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
goto out; goto out;
retry: retry:
close_cached = NULL;
host_err = fh_want_write(ffhp); host_err = fh_want_write(ffhp);
if (host_err) { if (host_err) {
err = nfserrno(host_err); err = nfserrno(host_err);
goto out; goto out;
} }
trap = lock_rename(tdentry, fdentry); rd.mnt_idmap = &nop_mnt_idmap;
if (IS_ERR(trap)) { rd.old_parent = fdentry;
err = nfserr_xdev; rd.new_parent = tdentry;
host_err = start_renaming(&rd, 0, &QSTR_LEN(fname, flen),
&QSTR_LEN(tname, tlen));
if (host_err) {
err = nfserrno(host_err);
goto out_want_write; goto out_want_write;
} }
err = fh_fill_pre_attrs(ffhp); err = fh_fill_pre_attrs(ffhp);
@ -1943,48 +1939,23 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
if (err != nfs_ok) if (err != nfs_ok)
goto out_unlock; goto out_unlock;
odentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(fname, flen), fdentry); type = d_inode(rd.old_dentry)->i_mode & S_IFMT;
host_err = PTR_ERR(odentry);
if (IS_ERR(odentry))
goto out_nfserr;
host_err = -ENOENT; if (d_inode(rd.new_dentry))
if (d_really_is_negative(odentry)) type = d_inode(rd.new_dentry)->i_mode & S_IFMT;
goto out_dput_old;
host_err = -EINVAL;
if (odentry == trap)
goto out_dput_old;
type = d_inode(odentry)->i_mode & S_IFMT;
ndentry = lookup_one(&nop_mnt_idmap, &QSTR_LEN(tname, tlen), tdentry); if ((rd.new_dentry->d_sb->s_export_op->flags & EXPORT_OP_CLOSE_BEFORE_UNLINK) &&
host_err = PTR_ERR(ndentry); nfsd_has_cached_files(rd.new_dentry)) {
if (IS_ERR(ndentry)) close_cached = dget(rd.new_dentry);
goto out_dput_old; goto out_unlock;
if (d_inode(ndentry))
type = d_inode(ndentry)->i_mode & S_IFMT;
host_err = -ENOTEMPTY;
if (ndentry == trap)
goto out_dput_new;
if ((ndentry->d_sb->s_export_op->flags & EXPORT_OP_CLOSE_BEFORE_UNLINK) &&
nfsd_has_cached_files(ndentry)) {
close_cached = true;
goto out_dput_old;
} else { } else {
struct renamedata rd = {
.mnt_idmap = &nop_mnt_idmap,
.old_parent = fdentry,
.old_dentry = odentry,
.new_parent = tdentry,
.new_dentry = ndentry,
};
int retries; int retries;
for (retries = 1;;) { for (retries = 1;;) {
host_err = vfs_rename(&rd); host_err = vfs_rename(&rd);
if (host_err != -EAGAIN || !retries--) if (host_err != -EAGAIN || !retries--)
break; break;
if (!nfsd_wait_for_delegreturn(rqstp, d_inode(odentry))) if (!nfsd_wait_for_delegreturn(rqstp, d_inode(rd.old_dentry)))
break; break;
} }
if (!host_err) { if (!host_err) {
@ -1993,11 +1964,6 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
host_err = commit_metadata(ffhp); host_err = commit_metadata(ffhp);
} }
} }
out_dput_new:
dput(ndentry);
out_dput_old:
dput(odentry);
out_nfserr:
if (host_err == -EBUSY) { if (host_err == -EBUSY) {
/* /*
* See RFC 8881 Section 18.26.4 para 1-3: NFSv4 RENAME * See RFC 8881 Section 18.26.4 para 1-3: NFSv4 RENAME
@ -2016,7 +1982,7 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
fh_fill_post_attrs(tfhp); fh_fill_post_attrs(tfhp);
} }
out_unlock: out_unlock:
unlock_rename(tdentry, fdentry); end_renaming(&rd);
out_want_write: out_want_write:
fh_drop_write(ffhp); fh_drop_write(ffhp);
@ -2027,9 +1993,8 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
* until this point and then reattempt the whole shebang. * until this point and then reattempt the whole shebang.
*/ */
if (close_cached) { if (close_cached) {
close_cached = false; nfsd_close_cached_files(close_cached);
nfsd_close_cached_files(ndentry); dput(close_cached);
dput(ndentry);
goto retry; goto retry;
} }
out: out:
@ -2054,7 +2019,7 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
{ {
struct dentry *dentry, *rdentry; struct dentry *dentry, *rdentry;
struct inode *dirp; struct inode *dirp;
struct inode *rinode; struct inode *rinode = NULL;
__be32 err; __be32 err;
int host_err; int host_err;
@ -2073,24 +2038,21 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
dentry = fhp->fh_dentry; dentry = fhp->fh_dentry;
dirp = d_inode(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); host_err = PTR_ERR(rdentry);
if (IS_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); err = fh_fill_pre_attrs(fhp);
if (err != nfs_ok) if (err != nfs_ok)
goto out_unlock; goto out_unlock;
rinode = d_inode(rdentry);
/* Prevent truncation until after locks dropped */
ihold(rinode); ihold(rinode);
if (!type) if (!type)
type = d_inode(rdentry)->i_mode & S_IFMT; type = d_inode(rdentry)->i_mode & S_IFMT;
@ -2112,10 +2074,10 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
} }
fh_fill_post_attrs(fhp); fh_fill_post_attrs(fhp);
inode_unlock(dirp); out_unlock:
if (!host_err) end_removing(rdentry);
if (!err && !host_err)
host_err = commit_metadata(fhp); host_err = commit_metadata(fhp);
dput(rdentry);
iput(rinode); /* truncate the inode here */ iput(rinode); /* truncate the inode here */
out_drop_write: out_drop_write:
@ -2133,9 +2095,6 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type,
} }
out: out:
return err != nfs_ok ? err : nfserrno(host_err); return err != nfs_ok ? err : nfserrno(host_err);
out_unlock:
inode_unlock(dirp);
goto out_drop_write;
} }
/* /*

View File

@ -523,8 +523,8 @@ static int ovl_create_index(struct dentry *dentry, const struct ovl_fh *fh,
{ {
struct ovl_fs *ofs = OVL_FS(dentry->d_sb); struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct dentry *indexdir = ovl_indexdir(dentry->d_sb); struct dentry *indexdir = ovl_indexdir(dentry->d_sb);
struct dentry *index = NULL;
struct dentry *temp = NULL; struct dentry *temp = NULL;
struct renamedata rd = {};
struct qstr name = { }; struct qstr name = { };
int err; int err;
@ -556,17 +556,15 @@ static int ovl_create_index(struct dentry *dentry, const struct ovl_fh *fh,
if (err) if (err)
goto out; goto out;
err = ovl_parent_lock(indexdir, temp); rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
rd.old_parent = indexdir;
rd.new_parent = indexdir;
err = start_renaming_dentry(&rd, 0, temp, &name);
if (err) if (err)
goto out; goto out;
index = ovl_lookup_upper(ofs, name.name, indexdir, name.len);
if (IS_ERR(index)) { err = ovl_do_rename_rd(&rd);
err = PTR_ERR(index); end_renaming(&rd);
} else {
err = ovl_do_rename(ofs, indexdir, temp, indexdir, index, 0);
dput(index);
}
ovl_parent_unlock(indexdir);
out: out:
if (err) if (err)
ovl_cleanup(ofs, indexdir, temp); ovl_cleanup(ofs, indexdir, temp);
@ -613,9 +611,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 +624,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);
} }
inode_unlock(udir);
if (err) if (err)
goto out; goto out;
@ -764,7 +761,8 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb); struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb);
struct inode *inode; struct inode *inode;
struct path path = { .mnt = ovl_upper_mnt(ofs) }; struct path path = { .mnt = ovl_upper_mnt(ofs) };
struct dentry *temp, *upper, *trap; struct renamedata rd = {};
struct dentry *temp;
struct ovl_cu_creds cc; struct ovl_cu_creds cc;
int err; int err;
struct ovl_cattr cattr = { struct ovl_cattr cattr = {
@ -808,29 +806,24 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
* ovl_copy_up_data(), so lock workdir and destdir and make sure that * ovl_copy_up_data(), so lock workdir and destdir and make sure that
* temp wasn't moved before copy up completion or cleanup. * temp wasn't moved before copy up completion or cleanup.
*/ */
trap = lock_rename(c->workdir, c->destdir); rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
if (trap || temp->d_parent != c->workdir) { rd.old_parent = c->workdir;
/* temp or workdir moved underneath us? abort without cleanup */ rd.new_parent = c->destdir;
dput(temp); rd.flags = 0;
err = start_renaming_dentry(&rd, 0, temp,
&QSTR_LEN(c->destname.name, c->destname.len));
if (err) {
/* temp or workdir moved underneath us? map to -EIO */
err = -EIO; err = -EIO;
if (!IS_ERR(trap))
unlock_rename(c->workdir, c->destdir);
goto out;
} }
if (err)
goto cleanup_unlocked;
err = ovl_copy_up_metadata(c, temp); err = ovl_copy_up_metadata(c, temp);
if (err) if (!err)
goto cleanup; err = ovl_do_rename_rd(&rd);
end_renaming(&rd);
upper = ovl_lookup_upper(ofs, c->destname.name, c->destdir,
c->destname.len);
err = PTR_ERR(upper);
if (IS_ERR(upper))
goto cleanup;
err = ovl_do_rename(ofs, c->workdir, temp, c->destdir, upper, 0);
unlock_rename(c->workdir, c->destdir);
dput(upper);
if (err) if (err)
goto cleanup_unlocked; goto cleanup_unlocked;
@ -851,8 +844,6 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
return err; return err;
cleanup:
unlock_rename(c->workdir, c->destdir);
cleanup_unlocked: cleanup_unlocked:
ovl_cleanup(ofs, c->workdir, temp); ovl_cleanup(ofs, c->workdir, temp);
dput(temp); dput(temp);
@ -894,16 +885,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);
} }
inode_unlock(udir);
if (err) if (err)
goto out; goto out;

View File

@ -47,79 +47,70 @@ static int ovl_cleanup_locked(struct ovl_fs *ofs, struct inode *wdir,
int ovl_cleanup(struct ovl_fs *ofs, struct dentry *workdir, int ovl_cleanup(struct ovl_fs *ofs, struct dentry *workdir,
struct dentry *wdentry) struct dentry *wdentry)
{ {
int err; wdentry = start_removing_dentry(workdir, wdentry);
if (IS_ERR(wdentry))
err = ovl_parent_lock(workdir, wdentry); return PTR_ERR(wdentry);
if (err)
return err;
ovl_cleanup_locked(ofs, workdir->d_inode, wdentry); ovl_cleanup_locked(ofs, workdir->d_inode, wdentry);
ovl_parent_unlock(workdir); end_removing(wdentry);
return 0; return 0;
} }
struct dentry *ovl_lookup_temp(struct ovl_fs *ofs, struct dentry *workdir) 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));
}
temp = ovl_lookup_upper(ofs, name, workdir, strlen(name)); static struct dentry *ovl_start_creating_temp(struct ovl_fs *ofs,
if (!IS_ERR(temp) && temp->d_inode) { struct dentry *workdir)
pr_err("workdir/%s already exists\n", name); {
dput(temp); char name[OVL_TEMPNAME_SIZE];
temp = ERR_PTR(-EIO);
}
return temp; 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);
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);
} 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;
} }
} }
@ -132,6 +123,7 @@ int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir,
struct dentry *dentry) struct dentry *dentry)
{ {
struct dentry *whiteout; struct dentry *whiteout;
struct renamedata rd = {};
int err; int err;
int flags = 0; int flags = 0;
@ -143,10 +135,14 @@ int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir,
if (d_is_dir(dentry)) if (d_is_dir(dentry))
flags = RENAME_EXCHANGE; flags = RENAME_EXCHANGE;
err = ovl_lock_rename_workdir(ofs->workdir, whiteout, dir, dentry); rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
rd.old_parent = ofs->workdir;
rd.new_parent = dir;
rd.flags = flags;
err = start_renaming_two_dentries(&rd, whiteout, dentry);
if (!err) { if (!err) {
err = ovl_do_rename(ofs, ofs->workdir, whiteout, dir, dentry, flags); err = ovl_do_rename_rd(&rd);
unlock_rename(ofs->workdir, dir); end_renaming(&rd);
} }
if (err) if (err)
goto kill_whiteout; goto kill_whiteout;
@ -191,7 +187,7 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) { if (!err && ofs->casefold != ovl_dentry_casefolded(newdentry)) {
pr_warn_ratelimited("wrong inherited casefold (%pd2)\n", pr_warn_ratelimited("wrong inherited casefold (%pd2)\n",
newdentry); newdentry);
dput(newdentry); end_creating(newdentry);
err = -EINVAL; err = -EINVAL;
} }
break; break;
@ -241,8 +237,7 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs, struct dentry *parent,
} }
out: out:
if (err) { if (err) {
if (!IS_ERR(newdentry)) end_creating(newdentry);
dput(newdentry);
return ERR_PTR(err); return ERR_PTR(err);
} }
return newdentry; return newdentry;
@ -252,11 +247,11 @@ 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);
return ret; return end_creating_keep(ret);
} }
static int ovl_set_opaque_xerr(struct dentry *dentry, struct dentry *upper, static int ovl_set_opaque_xerr(struct dentry *dentry, struct dentry *upper,
@ -354,18 +349,19 @@ 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))
return PTR_ERR(newdentry);
end_creating_keep(newdentry);
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)) {
@ -391,6 +387,7 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
struct ovl_fs *ofs = OVL_FS(dentry->d_sb); struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
struct dentry *workdir = ovl_workdir(dentry); struct dentry *workdir = ovl_workdir(dentry);
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
struct renamedata rd = {};
struct path upperpath; struct path upperpath;
struct dentry *upper; struct dentry *upper;
struct dentry *opaquedir; struct dentry *opaquedir;
@ -416,7 +413,11 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
if (IS_ERR(opaquedir)) if (IS_ERR(opaquedir))
goto out; goto out;
err = ovl_lock_rename_workdir(workdir, opaquedir, upperdir, upper); rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
rd.old_parent = workdir;
rd.new_parent = upperdir;
rd.flags = RENAME_EXCHANGE;
err = start_renaming_two_dentries(&rd, opaquedir, upper);
if (err) if (err)
goto out_cleanup_unlocked; goto out_cleanup_unlocked;
@ -434,8 +435,8 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
if (err) if (err)
goto out_cleanup; goto out_cleanup;
err = ovl_do_rename(ofs, workdir, opaquedir, upperdir, upper, RENAME_EXCHANGE); err = ovl_do_rename_rd(&rd);
unlock_rename(workdir, upperdir); end_renaming(&rd);
if (err) if (err)
goto out_cleanup_unlocked; goto out_cleanup_unlocked;
@ -448,7 +449,7 @@ static struct dentry *ovl_clear_empty(struct dentry *dentry,
return opaquedir; return opaquedir;
out_cleanup: out_cleanup:
unlock_rename(workdir, upperdir); end_renaming(&rd);
out_cleanup_unlocked: out_cleanup_unlocked:
ovl_cleanup(ofs, workdir, opaquedir); ovl_cleanup(ofs, workdir, opaquedir);
dput(opaquedir); dput(opaquedir);
@ -471,6 +472,7 @@ static int ovl_create_over_whiteout(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 *workdir = ovl_workdir(dentry); struct dentry *workdir = ovl_workdir(dentry);
struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent); struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);
struct renamedata rd = {};
struct dentry *upper; struct dentry *upper;
struct dentry *newdentry; struct dentry *newdentry;
int err; int err;
@ -502,7 +504,11 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
if (IS_ERR(newdentry)) if (IS_ERR(newdentry))
goto out_dput; goto out_dput;
err = ovl_lock_rename_workdir(workdir, newdentry, upperdir, upper); rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
rd.old_parent = workdir;
rd.new_parent = upperdir;
rd.flags = 0;
err = start_renaming_two_dentries(&rd, newdentry, upper);
if (err) if (err)
goto out_cleanup_unlocked; goto out_cleanup_unlocked;
@ -539,16 +545,16 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
if (err) if (err)
goto out_cleanup; goto out_cleanup;
err = ovl_do_rename(ofs, workdir, newdentry, upperdir, upper, rd.flags = RENAME_EXCHANGE;
RENAME_EXCHANGE); err = ovl_do_rename_rd(&rd);
unlock_rename(workdir, upperdir); end_renaming(&rd);
if (err) if (err)
goto out_cleanup_unlocked; goto out_cleanup_unlocked;
ovl_cleanup(ofs, workdir, upper); ovl_cleanup(ofs, workdir, upper);
} else { } else {
err = ovl_do_rename(ofs, workdir, newdentry, upperdir, upper, 0); err = ovl_do_rename_rd(&rd);
unlock_rename(workdir, upperdir); end_renaming(&rd);
if (err) if (err)
goto out_cleanup_unlocked; goto out_cleanup_unlocked;
} }
@ -568,7 +574,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode,
return err; return err;
out_cleanup: out_cleanup:
unlock_rename(workdir, upperdir); end_renaming(&rd);
out_cleanup_unlocked: out_cleanup_unlocked:
ovl_cleanup(ofs, workdir, newdentry); ovl_cleanup(ofs, workdir, newdentry);
dput(newdentry); dput(newdentry);
@ -850,17 +856,17 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
goto out; goto out;
} }
inode_lock_nested(dir, I_MUTEX_PARENT); upper = ovl_start_removing_upper(ofs, upperdir,
upper = ovl_lookup_upper(ofs, dentry->d_name.name, upperdir, &QSTR_LEN(dentry->d_name.name,
dentry->d_name.len); dentry->d_name.len));
err = PTR_ERR(upper); err = PTR_ERR(upper);
if (IS_ERR(upper)) if (IS_ERR(upper))
goto out_unlock; goto out_dput;
err = -ESTALE; err = -ESTALE;
if ((opaquedir && upper != opaquedir) || if ((opaquedir && upper != opaquedir) ||
(!opaquedir && !ovl_matches_upper(dentry, upper))) (!opaquedir && !ovl_matches_upper(dentry, upper)))
goto out_dput_upper; goto out_unlock;
if (is_dir) if (is_dir)
err = ovl_do_rmdir(ofs, dir, upper); err = ovl_do_rmdir(ofs, dir, upper);
@ -876,10 +882,9 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir,
*/ */
if (!err) if (!err)
d_drop(dentry); d_drop(dentry);
out_dput_upper:
dput(upper);
out_unlock: out_unlock:
inode_unlock(dir); end_removing(upper);
out_dput:
dput(opaquedir); dput(opaquedir);
out: out:
return err; return err;
@ -1111,9 +1116,7 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
int err; int err;
struct dentry *old_upperdir; struct dentry *old_upperdir;
struct dentry *new_upperdir; struct dentry *new_upperdir;
struct dentry *olddentry = NULL; struct renamedata rd = {};
struct dentry *newdentry = NULL;
struct dentry *trap, *de;
bool old_opaque; bool old_opaque;
bool new_opaque; bool new_opaque;
bool cleanup_whiteout = false; bool cleanup_whiteout = false;
@ -1123,6 +1126,7 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
bool new_is_dir = d_is_dir(new); bool new_is_dir = d_is_dir(new);
bool samedir = olddir == newdir; bool samedir = olddir == newdir;
struct dentry *opaquedir = NULL; struct dentry *opaquedir = NULL;
struct dentry *whiteout = NULL;
const struct cred *old_cred = NULL; const struct cred *old_cred = NULL;
struct ovl_fs *ofs = OVL_FS(old->d_sb); struct ovl_fs *ofs = OVL_FS(old->d_sb);
LIST_HEAD(list); LIST_HEAD(list);
@ -1220,45 +1224,37 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
} }
} }
trap = lock_rename(new_upperdir, old_upperdir); rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
if (IS_ERR(trap)) { rd.old_parent = old_upperdir;
err = PTR_ERR(trap); rd.new_parent = new_upperdir;
goto out_revert_creds; rd.flags = flags;
}
de = ovl_lookup_upper(ofs, old->d_name.name, old_upperdir, err = start_renaming(&rd, 0,
old->d_name.len); &QSTR_LEN(old->d_name.name, old->d_name.len),
err = PTR_ERR(de); &QSTR_LEN(new->d_name.name, new->d_name.len));
if (IS_ERR(de))
goto out_unlock; if (err)
olddentry = de; goto out_revert_creds;
err = -ESTALE; err = -ESTALE;
if (!ovl_matches_upper(old, olddentry)) if (!ovl_matches_upper(old, rd.old_dentry))
goto out_unlock; goto out_unlock;
de = ovl_lookup_upper(ofs, new->d_name.name, new_upperdir,
new->d_name.len);
err = PTR_ERR(de);
if (IS_ERR(de))
goto out_unlock;
newdentry = de;
old_opaque = ovl_dentry_is_opaque(old); old_opaque = ovl_dentry_is_opaque(old);
new_opaque = ovl_dentry_is_opaque(new); new_opaque = ovl_dentry_is_opaque(new);
err = -ESTALE; err = -ESTALE;
if (d_inode(new) && ovl_dentry_upper(new)) { if (d_inode(new) && ovl_dentry_upper(new)) {
if (opaquedir) { if (opaquedir) {
if (newdentry != opaquedir) if (rd.new_dentry != opaquedir)
goto out_unlock; goto out_unlock;
} else { } else {
if (!ovl_matches_upper(new, newdentry)) if (!ovl_matches_upper(new, rd.new_dentry))
goto out_unlock; goto out_unlock;
} }
} else { } else {
if (!d_is_negative(newdentry)) { if (!d_is_negative(rd.new_dentry)) {
if (!new_opaque || !ovl_upper_is_whiteout(ofs, newdentry)) if (!new_opaque || !ovl_upper_is_whiteout(ofs, rd.new_dentry))
goto out_unlock; goto out_unlock;
} else { } else {
if (flags & RENAME_EXCHANGE) if (flags & RENAME_EXCHANGE)
@ -1266,19 +1262,14 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
} }
} }
if (olddentry == trap) if (rd.old_dentry->d_inode == rd.new_dentry->d_inode)
goto out_unlock;
if (newdentry == trap)
goto out_unlock;
if (olddentry->d_inode == newdentry->d_inode)
goto out_unlock; goto out_unlock;
err = 0; err = 0;
if (ovl_type_merge_or_lower(old)) if (ovl_type_merge_or_lower(old))
err = ovl_set_redirect(old, samedir); err = ovl_set_redirect(old, samedir);
else if (is_dir && !old_opaque && ovl_type_merge(new->d_parent)) else if (is_dir && !old_opaque && ovl_type_merge(new->d_parent))
err = ovl_set_opaque_xerr(old, olddentry, -EXDEV); err = ovl_set_opaque_xerr(old, rd.old_dentry, -EXDEV);
if (err) if (err)
goto out_unlock; goto out_unlock;
@ -1286,18 +1277,24 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
err = ovl_set_redirect(new, samedir); err = ovl_set_redirect(new, samedir);
else if (!overwrite && new_is_dir && !new_opaque && else if (!overwrite && new_is_dir && !new_opaque &&
ovl_type_merge(old->d_parent)) ovl_type_merge(old->d_parent))
err = ovl_set_opaque_xerr(new, newdentry, -EXDEV); err = ovl_set_opaque_xerr(new, rd.new_dentry, -EXDEV);
if (err) if (err)
goto out_unlock; goto out_unlock;
err = ovl_do_rename(ofs, old_upperdir, olddentry, err = ovl_do_rename_rd(&rd);
new_upperdir, newdentry, flags);
unlock_rename(new_upperdir, old_upperdir); if (!err && cleanup_whiteout)
whiteout = dget(rd.new_dentry);
end_renaming(&rd);
if (err) if (err)
goto out_revert_creds; goto out_revert_creds;
if (cleanup_whiteout) if (whiteout) {
ovl_cleanup(ofs, old_upperdir, newdentry); ovl_cleanup(ofs, old_upperdir, whiteout);
dput(whiteout);
}
if (overwrite && d_inode(new)) { if (overwrite && d_inode(new)) {
if (new_is_dir) if (new_is_dir)
@ -1323,14 +1320,12 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
else else
ovl_drop_write(old); ovl_drop_write(old);
out: out:
dput(newdentry);
dput(olddentry);
dput(opaquedir); dput(opaquedir);
ovl_cache_free(&list); ovl_cache_free(&list);
return err; return err;
out_unlock: out_unlock:
unlock_rename(new_upperdir, old_upperdir); end_renaming(&rd);
goto out_revert_creds; goto out_revert_creds;
} }

View File

@ -355,11 +355,24 @@ static inline int ovl_do_remove_acl(struct ovl_fs *ofs, struct dentry *dentry,
return vfs_remove_acl(ovl_upper_mnt_idmap(ofs), dentry, acl_name); return vfs_remove_acl(ovl_upper_mnt_idmap(ofs), dentry, acl_name);
} }
static inline int ovl_do_rename_rd(struct renamedata *rd)
{
int err;
pr_debug("rename(%pd2, %pd2, 0x%x)\n", rd->old_dentry, rd->new_dentry,
rd->flags);
err = vfs_rename(rd);
if (err) {
pr_debug("...rename(%pd2, %pd2, ...) = %i\n",
rd->old_dentry, rd->new_dentry, err);
}
return err;
}
static inline int ovl_do_rename(struct ovl_fs *ofs, struct dentry *olddir, static inline int ovl_do_rename(struct ovl_fs *ofs, struct dentry *olddir,
struct dentry *olddentry, struct dentry *newdir, struct dentry *olddentry, struct dentry *newdir,
struct dentry *newdentry, unsigned int flags) struct dentry *newdentry, unsigned int flags)
{ {
int err;
struct renamedata rd = { struct renamedata rd = {
.mnt_idmap = ovl_upper_mnt_idmap(ofs), .mnt_idmap = ovl_upper_mnt_idmap(ofs),
.old_parent = olddir, .old_parent = olddir,
@ -369,13 +382,7 @@ static inline int ovl_do_rename(struct ovl_fs *ofs, struct dentry *olddir,
.flags = flags, .flags = flags,
}; };
pr_debug("rename(%pd2, %pd2, 0x%x)\n", olddentry, newdentry, flags); return ovl_do_rename_rd(&rd);
err = vfs_rename(&rd);
if (err) {
pr_debug("...rename(%pd2, %pd2, ...) = %i\n",
olddentry, newdentry, err);
}
return err;
} }
static inline int ovl_do_whiteout(struct ovl_fs *ofs, static inline int ovl_do_whiteout(struct ovl_fs *ofs,
@ -415,6 +422,22 @@ 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 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) static inline bool ovl_open_flags_need_copy_up(int flags)
{ {
if (!flags) if (!flags)
@ -424,11 +447,6 @@ static inline bool ovl_open_flags_need_copy_up(int flags)
} }
/* util.c */ /* util.c */
int ovl_parent_lock(struct dentry *parent, struct dentry *child);
static inline void ovl_parent_unlock(struct dentry *parent)
{
inode_unlock(parent->d_inode);
}
int ovl_get_write_access(struct dentry *dentry); int ovl_get_write_access(struct dentry *dentry);
void ovl_put_write_access(struct dentry *dentry); void ovl_put_write_access(struct dentry *dentry);
void ovl_start_write(struct dentry *dentry); void ovl_start_write(struct dentry *dentry);
@ -865,7 +883,8 @@ struct dentry *ovl_create_real(struct ovl_fs *ofs,
struct dentry *parent, struct dentry *newdentry, struct dentry *parent, struct dentry *newdentry,
struct ovl_cattr *attr); struct ovl_cattr *attr);
int ovl_cleanup(struct ovl_fs *ofs, struct dentry *workdir, struct dentry *dentry); int ovl_cleanup(struct ovl_fs *ofs, struct dentry *workdir, struct dentry *dentry);
struct dentry *ovl_lookup_temp(struct ovl_fs *ofs, struct dentry *workdir); #define OVL_TEMPNAME_SIZE 20
void ovl_tempname(char name[OVL_TEMPNAME_SIZE]);
struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir, struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
struct ovl_cattr *attr); struct ovl_cattr *attr);

View File

@ -1242,11 +1242,11 @@ int ovl_workdir_cleanup(struct ovl_fs *ofs, struct dentry *parent,
if (!d_is_dir(dentry) || level > 1) if (!d_is_dir(dentry) || level > 1)
return ovl_cleanup(ofs, parent, dentry); return ovl_cleanup(ofs, parent, dentry);
err = ovl_parent_lock(parent, dentry); dentry = start_removing_dentry(parent, dentry);
if (err) if (IS_ERR(dentry))
return err; return PTR_ERR(dentry);
err = ovl_do_rmdir(ofs, parent->d_inode, dentry); err = ovl_do_rmdir(ofs, parent->d_inode, dentry);
ovl_parent_unlock(parent); end_removing(dentry);
if (err) { if (err) {
struct path path = { .mnt = mnt, .dentry = dentry }; struct path path = { .mnt = mnt, .dentry = dentry };

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,12 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
}; };
if (work->d_inode) { if (work->d_inode) {
err = -EEXIST; end_creating_keep(work);
inode_unlock(dir);
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 +335,7 @@ 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); end_creating_keep(work);
err = PTR_ERR(work); err = PTR_ERR(work);
if (IS_ERR(work)) if (IS_ERR(work))
goto out_err; goto out_err;
@ -376,7 +373,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;
} }
@ -567,9 +563,10 @@ static int ovl_check_rename_whiteout(struct ovl_fs *ofs)
{ {
struct dentry *workdir = ofs->workdir; struct dentry *workdir = ofs->workdir;
struct dentry *temp; struct dentry *temp;
struct dentry *dest;
struct dentry *whiteout; struct dentry *whiteout;
struct name_snapshot name; struct name_snapshot name;
struct renamedata rd = {};
char name2[OVL_TEMPNAME_SIZE];
int err; int err;
temp = ovl_create_temp(ofs, workdir, OVL_CATTR(S_IFREG | 0)); temp = ovl_create_temp(ofs, workdir, OVL_CATTR(S_IFREG | 0));
@ -577,23 +574,21 @@ static int ovl_check_rename_whiteout(struct ovl_fs *ofs)
if (IS_ERR(temp)) if (IS_ERR(temp))
return err; return err;
err = ovl_parent_lock(workdir, temp); rd.mnt_idmap = ovl_upper_mnt_idmap(ofs);
rd.old_parent = workdir;
rd.new_parent = workdir;
rd.flags = RENAME_WHITEOUT;
ovl_tempname(name2);
err = start_renaming_dentry(&rd, 0, temp, &QSTR(name2));
if (err) { if (err) {
dput(temp); dput(temp);
return err; return err;
} }
dest = ovl_lookup_temp(ofs, workdir);
err = PTR_ERR(dest);
if (IS_ERR(dest)) {
dput(temp);
ovl_parent_unlock(workdir);
return err;
}
/* Name is inline and stable - using snapshot as a copy helper */ /* Name is inline and stable - using snapshot as a copy helper */
take_dentry_name_snapshot(&name, temp); take_dentry_name_snapshot(&name, temp);
err = ovl_do_rename(ofs, workdir, temp, workdir, dest, RENAME_WHITEOUT); err = ovl_do_rename_rd(&rd);
ovl_parent_unlock(workdir); end_renaming(&rd);
if (err) { if (err) {
if (err == -EINVAL) if (err == -EINVAL)
err = 0; err = 0;
@ -617,7 +612,6 @@ static int ovl_check_rename_whiteout(struct ovl_fs *ofs)
ovl_cleanup(ofs, workdir, temp); ovl_cleanup(ofs, workdir, temp);
release_dentry_name_snapshot(&name); release_dentry_name_snapshot(&name);
dput(temp); dput(temp);
dput(dest);
return err; return err;
} }
@ -626,14 +620,15 @@ 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));
end_creating_keep(child);
}
dput(parent); dput(parent);
return child; return child;

View File

@ -1548,14 +1548,3 @@ void ovl_copyattr(struct inode *inode)
i_size_write(inode, i_size_read(realinode)); i_size_write(inode, i_size_read(realinode));
spin_unlock(&inode->i_lock); spin_unlock(&inode->i_lock);
} }
int ovl_parent_lock(struct dentry *parent, struct dentry *child)
{
inode_lock_nested(parent->d_inode, I_MUTEX_PARENT);
if (!child ||
(!d_unhashed(child) && child->d_parent == parent))
return 0;
inode_unlock(parent->d_inode);
return -EINVAL;
}

View File

@ -6092,8 +6092,8 @@ static int smb2_create_link(struct ksmbd_work *work,
} }
ksmbd_debug(SMB, "target name is %s\n", target_name); ksmbd_debug(SMB, "target name is %s\n", target_name);
rc = ksmbd_vfs_kern_path_locked(work, link_name, LOOKUP_NO_SYMLINKS, rc = ksmbd_vfs_kern_path_start_removing(work, link_name, LOOKUP_NO_SYMLINKS,
&path, 0); &path, 0);
if (rc) { if (rc) {
if (rc != -ENOENT) if (rc != -ENOENT)
goto out; goto out;
@ -6111,7 +6111,7 @@ static int smb2_create_link(struct ksmbd_work *work,
ksmbd_debug(SMB, "link already exists\n"); ksmbd_debug(SMB, "link already exists\n");
goto out; goto out;
} }
ksmbd_vfs_kern_path_unlock(&path); ksmbd_vfs_kern_path_end_removing(&path);
} }
rc = ksmbd_vfs_link(work, target_name, link_name); rc = ksmbd_vfs_link(work, target_name, link_name);
if (rc) if (rc)

View File

@ -49,27 +49,9 @@ static void ksmbd_vfs_inherit_owner(struct ksmbd_work *work,
i_uid_write(inode, i_uid_read(parent_inode)); i_uid_write(inode, i_uid_read(parent_inode));
} }
/**
* ksmbd_vfs_lock_parent() - lock parent dentry if it is stable
* @parent: parent dentry
* @child: child dentry
*
* Returns: %0 on success, %-ENOENT if the parent dentry is not stable
*/
int ksmbd_vfs_lock_parent(struct dentry *parent, struct dentry *child)
{
inode_lock_nested(d_inode(parent), I_MUTEX_PARENT);
if (child->d_parent != parent) {
inode_unlock(d_inode(parent));
return -ENOENT;
}
return 0;
}
static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf, static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf,
char *pathname, unsigned int flags, char *pathname, unsigned int flags,
struct path *path, bool do_lock) struct path *path, bool for_remove)
{ {
struct qstr last; struct qstr last;
struct filename *filename __free(putname) = NULL; struct filename *filename __free(putname) = NULL;
@ -99,22 +81,20 @@ static int ksmbd_vfs_path_lookup(struct ksmbd_share_config *share_conf,
return -ENOENT; return -ENOENT;
} }
if (do_lock) { if (for_remove) {
err = mnt_want_write(path->mnt); err = mnt_want_write(path->mnt);
if (err) { if (err) {
path_put(path); path_put(path);
return -ENOENT; return -ENOENT;
} }
inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT); d = start_removing_noperm(path->dentry, &last);
d = lookup_one_qstr_excl(&last, path->dentry, 0);
if (!IS_ERR(d)) { if (!IS_ERR(d)) {
dput(path->dentry); dput(path->dentry);
path->dentry = d; path->dentry = d;
return 0; return 0;
} }
inode_unlock(path->dentry->d_inode);
mnt_drop_write(path->mnt); mnt_drop_write(path->mnt);
path_put(path); path_put(path);
return -ENOENT; return -ENOENT;
@ -680,7 +660,6 @@ int ksmbd_vfs_link(struct ksmbd_work *work, const char *oldname,
int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path, int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
char *newname, int flags) char *newname, int flags)
{ {
struct dentry *old_parent, *new_dentry, *trap;
struct dentry *old_child = old_path->dentry; struct dentry *old_child = old_path->dentry;
struct path new_path; struct path new_path;
struct qstr new_last; struct qstr new_last;
@ -690,7 +669,6 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
struct ksmbd_file *parent_fp; struct ksmbd_file *parent_fp;
int new_type; int new_type;
int err, lookup_flags = LOOKUP_NO_SYMLINKS; int err, lookup_flags = LOOKUP_NO_SYMLINKS;
int target_lookup_flags = LOOKUP_RENAME_TARGET | LOOKUP_CREATE;
if (ksmbd_override_fsids(work)) if (ksmbd_override_fsids(work))
return -ENOMEM; return -ENOMEM;
@ -701,14 +679,6 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
goto revert_fsids; goto revert_fsids;
} }
/*
* explicitly handle file overwrite case, for compatibility with
* filesystems that may not support rename flags (e.g: fuse)
*/
if (flags & RENAME_NOREPLACE)
target_lookup_flags |= LOOKUP_EXCL;
flags &= ~(RENAME_NOREPLACE);
retry: retry:
err = vfs_path_parent_lookup(to, lookup_flags | LOOKUP_BENEATH, err = vfs_path_parent_lookup(to, lookup_flags | LOOKUP_BENEATH,
&new_path, &new_last, &new_type, &new_path, &new_last, &new_type,
@ -725,17 +695,14 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
if (err) if (err)
goto out2; goto out2;
trap = lock_rename_child(old_child, new_path.dentry); rd.mnt_idmap = mnt_idmap(old_path->mnt);
if (IS_ERR(trap)) { rd.old_parent = NULL;
err = PTR_ERR(trap); rd.new_parent = new_path.dentry;
rd.flags = flags;
rd.delegated_inode = NULL,
err = start_renaming_dentry(&rd, lookup_flags, old_child, &new_last);
if (err)
goto out_drop_write; goto out_drop_write;
}
old_parent = dget(old_child->d_parent);
if (d_unhashed(old_child)) {
err = -EINVAL;
goto out3;
}
parent_fp = ksmbd_lookup_fd_inode(old_child->d_parent); parent_fp = ksmbd_lookup_fd_inode(old_child->d_parent);
if (parent_fp) { if (parent_fp) {
@ -748,44 +715,17 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
ksmbd_fd_put(work, parent_fp); ksmbd_fd_put(work, parent_fp);
} }
new_dentry = lookup_one_qstr_excl(&new_last, new_path.dentry, if (d_is_symlink(rd.new_dentry)) {
lookup_flags | target_lookup_flags); err = -EACCES;
if (IS_ERR(new_dentry)) {
err = PTR_ERR(new_dentry);
goto out3; goto out3;
} }
if (d_is_symlink(new_dentry)) {
err = -EACCES;
goto out4;
}
if (old_child == trap) {
err = -EINVAL;
goto out4;
}
if (new_dentry == trap) {
err = -ENOTEMPTY;
goto out4;
}
rd.mnt_idmap = mnt_idmap(old_path->mnt),
rd.old_parent = old_parent,
rd.old_dentry = old_child,
rd.new_parent = new_path.dentry,
rd.new_dentry = new_dentry,
rd.flags = flags,
rd.delegated_inode = NULL,
err = vfs_rename(&rd); err = vfs_rename(&rd);
if (err) if (err)
ksmbd_debug(VFS, "vfs_rename failed err %d\n", err); ksmbd_debug(VFS, "vfs_rename failed err %d\n", err);
out4:
dput(new_dentry);
out3: out3:
dput(old_parent); end_renaming(&rd);
unlock_rename(old_parent, new_path.dentry);
out_drop_write: out_drop_write:
mnt_drop_write(old_path->mnt); mnt_drop_write(old_path->mnt);
out2: out2:
@ -1083,18 +1023,17 @@ int ksmbd_vfs_unlink(struct file *filp)
return err; return err;
dir = dget_parent(dentry); dir = dget_parent(dentry);
err = ksmbd_vfs_lock_parent(dir, dentry); dentry = start_removing_dentry(dir, dentry);
if (err) err = PTR_ERR(dentry);
if (IS_ERR(dentry))
goto out; goto out;
dget(dentry);
if (S_ISDIR(d_inode(dentry)->i_mode)) if (S_ISDIR(d_inode(dentry)->i_mode))
err = vfs_rmdir(idmap, d_inode(dir), dentry, NULL); err = vfs_rmdir(idmap, d_inode(dir), dentry, NULL);
else else
err = vfs_unlink(idmap, d_inode(dir), dentry, NULL); err = vfs_unlink(idmap, d_inode(dir), dentry, NULL);
dput(dentry); end_removing(dentry);
inode_unlock(d_inode(dir));
if (err) if (err)
ksmbd_debug(VFS, "failed to delete, err %d\n", err); ksmbd_debug(VFS, "failed to delete, err %d\n", err);
out: out:
@ -1206,7 +1145,7 @@ static int ksmbd_vfs_lookup_in_dir(const struct path *dir, char *name,
static static
int __ksmbd_vfs_kern_path(struct ksmbd_work *work, char *filepath, int __ksmbd_vfs_kern_path(struct ksmbd_work *work, char *filepath,
unsigned int flags, unsigned int flags,
struct path *path, bool caseless, bool do_lock) struct path *path, bool caseless, bool for_remove)
{ {
struct ksmbd_share_config *share_conf = work->tcon->share_conf; struct ksmbd_share_config *share_conf = work->tcon->share_conf;
struct path parent_path; struct path parent_path;
@ -1214,7 +1153,7 @@ int __ksmbd_vfs_kern_path(struct ksmbd_work *work, char *filepath,
int err; int err;
retry: retry:
err = ksmbd_vfs_path_lookup(share_conf, filepath, flags, path, do_lock); err = ksmbd_vfs_path_lookup(share_conf, filepath, flags, path, for_remove);
if (!err || !caseless) if (!err || !caseless)
return err; return err;
@ -1285,7 +1224,7 @@ int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *filepath,
} }
/** /**
* ksmbd_vfs_kern_path_locked() - lookup a file and get path info * ksmbd_vfs_kern_path_start_remove() - lookup a file and get path info prior to removal
* @work: work * @work: work
* @filepath: file path that is relative to share * @filepath: file path that is relative to share
* @flags: lookup flags * @flags: lookup flags
@ -1297,20 +1236,19 @@ int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *filepath,
* filesystem will have been gained. * filesystem will have been gained.
* Return: 0 on if file was found, otherwise error * Return: 0 on if file was found, otherwise error
*/ */
int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *filepath, int ksmbd_vfs_kern_path_start_removing(struct ksmbd_work *work, char *filepath,
unsigned int flags, unsigned int flags,
struct path *path, bool caseless) struct path *path, bool caseless)
{ {
return __ksmbd_vfs_kern_path(work, filepath, flags, path, return __ksmbd_vfs_kern_path(work, filepath, flags, path,
caseless, true); caseless, true);
} }
void ksmbd_vfs_kern_path_unlock(const struct path *path) void ksmbd_vfs_kern_path_end_removing(const struct path *path)
{ {
/* While lock is still held, ->d_parent is safe */ end_removing(path->dentry);
inode_unlock(d_inode(path->dentry->d_parent));
mnt_drop_write(path->mnt); mnt_drop_write(path->mnt);
path_put(path); mntput(path->mnt);
} }
struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work, struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,

View File

@ -120,10 +120,10 @@ int ksmbd_vfs_remove_xattr(struct mnt_idmap *idmap,
int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *name, int ksmbd_vfs_kern_path(struct ksmbd_work *work, char *name,
unsigned int flags, unsigned int flags,
struct path *path, bool caseless); struct path *path, bool caseless);
int ksmbd_vfs_kern_path_locked(struct ksmbd_work *work, char *name, int ksmbd_vfs_kern_path_start_removing(struct ksmbd_work *work, char *name,
unsigned int flags, unsigned int flags,
struct path *path, bool caseless); struct path *path, bool caseless);
void ksmbd_vfs_kern_path_unlock(const struct path *path); void ksmbd_vfs_kern_path_end_removing(const struct path *path);
struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work, struct dentry *ksmbd_vfs_kern_path_create(struct ksmbd_work *work,
const char *name, const char *name,
unsigned int flags, unsigned int flags,

View File

@ -152,11 +152,10 @@ xrep_orphanage_create(
} }
/* Try to find the orphanage directory. */ /* Try to find the orphanage directory. */
inode_lock_nested(root_inode, I_MUTEX_PARENT); orphanage_dentry = start_creating_noperm(root_dentry, &QSTR(ORPHANAGE));
orphanage_dentry = lookup_noperm(&QSTR(ORPHANAGE), root_dentry);
if (IS_ERR(orphanage_dentry)) { if (IS_ERR(orphanage_dentry)) {
error = PTR_ERR(orphanage_dentry); error = PTR_ERR(orphanage_dentry);
goto out_unlock_root; goto out_dput_root;
} }
/* /*
@ -170,7 +169,7 @@ xrep_orphanage_create(
orphanage_dentry, 0750, NULL); orphanage_dentry, 0750, NULL);
error = PTR_ERR(orphanage_dentry); error = PTR_ERR(orphanage_dentry);
if (IS_ERR(orphanage_dentry)) if (IS_ERR(orphanage_dentry))
goto out_unlock_root; goto out_dput_orphanage;
} }
/* Not a directory? Bail out. */ /* Not a directory? Bail out. */
@ -200,9 +199,7 @@ xrep_orphanage_create(
sc->orphanage_ilock_flags = 0; sc->orphanage_ilock_flags = 0;
out_dput_orphanage: out_dput_orphanage:
dput(orphanage_dentry); end_creating(orphanage_dentry);
out_unlock_root:
inode_unlock(VFS_I(sc->mp->m_rootip));
out_dput_root: out_dput_root:
dput(root_dentry); dput(root_dentry);
out: out:

View File

@ -3175,6 +3175,8 @@ extern void iterate_supers_type(struct file_system_type *,
void filesystems_freeze(bool freeze_all); void filesystems_freeze(bool freeze_all);
void filesystems_thaw(void); void filesystems_thaw(void);
void end_dirop(struct dentry *de);
extern int dcache_dir_open(struct inode *, struct file *); extern int dcache_dir_open(struct inode *, struct file *);
extern int dcache_dir_close(struct inode *, struct file *); extern int dcache_dir_close(struct inode *, struct file *);
extern loff_t dcache_dir_lseek(struct file *, loff_t, int); extern loff_t dcache_dir_lseek(struct file *, loff_t, int);

View File

@ -89,6 +89,81 @@ 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);
struct dentry *start_removing(struct mnt_idmap *idmap, struct dentry *parent,
struct qstr *name);
struct dentry *start_creating_killable(struct mnt_idmap *idmap,
struct dentry *parent,
struct qstr *name);
struct dentry *start_removing_killable(struct mnt_idmap *idmap,
struct dentry *parent,
struct qstr *name);
struct dentry *start_creating_noperm(struct dentry *parent, struct qstr *name);
struct dentry *start_removing_noperm(struct dentry *parent, struct qstr *name);
struct dentry *start_creating_dentry(struct dentry *parent,
struct dentry *child);
struct dentry *start_removing_dentry(struct dentry *parent,
struct dentry *child);
/* end_creating - finish action started with start_creating
* @child: dentry returned by start_creating() or vfs_mkdir()
*
* Unlock and release the child. This can be called after
* start_creating() whether that function succeeded or not,
* but it is not needed on failure.
*
* 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.
*
*
* 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)
{
end_dirop(child);
}
/* end_creating_keep - finish action started with start_creating() and return result
* @child: dentry returned by start_creating() or vfs_mkdir()
*
* Unlock and return the child. This can be called after
* start_creating() whether that function succeeded or not,
* but it is not needed on failure.
*
* 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.
*
* Returns: @child, which may be a dentry or an error.
*
*/
static inline struct dentry *end_creating_keep(struct dentry *child)
{
if (!IS_ERR(child))
dget(child);
end_dirop(child);
return 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_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 *);
@ -96,6 +171,13 @@ extern int follow_up(struct path *);
extern struct dentry *lock_rename(struct dentry *, struct dentry *); extern struct dentry *lock_rename(struct dentry *, struct dentry *);
extern struct dentry *lock_rename_child(struct dentry *, struct dentry *); extern struct dentry *lock_rename_child(struct dentry *, struct dentry *);
extern void unlock_rename(struct dentry *, struct dentry *); extern void unlock_rename(struct dentry *, struct dentry *);
int start_renaming(struct renamedata *rd, int lookup_flags,
struct qstr *old_last, struct qstr *new_last);
int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
struct dentry *old_dentry, struct qstr *new_last);
int start_renaming_two_dentries(struct renamedata *rd,
struct dentry *old_dentry, struct dentry *new_dentry);
void end_renaming(struct renamedata *rd);
/** /**
* mode_strip_umask - handle vfs umask stripping * mode_strip_umask - handle vfs umask stripping

View File

@ -913,13 +913,12 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
goto out_putname; goto out_putname;
ro = mnt_want_write(mnt); /* we'll drop it in any case */ ro = mnt_want_write(mnt); /* we'll drop it in any case */
inode_lock(d_inode(root)); path.dentry = start_creating_noperm(root, &QSTR(name->name));
path.dentry = lookup_noperm(&QSTR(name->name), root);
if (IS_ERR(path.dentry)) { if (IS_ERR(path.dentry)) {
error = PTR_ERR(path.dentry); error = PTR_ERR(path.dentry);
goto out_putfd; goto out_putfd;
} }
path.mnt = mntget(mnt); path.mnt = mnt;
error = prepare_open(path.dentry, oflag, ro, mode, name, attr); error = prepare_open(path.dentry, oflag, ro, mode, name, attr);
if (!error) { if (!error) {
struct file *file = dentry_open(&path, oflag, current_cred()); struct file *file = dentry_open(&path, oflag, current_cred());
@ -928,13 +927,12 @@ static int do_mq_open(const char __user *u_name, int oflag, umode_t mode,
else else
error = PTR_ERR(file); error = PTR_ERR(file);
} }
path_put(&path);
out_putfd: out_putfd:
if (error) { if (error) {
put_unused_fd(fd); put_unused_fd(fd);
fd = error; fd = error;
} }
inode_unlock(d_inode(root)); end_creating(path.dentry);
if (!ro) if (!ro)
mnt_drop_write(mnt); mnt_drop_write(mnt);
out_putname: out_putname:
@ -957,7 +955,7 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name)
int err; int err;
struct filename *name; struct filename *name;
struct dentry *dentry; struct dentry *dentry;
struct inode *inode = NULL; struct inode *inode;
struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns; struct ipc_namespace *ipc_ns = current->nsproxy->ipc_ns;
struct vfsmount *mnt = ipc_ns->mq_mnt; struct vfsmount *mnt = ipc_ns->mq_mnt;
@ -969,26 +967,20 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name)
err = mnt_want_write(mnt); err = mnt_want_write(mnt);
if (err) if (err)
goto out_name; goto out_name;
inode_lock_nested(d_inode(mnt->mnt_root), I_MUTEX_PARENT); dentry = start_removing_noperm(mnt->mnt_root, &QSTR(name->name));
dentry = lookup_noperm(&QSTR(name->name), mnt->mnt_root);
if (IS_ERR(dentry)) { if (IS_ERR(dentry)) {
err = PTR_ERR(dentry); err = PTR_ERR(dentry);
goto out_unlock; goto out_drop_write;
} }
inode = d_inode(dentry); inode = d_inode(dentry);
if (!inode) { ihold(inode);
err = -ENOENT; err = vfs_unlink(&nop_mnt_idmap, d_inode(mnt->mnt_root),
} else { dentry, NULL);
ihold(inode); end_removing(dentry);
err = vfs_unlink(&nop_mnt_idmap, d_inode(dentry->d_parent),
dentry, NULL);
}
dput(dentry);
out_unlock:
inode_unlock(d_inode(mnt->mnt_root));
iput(inode); iput(inode);
out_drop_write:
mnt_drop_write(mnt); mnt_drop_write(mnt);
out_name: out_name:
putname(name); putname(name);

View File

@ -355,17 +355,17 @@ static void aafs_remove(struct dentry *dentry)
if (!dentry || IS_ERR(dentry)) if (!dentry || IS_ERR(dentry))
return; return;
/* ->d_parent is stable as rename is not supported */
dir = d_inode(dentry->d_parent); dir = d_inode(dentry->d_parent);
inode_lock(dir); dentry = start_removing_dentry(dentry->d_parent, dentry);
if (simple_positive(dentry)) { if (!IS_ERR(dentry) && simple_positive(dentry)) {
if (d_is_dir(dentry)) if (d_is_dir(dentry))
simple_rmdir(dir, dentry); simple_rmdir(dir, dentry);
else else
simple_unlink(dir, dentry); simple_unlink(dir, dentry);
d_delete(dentry); d_delete(dentry);
dput(dentry);
} }
inode_unlock(dir); end_removing(dentry);
simple_release_fs(&aafs_mnt, &aafs_count); simple_release_fs(&aafs_mnt, &aafs_count);
} }

View File

@ -506,6 +506,7 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
{ {
int ret = 0; int ret = 0;
struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir; struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir;
struct renamedata rd = {};
unsigned int bool_num = 0; unsigned int bool_num = 0;
char **bool_names = NULL; char **bool_names = NULL;
int *bool_values = NULL; int *bool_values = NULL;
@ -539,9 +540,14 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
if (ret) if (ret)
goto out; goto out;
lock_rename(tmp_parent, fsi->sb->s_root); rd.old_parent = tmp_parent;
rd.new_parent = fsi->sb->s_root;
/* booleans */ /* booleans */
ret = start_renaming_two_dentries(&rd, tmp_bool_dir, fsi->bool_dir);
if (ret)
goto out;
d_exchange(tmp_bool_dir, fsi->bool_dir); d_exchange(tmp_bool_dir, fsi->bool_dir);
swap(fsi->bool_num, bool_num); swap(fsi->bool_num, bool_num);
@ -549,12 +555,17 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
swap(fsi->bool_pending_values, bool_values); swap(fsi->bool_pending_values, bool_values);
fsi->bool_dir = tmp_bool_dir; fsi->bool_dir = tmp_bool_dir;
end_renaming(&rd);
/* classes */ /* classes */
ret = start_renaming_two_dentries(&rd, tmp_class_dir, fsi->class_dir);
if (ret)
goto out;
d_exchange(tmp_class_dir, fsi->class_dir); d_exchange(tmp_class_dir, fsi->class_dir);
fsi->class_dir = tmp_class_dir; fsi->class_dir = tmp_class_dir;
unlock_rename(tmp_parent, fsi->sb->s_root); end_renaming(&rd);
out: out:
sel_remove_old_bool_data(bool_num, bool_names, bool_values); sel_remove_old_bool_data(bool_num, bool_names, bool_values);