@@ -235,6 +235,7 @@ static const struct super_operations shmem_ops;
const struct address_space_operations shmem_aops;
static const struct file_operations shmem_file_operations;
static const struct inode_operations shmem_inode_operations;
+static const struct file_operations shmem_dir_operations;
static const struct inode_operations shmem_dir_inode_operations;
static const struct inode_operations shmem_special_inode_operations;
static const struct vm_operations_struct shmem_vm_ops;
@@ -2410,7 +2411,7 @@ static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block
/* Some things misbehave if size == 0 on a directory */
inode->i_size = 2 * BOGO_DIRENT_SIZE;
inode->i_op = &shmem_dir_inode_operations;
- inode->i_fop = &simple_dir_operations;
+ inode->i_fop = &shmem_dir_operations;
break;
case S_IFLNK:
/*
@@ -3235,6 +3236,137 @@ static const char *shmem_get_link(struct dentry *dentry,
return folio_address(folio);
}
+static struct dentry *scan_positives(struct dentry *cursor,
+ struct list_head *p,
+ loff_t count,
+ struct dentry *last)
+{
+ struct dentry *dentry = cursor->d_parent, *found = NULL;
+
+ spin_lock(&dentry->d_lock);
+ while ((p = p->next) != &dentry->d_subdirs) {
+ struct dentry *d = list_entry(p, struct dentry, d_child);
+ // we must at least skip cursors, to avoid livelocks
+ if (d->d_flags & DCACHE_DENTRY_CURSOR)
+ continue;
+ if (simple_positive(d) && !--count) {
+ spin_lock_nested(&d->d_lock, DENTRY_D_LOCK_NESTED);
+ if (simple_positive(d))
+ found = dget_dlock(d);
+ spin_unlock(&d->d_lock);
+ if (likely(found))
+ break;
+ count = 1;
+ }
+ if (need_resched()) {
+ list_move(&cursor->d_child, p);
+ p = &cursor->d_child;
+ spin_unlock(&dentry->d_lock);
+ cond_resched();
+ spin_lock(&dentry->d_lock);
+ }
+ }
+ spin_unlock(&dentry->d_lock);
+ dput(last);
+ return found;
+}
+
+static loff_t shmem_dir_llseek(struct file *file, loff_t offset, int whence)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ switch (whence) {
+ case 1:
+ offset += file->f_pos;
+ fallthrough;
+ case 0:
+ if (offset >= 0)
+ break;
+ fallthrough;
+ default:
+ return -EINVAL;
+ }
+ if (offset != file->f_pos) {
+ struct dentry *cursor = file->private_data;
+ struct dentry *to = NULL;
+
+ inode_lock_shared(dentry->d_inode);
+
+ if (offset > 2)
+ to = scan_positives(cursor, &dentry->d_subdirs,
+ offset - 2, NULL);
+ spin_lock(&dentry->d_lock);
+ if (to)
+ list_move(&cursor->d_child, &to->d_child);
+ else
+ list_del_init(&cursor->d_child);
+ spin_unlock(&dentry->d_lock);
+ dput(to);
+
+ file->f_pos = offset;
+
+ inode_unlock_shared(dentry->d_inode);
+ }
+ return offset;
+}
+
+/**
+ * shmem_readdir - Emit entries starting at offset @ctx->pos
+ * @file: an open directory to iterate over
+ * @ctx: directory iteration context
+ *
+ * Caller must hold @file's i_rwsem to prevent insertion or removal of
+ * entries during this call.
+ *
+ * On entry, @ctx->pos contains an offset that represents the first entry
+ * to be read from the directory.
+ *
+ * The operation continues until there are no more entries to read, or
+ * until the ctx->actor indicates there is no more space in the caller's
+ * output buffer.
+ *
+ * On return, @ctx->pos contains an offset that will read the next entry
+ * in this directory when shmem_readdir() is called again with @ctx.
+ *
+ * Return values:
+ * %0 - Complete
+ */
+static int shmem_readdir(struct file *file, struct dir_context *ctx)
+{
+ struct dentry *dentry = file->f_path.dentry;
+ struct dentry *cursor = file->private_data;
+ struct list_head *anchor = &dentry->d_subdirs;
+ struct dentry *next = NULL;
+ struct list_head *p;
+
+ if (!dir_emit_dots(file, ctx))
+ return 0;
+
+ if (ctx->pos == 2)
+ p = anchor;
+ else if (!list_empty(&cursor->d_child))
+ p = &cursor->d_child;
+ else
+ return 0;
+
+ while ((next = scan_positives(cursor, p, 1, next)) != NULL) {
+ if (!dir_emit(ctx, next->d_name.name, next->d_name.len,
+ d_inode(next)->i_ino,
+ fs_umode_to_dtype(d_inode(next)->i_mode)))
+ break;
+ ctx->pos++;
+ p = &next->d_child;
+ }
+ spin_lock(&dentry->d_lock);
+ if (next)
+ list_move_tail(&cursor->d_child, &next->d_child);
+ else
+ list_del_init(&cursor->d_child);
+ spin_unlock(&dentry->d_lock);
+ dput(next);
+
+ return 0;
+}
+
#ifdef CONFIG_TMPFS_XATTR
static int shmem_fileattr_get(struct dentry *dentry, struct fileattr *fa)
@@ -3987,6 +4119,15 @@ static const struct inode_operations shmem_inode_operations = {
#endif
};
+static const struct file_operations shmem_dir_operations = {
+#ifdef CONFIG_TMPFS
+ .llseek = shmem_dir_llseek,
+ .iterate_shared = shmem_readdir,
+#endif
+ .read = generic_read_dir,
+ .fsync = noop_fsync,
+};
+
static const struct inode_operations shmem_dir_inode_operations = {
#ifdef CONFIG_TMPFS
.getattr = shmem_getattr,