|
@@ -4022,7 +4022,8 @@ out:
|
|
|
}
|
|
|
|
|
|
static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry,
|
|
|
- struct inode *new_dir, struct dentry *new_dentry)
|
|
|
+ struct inode *new_dir, struct dentry *new_dentry,
|
|
|
+ struct inode **delegated_inode)
|
|
|
{
|
|
|
struct inode *target = new_dentry->d_inode;
|
|
|
struct inode *source = old_dentry->d_inode;
|
|
@@ -4039,6 +4040,14 @@ static int vfs_rename_other(struct inode *old_dir, struct dentry *old_dentry,
|
|
|
if (d_mountpoint(old_dentry)||d_mountpoint(new_dentry))
|
|
|
goto out;
|
|
|
|
|
|
+ error = try_break_deleg(source, delegated_inode);
|
|
|
+ if (error)
|
|
|
+ goto out;
|
|
|
+ if (target) {
|
|
|
+ error = try_break_deleg(target, delegated_inode);
|
|
|
+ if (error)
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
error = old_dir->i_op->rename(old_dir, old_dentry, new_dir, new_dentry);
|
|
|
if (error)
|
|
|
goto out;
|
|
@@ -4053,8 +4062,30 @@ out:
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * vfs_rename - rename a filesystem object
|
|
|
+ * @old_dir: parent of source
|
|
|
+ * @old_dentry: source
|
|
|
+ * @new_dir: parent of destination
|
|
|
+ * @new_dentry: destination
|
|
|
+ * @delegated_inode: returns an inode needing a delegation break
|
|
|
+ *
|
|
|
+ * The caller must hold multiple mutexes--see lock_rename()).
|
|
|
+ *
|
|
|
+ * If vfs_rename discovers a delegation in need of breaking at either
|
|
|
+ * the source or destination, it will return -EWOULDBLOCK and return a
|
|
|
+ * reference to the inode in delegated_inode. The caller should then
|
|
|
+ * break the delegation and retry. Because breaking a delegation may
|
|
|
+ * take a long time, the caller should drop all locks before doing
|
|
|
+ * so.
|
|
|
+ *
|
|
|
+ * Alternatively, a caller may pass NULL for delegated_inode. This may
|
|
|
+ * be appropriate for callers that expect the underlying filesystem not
|
|
|
+ * to be NFS exported.
|
|
|
+ */
|
|
|
int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
|
|
|
- struct inode *new_dir, struct dentry *new_dentry)
|
|
|
+ struct inode *new_dir, struct dentry *new_dentry,
|
|
|
+ struct inode **delegated_inode)
|
|
|
{
|
|
|
int error;
|
|
|
int is_dir = d_is_directory(old_dentry) || d_is_autodir(old_dentry);
|
|
@@ -4082,7 +4113,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry,
|
|
|
if (is_dir)
|
|
|
error = vfs_rename_dir(old_dir,old_dentry,new_dir,new_dentry);
|
|
|
else
|
|
|
- error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry);
|
|
|
+ error = vfs_rename_other(old_dir,old_dentry,new_dir,new_dentry,delegated_inode);
|
|
|
if (!error)
|
|
|
fsnotify_move(old_dir, new_dir, old_name, is_dir,
|
|
|
new_dentry->d_inode, old_dentry);
|
|
@@ -4098,6 +4129,7 @@ SYSCALL_DEFINE4(renameat, int, olddfd, const char __user *, oldname,
|
|
|
struct dentry *old_dentry, *new_dentry;
|
|
|
struct dentry *trap;
|
|
|
struct nameidata oldnd, newnd;
|
|
|
+ struct inode *delegated_inode = NULL;
|
|
|
struct filename *from;
|
|
|
struct filename *to;
|
|
|
unsigned int lookup_flags = 0;
|
|
@@ -4137,6 +4169,7 @@ retry:
|
|
|
newnd.flags &= ~LOOKUP_PARENT;
|
|
|
newnd.flags |= LOOKUP_RENAME_TARGET;
|
|
|
|
|
|
+retry_deleg:
|
|
|
trap = lock_rename(new_dir, old_dir);
|
|
|
|
|
|
old_dentry = lookup_hash(&oldnd);
|
|
@@ -4173,13 +4206,19 @@ retry:
|
|
|
if (error)
|
|
|
goto exit5;
|
|
|
error = vfs_rename(old_dir->d_inode, old_dentry,
|
|
|
- new_dir->d_inode, new_dentry);
|
|
|
+ new_dir->d_inode, new_dentry,
|
|
|
+ &delegated_inode);
|
|
|
exit5:
|
|
|
dput(new_dentry);
|
|
|
exit4:
|
|
|
dput(old_dentry);
|
|
|
exit3:
|
|
|
unlock_rename(new_dir, old_dir);
|
|
|
+ if (delegated_inode) {
|
|
|
+ error = break_deleg_wait(&delegated_inode);
|
|
|
+ if (!error)
|
|
|
+ goto retry_deleg;
|
|
|
+ }
|
|
|
mnt_drop_write(oldnd.path.mnt);
|
|
|
exit2:
|
|
|
if (retry_estale(error, lookup_flags))
|