* [PATCH v6 0/3] shmemfs stable directory offsets
@ 2023-06-28 15:25 Chuck Lever
2023-06-28 15:25 ` [PATCH v6 1/3] libfs: Add directory operations for stable offsets Chuck Lever
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Chuck Lever @ 2023-06-28 15:25 UTC (permalink / raw)
To: viro, brauner, hughd, akpm
Cc: Chuck Lever, Jeff Layton, Christoph Hellwig, jlayton, linux-mm,
linux-fsdevel
The following series implements stable directory offsets for
shmemfs/tmpfs and provides infrastructure for use by other file
systems that are based on simplefs.
Changes since v5:
- Rename functions and structures
Changes since v4:
- Remove new fields from struct inode/dentry
- Remove EXPORT_SYMBOL and extern for new functions
- Try again to fix error handling for rename_exchange
Changes since v3:
- Rebased on v6.4
- Fixed error handling bugs
Changes since v2:
- Move bulk of stable offset support into fs/libfs.c
- Replace xa_find_after with xas_find_next for efficiency
Changes since v1:
- Break the single patch up into a series
Changes since RFC:
- Destroy xarray in shmem_destroy_inode() instead of free_in_core_inode()
- A few cosmetic updates
---
Chuck Lever (3):
libfs: Add directory operations for stable offsets
shmem: Refactor shmem_symlink()
shmem: stable directory offsets
fs/libfs.c | 247 +++++++++++++++++++++++++++++++++++++++
include/linux/fs.h | 18 +++
include/linux/shmem_fs.h | 1 +
mm/shmem.c | 62 +++++++---
4 files changed, 313 insertions(+), 15 deletions(-)
--
Chuck Lever
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v6 1/3] libfs: Add directory operations for stable offsets
2023-06-28 15:25 [PATCH v6 0/3] shmemfs stable directory offsets Chuck Lever
@ 2023-06-28 15:25 ` Chuck Lever
2023-06-30 11:08 ` Bernd Schubert
2023-06-28 15:25 ` [PATCH v6 2/3] shmem: Refactor shmem_symlink() Chuck Lever
2023-06-28 15:25 ` [PATCH v6 3/3] shmem: stable directory offsets Chuck Lever
2 siblings, 1 reply; 5+ messages in thread
From: Chuck Lever @ 2023-06-28 15:25 UTC (permalink / raw)
To: viro, brauner, hughd, akpm; +Cc: Chuck Lever, jlayton, linux-mm, linux-fsdevel
From: Chuck Lever <chuck.lever@oracle.com>
Create a vector of directory operations in fs/libfs.c that handles
directory seeks and readdir via stable offsets instead of the
current cursor-based mechanism.
For the moment these are unused.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
fs/libfs.c | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/fs.h | 18 ++++
2 files changed, 265 insertions(+)
diff --git a/fs/libfs.c b/fs/libfs.c
index 89cf614a3271..2b0d5ac472df 100644
--- a/fs/libfs.c
+++ b/fs/libfs.c
@@ -239,6 +239,253 @@ const struct inode_operations simple_dir_inode_operations = {
};
EXPORT_SYMBOL(simple_dir_inode_operations);
+static void offset_set(struct dentry *dentry, unsigned long offset)
+{
+ dentry->d_fsdata = (void *)offset;
+}
+
+static unsigned long dentry2offset(struct dentry *dentry)
+{
+ return (unsigned long)dentry->d_fsdata;
+}
+
+/**
+ * simple_offset_init - initialize an offset_ctx
+ * @octx: directory offset map to be initialized
+ *
+ */
+void simple_offset_init(struct offset_ctx *octx)
+{
+ xa_init_flags(&octx->xa, XA_FLAGS_ALLOC1);
+
+ /* 0 is '.', 1 is '..', so always start with offset 2 */
+ octx->next_offset = 2;
+}
+
+/**
+ * simple_offset_add - Add an entry to a directory's offset map
+ * @octx: directory offset ctx to be updated
+ * @dentry: new dentry being added
+ *
+ * Returns zero on success. @so_ctx and the dentry offset are updated.
+ * Otherwise, a negative errno value is returned.
+ */
+int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry)
+{
+ static const struct xa_limit limit = XA_LIMIT(2, U32_MAX);
+ u32 offset;
+ int ret;
+
+ if (dentry2offset(dentry) != 0)
+ return -EBUSY;
+
+ ret = xa_alloc_cyclic(&octx->xa, &offset, dentry, limit,
+ &octx->next_offset, GFP_KERNEL);
+ if (ret < 0)
+ return ret;
+
+ offset_set(dentry, offset);
+ return 0;
+}
+
+/**
+ * simple_offset_remove - Remove an entry to a directory's offset map
+ * @octx: directory offset ctx to be updated
+ * @dentry: dentry being removed
+ *
+ */
+void simple_offset_remove(struct offset_ctx *octx, struct dentry *dentry)
+{
+ unsigned long index = dentry2offset(dentry);
+
+ if (index == 0)
+ return;
+
+ xa_erase(&octx->xa, index);
+ offset_set(dentry, 0);
+}
+
+/**
+ * simple_offset_rename_exchange - exchange rename with directory offsets
+ * @old_dir: parent of dentry being moved
+ * @old_dentry: dentry being moved
+ * @new_dir: destination parent
+ * @new_dentry: destination dentry
+ *
+ * Returns zero on success. Otherwise a negative errno is returned and the
+ * rename is rolled back.
+ */
+int simple_offset_rename_exchange(struct inode *old_dir,
+ struct dentry *old_dentry,
+ struct inode *new_dir,
+ struct dentry *new_dentry)
+{
+ struct offset_ctx *old_ctx = old_dir->i_op->get_offset_ctx(old_dir);
+ struct offset_ctx *new_ctx = new_dir->i_op->get_offset_ctx(new_dir);
+ unsigned long old_index = dentry2offset(old_dentry);
+ unsigned long new_index = dentry2offset(new_dentry);
+ int ret;
+
+ simple_offset_remove(old_ctx, old_dentry);
+ simple_offset_remove(new_ctx, new_dentry);
+
+ ret = simple_offset_add(new_ctx, old_dentry);
+ if (ret)
+ goto out_restore;
+
+ ret = simple_offset_add(old_ctx, new_dentry);
+ if (ret) {
+ simple_offset_remove(new_ctx, old_dentry);
+ goto out_restore;
+ }
+
+ ret = simple_rename_exchange(old_dir, old_dentry, new_dir, new_dentry);
+ if (ret) {
+ simple_offset_remove(new_ctx, old_dentry);
+ simple_offset_remove(old_ctx, new_dentry);
+ goto out_restore;
+ }
+ return 0;
+
+out_restore:
+ offset_set(old_dentry, old_index);
+ xa_store(&old_ctx->xa, old_index, old_dentry, GFP_KERNEL);
+ offset_set(new_dentry, new_index);
+ xa_store(&new_ctx->xa, new_index, new_dentry, GFP_KERNEL);
+ return ret;
+}
+
+/**
+ * simple_offset_destroy - Release offset map
+ * @octx: directory offset ctx that is about to be destroyed
+ *
+ * During fs teardown (eg. umount), a directory's offset map might still
+ * contain entries. xa_destroy() cleans out anything that remains.
+ */
+void simple_offset_destroy(struct offset_ctx *octx)
+{
+ xa_destroy(&octx->xa);
+}
+
+/**
+ * offset_dir_llseek - Advance the read position of a directory descriptor
+ * @file: an open directory whose position is to be updated
+ * @offset: a byte offset
+ * @whence: enumerator describing the starting position for this update
+ *
+ * SEEK_END, SEEK_DATA, and SEEK_HOLE are not supported for directories.
+ *
+ * Returns the updated read position if successful; otherwise a
+ * negative errno is returned and the read position remains unchanged.
+ */
+static loff_t offset_dir_llseek(struct file *file, loff_t offset, int whence)
+{
+ switch (whence) {
+ case SEEK_CUR:
+ offset += file->f_pos;
+ fallthrough;
+ case SEEK_SET:
+ if (offset >= 0)
+ break;
+ fallthrough;
+ default:
+ return -EINVAL;
+ }
+
+ return vfs_setpos(file, offset, U32_MAX);
+}
+
+static struct dentry *offset_find_next(struct xa_state *xas)
+{
+ struct dentry *child, *found = NULL;
+
+ rcu_read_lock();
+ child = xas_next_entry(xas, U32_MAX);
+ if (!child)
+ goto out;
+ spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
+ if (simple_positive(child))
+ found = dget_dlock(child);
+ spin_unlock(&child->d_lock);
+out:
+ rcu_read_unlock();
+ return found;
+}
+
+static bool offset_dir_emit(struct dir_context *ctx, struct dentry *dentry)
+{
+ loff_t offset = dentry2offset(dentry);
+ struct inode *inode = d_inode(dentry);
+
+ return ctx->actor(ctx, dentry->d_name.name, dentry->d_name.len, offset,
+ inode->i_ino, fs_umode_to_dtype(inode->i_mode));
+}
+
+static void offset_iterate_dir(struct dentry *dir, struct dir_context *ctx)
+{
+ struct inode *inode = d_inode(dir);
+ struct offset_ctx *so_ctx = inode->i_op->get_offset_ctx(inode);
+ XA_STATE(xas, &so_ctx->xa, ctx->pos);
+ struct dentry *dentry;
+
+ while (true) {
+ spin_lock(&dir->d_lock);
+ dentry = offset_find_next(&xas);
+ spin_unlock(&dir->d_lock);
+ if (!dentry)
+ break;
+
+ if (!offset_dir_emit(ctx, dentry)) {
+ dput(dentry);
+ break;
+ }
+
+ dput(dentry);
+ ctx->pos = xas.xa_index + 1;
+ }
+}
+
+/**
+ * offset_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 offset_readdir(struct file *file, struct dir_context *ctx)
+{
+ struct dentry *dir = file->f_path.dentry;
+
+ lockdep_assert_held(&d_inode(dir)->i_rwsem);
+
+ if (!dir_emit_dots(file, ctx))
+ return 0;
+
+ offset_iterate_dir(dir, ctx);
+ return 0;
+}
+
+const struct file_operations simple_offset_dir_operations = {
+ .llseek = offset_dir_llseek,
+ .iterate_shared = offset_readdir,
+ .read = generic_read_dir,
+ .fsync = noop_fsync,
+};
+
static struct dentry *find_next_child(struct dentry *parent, struct dentry *prev)
{
struct dentry *child = NULL;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 133f0640fb24..85de389e4eb8 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1767,6 +1767,7 @@ struct dir_context {
struct iov_iter;
struct io_uring_cmd;
+struct offset_ctx;
struct file_operations {
struct module *owner;
@@ -1854,6 +1855,7 @@ struct inode_operations {
int (*fileattr_set)(struct mnt_idmap *idmap,
struct dentry *dentry, struct fileattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
+ struct offset_ctx *(*get_offset_ctx)(struct inode *inode);
} ____cacheline_aligned;
static inline ssize_t call_read_iter(struct file *file, struct kiocb *kio,
@@ -2954,6 +2956,22 @@ extern ssize_t simple_read_from_buffer(void __user *to, size_t count,
extern ssize_t simple_write_to_buffer(void *to, size_t available, loff_t *ppos,
const void __user *from, size_t count);
+struct offset_ctx {
+ struct xarray xa;
+ u32 next_offset;
+};
+
+void simple_offset_init(struct offset_ctx *octx);
+int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry);
+void simple_offset_remove(struct offset_ctx *octx, struct dentry *dentry);
+int simple_offset_rename_exchange(struct inode *old_dir,
+ struct dentry *old_dentry,
+ struct inode *new_dir,
+ struct dentry *new_dentry);
+void simple_offset_destroy(struct offset_ctx *octx);
+
+extern const struct file_operations simple_offset_dir_operations;
+
extern int __generic_file_fsync(struct file *, loff_t, loff_t, int);
extern int generic_file_fsync(struct file *, loff_t, loff_t, int);
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v6 2/3] shmem: Refactor shmem_symlink()
2023-06-28 15:25 [PATCH v6 0/3] shmemfs stable directory offsets Chuck Lever
2023-06-28 15:25 ` [PATCH v6 1/3] libfs: Add directory operations for stable offsets Chuck Lever
@ 2023-06-28 15:25 ` Chuck Lever
2023-06-28 15:25 ` [PATCH v6 3/3] shmem: stable directory offsets Chuck Lever
2 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2023-06-28 15:25 UTC (permalink / raw)
To: viro, brauner, hughd, akpm
Cc: Jeff Layton, Christoph Hellwig, Chuck Lever, jlayton, linux-mm,
linux-fsdevel
From: Chuck Lever <chuck.lever@oracle.com>
De-duplicate the error handling paths. No change in behavior is
expected.
Suggested-by: Jeff Layton <jlayton@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
mm/shmem.c | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/mm/shmem.c b/mm/shmem.c
index e40a08c5c6d7..721f9fd064aa 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -3161,26 +3161,22 @@ static int shmem_symlink(struct mnt_idmap *idmap, struct inode *dir,
error = security_inode_init_security(inode, dir, &dentry->d_name,
shmem_initxattrs, NULL);
- if (error && error != -EOPNOTSUPP) {
- iput(inode);
- return error;
- }
+ if (error && error != -EOPNOTSUPP)
+ goto out_iput;
inode->i_size = len-1;
if (len <= SHORT_SYMLINK_LEN) {
inode->i_link = kmemdup(symname, len, GFP_KERNEL);
if (!inode->i_link) {
- iput(inode);
- return -ENOMEM;
+ error = -ENOMEM;
+ goto out_iput;
}
inode->i_op = &shmem_short_symlink_operations;
} else {
inode_nohighmem(inode);
error = shmem_get_folio(inode, 0, &folio, SGP_WRITE);
- if (error) {
- iput(inode);
- return error;
- }
+ if (error)
+ goto out_iput;
inode->i_mapping->a_ops = &shmem_aops;
inode->i_op = &shmem_symlink_inode_operations;
memcpy(folio_address(folio), symname, len);
@@ -3195,6 +3191,9 @@ static int shmem_symlink(struct mnt_idmap *idmap, struct inode *dir,
d_instantiate(dentry, inode);
dget(dentry);
return 0;
+out_iput:
+ iput(inode);
+ return error;
}
static void shmem_put_link(void *arg)
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v6 3/3] shmem: stable directory offsets
2023-06-28 15:25 [PATCH v6 0/3] shmemfs stable directory offsets Chuck Lever
2023-06-28 15:25 ` [PATCH v6 1/3] libfs: Add directory operations for stable offsets Chuck Lever
2023-06-28 15:25 ` [PATCH v6 2/3] shmem: Refactor shmem_symlink() Chuck Lever
@ 2023-06-28 15:25 ` Chuck Lever
2 siblings, 0 replies; 5+ messages in thread
From: Chuck Lever @ 2023-06-28 15:25 UTC (permalink / raw)
To: viro, brauner, hughd, akpm; +Cc: Chuck Lever, jlayton, linux-mm, linux-fsdevel
From: Chuck Lever <chuck.lever@oracle.com>
The current cursor-based directory offset mechanism doesn't work
when a tmpfs filesystem is exported via NFS. This is because NFS
clients do not open directories. Each server-side READDIR operation
has to open the directory, read it, then close it. The cursor state
for that directory, being associated strictly with the opened
struct file, is thus discarded after each NFS READDIR operation.
Directory offsets are cached not only by NFS clients, but also by
user space libraries on those clients. Essentially there is no way
to invalidate those caches when directory offsets have changed on
an NFS server after the offset-to-dentry mapping changes. Thus the
whole application stack depends on unchanging directory offsets.
The solution we've come up with is to make the directory offset for
each file in a tmpfs filesystem stable for the life of the directory
entry it represents.
shmem_readdir() and shmem_dir_llseek() now use an xarray to map each
directory offset (an loff_t integer) to the memory address of a
struct dentry.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
---
include/linux/shmem_fs.h | 1 +
mm/shmem.c | 47 +++++++++++++++++++++++++++++++++++++++-------
2 files changed, 41 insertions(+), 7 deletions(-)
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
index 9029abd29b1c..a5454a80ab30 100644
--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -27,6 +27,7 @@ struct shmem_inode_info {
atomic_t stop_eviction; /* hold when working on inode */
struct timespec64 i_crtime; /* file creation time */
unsigned int fsflags; /* flags for FS_IOC_[SG]ETFLAGS */
+ struct offset_ctx dir_offsets; /* stable entry offsets */
struct inode vfs_inode;
};
diff --git a/mm/shmem.c b/mm/shmem.c
index 721f9fd064aa..318a6e70b6b1 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2355,6 +2355,11 @@ static void shmem_set_inode_flags(struct inode *inode, unsigned int fsflags)
#define shmem_initxattrs NULL
#endif
+static struct offset_ctx *shmem_get_offset_ctx(struct inode *inode)
+{
+ return &SHMEM_I(inode)->dir_offsets;
+}
+
static struct inode *shmem_get_inode(struct mnt_idmap *idmap, struct super_block *sb,
struct inode *dir, umode_t mode, dev_t dev,
unsigned long flags)
@@ -2410,7 +2415,8 @@ 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 = &simple_offset_dir_operations;
+ simple_offset_init(shmem_get_offset_ctx(inode));
break;
case S_IFLNK:
/*
@@ -2950,7 +2956,10 @@ shmem_mknod(struct mnt_idmap *idmap, struct inode *dir,
if (error && error != -EOPNOTSUPP)
goto out_iput;
- error = 0;
+ error = simple_offset_add(shmem_get_offset_ctx(dir), dentry);
+ if (error)
+ goto out_iput;
+
dir->i_size += BOGO_DIRENT_SIZE;
dir->i_ctime = dir->i_mtime = current_time(dir);
inode_inc_iversion(dir);
@@ -3027,6 +3036,13 @@ static int shmem_link(struct dentry *old_dentry, struct inode *dir, struct dentr
goto out;
}
+ ret = simple_offset_add(shmem_get_offset_ctx(dir), dentry);
+ if (ret) {
+ if (inode->i_nlink)
+ shmem_free_inode(inode->i_sb);
+ goto out;
+ }
+
dir->i_size += BOGO_DIRENT_SIZE;
inode->i_ctime = dir->i_ctime = dir->i_mtime = current_time(inode);
inode_inc_iversion(dir);
@@ -3045,6 +3061,8 @@ static int shmem_unlink(struct inode *dir, struct dentry *dentry)
if (inode->i_nlink > 1 && !S_ISDIR(inode->i_mode))
shmem_free_inode(inode->i_sb);
+ simple_offset_remove(shmem_get_offset_ctx(dir), dentry);
+
dir->i_size -= BOGO_DIRENT_SIZE;
inode->i_ctime = dir->i_ctime = dir->i_mtime = current_time(inode);
inode_inc_iversion(dir);
@@ -3103,24 +3121,29 @@ static int shmem_rename2(struct mnt_idmap *idmap,
{
struct inode *inode = d_inode(old_dentry);
int they_are_dirs = S_ISDIR(inode->i_mode);
+ int error;
if (flags & ~(RENAME_NOREPLACE | RENAME_EXCHANGE | RENAME_WHITEOUT))
return -EINVAL;
if (flags & RENAME_EXCHANGE)
- return simple_rename_exchange(old_dir, old_dentry, new_dir, new_dentry);
+ return simple_offset_rename_exchange(old_dir, old_dentry,
+ new_dir, new_dentry);
if (!simple_empty(new_dentry))
return -ENOTEMPTY;
if (flags & RENAME_WHITEOUT) {
- int error;
-
error = shmem_whiteout(idmap, old_dir, old_dentry);
if (error)
return error;
}
+ simple_offset_remove(shmem_get_offset_ctx(old_dir), old_dentry);
+ error = simple_offset_add(shmem_get_offset_ctx(new_dir), old_dentry);
+ if (error)
+ return error;
+
if (d_really_is_positive(new_dentry)) {
(void) shmem_unlink(new_dir, new_dentry);
if (they_are_dirs) {
@@ -3164,19 +3187,23 @@ static int shmem_symlink(struct mnt_idmap *idmap, struct inode *dir,
if (error && error != -EOPNOTSUPP)
goto out_iput;
+ error = simple_offset_add(shmem_get_offset_ctx(dir), dentry);
+ if (error)
+ goto out_iput;
+
inode->i_size = len-1;
if (len <= SHORT_SYMLINK_LEN) {
inode->i_link = kmemdup(symname, len, GFP_KERNEL);
if (!inode->i_link) {
error = -ENOMEM;
- goto out_iput;
+ goto out_remove_offset;
}
inode->i_op = &shmem_short_symlink_operations;
} else {
inode_nohighmem(inode);
error = shmem_get_folio(inode, 0, &folio, SGP_WRITE);
if (error)
- goto out_iput;
+ goto out_remove_offset;
inode->i_mapping->a_ops = &shmem_aops;
inode->i_op = &shmem_symlink_inode_operations;
memcpy(folio_address(folio), symname, len);
@@ -3191,6 +3218,9 @@ static int shmem_symlink(struct mnt_idmap *idmap, struct inode *dir,
d_instantiate(dentry, inode);
dget(dentry);
return 0;
+
+out_remove_offset:
+ simple_offset_remove(shmem_get_offset_ctx(dir), dentry);
out_iput:
iput(inode);
return error;
@@ -3920,6 +3950,8 @@ static void shmem_destroy_inode(struct inode *inode)
{
if (S_ISREG(inode->i_mode))
mpol_free_shared_policy(&SHMEM_I(inode)->policy);
+ if (S_ISDIR(inode->i_mode))
+ simple_offset_destroy(shmem_get_offset_ctx(inode));
}
static void shmem_init_inode(void *foo)
@@ -4000,6 +4032,7 @@ static const struct inode_operations shmem_dir_inode_operations = {
.mknod = shmem_mknod,
.rename = shmem_rename2,
.tmpfile = shmem_tmpfile,
+ .get_offset_ctx = shmem_get_offset_ctx,
#endif
#ifdef CONFIG_TMPFS_XATTR
.listxattr = shmem_listxattr,
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v6 1/3] libfs: Add directory operations for stable offsets
2023-06-28 15:25 ` [PATCH v6 1/3] libfs: Add directory operations for stable offsets Chuck Lever
@ 2023-06-30 11:08 ` Bernd Schubert
0 siblings, 0 replies; 5+ messages in thread
From: Bernd Schubert @ 2023-06-30 11:08 UTC (permalink / raw)
To: Chuck Lever, viro, brauner, hughd, akpm
Cc: Chuck Lever, jlayton, linux-mm, linux-fsdevel
On 6/28/23 17:25, Chuck Lever wrote:
> From: Chuck Lever <chuck.lever@oracle.com>
>
> Create a vector of directory operations in fs/libfs.c that handles
> directory seeks and readdir via stable offsets instead of the
> current cursor-based mechanism.
>
> For the moment these are unused.
>
> Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
> ---
> fs/libfs.c | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/fs.h | 18 ++++
> 2 files changed, 265 insertions(+)
>
> diff --git a/fs/libfs.c b/fs/libfs.c
> index 89cf614a3271..2b0d5ac472df 100644
> --- a/fs/libfs.c
> +++ b/fs/libfs.c
> @@ -239,6 +239,253 @@ const struct inode_operations simple_dir_inode_operations = {
> };
> EXPORT_SYMBOL(simple_dir_inode_operations);
>
> +static void offset_set(struct dentry *dentry, unsigned long offset)
> +{
> + dentry->d_fsdata = (void *)offset;
> +}
> +
> +static unsigned long dentry2offset(struct dentry *dentry)
> +{
> + return (unsigned long)dentry->d_fsdata;
> +}
> +
> +/**
> + * simple_offset_init - initialize an offset_ctx
> + * @octx: directory offset map to be initialized
> + *
> + */
> +void simple_offset_init(struct offset_ctx *octx)
> +{
> + xa_init_flags(&octx->xa, XA_FLAGS_ALLOC1);
> +
> + /* 0 is '.', 1 is '..', so always start with offset 2 */
> + octx->next_offset = 2;
> +}
> +
> +/**
> + * simple_offset_add - Add an entry to a directory's offset map
> + * @octx: directory offset ctx to be updated
> + * @dentry: new dentry being added
> + *
> + * Returns zero on success. @so_ctx and the dentry offset are updated.
> + * Otherwise, a negative errno value is returned.
> + */
> +int simple_offset_add(struct offset_ctx *octx, struct dentry *dentry)
> +{
> + static const struct xa_limit limit = XA_LIMIT(2, U32_MAX);
> + u32 offset;
> + int ret;
> +
> + if (dentry2offset(dentry) != 0)
> + return -EBUSY;
> +
> + ret = xa_alloc_cyclic(&octx->xa, &offset, dentry, limit,
> + &octx->next_offset, GFP_KERNEL);
> + if (ret < 0)
> + return ret;
> +
> + offset_set(dentry, offset);
> + return 0;
> +}
> +
> +/**
> + * simple_offset_remove - Remove an entry to a directory's offset map
> + * @octx: directory offset ctx to be updated
> + * @dentry: dentry being removed
> + *
> + */
> +void simple_offset_remove(struct offset_ctx *octx, struct dentry *dentry)
> +{
> + unsigned long index = dentry2offset(dentry);
> +
> + if (index == 0)
> + return;
> +
> + xa_erase(&octx->xa, index);
> + offset_set(dentry, 0);
> +}
> +
> +/**
> + * simple_offset_rename_exchange - exchange rename with directory offsets
> + * @old_dir: parent of dentry being moved
> + * @old_dentry: dentry being moved
> + * @new_dir: destination parent
> + * @new_dentry: destination dentry
> + *
> + * Returns zero on success. Otherwise a negative errno is returned and the
> + * rename is rolled back.
> + */
> +int simple_offset_rename_exchange(struct inode *old_dir,
> + struct dentry *old_dentry,
> + struct inode *new_dir,
> + struct dentry *new_dentry)
> +{
> + struct offset_ctx *old_ctx = old_dir->i_op->get_offset_ctx(old_dir);
> + struct offset_ctx *new_ctx = new_dir->i_op->get_offset_ctx(new_dir);
> + unsigned long old_index = dentry2offset(old_dentry);
> + unsigned long new_index = dentry2offset(new_dentry);
> + int ret;
> +
> + simple_offset_remove(old_ctx, old_dentry);
> + simple_offset_remove(new_ctx, new_dentry);
> +
> + ret = simple_offset_add(new_ctx, old_dentry);
> + if (ret)
> + goto out_restore;
> +
> + ret = simple_offset_add(old_ctx, new_dentry);
> + if (ret) {
> + simple_offset_remove(new_ctx, old_dentry);
> + goto out_restore;
> + }
> +
> + ret = simple_rename_exchange(old_dir, old_dentry, new_dir, new_dentry);
> + if (ret) {
> + simple_offset_remove(new_ctx, old_dentry);
> + simple_offset_remove(old_ctx, new_dentry);
> + goto out_restore;
> + }
> + return 0;
> +
> +out_restore:
> + offset_set(old_dentry, old_index);
> + xa_store(&old_ctx->xa, old_index, old_dentry, GFP_KERNEL);
> + offset_set(new_dentry, new_index);
> + xa_store(&new_ctx->xa, new_index, new_dentry, GFP_KERNEL);
> + return ret;
> +}
Thanks for the update, looks great!
> +
> +/**
> + * simple_offset_destroy - Release offset map
> + * @octx: directory offset ctx that is about to be destroyed
> + *
> + * During fs teardown (eg. umount), a directory's offset map might still
> + * contain entries. xa_destroy() cleans out anything that remains.
> + */
> +void simple_offset_destroy(struct offset_ctx *octx)
> +{
> + xa_destroy(&octx->xa);
> +}
> +
> +/**
> + * offset_dir_llseek - Advance the read position of a directory descriptor
> + * @file: an open directory whose position is to be updated
> + * @offset: a byte offset
> + * @whence: enumerator describing the starting position for this update
> + *
> + * SEEK_END, SEEK_DATA, and SEEK_HOLE are not supported for directories.
> + *
> + * Returns the updated read position if successful; otherwise a
> + * negative errno is returned and the read position remains unchanged.
> + */
> +static loff_t offset_dir_llseek(struct file *file, loff_t offset, int whence)
> +{
> + switch (whence) {
> + case SEEK_CUR:
> + offset += file->f_pos;
> + fallthrough;
> + case SEEK_SET:
> + if (offset >= 0)
> + break;
> + fallthrough;
> + default:
> + return -EINVAL;
> + }
> +
> + return vfs_setpos(file, offset, U32_MAX);
> +}
> +
> +static struct dentry *offset_find_next(struct xa_state *xas)
> +{
> + struct dentry *child, *found = NULL;
> +
> + rcu_read_lock();
> + child = xas_next_entry(xas, U32_MAX);
> + if (!child)
> + goto out;
> + spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED);
> + if (simple_positive(child))
> + found = dget_dlock(child);
> + spin_unlock(&child->d_lock);
> +out:
> + rcu_read_unlock();
> + return found;
> +}
> +
> +static bool offset_dir_emit(struct dir_context *ctx, struct dentry *dentry)
> +{
> + loff_t offset = dentry2offset(dentry);
> + struct inode *inode = d_inode(dentry);
> +
> + return ctx->actor(ctx, dentry->d_name.name, dentry->d_name.len, offset,
> + inode->i_ino, fs_umode_to_dtype(inode->i_mode));
> +}
> +
> +static void offset_iterate_dir(struct dentry *dir, struct dir_context *ctx)
> +{
> + struct inode *inode = d_inode(dir);
> + struct offset_ctx *so_ctx = inode->i_op->get_offset_ctx(inode);
> + XA_STATE(xas, &so_ctx->xa, ctx->pos);
> + struct dentry *dentry;
> +
> + while (true) {
> + spin_lock(&dir->d_lock);
> + dentry = offset_find_next(&xas);
> + spin_unlock(&dir->d_lock);
> + if (!dentry)
> + break;
> +
> + if (!offset_dir_emit(ctx, dentry)) {
> + dput(dentry);
> + break;
> + }
> +
> + dput(dentry);
> + ctx->pos = xas.xa_index + 1;
> + }
> +}
> +
> +/**
> + * offset_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 offset_readdir(struct file *file, struct dir_context *ctx)
> +{
> + struct dentry *dir = file->f_path.dentry;
> +
> + lockdep_assert_held(&d_inode(dir)->i_rwsem);
> +
> + if (!dir_emit_dots(file, ctx))
> + return 0;
> +
> + offset_iterate_dir(dir, ctx);
> + return 0;
> +}
> +
> +const struct file_operations simple_offset_dir_operations = {
> + .llseek = offset_dir_llseek,
> + .iterate_shared = offset_readdir,
> + .read = generic_read_dir,
> + .fsync = noop_fsync,
> +};
> +
> static struct dentry *find_next_child(struct dentry *parent, struct dentry *prev)
> {
> struct dentry *child = NULL;
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index 133f0640fb24..85de389e4eb8 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -1767,6 +1767,7 @@ struct dir_context {
>
> struct iov_iter;
> struct io_uring_cmd;
> +struct offset_ctx;
>
> struct file_operations {
> struct module *owner;
> @@ -1854,6 +1855,7 @@ struct inode_operations {
> int (*fileattr_set)(struct mnt_idmap *idmap,
> struct dentry *dentry, struct fileattr *fa);
> int (*fileattr_get)(struct dentry *dentry, struct fileattr *fa);
> + struct offset_ctx *(*get_offset_ctx)(struct inode *inode);
> } ____cacheline_aligned;
Should this be documented in filesystems/vfs.rst and
filesystems/locking.rst?
Thanks,
Bernd
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2023-06-30 11:08 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-28 15:25 [PATCH v6 0/3] shmemfs stable directory offsets Chuck Lever
2023-06-28 15:25 ` [PATCH v6 1/3] libfs: Add directory operations for stable offsets Chuck Lever
2023-06-30 11:08 ` Bernd Schubert
2023-06-28 15:25 ` [PATCH v6 2/3] shmem: Refactor shmem_symlink() Chuck Lever
2023-06-28 15:25 ` [PATCH v6 3/3] shmem: stable directory offsets Chuck Lever
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox