diff options
Diffstat (limited to 'fs/libfs.c')
| -rw-r--r-- | fs/libfs.c | 113 | 
1 files changed, 72 insertions, 41 deletions
diff --git a/fs/libfs.c b/fs/libfs.c index 3db2721144c2..74dc8b9e7f53 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -71,9 +71,7 @@ EXPORT_SYMBOL(simple_lookup);  int dcache_dir_open(struct inode *inode, struct file *file)  { -	static struct qstr cursor_name = QSTR_INIT(".", 1); - -	file->private_data = d_alloc(file->f_path.dentry, &cursor_name); +	file->private_data = d_alloc_cursor(file->f_path.dentry);  	return file->private_data ? 0 : -ENOMEM;  } @@ -86,6 +84,61 @@ int dcache_dir_close(struct inode *inode, struct file *file)  }  EXPORT_SYMBOL(dcache_dir_close); +/* parent is locked at least shared */ +static struct dentry *next_positive(struct dentry *parent, +				    struct list_head *from, +				    int count) +{ +	unsigned *seq = &parent->d_inode->i_dir_seq, n; +	struct dentry *res; +	struct list_head *p; +	bool skipped; +	int i; + +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)) { +			skipped = true; +		} else if (!--i) { +			res = d; +			break; +		} +	} +	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); +} +  loff_t dcache_dir_lseek(struct file *file, loff_t offset, int whence)  {  	struct dentry *dentry = file->f_path.dentry; @@ -101,25 +154,14 @@ loff_t dcache_dir_lseek(struct file *file, loff_t offset, int whence)  	if (offset != file->f_pos) {  		file->f_pos = offset;  		if (file->f_pos >= 2) { -			struct list_head *p;  			struct dentry *cursor = file->private_data; +			struct dentry *to;  			loff_t n = file->f_pos - 2; -			spin_lock(&dentry->d_lock); -			/* d_lock not required for cursor */ -			list_del(&cursor->d_child); -			p = dentry->d_subdirs.next; -			while (n && p != &dentry->d_subdirs) { -				struct dentry *next; -				next = list_entry(p, struct dentry, d_child); -				spin_lock_nested(&next->d_lock, DENTRY_D_LOCK_NESTED); -				if (simple_positive(next)) -					n--; -				spin_unlock(&next->d_lock); -				p = p->next; -			} -			list_add_tail(&cursor->d_child, p); -			spin_unlock(&dentry->d_lock); +			inode_lock_shared(dentry->d_inode); +			to = next_positive(dentry, &dentry->d_subdirs, n); +			move_cursor(cursor, to ? &to->d_child : NULL); +			inode_unlock_shared(dentry->d_inode);  		}  	}  	return offset; @@ -142,36 +184,25 @@ int dcache_readdir(struct file *file, struct dir_context *ctx)  {  	struct dentry *dentry = file->f_path.dentry;  	struct dentry *cursor = file->private_data; -	struct list_head *p, *q = &cursor->d_child; +	struct list_head *p = &cursor->d_child; +	struct dentry *next; +	bool moved = false;  	if (!dir_emit_dots(file, ctx))  		return 0; -	spin_lock(&dentry->d_lock); -	if (ctx->pos == 2) -		list_move(q, &dentry->d_subdirs); -	for (p = q->next; p != &dentry->d_subdirs; p = p->next) { -		struct dentry *next = list_entry(p, struct dentry, d_child); -		spin_lock_nested(&next->d_lock, DENTRY_D_LOCK_NESTED); -		if (!simple_positive(next)) { -			spin_unlock(&next->d_lock); -			continue; -		} - -		spin_unlock(&next->d_lock); -		spin_unlock(&dentry->d_lock); +	if (ctx->pos == 2) +		p = &dentry->d_subdirs; +	while ((next = next_positive(dentry, p, 1)) != NULL) {  		if (!dir_emit(ctx, next->d_name.name, next->d_name.len,  			      d_inode(next)->i_ino, dt_type(d_inode(next)))) -			return 0; -		spin_lock(&dentry->d_lock); -		spin_lock_nested(&next->d_lock, DENTRY_D_LOCK_NESTED); -		/* next is still alive */ -		list_move(q, p); -		spin_unlock(&next->d_lock); -		p = q; +			break; +		moved = true; +		p = &next->d_child;  		ctx->pos++;  	} -	spin_unlock(&dentry->d_lock); +	if (moved) +		move_cursor(cursor, p);  	return 0;  }  EXPORT_SYMBOL(dcache_readdir);  | 
