mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2024-12-29 09:12:07 +00:00
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:
commit
bf4e7080ae
@ -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
|
||||
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.
|
||||
The lock is taken shared.
|
||||
Primitives
|
||||
==========
|
||||
|
||||
2) object creation. Locking rules: same as above, but the lock is taken
|
||||
exclusive.
|
||||
For our purposes all operations fall in 6 classes:
|
||||
|
||||
3) object removal. Locking rules: caller locks parent, finds victim,
|
||||
locks victim and calls the method. Locks are exclusive.
|
||||
1. read access. Locking rules:
|
||||
|
||||
4) rename() that is _not_ cross-directory. Locking rules: caller locks the
|
||||
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.
|
||||
* lock the directory we are accessing (shared)
|
||||
|
||||
5) link creation. Locking rules:
|
||||
2. object creation. Locking rules:
|
||||
|
||||
* lock parent
|
||||
* check that source is not a directory
|
||||
* lock source
|
||||
* call the method.
|
||||
* lock the directory we are accessing (exclusive)
|
||||
|
||||
All locks are exclusive.
|
||||
3. object removal. Locking rules:
|
||||
|
||||
6) cross-directory rename. The trickiest in the whole bunch. Locking
|
||||
rules:
|
||||
* lock the parent (exclusive)
|
||||
* 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 parents in "ancestors first" order. If one is not ancestor of
|
||||
the other, lock them in inode pointer order.
|
||||
* find source and target.
|
||||
* if old parent is equal to or is a descendent of target
|
||||
fail with -ENOTEMPTY
|
||||
* if new parent is equal to or is a descendent of source
|
||||
fail with -ELOOP
|
||||
* Lock both the source and the target provided they exist. If we
|
||||
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.
|
||||
* if the parents don't have a common ancestor, fail the operation.
|
||||
* lock the parents in "ancestors first" order (exclusive). If neither is an
|
||||
ancestor of the other, lock the parent of source first.
|
||||
* find the source and target.
|
||||
* verify that the source is not a descendent of the target and
|
||||
target is not a descendent of source; fail the operation otherwise.
|
||||
* lock the subdirectories involved (exclusive), source before target.
|
||||
* lock the non-directories involved (exclusive), in inode pointer order.
|
||||
|
||||
All ->i_rwsem are taken exclusive. Again, we might get away with locking
|
||||
the source (and target in exchange case) shared.
|
||||
The rules above obviously guarantee that all directories that are going
|
||||
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.
|
||||
|
||||
Proof:
|
||||
|
||||
First of all, at any moment we have a linear ordering of the
|
||||
objects - A < B iff (A is an ancestor of B) or (B is not an ancestor
|
||||
of A and ptr(A) < ptr(B)).
|
||||
There is a ranking on the locks, such that all primitives take
|
||||
them in order of non-decreasing rank. Namely,
|
||||
|
||||
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
|
||||
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).
|
||||
For example, if we have NFS filesystem caching on a local one, we have
|
||||
|
||||
(2) if cross-directory rename holds the lock on filesystem, order will not
|
||||
change until rename acquires all locks. (Proof: other cross-directory
|
||||
renames will be blocked on filesystem lock and we don't start changing
|
||||
the order until we had acquired all locks).
|
||||
1. ->s_vfs_rename_mutex of NFS filesystem
|
||||
2. ->i_rwsem of directories on that NFS filesystem, same rank for all
|
||||
3. ->i_rwsem of non-directories on that filesystem, in order of
|
||||
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
|
||||
directory objects, and are acquired in inode pointer order.
|
||||
(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.)
|
||||
It's easy to verify that operations never take a lock with rank
|
||||
lower than that of an already held lock.
|
||||
|
||||
Now consider the minimal deadlock. Each process is blocked on
|
||||
attempt to acquire some lock and already holds at least one lock. Let's
|
||||
consider the set of contended locks. First of all, filesystem lock is
|
||||
not contended, since any process blocked on it is not holding any locks.
|
||||
Thus all processes are blocked on ->i_rwsem.
|
||||
Suppose deadlocks are possible. Consider the minimal deadlocked
|
||||
set of threads. It is a cycle of several threads, each blocked on a lock
|
||||
held by the next thread in the cycle.
|
||||
|
||||
By (3), any process holding a non-directory lock can only be
|
||||
waiting on another non-directory lock with a larger address. Therefore
|
||||
the process holding the "largest" such lock can always make progress, and
|
||||
non-directory objects are not included in the set of contended locks.
|
||||
Since the locking order is consistent with the ranking, all
|
||||
contended locks in the minimal deadlock will be of the same rank,
|
||||
i.e. they all will be ->i_rwsem of directories on the same filesystem.
|
||||
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
|
||||
blocked on source and it means that it doesn't hold any locks.
|
||||
In other words, we have a cycle of threads, T1,..., Tn,
|
||||
and the same number of directories (D1,...,Dn) such that
|
||||
|
||||
Any contended object is either held by cross-directory rename or
|
||||
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).
|
||||
T1 is blocked on D1 which is held by T2
|
||||
|
||||
It means that one of the operations is cross-directory rename.
|
||||
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).
|
||||
T2 is blocked on D2 which is held by T3
|
||||
|
||||
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,
|
||||
the only operation that could introduce loops is cross-directory rename.
|
||||
Since the only new (parent, child) pair added by rename() is (new parent,
|
||||
source), such loop would have to contain these objects and the rest of it
|
||||
would have to exist before rename(). I.e. at the moment of loop creation
|
||||
rename() responsible for that would be holding filesystem lock and new parent
|
||||
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
|
||||
we had acquired filesystem lock and rename() would fail with -ELOOP in that
|
||||
case.
|
||||
Suppose after the operation there is a loop; since there hadn't been such
|
||||
loops before the operation, at least on of the nodes in that loop must've
|
||||
had its parent changed. In other words, the loop must be passing through
|
||||
the source or, in case of exchange, possibly the target.
|
||||
|
||||
Since the operation has succeeded, neither source nor target could have
|
||||
been ancestors of each other. Therefore the chain of ancestors starting
|
||||
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
|
||||
ability to check that directory is a descendent of another object. Current
|
||||
|
@ -101,7 +101,7 @@ symlink: exclusive
|
||||
mkdir: exclusive
|
||||
unlink: exclusive (both)
|
||||
rmdir: exclusive (both)(see below)
|
||||
rename: exclusive (all) (see below)
|
||||
rename: exclusive (both parents, some children) (see below)
|
||||
readlink: no
|
||||
get_link: no
|
||||
setattr: exclusive
|
||||
@ -123,6 +123,9 @@ get_offset_ctx no
|
||||
Additionally, ->rmdir(), ->unlink() and ->rename() have ->i_rwsem
|
||||
exclusive on victim.
|
||||
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
|
||||
of the locking scheme for directory operations.
|
||||
|
@ -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**
|
||||
|
||||
Block device freezing and thawing have been moved to holder operations.
|
||||
|
@ -305,6 +305,8 @@ int cachefiles_bury_object(struct cachefiles_cache *cache,
|
||||
|
||||
/* do the multiway lock magic */
|
||||
trap = lock_rename(cache->graveyard, dir);
|
||||
if (IS_ERR(trap))
|
||||
return PTR_ERR(trap);
|
||||
|
||||
/* do some checks before getting the grave dentry */
|
||||
if (rep->d_parent != dir || IS_DEADDIR(d_inode(rep))) {
|
||||
|
@ -607,6 +607,8 @@ ecryptfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
target_inode = d_inode(new_dentry);
|
||||
|
||||
trap = lock_rename(lower_old_dir_dentry, lower_new_dir_dentry);
|
||||
if (IS_ERR(trap))
|
||||
return PTR_ERR(trap);
|
||||
dget(lower_new_dentry);
|
||||
rc = -EINVAL;
|
||||
if (lower_old_dentry->d_parent != lower_old_dir_dentry)
|
||||
|
@ -325,6 +325,7 @@ static int ext2_rename (struct mnt_idmap * idmap,
|
||||
struct ext2_dir_entry_2 * dir_de = NULL;
|
||||
struct folio * old_folio;
|
||||
struct ext2_dir_entry_2 * old_de;
|
||||
bool old_is_dir = S_ISDIR(old_inode->i_mode);
|
||||
int err;
|
||||
|
||||
if (flags & ~RENAME_NOREPLACE)
|
||||
@ -342,7 +343,7 @@ static int ext2_rename (struct mnt_idmap * idmap,
|
||||
if (IS_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;
|
||||
dir_de = ext2_dotdot(old_inode, &dir_folio);
|
||||
if (!dir_de)
|
||||
@ -354,7 +355,7 @@ static int ext2_rename (struct mnt_idmap * idmap,
|
||||
struct ext2_dir_entry_2 *new_de;
|
||||
|
||||
err = -ENOTEMPTY;
|
||||
if (dir_de && !ext2_empty_dir (new_inode))
|
||||
if (old_is_dir && !ext2_empty_dir(new_inode))
|
||||
goto out_dir;
|
||||
|
||||
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)
|
||||
goto out_dir;
|
||||
inode_set_ctime_current(new_inode);
|
||||
if (dir_de)
|
||||
if (old_is_dir)
|
||||
drop_nlink(new_inode);
|
||||
inode_dec_link_count(new_inode);
|
||||
} else {
|
||||
err = ext2_add_link(new_dentry, old_inode);
|
||||
if (err)
|
||||
goto out_dir;
|
||||
if (dir_de)
|
||||
if (old_is_dir)
|
||||
inode_inc_link_count(new_dir);
|
||||
}
|
||||
|
||||
@ -387,7 +388,7 @@ static int ext2_rename (struct mnt_idmap * idmap,
|
||||
mark_inode_dirty(old_inode);
|
||||
|
||||
err = ext2_delete_entry(old_de, old_folio);
|
||||
if (!err && dir_de) {
|
||||
if (!err && old_is_dir) {
|
||||
if (old_dir != new_dir)
|
||||
err = ext2_set_link(old_inode, dir_de, dir_folio,
|
||||
new_dir, false);
|
||||
|
@ -3591,10 +3591,14 @@ struct ext4_renament {
|
||||
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;
|
||||
|
||||
ent->is_dir = true;
|
||||
if (!is_cross)
|
||||
return 0;
|
||||
|
||||
ent->dir_bh = ext4_get_first_dir_block(handle, ent->inode,
|
||||
&retval, &ent->parent_de,
|
||||
&ent->dir_inlined);
|
||||
@ -3612,6 +3616,9 @@ static int ext4_rename_dir_finish(handle_t *handle, struct ext4_renament *ent,
|
||||
{
|
||||
int retval;
|
||||
|
||||
if (!ent->dir_bh)
|
||||
return 0;
|
||||
|
||||
ent->parent_de->inode = cpu_to_le32(dir_ino);
|
||||
BUFFER_TRACE(ent->dir_bh, "call ext4_handle_dirty_metadata");
|
||||
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))
|
||||
goto end_rename;
|
||||
}
|
||||
retval = ext4_rename_dir_prepare(handle, &old);
|
||||
retval = ext4_rename_dir_prepare(handle, &old, new.dir != old.dir);
|
||||
if (retval)
|
||||
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));
|
||||
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);
|
||||
if (retval)
|
||||
goto end_rename;
|
||||
@ -3987,7 +3994,7 @@ static int ext4_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
if (unlikely(retval))
|
||||
goto end_rename;
|
||||
|
||||
if (S_ISDIR(old.inode->i_mode)) {
|
||||
if (old.is_dir) {
|
||||
/*
|
||||
* We disable fast commits here that's because the
|
||||
* 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);
|
||||
|
||||
if (S_ISDIR(old.inode->i_mode)) {
|
||||
old.is_dir = true;
|
||||
retval = ext4_rename_dir_prepare(handle, &old);
|
||||
retval = ext4_rename_dir_prepare(handle, &old, new.dir != old.dir);
|
||||
if (retval)
|
||||
goto end_rename;
|
||||
}
|
||||
if (S_ISDIR(new.inode->i_mode)) {
|
||||
new.is_dir = true;
|
||||
retval = ext4_rename_dir_prepare(handle, &new);
|
||||
retval = ext4_rename_dir_prepare(handle, &new, new.dir != old.dir);
|
||||
if (retval)
|
||||
goto end_rename;
|
||||
}
|
||||
|
@ -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_entry;
|
||||
struct f2fs_dir_entry *new_entry;
|
||||
bool old_is_dir = S_ISDIR(old_inode->i_mode);
|
||||
int err;
|
||||
|
||||
if (unlikely(f2fs_cp_error(sbi)))
|
||||
@ -1017,7 +1018,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
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);
|
||||
if (!old_dir_entry) {
|
||||
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) {
|
||||
|
||||
err = -ENOTEMPTY;
|
||||
if (old_dir_entry && !f2fs_empty_dir(new_inode))
|
||||
if (old_is_dir && !f2fs_empty_dir(new_inode))
|
||||
goto out_dir;
|
||||
|
||||
err = -ENOENT;
|
||||
@ -1054,7 +1055,7 @@ static int f2fs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
|
||||
|
||||
inode_set_ctime_current(new_inode);
|
||||
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_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;
|
||||
}
|
||||
|
||||
if (old_dir_entry)
|
||||
if (old_is_dir)
|
||||
f2fs_i_links_write(new_dir, true);
|
||||
}
|
||||
|
||||
f2fs_down_write(&F2FS_I(old_inode)->i_sem);
|
||||
if (!old_dir_entry || whiteout)
|
||||
if (!old_is_dir || whiteout)
|
||||
file_lost_pino(old_inode);
|
||||
else
|
||||
/* 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);
|
||||
}
|
||||
|
||||
if (old_dir_entry) {
|
||||
if (old_dir != new_dir && !whiteout)
|
||||
if (old_is_dir) {
|
||||
if (old_dir_entry && !whiteout)
|
||||
f2fs_set_link(old_inode, old_dir_entry,
|
||||
old_dir_page, new_dir);
|
||||
else
|
||||
|
49
fs/inode.c
49
fs/inode.c
@ -1088,48 +1088,6 @@ void discard_new_inode(struct inode *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
|
||||
*
|
||||
@ -1145,7 +1103,12 @@ void lock_two_nondirectories(struct inode *inode1, struct inode *inode2)
|
||||
WARN_ON_ONCE(S_ISDIR(inode1->i_mode));
|
||||
if (inode2)
|
||||
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);
|
||||
|
||||
|
@ -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);
|
||||
bool in_group_or_capable(struct mnt_idmap *idmap,
|
||||
const struct inode *inode, vfsgid_t vfsgid);
|
||||
void lock_two_inodes(struct inode *inode1, struct inode *inode2,
|
||||
unsigned subclass1, unsigned subclass2);
|
||||
|
||||
/*
|
||||
* fs-writeback.c
|
||||
|
87
fs/namei.c
87
fs/namei.c
@ -3013,27 +3013,37 @@ static inline int may_create(struct mnt_idmap *idmap,
|
||||
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)
|
||||
{
|
||||
struct dentry *p;
|
||||
struct dentry *p = p1, *q = p2, *r;
|
||||
|
||||
p = d_ancestor(p2, p1);
|
||||
if (p) {
|
||||
while ((r = p->d_parent) != p2 && r != 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(p1->d_inode, I_MUTEX_CHILD);
|
||||
inode_lock_nested(p1->d_inode, I_MUTEX_PARENT2);
|
||||
return p;
|
||||
}
|
||||
|
||||
p = d_ancestor(p1, p2);
|
||||
if (p) {
|
||||
// p is the root of connected component that contains p1
|
||||
// p2 does not occur on the path from p to p1
|
||||
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(p2->d_inode, I_MUTEX_CHILD);
|
||||
return p;
|
||||
inode_lock_nested(p2->d_inode, I_MUTEX_PARENT2);
|
||||
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.
|
||||
* 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
|
||||
* story.
|
||||
* c) we have to lock _four_ objects - parents and victim (if it exists),
|
||||
* and source.
|
||||
* c) we may have to lock up to _four_ objects - parents and victim (if it exists),
|
||||
* 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
|
||||
* whether the target exists). Solution: try to be smart with locking
|
||||
* 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;
|
||||
unsigned max_links = new_dir->i_sb->s_max_links;
|
||||
struct name_snapshot old_name;
|
||||
bool lock_old_subdir, lock_new_subdir;
|
||||
|
||||
if (source == target)
|
||||
return 0;
|
||||
@ -4801,15 +4813,32 @@ int vfs_rename(struct renamedata *rd)
|
||||
take_dentry_name_snapshot(&old_name, old_dentry);
|
||||
dget(new_dentry);
|
||||
/*
|
||||
* Lock all moved children. Moved directories may need to change parent
|
||||
* pointer so they need the lock to prevent against concurrent
|
||||
* directory changes moving parent pointer. For regular files we've
|
||||
* historically always done this. The lockdep locking subclasses are
|
||||
* somewhat arbitrary but RENAME_EXCHANGE in particular can swap
|
||||
* regular files and directories so it's difficult to tell which
|
||||
* subclasses to use.
|
||||
* Lock children.
|
||||
* The source subdirectory needs to be locked on cross-directory
|
||||
* rename or cross-directory exchange since its parent changes.
|
||||
* The target subdirectory needs to be locked on cross-directory
|
||||
* exchange due to parent change and on any rename due to becoming
|
||||
* a victim.
|
||||
* 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;
|
||||
if (IS_SWAPFILE(source) || (target && IS_SWAPFILE(target)))
|
||||
@ -4857,8 +4886,9 @@ int vfs_rename(struct renamedata *rd)
|
||||
d_exchange(old_dentry, new_dentry);
|
||||
}
|
||||
out:
|
||||
inode_unlock(source);
|
||||
if (target)
|
||||
if (!is_dir || lock_old_subdir)
|
||||
inode_unlock(source);
|
||||
if (target && (!new_is_dir || lock_new_subdir))
|
||||
inode_unlock(target);
|
||||
dput(new_dentry);
|
||||
if (!error) {
|
||||
@ -4929,6 +4959,10 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
|
||||
|
||||
retry_deleg:
|
||||
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,
|
||||
lookup_flags);
|
||||
@ -4996,6 +5030,7 @@ int do_renameat2(int olddfd, struct filename *from, int newdfd,
|
||||
dput(old_dentry);
|
||||
exit3:
|
||||
unlock_rename(new_path.dentry, old_path.dentry);
|
||||
exit_lock_rename:
|
||||
if (delegated_inode) {
|
||||
error = break_deleg_wait(&delegated_inode);
|
||||
if (!error)
|
||||
|
@ -1831,6 +1831,10 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen,
|
||||
}
|
||||
|
||||
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);
|
||||
if (err != nfs_ok)
|
||||
goto out_unlock;
|
||||
|
@ -1336,7 +1336,7 @@ static int ocfs2_rename(struct mnt_idmap *idmap,
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (S_ISDIR(old_inode->i_mode)) {
|
||||
if (S_ISDIR(old_inode->i_mode) && new_dir != old_dir) {
|
||||
u64 old_inode_parent;
|
||||
|
||||
update_dot_dot = 1;
|
||||
@ -1353,8 +1353,7 @@ static int ocfs2_rename(struct mnt_idmap *idmap,
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (!new_inode && new_dir != old_dir &&
|
||||
new_dir->i_nlink >= ocfs2_link_max(osb)) {
|
||||
if (!new_inode && new_dir->i_nlink >= ocfs2_link_max(osb)) {
|
||||
status = -EMLINK;
|
||||
goto bail;
|
||||
}
|
||||
@ -1601,6 +1600,9 @@ static int ocfs2_rename(struct mnt_idmap *idmap,
|
||||
mlog_errno(status);
|
||||
goto bail;
|
||||
}
|
||||
}
|
||||
|
||||
if (S_ISDIR(old_inode->i_mode)) {
|
||||
drop_nlink(old_dir);
|
||||
if (new_inode) {
|
||||
drop_nlink(new_inode);
|
||||
|
@ -744,7 +744,7 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
|
||||
struct inode *inode;
|
||||
struct inode *udir = d_inode(c->destdir), *wdir = d_inode(c->workdir);
|
||||
struct path path = { .mnt = ovl_upper_mnt(ofs) };
|
||||
struct dentry *temp, *upper;
|
||||
struct dentry *temp, *upper, *trap;
|
||||
struct ovl_cu_creds cc;
|
||||
int err;
|
||||
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.
|
||||
*/
|
||||
ovl_start_write(c->dentry);
|
||||
if (lock_rename(c->workdir, c->destdir) != NULL ||
|
||||
temp->d_parent != c->workdir) {
|
||||
trap = lock_rename(c->workdir, c->destdir);
|
||||
if (trap || temp->d_parent != c->workdir) {
|
||||
/* temp or workdir moved underneath us? abort without cleanup */
|
||||
dput(temp);
|
||||
err = -EIO;
|
||||
if (IS_ERR(trap))
|
||||
goto out;
|
||||
goto unlock;
|
||||
} else if (err) {
|
||||
goto cleanup;
|
||||
@ -826,6 +828,7 @@ static int ovl_copy_up_workdir(struct ovl_copy_up_ctx *c)
|
||||
ovl_set_flag(OVL_WHITEOUTS, inode);
|
||||
unlock:
|
||||
unlock_rename(c->workdir, c->destdir);
|
||||
out:
|
||||
ovl_end_write(c->dentry);
|
||||
|
||||
return err;
|
||||
|
@ -1180,6 +1180,10 @@ static int ovl_rename(struct mnt_idmap *idmap, struct inode *olddir,
|
||||
}
|
||||
|
||||
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,
|
||||
old->d_name.len);
|
||||
|
@ -439,8 +439,10 @@ static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir)
|
||||
bool ok = false;
|
||||
|
||||
if (workdir != upperdir) {
|
||||
ok = (lock_rename(workdir, upperdir) == NULL);
|
||||
unlock_rename(workdir, upperdir);
|
||||
struct dentry *trap = lock_rename(workdir, upperdir);
|
||||
if (!IS_ERR(trap))
|
||||
unlock_rename(workdir, upperdir);
|
||||
ok = (trap == NULL);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
@ -1198,12 +1198,17 @@ void ovl_nlink_end(struct dentry *dentry)
|
||||
|
||||
int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir)
|
||||
{
|
||||
struct dentry *trap;
|
||||
|
||||
/* Workdir should not be the same as upperdir */
|
||||
if (workdir == upperdir)
|
||||
goto err;
|
||||
|
||||
/* 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;
|
||||
|
||||
return 0;
|
||||
|
@ -1324,8 +1324,8 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
|
||||
struct inode *old_inode, *new_dentry_inode;
|
||||
struct reiserfs_transaction_handle th;
|
||||
int jbegin_count;
|
||||
umode_t old_inode_mode;
|
||||
unsigned long savelink = 1;
|
||||
bool update_dir_parent = false;
|
||||
|
||||
if (flags & ~RENAME_NOREPLACE)
|
||||
return -EINVAL;
|
||||
@ -1375,8 +1375,7 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
old_inode_mode = old_inode->i_mode;
|
||||
if (S_ISDIR(old_inode_mode)) {
|
||||
if (S_ISDIR(old_inode->i_mode)) {
|
||||
/*
|
||||
* make sure that directory being renamed has correct ".."
|
||||
* and that its new parent directory has not too many links
|
||||
@ -1389,24 +1388,28 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* directory is renamed, its parent directory will be changed,
|
||||
* so find ".." entry
|
||||
*/
|
||||
dot_dot_de.de_gen_number_bit_string = NULL;
|
||||
retval =
|
||||
reiserfs_find_entry(old_inode, "..", 2, &dot_dot_entry_path,
|
||||
if (old_dir != new_dir) {
|
||||
/*
|
||||
* directory is renamed, its parent directory will be
|
||||
* changed, so find ".." entry
|
||||
*/
|
||||
dot_dot_de.de_gen_number_bit_string = NULL;
|
||||
retval =
|
||||
reiserfs_find_entry(old_inode, "..", 2,
|
||||
&dot_dot_entry_path,
|
||||
&dot_dot_de);
|
||||
pathrelse(&dot_dot_entry_path);
|
||||
if (retval != NAME_FOUND) {
|
||||
reiserfs_write_unlock(old_dir->i_sb);
|
||||
return -EIO;
|
||||
}
|
||||
pathrelse(&dot_dot_entry_path);
|
||||
if (retval != NAME_FOUND) {
|
||||
reiserfs_write_unlock(old_dir->i_sb);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* inode number of .. must equal old_dir->i_ino */
|
||||
if (dot_dot_de.de_objectid != old_dir->i_ino) {
|
||||
reiserfs_write_unlock(old_dir->i_sb);
|
||||
return -EIO;
|
||||
/* inode number of .. must equal old_dir->i_ino */
|
||||
if (dot_dot_de.de_objectid != old_dir->i_ino) {
|
||||
reiserfs_write_unlock(old_dir->i_sb);
|
||||
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);
|
||||
|
||||
if (S_ISDIR(old_inode->i_mode)) {
|
||||
if (update_dir_parent) {
|
||||
if ((retval =
|
||||
search_by_entry_key(new_dir->i_sb,
|
||||
&dot_dot_de.de_entry_key,
|
||||
@ -1534,14 +1537,14 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
|
||||
new_de.de_bh);
|
||||
reiserfs_restore_prepared_buffer(old_inode->i_sb,
|
||||
old_de.de_bh);
|
||||
if (S_ISDIR(old_inode_mode))
|
||||
if (update_dir_parent)
|
||||
reiserfs_restore_prepared_buffer(old_inode->
|
||||
i_sb,
|
||||
dot_dot_de.
|
||||
de_bh);
|
||||
continue;
|
||||
}
|
||||
if (S_ISDIR(old_inode_mode)) {
|
||||
if (update_dir_parent) {
|
||||
if (item_moved(&dot_dot_ih, &dot_dot_entry_path) ||
|
||||
!entry_points_to_object("..", 2, &dot_dot_de,
|
||||
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), "");
|
||||
|
||||
break;
|
||||
@ -1592,11 +1595,12 @@ static int reiserfs_rename(struct mnt_idmap *idmap,
|
||||
savelink = new_dentry_inode->i_nlink;
|
||||
}
|
||||
|
||||
if (S_ISDIR(old_inode_mode)) {
|
||||
if (update_dir_parent) {
|
||||
/* adjust ".." of renamed directory */
|
||||
set_ino_in_dir_entry(&dot_dot_de, INODE_PKEY(new_dir));
|
||||
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
|
||||
* (".." of renamed directory)
|
||||
|
@ -715,6 +715,10 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
|
||||
goto out2;
|
||||
|
||||
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);
|
||||
if (d_unhashed(old_child)) {
|
||||
@ -777,6 +781,7 @@ int ksmbd_vfs_rename(struct ksmbd_work *work, const struct path *old_path,
|
||||
out3:
|
||||
dput(old_parent);
|
||||
unlock_rename(old_parent, new_path.dentry);
|
||||
out_drop_write:
|
||||
mnt_drop_write(old_path->mnt);
|
||||
out2:
|
||||
path_put(&new_path);
|
||||
|
@ -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 *new_inode = d_inode(new_dentry);
|
||||
struct udf_fileident_iter oiter, niter, diriter;
|
||||
bool has_diriter = false;
|
||||
bool has_diriter = false, is_dir = false;
|
||||
int retval;
|
||||
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))
|
||||
goto out_oiter;
|
||||
}
|
||||
is_dir = true;
|
||||
}
|
||||
if (is_dir && old_dir != new_dir) {
|
||||
retval = udf_fiiter_find_entry(old_inode, &dotdot_name,
|
||||
&diriter);
|
||||
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_fiiter_write_fi(&diriter, NULL);
|
||||
udf_fiiter_release(&diriter);
|
||||
}
|
||||
|
||||
if (is_dir) {
|
||||
inode_dec_link_count(old_dir);
|
||||
if (new_inode)
|
||||
inode_dec_link_count(new_inode);
|
||||
|
Loading…
Reference in New Issue
Block a user