fix directory locking scheme on rename

broken in 6.5; we really can't lock two unrelated directories
 without holding ->s_vfs_rename_mutex first and in case of
 same-parent rename of a subdirectory 6.5 ends up doing just
 that.
 
 Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYIAB0WIQQqUNBr3gm4hGXdBJlZ7Krx/gZQ6wUCZZ+lyQAKCRBZ7Krx/gZQ
 60MWAP94hTqeMIpjhsUIkrTnylrIFaiw4UCWFJzIRG1QQYKqCgD/XUaWI9np7dL6
 0wR/j4CQSdJjiEFKUFE2pD3QoSuJYAQ=
 =+x0+
 -----END PGP SIGNATURE-----

Merge tag 'pull-rename' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs

Pull rename updates from Al Viro:
 "Fix directory locking scheme on rename

  This was broken in 6.5; we really can't lock two unrelated directories
  without holding ->s_vfs_rename_mutex first and in case of same-parent
  rename of a subdirectory 6.5 ends up doing just that"

* tag 'pull-rename' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  rename(): avoid a deadlock in the case of parents having no common ancestor
  kill lock_two_inodes()
  rename(): fix the locking of subdirectories
  f2fs: Avoid reading renamed directory if parent does not change
  ext4: don't access the source subdirectory content on same-directory rename
  ext2: Avoid reading renamed directory if parent does not change
  udf_rename(): only access the child content on cross-directory rename
  ocfs2: Avoid touching renamed directory if parent does not change
  reiserfs: Avoid touching renamed directory if parent does not change
This commit is contained in:
Linus Torvalds 2024-01-11 20:00:22 -08:00
commit bf4e7080ae
20 changed files with 428 additions and 218 deletions

View File

@ -11,129 +11,268 @@ When taking the i_rwsem on multiple non-directory objects, we
always acquire the locks in order by increasing address. We'll call always acquire the locks in order by increasing address. We'll call
that "inode pointer" order in the following. that "inode pointer" order in the following.
For our purposes all operations fall in 5 classes:
1) read access. Locking rules: caller locks directory we are accessing. Primitives
The lock is taken shared. ==========
2) object creation. Locking rules: same as above, but the lock is taken For our purposes all operations fall in 6 classes:
exclusive.
3) object removal. Locking rules: caller locks parent, finds victim, 1. read access. Locking rules:
locks victim and calls the method. Locks are exclusive.
4) rename() that is _not_ cross-directory. Locking rules: caller locks the * lock the directory we are accessing (shared)
parent and finds source and target. We lock both (provided they exist). If we
need to lock two inodes of different type (dir vs non-dir), we lock directory
first. If we need to lock two inodes of the same type, lock them in inode
pointer order. Then call the method. All locks are exclusive.
NB: we might get away with locking the source (and target in exchange
case) shared.
5) link creation. Locking rules: 2. object creation. Locking rules:
* lock parent * lock the directory we are accessing (exclusive)
* check that source is not a directory
* lock source
* call the method.
All locks are exclusive. 3. object removal. Locking rules:
6) cross-directory rename. The trickiest in the whole bunch. Locking * lock the parent (exclusive)
rules: * find the victim
* lock the victim (exclusive)
4. link creation. Locking rules:
* lock the parent (exclusive)
* check that the source is not a directory
* lock the source (exclusive; probably could be weakened to shared)
5. rename that is _not_ cross-directory. Locking rules:
* lock the parent (exclusive)
* find the source and target
* decide which of the source and target need to be locked.
The source needs to be locked if it's a non-directory, target - if it's
a non-directory or about to be removed.
* take the locks that need to be taken (exlusive), in inode pointer order
if need to take both (that can happen only when both source and target
are non-directories - the source because it wouldn't need to be locked
otherwise and the target because mixing directory and non-directory is
allowed only with RENAME_EXCHANGE, and that won't be removing the target).
6. cross-directory rename. The trickiest in the whole bunch. Locking rules:
* lock the filesystem * lock the filesystem
* lock parents in "ancestors first" order. If one is not ancestor of * if the parents don't have a common ancestor, fail the operation.
the other, lock them in inode pointer order. * lock the parents in "ancestors first" order (exclusive). If neither is an
* find source and target. ancestor of the other, lock the parent of source first.
* if old parent is equal to or is a descendent of target * find the source and target.
fail with -ENOTEMPTY * verify that the source is not a descendent of the target and
* if new parent is equal to or is a descendent of source target is not a descendent of source; fail the operation otherwise.
fail with -ELOOP * lock the subdirectories involved (exclusive), source before target.
* Lock both the source and the target provided they exist. If we * lock the non-directories involved (exclusive), in inode pointer order.
need to lock two inodes of different type (dir vs non-dir), we lock
the directory first. If we need to lock two inodes of the same type,
lock them in inode pointer order.
* call the method.
All ->i_rwsem are taken exclusive. Again, we might get away with locking The rules above obviously guarantee that all directories that are going
the source (and target in exchange case) shared. to be read, modified or removed by method will be locked by the caller.
The rules above obviously guarantee that all directories that are going to be
read, modified or removed by method will be locked by caller.
Splicing
========
There is one more thing to consider - splicing. It's not an operation
in its own right; it may happen as part of lookup. We speak of the
operations on directory trees, but we obviously do not have the full
picture of those - especially for network filesystems. What we have
is a bunch of subtrees visible in dcache and locking happens on those.
Trees grow as we do operations; memory pressure prunes them. Normally
that's not a problem, but there is a nasty twist - what should we do
when one growing tree reaches the root of another? That can happen in
several scenarios, starting from "somebody mounted two nested subtrees
from the same NFS4 server and doing lookups in one of them has reached
the root of another"; there's also open-by-fhandle stuff, and there's a
possibility that directory we see in one place gets moved by the server
to another and we run into it when we do a lookup.
For a lot of reasons we want to have the same directory present in dcache
only once. Multiple aliases are not allowed. So when lookup runs into
a subdirectory that already has an alias, something needs to be done with
dcache trees. Lookup is already holding the parent locked. If alias is
a root of separate tree, it gets attached to the directory we are doing a
lookup in, under the name we'd been looking for. If the alias is already
a child of the directory we are looking in, it changes name to the one
we'd been looking for. No extra locking is involved in these two cases.
However, if it's a child of some other directory, the things get trickier.
First of all, we verify that it is *not* an ancestor of our directory
and fail the lookup if it is. Then we try to lock the filesystem and the
current parent of the alias. If either trylock fails, we fail the lookup.
If trylocks succeed, we detach the alias from its current parent and
attach to our directory, under the name we are looking for.
Note that splicing does *not* involve any modification of the filesystem;
all we change is the view in dcache. Moreover, holding a directory locked
exclusive prevents such changes involving its children and holding the
filesystem lock prevents any changes of tree topology, other than having a
root of one tree becoming a child of directory in another. In particular,
if two dentries have been found to have a common ancestor after taking
the filesystem lock, their relationship will remain unchanged until
the lock is dropped. So from the directory operations' point of view
splicing is almost irrelevant - the only place where it matters is one
step in cross-directory renames; we need to be careful when checking if
parents have a common ancestor.
Multiple-filesystem stuff
=========================
For some filesystems a method can involve a directory operation on
another filesystem; it may be ecryptfs doing operation in the underlying
filesystem, overlayfs doing something to the layers, network filesystem
using a local one as a cache, etc. In all such cases the operations
on other filesystems must follow the same locking rules. Moreover, "a
directory operation on this filesystem might involve directory operations
on that filesystem" should be an asymmetric relation (or, if you will,
it should be possible to rank the filesystems so that directory operation
on a filesystem could trigger directory operations only on higher-ranked
ones - in these terms overlayfs ranks lower than its layers, network
filesystem ranks lower than whatever it caches on, etc.)
Deadlock avoidance
==================
If no directory is its own ancestor, the scheme above is deadlock-free. If no directory is its own ancestor, the scheme above is deadlock-free.
Proof: Proof:
First of all, at any moment we have a linear ordering of the There is a ranking on the locks, such that all primitives take
objects - A < B iff (A is an ancestor of B) or (B is not an ancestor them in order of non-decreasing rank. Namely,
of A and ptr(A) < ptr(B)).
That ordering can change. However, the following is true: * rank ->i_rwsem of non-directories on given filesystem in inode pointer
order.
* put ->i_rwsem of all directories on a filesystem at the same rank,
lower than ->i_rwsem of any non-directory on the same filesystem.
* put ->s_vfs_rename_mutex at rank lower than that of any ->i_rwsem
on the same filesystem.
* among the locks on different filesystems use the relative
rank of those filesystems.
(1) if object removal or non-cross-directory rename holds lock on A and For example, if we have NFS filesystem caching on a local one, we have
attempts to acquire lock on B, A will remain the parent of B until we
acquire the lock on B. (Proof: only cross-directory rename can change
the parent of object and it would have to lock the parent).
(2) if cross-directory rename holds the lock on filesystem, order will not 1. ->s_vfs_rename_mutex of NFS filesystem
change until rename acquires all locks. (Proof: other cross-directory 2. ->i_rwsem of directories on that NFS filesystem, same rank for all
renames will be blocked on filesystem lock and we don't start changing 3. ->i_rwsem of non-directories on that filesystem, in order of
the order until we had acquired all locks). increasing address of inode
4. ->s_vfs_rename_mutex of local filesystem
5. ->i_rwsem of directories on the local filesystem, same rank for all
6. ->i_rwsem of non-directories on local filesystem, in order of
increasing address of inode.
(3) locks on non-directory objects are acquired only after locks on It's easy to verify that operations never take a lock with rank
directory objects, and are acquired in inode pointer order. lower than that of an already held lock.
(Proof: all operations but renames take lock on at most one
non-directory object, except renames, which take locks on source and
target in inode pointer order in the case they are not directories.)
Now consider the minimal deadlock. Each process is blocked on Suppose deadlocks are possible. Consider the minimal deadlocked
attempt to acquire some lock and already holds at least one lock. Let's set of threads. It is a cycle of several threads, each blocked on a lock
consider the set of contended locks. First of all, filesystem lock is held by the next thread in the cycle.
not contended, since any process blocked on it is not holding any locks.
Thus all processes are blocked on ->i_rwsem.
By (3), any process holding a non-directory lock can only be Since the locking order is consistent with the ranking, all
waiting on another non-directory lock with a larger address. Therefore contended locks in the minimal deadlock will be of the same rank,
the process holding the "largest" such lock can always make progress, and i.e. they all will be ->i_rwsem of directories on the same filesystem.
non-directory objects are not included in the set of contended locks. Moreover, without loss of generality we can assume that all operations
are done directly to that filesystem and none of them has actually
reached the method call.
Thus link creation can't be a part of deadlock - it can't be In other words, we have a cycle of threads, T1,..., Tn,
blocked on source and it means that it doesn't hold any locks. and the same number of directories (D1,...,Dn) such that
Any contended object is either held by cross-directory rename or T1 is blocked on D1 which is held by T2
has a child that is also contended. Indeed, suppose that it is held by
operation other than cross-directory rename. Then the lock this operation
is blocked on belongs to child of that object due to (1).
It means that one of the operations is cross-directory rename. T2 is blocked on D2 which is held by T3
Otherwise the set of contended objects would be infinite - each of them
would have a contended child and we had assumed that no object is its
own descendent. Moreover, there is exactly one cross-directory rename
(see above).
Consider the object blocking the cross-directory rename. One ...
of its descendents is locked by cross-directory rename (otherwise we
would again have an infinite set of contended objects). But that
means that cross-directory rename is taking locks out of order. Due
to (2) the order hadn't changed since we had acquired filesystem lock.
But locking rules for cross-directory rename guarantee that we do not
try to acquire lock on descendent before the lock on ancestor.
Contradiction. I.e. deadlock is impossible. Q.E.D.
Tn is blocked on Dn which is held by T1.
Each operation in the minimal cycle must have locked at least
one directory and blocked on attempt to lock another. That leaves
only 3 possible operations: directory removal (locks parent, then
child), same-directory rename killing a subdirectory (ditto) and
cross-directory rename of some sort.
There must be a cross-directory rename in the set; indeed,
if all operations had been of the "lock parent, then child" sort
we would have Dn a parent of D1, which is a parent of D2, which is
a parent of D3, ..., which is a parent of Dn. Relationships couldn't
have changed since the moment directory locks had been acquired,
so they would all hold simultaneously at the deadlock time and
we would have a loop.
Since all operations are on the same filesystem, there can't be
more than one cross-directory rename among them. Without loss of
generality we can assume that T1 is the one doing a cross-directory
rename and everything else is of the "lock parent, then child" sort.
In other words, we have a cross-directory rename that locked
Dn and blocked on attempt to lock D1, which is a parent of D2, which is
a parent of D3, ..., which is a parent of Dn. Relationships between
D1,...,Dn all hold simultaneously at the deadlock time. Moreover,
cross-directory rename does not get to locking any directories until it
has acquired filesystem lock and verified that directories involved have
a common ancestor, which guarantees that ancestry relationships between
all of them had been stable.
Consider the order in which directories are locked by the
cross-directory rename; parents first, then possibly their children.
Dn and D1 would have to be among those, with Dn locked before D1.
Which pair could it be?
It can't be the parents - indeed, since D1 is an ancestor of Dn,
it would be the first parent to be locked. Therefore at least one of the
children must be involved and thus neither of them could be a descendent
of another - otherwise the operation would not have progressed past
locking the parents.
It can't be a parent and its child; otherwise we would've had
a loop, since the parents are locked before the children, so the parent
would have to be a descendent of its child.
It can't be a parent and a child of another parent either.
Otherwise the child of the parent in question would've been a descendent
of another child.
That leaves only one possibility - namely, both Dn and D1 are
among the children, in some order. But that is also impossible, since
neither of the children is a descendent of another.
That concludes the proof, since the set of operations with the
properties requiered for a minimal deadlock can not exist.
Note that the check for having a common ancestor in cross-directory
rename is crucial - without it a deadlock would be possible. Indeed,
suppose the parents are initially in different trees; we would lock the
parent of source, then try to lock the parent of target, only to have
an unrelated lookup splice a distant ancestor of source to some distant
descendent of the parent of target. At that point we have cross-directory
rename holding the lock on parent of source and trying to lock its
distant ancestor. Add a bunch of rmdir() attempts on all directories
in between (all of those would fail with -ENOTEMPTY, had they ever gotten
the locks) and voila - we have a deadlock.
Loop avoidance
==============
These operations are guaranteed to avoid loop creation. Indeed, These operations are guaranteed to avoid loop creation. Indeed,
the only operation that could introduce loops is cross-directory rename. the only operation that could introduce loops is cross-directory rename.
Since the only new (parent, child) pair added by rename() is (new parent, Suppose after the operation there is a loop; since there hadn't been such
source), such loop would have to contain these objects and the rest of it loops before the operation, at least on of the nodes in that loop must've
would have to exist before rename(). I.e. at the moment of loop creation had its parent changed. In other words, the loop must be passing through
rename() responsible for that would be holding filesystem lock and new parent the source or, in case of exchange, possibly the target.
would have to be equal to or a descendent of source. But that means that
new parent had been equal to or a descendent of source since the moment when Since the operation has succeeded, neither source nor target could have
we had acquired filesystem lock and rename() would fail with -ELOOP in that been ancestors of each other. Therefore the chain of ancestors starting
case. in the parent of source could not have passed through the target and
vice versa. On the other hand, the chain of ancestors of any node could
not have passed through the node itself, or we would've had a loop before
the operation. But everything other than source and target has kept
the parent after the operation, so the operation does not change the
chains of ancestors of (ex-)parents of source and target. In particular,
those chains must end after a finite number of steps.
Now consider the loop created by the operation. It passes through either
source or target; the next node in the loop would be the ex-parent of
target or source resp. After that the loop would follow the chain of
ancestors of that parent. But as we have just shown, that chain must
end after a finite number of steps, which means that it can't be a part
of any loop. Q.E.D.
While this locking scheme works for arbitrary DAGs, it relies on While this locking scheme works for arbitrary DAGs, it relies on
ability to check that directory is a descendent of another object. Current ability to check that directory is a descendent of another object. Current

View File

@ -101,7 +101,7 @@ symlink: exclusive
mkdir: exclusive mkdir: exclusive
unlink: exclusive (both) unlink: exclusive (both)
rmdir: exclusive (both)(see below) rmdir: exclusive (both)(see below)
rename: exclusive (all) (see below) rename: exclusive (both parents, some children) (see below)
readlink: no readlink: no
get_link: no get_link: no
setattr: exclusive setattr: exclusive
@ -123,6 +123,9 @@ get_offset_ctx no
Additionally, ->rmdir(), ->unlink() and ->rename() have ->i_rwsem Additionally, ->rmdir(), ->unlink() and ->rename() have ->i_rwsem
exclusive on victim. exclusive on victim.
cross-directory ->rename() has (per-superblock) ->s_vfs_rename_sem. cross-directory ->rename() has (per-superblock) ->s_vfs_rename_sem.
->unlink() and ->rename() have ->i_rwsem exclusive on all non-directories
involved.
->rename() has ->i_rwsem exclusive on any subdirectory that changes parent.
See Documentation/filesystems/directory-locking.rst for more detailed discussion See Documentation/filesystems/directory-locking.rst for more detailed discussion
of the locking scheme for directory operations. of the locking scheme for directory operations.

View File

@ -1064,6 +1064,33 @@ generic_encode_ino32_fh() explicitly.
--- ---
**mandatory**
If ->rename() update of .. on cross-directory move needs an exclusion with
directory modifications, do *not* lock the subdirectory in question in your
->rename() - it's done by the caller now [that item should've been added in
28eceeda130f "fs: Lock moved directories"].
---
**mandatory**
On same-directory ->rename() the (tautological) update of .. is not protected
by any locks; just don't do it if the old parent is the same as the new one.
We really can't lock two subdirectories in same-directory rename - not without
deadlocks.
---
**mandatory**
lock_rename() and lock_rename_child() may fail in cross-directory case, if
their arguments do not have a common ancestor. In that case ERR_PTR(-EXDEV)
is returned, with no locks taken. In-tree users updated; out-of-tree ones
would need to do so.
---
**recommended** **recommended**
Block device freezing and thawing have been moved to holder operations. Block device freezing and thawing have been moved to holder operations.

View File

@ -305,6 +305,8 @@ try_again:
/* do the multiway lock magic */ /* do the multiway lock magic */
trap = lock_rename(cache->graveyard, dir); trap = lock_rename(cache->graveyard, dir);
if (IS_ERR(trap))
return PTR_ERR(trap);
/* do some checks before getting the grave dentry */ /* do some checks before getting the grave dentry */
if (rep->d_parent != dir || IS_DEADDIR(d_inode(rep))) { if (rep->d_parent != dir || IS_DEADDIR(d_inode(rep))) {

View File

@ -607,6 +607,8 @@ 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); trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
if (IS_ERR(trap))
return PTR_ERR(trap);
dget(lower_new_dentry); dget(lower_new_dentry);
rc = -EINVAL; rc = -EINVAL;
if (lower_old_dentry->d_parent != lower_old_dir_dentry) if (lower_old_dentry->d_parent != lower_old_dir_dentry)

View File

@ -325,6 +325,7 @@ static int ext2_rename (struct mnt_idmap * idmap,
struct ext2_dir_entry_2 * dir_de = NULL; struct ext2_dir_entry_2 * dir_de = NULL;
struct folio * old_folio; struct folio * old_folio;
struct ext2_dir_entry_2 * old_de; struct ext2_dir_entry_2 * old_de;
bool old_is_dir = S_ISDIR(old_inode->i_mode);
int err; int err;
if (flags & ~RENAME_NOREPLACE) if (flags & ~RENAME_NOREPLACE)
@ -342,7 +343,7 @@ static int ext2_rename (struct mnt_idmap * idmap,
if (IS_ERR(old_de)) if (IS_ERR(old_de))
return PTR_ERR(old_de); return PTR_ERR(old_de);
if (S_ISDIR(old_inode->i_mode)) { if (old_is_dir && old_dir != new_dir) {
err = -EIO; err = -EIO;
dir_de = ext2_dotdot(old_inode, &dir_folio); dir_de = ext2_dotdot(old_inode, &dir_folio);
if (!dir_de) if (!dir_de)
@ -354,7 +355,7 @@ static int ext2_rename (struct mnt_idmap * idmap,
struct ext2_dir_entry_2 *new_de; struct ext2_dir_entry_2 *new_de;
err = -ENOTEMPTY; err = -ENOTEMPTY;
if (dir_de && !ext2_empty_dir (new_inode)) if (old_is_dir && !ext2_empty_dir(new_inode))
goto out_dir; goto out_dir;
new_de = ext2_find_entry(new_dir, &new_dentry->d_name, new_de = ext2_find_entry(new_dir, &new_dentry->d_name,
@ -368,14 +369,14 @@ static int ext2_rename (struct mnt_idmap * idmap,
if (err) if (err)
goto out_dir; goto out_dir;
inode_set_ctime_current(new_inode); inode_set_ctime_current(new_inode);
if (dir_de) if (old_is_dir)
drop_nlink(new_inode); drop_nlink(new_inode);
inode_dec_link_count(new_inode); inode_dec_link_count(new_inode);
} else { } else {
err = ext2_add_link(new_dentry, old_inode); err = ext2_add_link(new_dentry, old_inode);
if (err) if (err)
goto out_dir; goto out_dir;
if (dir_de) if (old_is_dir)
inode_inc_link_count(new_dir); inode_inc_link_count(new_dir);
} }
@ -387,7 +388,7 @@ static int ext2_rename (struct mnt_idmap * idmap,
mark_inode_dirty(old_inode); mark_inode_dirty(old_inode);
err = ext2_delete_entry(old_de, old_folio); err = ext2_delete_entry(old_de, old_folio);
if (!err && dir_de) { if (!err && old_is_dir) {
if (old_dir != new_dir) if (old_dir != new_dir)
err = ext2_set_link(old_inode, dir_de, dir_folio, err = ext2_set_link(old_inode, dir_de, dir_folio,
new_dir, false); new_dir, false);

View File

@ -3591,10 +3591,14 @@ struct ext4_renament {
int dir_inlined; int dir_inlined;
}; };
static int ext4_rename_dir_prepare(handle_t *handle, struct ext4_renament *ent) static int ext4_rename_dir_prepare(handle_t *handle, struct ext4_renament *ent, bool is_cross)
{ {
int retval; int retval;
ent->is_dir = true;
if (!is_cross)
return 0;
ent->dir_bh = ext4_get_first_dir_block(handle, ent->inode, ent->dir_bh = ext4_get_first_dir_block(handle, ent->inode,
&retval, &ent->parent_de, &retval, &ent->parent_de,
&ent->dir_inlined); &ent->dir_inlined);
@ -3612,6 +3616,9 @@ static int ext4_rename_dir_finish(handle_t *handle, struct ext4_renament *ent,
{ {
int retval; int retval;
if (!ent->dir_bh)
return 0;
ent->parent_de->inode = cpu_to_le32(dir_ino); ent->parent_de->inode = cpu_to_le32(dir_ino);
BUFFER_TRACE(ent->dir_bh, "call ext4_handle_dirty_metadata"); BUFFER_TRACE(ent->dir_bh, "call ext4_handle_dirty_metadata");
if (!ent->dir_inlined) { if (!ent->dir_inlined) {
@ -3900,7 +3907,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,
if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir)) if (new.dir != old.dir && EXT4_DIR_LINK_MAX(new.dir))
goto end_rename; goto end_rename;
} }
retval = ext4_rename_dir_prepare(handle, &old); retval = ext4_rename_dir_prepare(handle, &old, new.dir != old.dir);
if (retval) if (retval)
goto end_rename; goto end_rename;
} }
@ -3964,7 +3971,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,
} }
inode_set_mtime_to_ts(old.dir, inode_set_ctime_current(old.dir)); inode_set_mtime_to_ts(old.dir, inode_set_ctime_current(old.dir));
ext4_update_dx_flag(old.dir); ext4_update_dx_flag(old.dir);
if (old.dir_bh) { if (old.is_dir) {
retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino); retval = ext4_rename_dir_finish(handle, &old, new.dir->i_ino);
if (retval) if (retval)
goto end_rename; goto end_rename;
@ -3987,7 +3994,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,
if (unlikely(retval)) if (unlikely(retval))
goto end_rename; goto end_rename;
if (S_ISDIR(old.inode->i_mode)) { if (old.is_dir) {
/* /*
* We disable fast commits here that's because the * We disable fast commits here that's because the
* replay code is not yet capable of changing dot dot * replay code is not yet capable of changing dot dot
@ -4114,14 +4121,12 @@ static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
ext4_handle_sync(handle); ext4_handle_sync(handle);
if (S_ISDIR(old.inode->i_mode)) { if (S_ISDIR(old.inode->i_mode)) {
old.is_dir = true; retval = ext4_rename_dir_prepare(handle, &old, new.dir != old.dir);
retval = ext4_rename_dir_prepare(handle, &old);
if (retval) if (retval)
goto end_rename; goto end_rename;
} }
if (S_ISDIR(new.inode->i_mode)) { if (S_ISDIR(new.inode->i_mode)) {
new.is_dir = true; retval = ext4_rename_dir_prepare(handle, &new, new.dir != old.dir);
retval = ext4_rename_dir_prepare(handle, &new);
if (retval) if (retval)
goto end_rename; goto end_rename;
} }

View File

@ -963,6 +963,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
struct f2fs_dir_entry *old_dir_entry = NULL; struct f2fs_dir_entry *old_dir_entry = NULL;
struct f2fs_dir_entry *old_entry; struct f2fs_dir_entry *old_entry;
struct f2fs_dir_entry *new_entry; struct f2fs_dir_entry *new_entry;
bool old_is_dir = S_ISDIR(old_inode->i_mode);
int err; int err;
if (unlikely(f2fs_cp_error(sbi))) if (unlikely(f2fs_cp_error(sbi)))
@ -1017,7 +1018,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
goto out; goto out;
} }
if (S_ISDIR(old_inode->i_mode)) { if (old_is_dir && old_dir != new_dir) {
old_dir_entry = f2fs_parent_dir(old_inode, &old_dir_page); old_dir_entry = f2fs_parent_dir(old_inode, &old_dir_page);
if (!old_dir_entry) { if (!old_dir_entry) {
if (IS_ERR(old_dir_page)) if (IS_ERR(old_dir_page))
@ -1029,7 +1030,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
if (new_inode) { if (new_inode) {
err = -ENOTEMPTY; err = -ENOTEMPTY;
if (old_dir_entry && !f2fs_empty_dir(new_inode)) if (old_is_dir && !f2fs_empty_dir(new_inode))
goto out_dir; goto out_dir;
err = -ENOENT; err = -ENOENT;
@ -1054,7 +1055,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
inode_set_ctime_current(new_inode); inode_set_ctime_current(new_inode);
f2fs_down_write(&F2FS_I(new_inode)->i_sem); f2fs_down_write(&F2FS_I(new_inode)->i_sem);
if (old_dir_entry) if (old_is_dir)
f2fs_i_links_write(new_inode, false); f2fs_i_links_write(new_inode, false);
f2fs_i_links_write(new_inode, false); f2fs_i_links_write(new_inode, false);
f2fs_up_write(&F2FS_I(new_inode)->i_sem); f2fs_up_write(&F2FS_I(new_inode)->i_sem);
@ -1074,12 +1075,12 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
goto out_dir; goto out_dir;
} }
if (old_dir_entry) if (old_is_dir)
f2fs_i_links_write(new_dir, true); f2fs_i_links_write(new_dir, true);
} }
f2fs_down_write(&F2FS_I(old_inode)->i_sem); f2fs_down_write(&F2FS_I(old_inode)->i_sem);
if (!old_dir_entry || whiteout) if (!old_is_dir || whiteout)
file_lost_pino(old_inode); file_lost_pino(old_inode);
else else
/* adjust dir's i_pino to pass fsck check */ /* adjust dir's i_pino to pass fsck check */
@ -1105,8 +1106,8 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
iput(whiteout); iput(whiteout);
} }
if (old_dir_entry) { if (old_is_dir) {
if (old_dir != new_dir && !whiteout) if (old_dir_entry && !whiteout)
f2fs_set_link(old_inode, old_dir_entry, f2fs_set_link(old_inode, old_dir_entry,
old_dir_page, new_dir); old_dir_page, new_dir);
else else

View File

@ -1088,48 +1088,6 @@ void discard_new_inode(struct inode *inode)
} }
EXPORT_SYMBOL(discard_new_inode); EXPORT_SYMBOL(discard_new_inode);
/**
* lock_two_inodes - lock two inodes (may be regular files but also dirs)
*
* Lock any non-NULL argument. The caller must make sure that if he is passing
* in two directories, one is not ancestor of the other. Zero, one or two
* objects may be locked by this function.
*
* @inode1: first inode to lock
* @inode2: second inode to lock
* @subclass1: inode lock subclass for the first lock obtained
* @subclass2: inode lock subclass for the second lock obtained
*/
void lock_two_inodes(struct inode *inode1, struct inode *inode2,
unsigned subclass1, unsigned subclass2)
{
if (!inode1 || !inode2) {
/*
* Make sure @subclass1 will be used for the acquired lock.
* This is not strictly necessary (no current caller cares) but
* let's keep things consistent.
*/
if (!inode1)
swap(inode1, inode2);
goto lock;
}
/*
* If one object is directory and the other is not, we must make sure
* to lock directory first as the other object may be its child.
*/
if (S_ISDIR(inode2->i_mode) == S_ISDIR(inode1->i_mode)) {
if (inode1 > inode2)
swap(inode1, inode2);
} else if (!S_ISDIR(inode1->i_mode))
swap(inode1, inode2);
lock:
if (inode1)
inode_lock_nested(inode1, subclass1);
if (inode2 && inode2 != inode1)
inode_lock_nested(inode2, subclass2);
}
/** /**
* lock_two_nondirectories - take two i_mutexes on non-directory objects * lock_two_nondirectories - take two i_mutexes on non-directory objects
* *
@ -1145,7 +1103,12 @@ void lock_two_nondirectories(struct inode *inode1, struct inode *inode2)
WARN_ON_ONCE(S_ISDIR(inode1->i_mode)); WARN_ON_ONCE(S_ISDIR(inode1->i_mode));
if (inode2) if (inode2)
WARN_ON_ONCE(S_ISDIR(inode2->i_mode)); WARN_ON_ONCE(S_ISDIR(inode2->i_mode));
lock_two_inodes(inode1, inode2, I_MUTEX_NORMAL, I_MUTEX_NONDIR2); if (inode1 > inode2)
swap(inode1, inode2);
if (inode1)
inode_lock(inode1);
if (inode2 && inode2 != inode1)
inode_lock_nested(inode2, I_MUTEX_NONDIR2);
} }
EXPORT_SYMBOL(lock_two_nondirectories); EXPORT_SYMBOL(lock_two_nondirectories);

View File

@ -197,8 +197,6 @@ extern long prune_icache_sb(struct super_block *sb, struct shrink_control *sc);
int dentry_needs_remove_privs(struct mnt_idmap *, struct dentry *dentry); int dentry_needs_remove_privs(struct mnt_idmap *, struct dentry *dentry);
bool in_group_or_capable(struct mnt_idmap *idmap, bool in_group_or_capable(struct mnt_idmap *idmap,
const struct inode *inode, vfsgid_t vfsgid); const struct inode *inode, vfsgid_t vfsgid);
void lock_two_inodes(struct inode *inode1, struct inode *inode2,
unsigned subclass1, unsigned subclass2);
/* /*
* fs-writeback.c * fs-writeback.c

View File

@ -3013,27 +3013,37 @@ static inline int may_create(struct mnt_idmap *idmap,
return inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC); return inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC);
} }
// p1 != p2, both are on the same filesystem, ->s_vfs_rename_mutex is held
static struct dentry *lock_two_directories(struct dentry *p1, struct dentry *p2) static struct dentry *lock_two_directories(struct dentry *p1, struct dentry *p2)
{ {
struct dentry *p; struct dentry *p = p1, *q = p2, *r;
p = d_ancestor(p2, p1); while ((r = p->d_parent) != p2 && r != p)
if (p) { p = r;
if (r == p2) {
// p is a child of p2 and an ancestor of p1 or p1 itself
inode_lock_nested(p2->d_inode, I_MUTEX_PARENT); inode_lock_nested(p2->d_inode, I_MUTEX_PARENT);
inode_lock_nested(p1->d_inode, I_MUTEX_CHILD); inode_lock_nested(p1->d_inode, I_MUTEX_PARENT2);
return p; return p;
} }
// p is the root of connected component that contains p1
p = d_ancestor(p1, p2); // p2 does not occur on the path from p to p1
if (p) { while ((r = q->d_parent) != p1 && r != p && r != q)
q = r;
if (r == p1) {
// q is a child of p1 and an ancestor of p2 or p2 itself
inode_lock_nested(p1->d_inode, I_MUTEX_PARENT); inode_lock_nested(p1->d_inode, I_MUTEX_PARENT);
inode_lock_nested(p2->d_inode, I_MUTEX_CHILD); inode_lock_nested(p2->d_inode, I_MUTEX_PARENT2);
return p; return q;
} else if (likely(r == p)) {
// both p2 and p1 are descendents of p
inode_lock_nested(p1->d_inode, I_MUTEX_PARENT);
inode_lock_nested(p2->d_inode, I_MUTEX_PARENT2);
return NULL;
} else { // no common ancestor at the time we'd been called
mutex_unlock(&p1->d_sb->s_vfs_rename_mutex);
return ERR_PTR(-EXDEV);
} }
lock_two_inodes(p1->d_inode, p2->d_inode,
I_MUTEX_PARENT, I_MUTEX_PARENT2);
return NULL;
} }
/* /*
@ -4712,11 +4722,12 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname
* *
* a) we can get into loop creation. * a) we can get into loop creation.
* b) race potential - two innocent renames can create a loop together. * b) race potential - two innocent renames can create a loop together.
* That's where 4.4 screws up. Current fix: serialization on * That's where 4.4BSD screws up. Current fix: serialization on
* sb->s_vfs_rename_mutex. We might be more accurate, but that's another * sb->s_vfs_rename_mutex. We might be more accurate, but that's another
* story. * story.
* c) we have to lock _four_ objects - parents and victim (if it exists), * c) we may have to lock up to _four_ objects - parents and victim (if it exists),
* and source. * and source (if it's a non-directory or a subdirectory that moves to
* different parent).
* And that - after we got ->i_mutex on parents (until then we don't know * And that - after we got ->i_mutex on parents (until then we don't know
* whether the target exists). Solution: try to be smart with locking * whether the target exists). Solution: try to be smart with locking
* order for inodes. We rely on the fact that tree topology may change * order for inodes. We rely on the fact that tree topology may change
@ -4748,6 +4759,7 @@ int vfs_rename(struct renamedata *rd)
bool new_is_dir = false; bool new_is_dir = false;
unsigned max_links = new_dir->i_sb->s_max_links; unsigned max_links = new_dir->i_sb->s_max_links;
struct name_snapshot old_name; struct name_snapshot old_name;
bool lock_old_subdir, lock_new_subdir;
if (source == target) if (source == target)
return 0; return 0;
@ -4801,15 +4813,32 @@ int vfs_rename(struct renamedata *rd)
take_dentry_name_snapshot(&old_name, old_dentry); take_dentry_name_snapshot(&old_name, old_dentry);
dget(new_dentry); dget(new_dentry);
/* /*
* Lock all moved children. Moved directories may need to change parent * Lock children.
* pointer so they need the lock to prevent against concurrent * The source subdirectory needs to be locked on cross-directory
* directory changes moving parent pointer. For regular files we've * rename or cross-directory exchange since its parent changes.
* historically always done this. The lockdep locking subclasses are * The target subdirectory needs to be locked on cross-directory
* somewhat arbitrary but RENAME_EXCHANGE in particular can swap * exchange due to parent change and on any rename due to becoming
* regular files and directories so it's difficult to tell which * a victim.
* subclasses to use. * Non-directories need locking in all cases (for NFS reasons);
* they get locked after any subdirectories (in inode address order).
*
* NOTE: WE ONLY LOCK UNRELATED DIRECTORIES IN CROSS-DIRECTORY CASE.
* NEVER, EVER DO THAT WITHOUT ->s_vfs_rename_mutex.
*/ */
lock_two_inodes(source, target, I_MUTEX_NORMAL, I_MUTEX_NONDIR2); lock_old_subdir = new_dir != old_dir;
lock_new_subdir = new_dir != old_dir || !(flags & RENAME_EXCHANGE);
if (is_dir) {
if (lock_old_subdir)
inode_lock_nested(source, I_MUTEX_CHILD);
if (target && (!new_is_dir || lock_new_subdir))
inode_lock(target);
} else if (new_is_dir) {
if (lock_new_subdir)
inode_lock_nested(target, I_MUTEX_CHILD);
inode_lock(source);
} else {
lock_two_nondirectories(source, target);
}
error = -EPERM; error = -EPERM;
if (IS_SWAPFILE(source) || (target && IS_SWAPFILE(target))) if (IS_SWAPFILE(source) || (target && IS_SWAPFILE(target)))
@ -4857,8 +4886,9 @@ int vfs_rename(struct renamedata *rd)
d_exchange(old_dentry, new_dentry); d_exchange(old_dentry, new_dentry);
} }
out: out:
inode_unlock(source); if (!is_dir || lock_old_subdir)
if (target) inode_unlock(source);
if (target && (!new_is_dir || lock_new_subdir))
inode_unlock(target); inode_unlock(target);
dput(new_dentry); dput(new_dentry);
if (!error) { if (!error) {
@ -4929,6 +4959,10 @@ retry:
retry_deleg: retry_deleg:
trap = lock_rename(new_path.dentry, old_path.dentry); trap = lock_rename(new_path.dentry, old_path.dentry);
if (IS_ERR(trap)) {
error = PTR_ERR(trap);
goto exit_lock_rename;
}
old_dentry = lookup_one_qstr_excl(&old_last, old_path.dentry, old_dentry = lookup_one_qstr_excl(&old_last, old_path.dentry,
lookup_flags); lookup_flags);
@ -4996,6 +5030,7 @@ exit4:
dput(old_dentry); dput(old_dentry);
exit3: exit3:
unlock_rename(new_path.dentry, old_path.dentry); unlock_rename(new_path.dentry, old_path.dentry);
exit_lock_rename:
if (delegated_inode) { if (delegated_inode) {
error = break_deleg_wait(&delegated_inode); error = break_deleg_wait(&delegated_inode);
if (!error) if (!error)

View File

@ -1831,6 +1831,10 @@ retry:
} }
trap = lock_rename(tdentry, fdentry); trap = lock_rename(tdentry, fdentry);
if (IS_ERR(trap)) {
err = (rqstp->rq_vers == 2) ? nfserr_acces : nfserr_xdev;
goto out;
}
err = fh_fill_pre_attrs(ffhp); err = fh_fill_pre_attrs(ffhp);
if (err != nfs_ok) if (err != nfs_ok)
goto out_unlock; goto out_unlock;

View File

@ -1336,7 +1336,7 @@ static int ocfs2_rename(struct mnt_idmap *idmap,
goto bail; goto bail;
} }
if (S_ISDIR(old_inode->i_mode)) { if (S_ISDIR(old_inode->i_mode) && new_dir != old_dir) {
u64 old_inode_parent; u64 old_inode_parent;
update_dot_dot = 1; update_dot_dot = 1;
@ -1353,8 +1353,7 @@ static int ocfs2_rename(struct mnt_idmap *idmap,
goto bail; goto bail;
} }
if (!new_inode && new_dir != old_dir && if (!new_inode && new_dir->i_nlink >= ocfs2_link_max(osb)) {
new_dir->i_nlink >= ocfs2_link_max(osb)) {
status = -EMLINK; status = -EMLINK;
goto bail; goto bail;
} }
@ -1601,6 +1600,9 @@ static int ocfs2_rename(struct mnt_idmap *idmap,
mlog_errno(status); mlog_errno(status);
goto bail; goto bail;
} }
}
if (S_ISDIR(old_inode->i_mode)) {
drop_nlink(old_dir); drop_nlink(old_dir);
if (new_inode) { if (new_inode) {
drop_nlink(new_inode); drop_nlink(new_inode);

View File

@ -744,7 +744,7 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
struct inode *inode; struct inode *inode;
struct inode *udir = d_inode(c->destdir), *wdir = d_inode(c->workdir); struct inode *udir = d_inode(c->destdir), *wdir = d_inode(c->workdir);
struct path path = { .mnt = ovl_upper_mnt(ofs) }; struct path path = { .mnt = ovl_upper_mnt(ofs) };
struct dentry *temp, *upper; struct dentry *temp, *upper, *trap;
struct ovl_cu_creds cc; struct ovl_cu_creds cc;
int err; int err;
struct ovl_cattr cattr = { struct ovl_cattr cattr = {
@ -781,11 +781,13 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
* temp wasn't moved before copy up completion or cleanup. * temp wasn't moved before copy up completion or cleanup.
*/ */
ovl_start_write(c->dentry); ovl_start_write(c->dentry);
if (lock_rename(c->workdir, c->destdir) != NULL || trap = lock_rename(c->workdir, c->destdir);
temp->d_parent != c->workdir) { if (trap || temp->d_parent != c->workdir) {
/* temp or workdir moved underneath us? abort without cleanup */ /* temp or workdir moved underneath us? abort without cleanup */
dput(temp); dput(temp);
err = -EIO; err = -EIO;
if (IS_ERR(trap))
goto out;
goto unlock; goto unlock;
} else if (err) { } else if (err) {
goto cleanup; goto cleanup;
@ -826,6 +828,7 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
ovl_set_flag(OVL_WHITEOUTS, inode); ovl_set_flag(OVL_WHITEOUTS, inode);
unlock: unlock:
unlock_rename(c->workdir, c->destdir); unlock_rename(c->workdir, c->destdir);
out:
ovl_end_write(c->dentry); ovl_end_write(c->dentry);
return err; return err;

View File

@ -1180,6 +1180,10 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
} }
trap = lock_rename(new_upperdir, old_upperdir); trap = lock_rename(new_upperdir, old_upperdir);
if (IS_ERR(trap)) {
err = PTR_ERR(trap);
goto out_revert_creds;
}
olddentry = ovl_lookup_upper(ofs, old->d_name.name, old_upperdir, olddentry = ovl_lookup_upper(ofs, old->d_name.name, old_upperdir,
old->d_name.len); old->d_name.len);

View File

@ -439,8 +439,10 @@ static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir)
bool ok = false; bool ok = false;
if (workdir != upperdir) { if (workdir != upperdir) {
ok = (lock_rename(workdir, upperdir) == NULL); struct dentry *trap = lock_rename(workdir, upperdir);
unlock_rename(workdir, upperdir); if (!IS_ERR(trap))
unlock_rename(workdir, upperdir);
ok = (trap == NULL);
} }
return ok; return ok;
} }

View File

@ -1198,12 +1198,17 @@ void ovl_nlink_end(struct dentry *dentry)
int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir) int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir)
{ {
struct dentry *trap;
/* Workdir should not be the same as upperdir */ /* Workdir should not be the same as upperdir */
if (workdir == upperdir) if (workdir == upperdir)
goto err; goto err;
/* Workdir should not be subdir of upperdir and vice versa */ /* Workdir should not be subdir of upperdir and vice versa */
if (lock_rename(workdir, upperdir) != NULL) trap = lock_rename(workdir, upperdir);
if (IS_ERR(trap))
goto err;
if (trap)
goto err_unlock; goto err_unlock;
return 0; return 0;

View File

@ -1324,8 +1324,8 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
struct inode *old_inode, *new_dentry_inode; struct inode *old_inode, *new_dentry_inode;
struct reiserfs_transaction_handle th; struct reiserfs_transaction_handle th;
int jbegin_count; int jbegin_count;
umode_t old_inode_mode;
unsigned long savelink = 1; unsigned long savelink = 1;
bool update_dir_parent = false;
if (flags & ~RENAME_NOREPLACE) if (flags & ~RENAME_NOREPLACE)
return -EINVAL; return -EINVAL;
@ -1375,8 +1375,7 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
return -ENOENT; return -ENOENT;
} }
old_inode_mode = old_inode->i_mode; if (S_ISDIR(old_inode->i_mode)) {
if (S_ISDIR(old_inode_mode)) {
/* /*
* make sure that directory being renamed has correct ".." * make sure that directory being renamed has correct ".."
* and that its new parent directory has not too many links * and that its new parent directory has not too many links
@ -1389,24 +1388,28 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
} }
} }
/* if (old_dir != new_dir) {
* directory is renamed, its parent directory will be changed, /*
* so find ".." entry * directory is renamed, its parent directory will be
*/ * changed, so find ".." entry
dot_dot_de.de_gen_number_bit_string = NULL; */
retval = dot_dot_de.de_gen_number_bit_string = NULL;
reiserfs_find_entry(old_inode, "..", 2, &dot_dot_entry_path, retval =
reiserfs_find_entry(old_inode, "..", 2,
&dot_dot_entry_path,
&dot_dot_de); &dot_dot_de);
pathrelse(&dot_dot_entry_path); pathrelse(&dot_dot_entry_path);
if (retval != NAME_FOUND) { if (retval != NAME_FOUND) {
reiserfs_write_unlock(old_dir->i_sb); reiserfs_write_unlock(old_dir->i_sb);
return -EIO; return -EIO;
} }
/* inode number of .. must equal old_dir->i_ino */ /* inode number of .. must equal old_dir->i_ino */
if (dot_dot_de.de_objectid != old_dir->i_ino) { if (dot_dot_de.de_objectid != old_dir->i_ino) {
reiserfs_write_unlock(old_dir->i_sb); reiserfs_write_unlock(old_dir->i_sb);
return -EIO; return -EIO;
}
update_dir_parent = true;
} }
} }
@ -1486,7 +1489,7 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
reiserfs_prepare_for_journal(old_inode->i_sb, new_de.de_bh, 1); reiserfs_prepare_for_journal(old_inode->i_sb, new_de.de_bh, 1);
if (S_ISDIR(old_inode->i_mode)) { if (update_dir_parent) {
if ((retval = if ((retval =
search_by_entry_key(new_dir->i_sb, search_by_entry_key(new_dir->i_sb,
&dot_dot_de.de_entry_key, &dot_dot_de.de_entry_key,
@ -1534,14 +1537,14 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
new_de.de_bh); new_de.de_bh);
reiserfs_restore_prepared_buffer(old_inode->i_sb, reiserfs_restore_prepared_buffer(old_inode->i_sb,
old_de.de_bh); old_de.de_bh);
if (S_ISDIR(old_inode_mode)) if (update_dir_parent)
reiserfs_restore_prepared_buffer(old_inode-> reiserfs_restore_prepared_buffer(old_inode->
i_sb, i_sb,
dot_dot_de. dot_dot_de.
de_bh); de_bh);
continue; continue;
} }
if (S_ISDIR(old_inode_mode)) { if (update_dir_parent) {
if (item_moved(&dot_dot_ih, &dot_dot_entry_path) || if (item_moved(&dot_dot_ih, &dot_dot_entry_path) ||
!entry_points_to_object("..", 2, &dot_dot_de, !entry_points_to_object("..", 2, &dot_dot_de,
old_dir)) { old_dir)) {
@ -1559,7 +1562,7 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
} }
} }
RFALSE(S_ISDIR(old_inode_mode) && RFALSE(update_dir_parent &&
!buffer_journal_prepared(dot_dot_de.de_bh), ""); !buffer_journal_prepared(dot_dot_de.de_bh), "");
break; break;
@ -1592,11 +1595,12 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
savelink = new_dentry_inode->i_nlink; savelink = new_dentry_inode->i_nlink;
} }
if (S_ISDIR(old_inode_mode)) { if (update_dir_parent) {
/* adjust ".." of renamed directory */ /* adjust ".." of renamed directory */
set_ino_in_dir_entry(&dot_dot_de, INODE_PKEY(new_dir)); set_ino_in_dir_entry(&dot_dot_de, INODE_PKEY(new_dir));
journal_mark_dirty(&th, dot_dot_de.de_bh); journal_mark_dirty(&th, dot_dot_de.de_bh);
}
if (S_ISDIR(old_inode->i_mode)) {
/* /*
* there (in new_dir) was no directory, so it got new link * there (in new_dir) was no directory, so it got new link
* (".." of renamed directory) * (".." of renamed directory)

View File

@ -715,6 +715,10 @@ retry:
goto out2; goto out2;
trap = lock_rename_child(old_child, new_path.dentry); trap = lock_rename_child(old_child, new_path.dentry);
if (IS_ERR(trap)) {
err = PTR_ERR(trap);
goto out_drop_write;
}
old_parent = dget(old_child->d_parent); old_parent = dget(old_child->d_parent);
if (d_unhashed(old_child)) { if (d_unhashed(old_child)) {
@ -777,6 +781,7 @@ out4:
out3: out3:
dput(old_parent); dput(old_parent);
unlock_rename(old_parent, new_path.dentry); unlock_rename(old_parent, new_path.dentry);
out_drop_write:
mnt_drop_write(old_path->mnt); mnt_drop_write(old_path->mnt);
out2: out2:
path_put(&new_path); path_put(&new_path);

View File

@ -766,7 +766,7 @@ static int udf_rename(struct mnt_idmap *idmap, struct inode *old_dir,
struct inode *old_inode = d_inode(old_dentry); struct inode *old_inode = d_inode(old_dentry);
struct inode *new_inode = d_inode(new_dentry); struct inode *new_inode = d_inode(new_dentry);
struct udf_fileident_iter oiter, niter, diriter; struct udf_fileident_iter oiter, niter, diriter;
bool has_diriter = false; bool has_diriter = false, is_dir = false;
int retval; int retval;
struct kernel_lb_addr tloc; struct kernel_lb_addr tloc;
@ -789,6 +789,9 @@ static int udf_rename(struct mnt_idmap *idmap, struct inode *old_dir,
if (!empty_dir(new_inode)) if (!empty_dir(new_inode))
goto out_oiter; goto out_oiter;
} }
is_dir = true;
}
if (is_dir && old_dir != new_dir) {
retval = udf_fiiter_find_entry(old_inode, &dotdot_name, retval = udf_fiiter_find_entry(old_inode, &dotdot_name,
&diriter); &diriter);
if (retval == -ENOENT) { if (retval == -ENOENT) {
@ -878,7 +881,9 @@ static int udf_rename(struct mnt_idmap *idmap, struct inode *old_dir,
udf_dir_entry_len(&diriter.fi)); udf_dir_entry_len(&diriter.fi));
udf_fiiter_write_fi(&diriter, NULL); udf_fiiter_write_fi(&diriter, NULL);
udf_fiiter_release(&diriter); udf_fiiter_release(&diriter);
}
if (is_dir) {
inode_dec_link_count(old_dir); inode_dec_link_count(old_dir);
if (new_inode) if (new_inode)
inode_dec_link_count(new_inode); inode_dec_link_count(new_inode);