summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/libfs.c32
1 files changed, 27 insertions, 5 deletions
diff --git a/fs/libfs.c b/fs/libfs.c
index b05b74ae3f16..74dc8b9e7f53 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -89,31 +89,53 @@ static struct dentry *next_positive(struct dentry *parent,
struct list_head *from,
int count)
{
- struct dentry *res = NULL;
+ unsigned *seq = &parent->d_inode->i_dir_seq, n;
+ struct dentry *res;
struct list_head *p;
+ bool skipped;
+ int i;
- spin_lock(&parent->d_lock);
+retry:
+ i = count;
+ skipped = false;
+ n = smp_load_acquire(seq) & ~1;
+ res = NULL;
+ rcu_read_lock();
for (p = from->next; p != &parent->d_subdirs; p = p->next) {
struct dentry *d = list_entry(p, struct dentry, d_child);
- if (simple_positive(d) && !--count) {
+ if (!simple_positive(d)) {
+ skipped = true;
+ } else if (!--i) {
res = d;
break;
}
}
- spin_unlock(&parent->d_lock);
+ rcu_read_unlock();
+ if (skipped) {
+ smp_rmb();
+ if (unlikely(*seq != n))
+ goto retry;
+ }
return res;
}
static void move_cursor(struct dentry *cursor, struct list_head *after)
{
struct dentry *parent = cursor->d_parent;
-
+ unsigned n, *seq = &parent->d_inode->i_dir_seq;
spin_lock(&parent->d_lock);
+ for (;;) {
+ n = *seq;
+ if (!(n & 1) && cmpxchg(seq, n, n + 1) == n)
+ break;
+ cpu_relax();
+ }
__list_del(cursor->d_child.prev, cursor->d_child.next);
if (after)
list_add(&cursor->d_child, after);
else
list_add_tail(&cursor->d_child, &parent->d_subdirs);
+ smp_store_release(seq, n + 2);
spin_unlock(&parent->d_lock);
}