@@ -241,9 +241,9 @@ const struct inode_operations simple_dir_inode_operations = {
};
EXPORT_SYMBOL(simple_dir_inode_operations);
-/* 0 is '.', 1 is '..', so always start with offset 2 or more */
enum {
- DIR_OFFSET_MIN = 2,
+ DIR_OFFSET_FIRST = 2, /* seek to the first real entry */
+ DIR_OFFSET_MIN = 3, /* minimum allocated offset value */
};
static void offset_set(struct dentry *dentry, long offset)
@@ -507,19 +507,53 @@ static loff_t offset_dir_llseek(struct file *file, loff_t offset, int whence)
return vfs_setpos(file, offset, LONG_MAX);
}
-static struct dentry *offset_find_next(struct offset_ctx *octx, loff_t offset)
+/* Cf. find_next_child() */
+static struct dentry *find_next_sibling_locked(struct dentry *dentry)
{
- MA_STATE(mas, &octx->mt, offset, offset);
+ struct dentry *found = NULL;
+
+ hlist_for_each_entry_from(dentry, d_sib) {
+ if (!simple_positive(dentry))
+ continue;
+ spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED);
+ if (simple_positive(dentry))
+ found = dget_dlock(dentry);
+ spin_unlock(&dentry->d_lock);
+ if (likely(found))
+ break;
+ }
+ return found;
+}
+
+static noinline_for_stack struct dentry *offset_dir_first(struct file *file)
+{
+ struct dentry *parent = file->f_path.dentry;
+ struct dentry *found;
+
+ spin_lock(&parent->d_lock);
+ found = find_next_sibling_locked(d_first_child(parent));
+ spin_unlock(&parent->d_lock);
+ return found;
+}
+
+static noinline_for_stack struct dentry *
+offset_dir_lookup(struct file *file, loff_t offset)
+{
+ struct dentry *parent = file->f_path.dentry;
struct dentry *child, *found = NULL;
+ struct inode *inode = d_inode(parent);
+ struct offset_ctx *octx = inode->i_op->get_offset_ctx(inode);
+
+ MA_STATE(mas, &octx->mt, offset, offset);
rcu_read_lock();
child = mas_find(&mas, LONG_MAX);
if (!child)
goto out;
- spin_lock(&child->d_lock);
- if (simple_positive(child))
- found = dget_dlock(child);
- spin_unlock(&child->d_lock);
+
+ spin_lock(&parent->d_lock);
+ found = find_next_sibling_locked(child);
+ spin_unlock(&parent->d_lock);
out:
rcu_read_unlock();
return found;
@@ -534,29 +568,48 @@ static bool offset_dir_emit(struct dir_context *ctx, struct dentry *dentry)
inode->i_ino, fs_umode_to_dtype(inode->i_mode));
}
+static struct dentry *offset_dir_next(struct dentry *child)
+{
+ struct dentry *parent = child->d_parent;
+ struct dentry *found;
+
+ spin_lock(&parent->d_lock);
+ found = find_next_sibling_locked(d_next_sibling(child));
+ spin_unlock(&parent->d_lock);
+ return found;
+}
+
static void offset_iterate_dir(struct file *file, struct dir_context *ctx)
{
- struct dentry *dir = file->f_path.dentry;
- struct inode *inode = d_inode(dir);
- struct offset_ctx *octx = inode->i_op->get_offset_ctx(inode);
- struct dentry *dentry;
+ struct dentry *dentry, *next = NULL;
+
+ if (ctx->pos == DIR_OFFSET_FIRST)
+ dentry = offset_dir_first(file);
+ else
+ dentry = offset_dir_lookup(file, ctx->pos);
+ if (!dentry) {
+ /* ->private_data is protected by f_pos_lock */
+ offset_set_eod(file);
+ return;
+ }
while (true) {
- dentry = offset_find_next(octx, ctx->pos);
- if (!dentry) {
- /* ->private_data is protected by f_pos_lock */
- offset_set_eod(file);
- return;
- }
-
if (!offset_dir_emit(ctx, dentry)) {
- dput(dentry);
+ ctx->pos = dentry2offset(dentry);
+ break;
+ }
+
+ next = offset_dir_next(dentry);
+ if (!next) {
+ offset_set_eod(file);
break;
}
- ctx->pos = dentry2offset(dentry) + 1;
dput(dentry);
+ dentry = next;
}
+
+ dput(dentry);
}
/**