@@ -243,12 +243,13 @@ EXPORT_SYMBOL(simple_dir_inode_operations);
/* simple_offset_add() allocation range */
enum {
- DIR_OFFSET_MIN = 2,
+ DIR_OFFSET_MIN = 3,
DIR_OFFSET_MAX = LONG_MAX - 1,
};
/* simple_offset_add() never assigns these to a dentry */
enum {
+ DIR_OFFSET_FIRST = 2, /* Find first real entry */
DIR_OFFSET_EOD = LONG_MAX, /* Marks EOD */
};
@@ -456,19 +457,43 @@ 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 *parent,
+ 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_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, DIR_OFFSET_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(parent, child);
+ spin_unlock(&parent->d_lock);
out:
rcu_read_unlock();
return found;
@@ -477,30 +502,42 @@ static struct dentry *offset_find_next(struct offset_ctx *octx, loff_t offset)
static bool offset_dir_emit(struct dir_context *ctx, struct dentry *dentry)
{
struct inode *inode = d_inode(dentry);
- long offset = dentry2offset(dentry);
- return ctx->actor(ctx, dentry->d_name.name, dentry->d_name.len, offset,
- inode->i_ino, fs_umode_to_dtype(inode->i_mode));
+ return dir_emit(ctx, dentry->d_name.name, dentry->d_name.len,
+ inode->i_ino, fs_umode_to_dtype(inode->i_mode));
}
-static void offset_iterate_dir(struct inode *inode, struct dir_context *ctx)
+static void offset_iterate_dir(struct file *file, struct dir_context *ctx)
{
- struct offset_ctx *octx = inode->i_op->get_offset_ctx(inode);
+ struct dentry *dir = file->f_path.dentry;
struct dentry *dentry;
+ if (ctx->pos == DIR_OFFSET_FIRST) {
+ spin_lock(&dir->d_lock);
+ dentry = find_next_sibling_locked(dir, d_first_child(dir));
+ spin_unlock(&dir->d_lock);
+ } else
+ dentry = offset_dir_lookup(file, ctx->pos);
+ if (!dentry)
+ goto out_eod;
+
while (true) {
- dentry = offset_find_next(octx, ctx->pos);
- if (!dentry)
- goto out_eod;
+ struct dentry *next;
- if (!offset_dir_emit(ctx, dentry)) {
- dput(dentry);
+ ctx->pos = dentry2offset(dentry);
+ if (!offset_dir_emit(ctx, dentry))
break;
- }
- ctx->pos = dentry2offset(dentry) + 1;
+ spin_lock(&dir->d_lock);
+ next = find_next_sibling_locked(dir, d_next_sibling(dentry));
+ spin_unlock(&dir->d_lock);
dput(dentry);
+
+ if (!next)
+ goto out_eod;
+ dentry = next;
}
+ dput(dentry);
return;
out_eod:
@@ -539,7 +576,7 @@ static int offset_readdir(struct file *file, struct dir_context *ctx)
if (!dir_emit_dots(file, ctx))
return 0;
if (ctx->pos != DIR_OFFSET_EOD)
- offset_iterate_dir(d_inode(dir), ctx);
+ offset_iterate_dir(file, ctx);
return 0;
}