diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2014-10-26 19:31:10 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2014-11-03 15:22:16 -0500 |
commit | ca5358ef75fc69fee5322a38a340f5739d997c10 (patch) | |
tree | 88d4abe38ce55bdac9c8b207a0dce743504e0a28 /fs/dcache.c | |
parent | 946e51f2bf37f1656916eb75bd0742ba33983c28 (diff) |
deal with deadlock in d_walk()
... by not hitting rename_retry for reasons other than rename having
happened. In other words, do _not_ restart when finding that
between unlocking the child and locking the parent the former got
into __dentry_kill(). Skip the killed siblings instead...
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/dcache.c')
-rw-r--r-- | fs/dcache.c | 31 |
1 files changed, 16 insertions, 15 deletions
diff --git a/fs/dcache.c b/fs/dcache.c index 8b4c45e40834..e90aa825cc03 100644 --- a/fs/dcache.c +++ b/fs/dcache.c | |||
@@ -495,7 +495,7 @@ static void __dentry_kill(struct dentry *dentry) | |||
495 | } | 495 | } |
496 | /* if it was on the hash then remove it */ | 496 | /* if it was on the hash then remove it */ |
497 | __d_drop(dentry); | 497 | __d_drop(dentry); |
498 | list_del(&dentry->d_child); | 498 | __list_del_entry(&dentry->d_child); |
499 | /* | 499 | /* |
500 | * Inform d_walk() that we are no longer attached to the | 500 | * Inform d_walk() that we are no longer attached to the |
501 | * dentry tree | 501 | * dentry tree |
@@ -1081,33 +1081,31 @@ resume: | |||
1081 | /* | 1081 | /* |
1082 | * All done at this level ... ascend and resume the search. | 1082 | * All done at this level ... ascend and resume the search. |
1083 | */ | 1083 | */ |
1084 | rcu_read_lock(); | ||
1085 | ascend: | ||
1084 | if (this_parent != parent) { | 1086 | if (this_parent != parent) { |
1085 | struct dentry *child = this_parent; | 1087 | struct dentry *child = this_parent; |
1086 | this_parent = child->d_parent; | 1088 | this_parent = child->d_parent; |
1087 | 1089 | ||
1088 | rcu_read_lock(); | ||
1089 | spin_unlock(&child->d_lock); | 1090 | spin_unlock(&child->d_lock); |
1090 | spin_lock(&this_parent->d_lock); | 1091 | spin_lock(&this_parent->d_lock); |
1091 | 1092 | ||
1092 | /* | 1093 | /* might go back up the wrong parent if we have had a rename. */ |
1093 | * might go back up the wrong parent if we have had a rename | 1094 | if (need_seqretry(&rename_lock, seq)) |
1094 | * or deletion | ||
1095 | */ | ||
1096 | if (this_parent != child->d_parent || | ||
1097 | (child->d_flags & DCACHE_DENTRY_KILLED) || | ||
1098 | need_seqretry(&rename_lock, seq)) { | ||
1099 | spin_unlock(&this_parent->d_lock); | ||
1100 | rcu_read_unlock(); | ||
1101 | goto rename_retry; | 1095 | goto rename_retry; |
1096 | next = child->d_child.next; | ||
1097 | while (unlikely(child->d_flags & DCACHE_DENTRY_KILLED)) { | ||
1098 | if (next == &this_parent->d_subdirs) | ||
1099 | goto ascend; | ||
1100 | child = list_entry(next, struct dentry, d_child); | ||
1101 | next = next->next; | ||
1102 | } | 1102 | } |
1103 | rcu_read_unlock(); | 1103 | rcu_read_unlock(); |
1104 | next = child->d_child.next; | ||
1105 | goto resume; | 1104 | goto resume; |
1106 | } | 1105 | } |
1107 | if (need_seqretry(&rename_lock, seq)) { | 1106 | if (need_seqretry(&rename_lock, seq)) |
1108 | spin_unlock(&this_parent->d_lock); | ||
1109 | goto rename_retry; | 1107 | goto rename_retry; |
1110 | } | 1108 | rcu_read_unlock(); |
1111 | if (finish) | 1109 | if (finish) |
1112 | finish(data); | 1110 | finish(data); |
1113 | 1111 | ||
@@ -1117,6 +1115,9 @@ out_unlock: | |||
1117 | return; | 1115 | return; |
1118 | 1116 | ||
1119 | rename_retry: | 1117 | rename_retry: |
1118 | spin_unlock(&this_parent->d_lock); | ||
1119 | rcu_read_unlock(); | ||
1120 | BUG_ON(seq & 1); | ||
1120 | if (!retry) | 1121 | if (!retry) |
1121 | return; | 1122 | return; |
1122 | seq = 1; | 1123 | seq = 1; |