lockless next_positive()

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
diff --git a/fs/libfs.c b/fs/libfs.c
index b05b74a..74dc8b9 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -89,31 +89,53 @@
 				    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);
 }