* [PATCH v3 0/2] nfs_complete_rename() calls d_move() without i_mutex
@ 2017-06-16 13:50 Benjamin Coddington
2017-06-16 13:50 ` [PATCH v3 1/2] Revert "NFS: nfs_rename() handle -ERESTARTSYS dentry left behind" Benjamin Coddington
2017-06-16 13:50 ` [PATCH v3 2/2] NFS: nfs_rename() - revalidate directories on -ERESTARTSYS Benjamin Coddington
0 siblings, 2 replies; 5+ messages in thread
From: Benjamin Coddington @ 2017-06-16 13:50 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker; +Cc: Jeff Layton, mszeredi, bfields, linux-nfs
Once commit 920b4530fb80430ff30ef83efe21ba1fa5623731 "NFS: nfs_rename()
handle -ERESTARTSYS dentry left behind" moved the local d_move() into the
RPC asyncronous context, d_move() could be called without holding the
directories' i_mutex.
Let's revert that commit, and a follow-up fix for it in 1/2, and then fix
the original problem once more by forcing a revalidation of the old and new
directories if we notice that the rename was interrupted in 2/2.
v2: Add memory barrier, hold directory i_locks while revalidating, specify
single-bit field width for cancelled flag.
v3: Add some comments, change cancelled flag to bool.
Benjamin Coddington (2):
Revert "NFS: nfs_rename() handle -ERESTARTSYS dentry left behind"
NFS: nfs_rename() - revalidate directories on -ERESTARTSYS
fs/nfs/dir.c | 57 +++++++++++++++++++++++++------------------------
fs/nfs/unlink.c | 11 ++++++++++
include/linux/nfs_xdr.h | 1 +
3 files changed, 41 insertions(+), 28 deletions(-)
--
2.9.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 1/2] Revert "NFS: nfs_rename() handle -ERESTARTSYS dentry left behind"
2017-06-16 13:50 [PATCH v3 0/2] nfs_complete_rename() calls d_move() without i_mutex Benjamin Coddington
@ 2017-06-16 13:50 ` Benjamin Coddington
2017-06-16 13:50 ` [PATCH v3 2/2] NFS: nfs_rename() - revalidate directories on -ERESTARTSYS Benjamin Coddington
1 sibling, 0 replies; 5+ messages in thread
From: Benjamin Coddington @ 2017-06-16 13:50 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker; +Cc: Jeff Layton, mszeredi, bfields, linux-nfs
This reverts commit 920b4530fb80430ff30ef83efe21ba1fa5623731 which could
call d_move() without holding the directory's i_mutex, and reverts commit
d4ea7e3c5c0e341c15b073016dbf3ab6c65f12f3 "NFS: Fix old dentry rehash after
move", which was a follow-up fix.
Signed-off-by: Benjamin Coddington <bcodding@redhat.com>
Fixes: 920b4530fb80 ("NFS: nfs_rename() handle -ERESTARTSYS dentry left behind")
Cc: stable@vger.kernel.org # v4.10+
Reviewed-by: Jeff Layton <jlayton@redhat.com>
---
fs/nfs/dir.c | 51 ++++++++++++++++++++++++---------------------------
1 file changed, 24 insertions(+), 27 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 32ccd7754f8a..2ac00bf4ecf1 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1946,29 +1946,6 @@ nfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
}
EXPORT_SYMBOL_GPL(nfs_link);
-static void
-nfs_complete_rename(struct rpc_task *task, struct nfs_renamedata *data)
-{
- struct dentry *old_dentry = data->old_dentry;
- struct dentry *new_dentry = data->new_dentry;
- struct inode *old_inode = d_inode(old_dentry);
- struct inode *new_inode = d_inode(new_dentry);
-
- nfs_mark_for_revalidate(old_inode);
-
- switch (task->tk_status) {
- case 0:
- if (new_inode != NULL)
- nfs_drop_nlink(new_inode);
- d_move(old_dentry, new_dentry);
- nfs_set_verifier(new_dentry,
- nfs_save_change_attribute(data->new_dir));
- break;
- case -ENOENT:
- nfs_dentry_handle_enoent(old_dentry);
- }
-}
-
/*
* RENAME
* FIXME: Some nfsds, like the Linux user space nfsd, may generate a
@@ -1999,7 +1976,7 @@ int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
{
struct inode *old_inode = d_inode(old_dentry);
struct inode *new_inode = d_inode(new_dentry);
- struct dentry *dentry = NULL;
+ struct dentry *dentry = NULL, *rehash = NULL;
struct rpc_task *task;
int error = -EBUSY;
@@ -2022,8 +1999,10 @@ int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
* To prevent any new references to the target during the
* rename, we unhash the dentry in advance.
*/
- if (!d_unhashed(new_dentry))
+ if (!d_unhashed(new_dentry)) {
d_drop(new_dentry);
+ rehash = new_dentry;
+ }
if (d_count(new_dentry) > 2) {
int err;
@@ -2040,6 +2019,7 @@ int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
goto out;
new_dentry = dentry;
+ rehash = NULL;
new_inode = NULL;
}
}
@@ -2048,8 +2028,7 @@ int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (new_inode != NULL)
NFS_PROTO(new_inode)->return_delegation(new_inode);
- task = nfs_async_rename(old_dir, new_dir, old_dentry, new_dentry,
- nfs_complete_rename);
+ task = nfs_async_rename(old_dir, new_dir, old_dentry, new_dentry, NULL);
if (IS_ERR(task)) {
error = PTR_ERR(task);
goto out;
@@ -2059,9 +2038,27 @@ int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
if (error == 0)
error = task->tk_status;
rpc_put_task(task);
+ nfs_mark_for_revalidate(old_inode);
out:
+ if (rehash)
+ d_rehash(rehash);
trace_nfs_rename_exit(old_dir, old_dentry,
new_dir, new_dentry, error);
+ if (!error) {
+ if (new_inode != NULL)
+ nfs_drop_nlink(new_inode);
+ /*
+ * The d_move() should be here instead of in an async RPC completion
+ * handler because we need the proper locks to move the dentry. If
+ * we're interrupted by a signal, the async RPC completion handler
+ * should mark the directories for revalidation.
+ */
+ d_move(old_dentry, new_dentry);
+ nfs_set_verifier(new_dentry,
+ nfs_save_change_attribute(new_dir));
+ } else if (error == -ENOENT)
+ nfs_dentry_handle_enoent(old_dentry);
+
/* new dentry created? */
if (dentry)
dput(dentry);
--
2.9.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 2/2] NFS: nfs_rename() - revalidate directories on -ERESTARTSYS
2017-06-16 13:50 [PATCH v3 0/2] nfs_complete_rename() calls d_move() without i_mutex Benjamin Coddington
2017-06-16 13:50 ` [PATCH v3 1/2] Revert "NFS: nfs_rename() handle -ERESTARTSYS dentry left behind" Benjamin Coddington
@ 2017-06-16 13:50 ` Benjamin Coddington
2017-06-16 14:54 ` Jeff Layton
1 sibling, 1 reply; 5+ messages in thread
From: Benjamin Coddington @ 2017-06-16 13:50 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker; +Cc: Jeff Layton, mszeredi, bfields, linux-nfs
An interrupted rename will leave the old dentry behind if the rename
succeeds. Fix this by forcing a lookup the next time through
->d_revalidate.
A previous attempt at solving this problem took the approach to complete
the work of the rename asynchronously, however that approach was wrong
since it would allow the d_move() to occur after the directory's i_mutex
had been dropped by the original process.
Signed-off-by: Benjamin Coddington <bcodding@redhat.com>
---
fs/nfs/dir.c | 6 +++++-
fs/nfs/unlink.c | 11 +++++++++++
include/linux/nfs_xdr.h | 1 +
3 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 2ac00bf4ecf1..7108d272bc87 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -2035,7 +2035,11 @@ int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
}
error = rpc_wait_for_completion_task(task);
- if (error == 0)
+ if (error != 0) {
+ ((struct nfs_renamedata *)task->tk_calldata)->cancelled = 1;
+ /* Paired with the atomic_dec_and_test() barrier in rpc_do_put_task() */
+ smp_wmb();
+ } else
error = task->tk_status;
rpc_put_task(task);
nfs_mark_for_revalidate(old_inode);
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index 191aa577dd1f..28628dde38b9 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -288,6 +288,17 @@ static void nfs_async_rename_release(void *calldata)
if (d_really_is_positive(data->old_dentry))
nfs_mark_for_revalidate(d_inode(data->old_dentry));
+ /* The result of the rename is unknown. Play it safe by
+ * forcing a new lookup */
+ if (data->cancelled) {
+ spin_lock(&data->old_dir->i_lock);
+ nfs_force_lookup_revalidate(data->old_dir);
+ spin_unlock(&data->old_dir->i_lock);
+ spin_lock(&data->new_dir->i_lock);
+ nfs_force_lookup_revalidate(data->new_dir);
+ spin_unlock(&data->new_dir->i_lock);
+ }
+
dput(data->old_dentry);
dput(data->new_dentry);
iput(data->old_dir);
diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
index b28c83475ee8..1aca3d7c1810 100644
--- a/include/linux/nfs_xdr.h
+++ b/include/linux/nfs_xdr.h
@@ -1533,6 +1533,7 @@ struct nfs_renamedata {
struct nfs_fattr new_fattr;
void (*complete)(struct rpc_task *, struct nfs_renamedata *);
long timeout;
+ bool cancelled;
};
struct nfs_access_entry;
--
2.9.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 2/2] NFS: nfs_rename() - revalidate directories on -ERESTARTSYS
2017-06-16 13:50 ` [PATCH v3 2/2] NFS: nfs_rename() - revalidate directories on -ERESTARTSYS Benjamin Coddington
@ 2017-06-16 14:54 ` Jeff Layton
2017-06-16 15:03 ` Benjamin Coddington
0 siblings, 1 reply; 5+ messages in thread
From: Jeff Layton @ 2017-06-16 14:54 UTC (permalink / raw)
To: Benjamin Coddington, Trond Myklebust, Anna Schumaker
Cc: mszeredi, bfields, linux-nfs
On Fri, 2017-06-16 at 09:50 -0400, Benjamin Coddington wrote:
> An interrupted rename will leave the old dentry behind if the rename
> succeeds. Fix this by forcing a lookup the next time through
> ->d_revalidate.
>
> A previous attempt at solving this problem took the approach to complete
> the work of the rename asynchronously, however that approach was wrong
> since it would allow the d_move() to occur after the directory's i_mutex
> had been dropped by the original process.
>
> Signed-off-by: Benjamin Coddington <bcodding@redhat.com>
> ---
> fs/nfs/dir.c | 6 +++++-
> fs/nfs/unlink.c | 11 +++++++++++
> include/linux/nfs_xdr.h | 1 +
> 3 files changed, 17 insertions(+), 1 deletion(-)
>
> diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
> index 2ac00bf4ecf1..7108d272bc87 100644
> --- a/fs/nfs/dir.c
> +++ b/fs/nfs/dir.c
> @@ -2035,7 +2035,11 @@ int nfs_rename(struct inode *old_dir, struct dentry *old_dentry,
> }
>
> error = rpc_wait_for_completion_task(task);
> - if (error == 0)
> + if (error != 0) {
> + ((struct nfs_renamedata *)task->tk_calldata)->cancelled = 1;
> + /* Paired with the atomic_dec_and_test() barrier in rpc_do_put_task() */
> + smp_wmb();
> + } else
> error = task->tk_status;
> rpc_put_task(task);
> nfs_mark_for_revalidate(old_inode);
> diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
> index 191aa577dd1f..28628dde38b9 100644
> --- a/fs/nfs/unlink.c
> +++ b/fs/nfs/unlink.c
> @@ -288,6 +288,17 @@ static void nfs_async_rename_release(void *calldata)
> if (d_really_is_positive(data->old_dentry))
> nfs_mark_for_revalidate(d_inode(data->old_dentry));
>
> + /* The result of the rename is unknown. Play it safe by
> + * forcing a new lookup */
> + if (data->cancelled) {
> + spin_lock(&data->old_dir->i_lock);
> + nfs_force_lookup_revalidate(data->old_dir);
> + spin_unlock(&data->old_dir->i_lock);
> + spin_lock(&data->new_dir->i_lock);
> + nfs_force_lookup_revalidate(data->new_dir);
> + spin_unlock(&data->new_dir->i_lock);
> + }
> +
One more minor nit:
It's quite possible that old_dir == new_dir. If that's the case you'll
be doing the same operation twice here. Maybe add a check for that and
only mess with new_dir here if old_dir != new_dir?
> dput(data->old_dentry);
> dput(data->new_dentry);
> iput(data->old_dir);
> diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
> index b28c83475ee8..1aca3d7c1810 100644
> --- a/include/linux/nfs_xdr.h
> +++ b/include/linux/nfs_xdr.h
> @@ -1533,6 +1533,7 @@ struct nfs_renamedata {
> struct nfs_fattr new_fattr;
> void (*complete)(struct rpc_task *, struct nfs_renamedata *);
> long timeout;
> + bool cancelled;
> };
>
> struct nfs_access_entry;
This looks good to me though. You can add this to both patches.
Reviewed-by: Jeff Layton <jlayton@redhat.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 2/2] NFS: nfs_rename() - revalidate directories on -ERESTARTSYS
2017-06-16 14:54 ` Jeff Layton
@ 2017-06-16 15:03 ` Benjamin Coddington
0 siblings, 0 replies; 5+ messages in thread
From: Benjamin Coddington @ 2017-06-16 15:03 UTC (permalink / raw)
To: Jeff Layton; +Cc: Trond Myklebust, Anna Schumaker, mszeredi, bfields, linux-nfs
On 16 Jun 2017, at 10:54, Jeff Layton wrote:
> On Fri, 2017-06-16 at 09:50 -0400, Benjamin Coddington wrote:
>> diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
>> index 191aa577dd1f..28628dde38b9 100644
>> --- a/fs/nfs/unlink.c
>> +++ b/fs/nfs/unlink.c
>> @@ -288,6 +288,17 @@ static void nfs_async_rename_release(void *calldata)
>> if (d_really_is_positive(data->old_dentry))
>> nfs_mark_for_revalidate(d_inode(data->old_dentry));
>>
>> + /* The result of the rename is unknown. Play it safe by
>> + * forcing a new lookup */
>> + if (data->cancelled) {
>> + spin_lock(&data->old_dir->i_lock);
>> + nfs_force_lookup_revalidate(data->old_dir);
>> + spin_unlock(&data->old_dir->i_lock);
>> + spin_lock(&data->new_dir->i_lock);
>> + nfs_force_lookup_revalidate(data->new_dir);
>> + spin_unlock(&data->new_dir->i_lock);
>> + }
>> +
>
> One more minor nit:
>
> It's quite possible that old_dir == new_dir. If that's the case you'll
> be doing the same operation twice here. Maybe add a check for that and
> only mess with new_dir here if old_dir != new_dir?
Hmm.. yes that would be better, OK! I'll send it again.
>> dput(data->old_dentry);
>> dput(data->new_dentry);
>> iput(data->old_dir);
>> diff --git a/include/linux/nfs_xdr.h b/include/linux/nfs_xdr.h
>> index b28c83475ee8..1aca3d7c1810 100644
>> --- a/include/linux/nfs_xdr.h
>> +++ b/include/linux/nfs_xdr.h
>> @@ -1533,6 +1533,7 @@ struct nfs_renamedata {
>> struct nfs_fattr new_fattr;
>> void (*complete)(struct rpc_task *, struct nfs_renamedata *);
>> long timeout;
>> + bool cancelled;
>> };
>>
>> struct nfs_access_entry;
>
> This looks good to me though. You can add this to both patches.
>
> Reviewed-by: Jeff Layton <jlayton@redhat.com>
Thanks for all the suggestions and review!
Ben
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2017-06-16 15:03 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-06-16 13:50 [PATCH v3 0/2] nfs_complete_rename() calls d_move() without i_mutex Benjamin Coddington
2017-06-16 13:50 ` [PATCH v3 1/2] Revert "NFS: nfs_rename() handle -ERESTARTSYS dentry left behind" Benjamin Coddington
2017-06-16 13:50 ` [PATCH v3 2/2] NFS: nfs_rename() - revalidate directories on -ERESTARTSYS Benjamin Coddington
2017-06-16 14:54 ` Jeff Layton
2017-06-16 15:03 ` Benjamin Coddington
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).