From: Dan Schatzberg <dschatzberg(a)fb.com>
commit 5571f1e65486be025f73fa6aa30fb03725d362a2 upstream
FUSE file reads are cached in the page cache, but symlink reads are
not. This patch enables FUSE READLINK operations to be cached which
can improve performance of some FUSE workloads.
In particular, I'm working on a FUSE filesystem for access to source
code and discovered that about a 10% improvement to build times is
achieved with this patch (there are a lot of symlinks in the source
tree).
Signed-off-by: Miklos Szeredi <mszeredi(a)redhat.com>
Signed-off-by: Jeffle Xu <jefflexu(a)linux.alibaba.com>
---
fs/fuse/dir.c | 108 +++++++++++++++++++++++++++++---------
fs/fuse/fuse_i.h | 3 ++
fs/fuse/inode.c | 4 +-
include/uapi/linux/fuse.h | 3 ++
4 files changed, 92 insertions(+), 26 deletions(-)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 27814ce8b52a..c3d14a2d5966 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -1169,38 +1169,78 @@ static int fuse_permission(struct inode *inode, int mask)
return err;
}
-static const char *fuse_get_link(struct dentry *dentry,
- struct inode *inode,
- struct delayed_call *done)
+static int fuse_readlink_page(struct inode *inode, struct page *page)
{
struct fuse_conn *fc = get_fuse_conn(inode);
- FUSE_ARGS(args);
- char *link;
- ssize_t ret;
+ struct fuse_req *req;
+ int err;
- if (!dentry)
- return ERR_PTR(-ECHILD);
+ req = fuse_get_req(fc, 1);
+ if (IS_ERR(req))
+ return PTR_ERR(req);
+
+ req->out.page_zeroing = 1;
+ req->out.argpages = 1;
+ req->num_pages = 1;
+ req->pages[0] = page;
+ req->page_descs[0].length = PAGE_SIZE - 1;
+ req->in.h.opcode = FUSE_READLINK;
+ req->in.h.nodeid = get_node_id(inode);
+ req->out.argvar = 1;
+ req->out.numargs = 1;
+ req->out.args[0].size = PAGE_SIZE - 1;
+ fuse_request_send(fc, req);
+ err = req->out.h.error;
- link = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!link)
- return ERR_PTR(-ENOMEM);
+ if (!err) {
+ char *link = page_address(page);
+ size_t len = req->out.args[0].size;
- args.in.h.opcode = FUSE_READLINK;
- args.in.h.nodeid = get_node_id(inode);
- args.out.argvar = 1;
- args.out.numargs = 1;
- args.out.args[0].size = PAGE_SIZE - 1;
- args.out.args[0].value = link;
- ret = fuse_simple_request(fc, &args);
- if (ret < 0) {
- kfree(link);
- link = ERR_PTR(ret);
- } else {
- link[ret] = '\0';
- set_delayed_call(done, kfree_link, link);
+ BUG_ON(len >= PAGE_SIZE);
+ link[len] = '\0';
}
+
+ fuse_put_request(fc, req);
fuse_invalidate_atime(inode);
- return link;
+
+ return err;
+}
+
+static const char *fuse_get_link(struct dentry *dentry, struct inode *inode,
+ struct delayed_call *callback)
+{
+ struct fuse_conn *fc = get_fuse_conn(inode);
+ struct page *page;
+ int err;
+
+ err = -EIO;
+ if (is_bad_inode(inode))
+ goto out_err;
+
+ if (fc->cache_symlinks)
+ return page_get_link(dentry, inode, callback);
+
+ err = -ECHILD;
+ if (!dentry)
+ goto out_err;
+
+ page = alloc_page(GFP_KERNEL);
+ err = -ENOMEM;
+ if (!page)
+ goto out_err;
+
+ err = fuse_readlink_page(inode, page);
+ if (err) {
+ __free_page(page);
+ goto out_err;
+ }
+
+ set_delayed_call(callback, page_put_link, page);
+
+ return page_address(page);
+
+out_err:
+ return ERR_PTR(err);
}
static int fuse_dir_open(struct inode *inode, struct file *file)
@@ -1673,7 +1713,25 @@ void fuse_init_dir(struct inode *inode)
inode->i_fop = &fuse_dir_operations;
}
+static int fuse_symlink_readpage(struct file *null, struct page *page)
+{
+ int err = fuse_readlink_page(page->mapping->host, page);
+
+ if (!err)
+ SetPageUptodate(page);
+
+ unlock_page(page);
+
+ return err;
+}
+
+static const struct address_space_operations fuse_symlink_aops = {
+ .readpage = fuse_symlink_readpage,
+};
+
void fuse_init_symlink(struct inode *inode)
{
inode->i_op = &fuse_symlink_inode_operations;
+ inode->i_data.a_ops = &fuse_symlink_aops;
+ inode_nohighmem(inode);
}
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 086b0c4c4a59..ec608058f338 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -708,6 +708,9 @@ struct fuse_conn {
/** handle fs handles killing suid/sgid/cap on write/chown/trunc */
unsigned handle_killpriv:1;
+ /** cache READLINK responses in page cache */
+ unsigned cache_symlinks:1;
+
/*
* The following bitfields are only for optimization purposes
* and hence races in setting them will not cause malfunction
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 1757fbc0d631..b27a0ecec0ee 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -978,6 +978,8 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req
*req)
fc->posix_acl = 1;
fc->sb->s_xattr = fuse_acl_xattr_handlers;
}
+ if (arg->flags & FUSE_CACHE_SYMLINKS)
+ fc->cache_symlinks = 1;
if (arg->flags & FUSE_ABORT_ERROR)
fc->abort_err = 1;
if (IS_ENABLED(CONFIG_FUSE_DAX) &&
@@ -1022,7 +1024,7 @@ void fuse_send_init(struct fuse_conn *fc, struct fuse_req *req)
FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO | FUSE_ASYNC_DIO |
FUSE_WRITEBACK_CACHE | FUSE_NO_OPEN_SUPPORT |
FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL |
- FUSE_ABORT_ERROR;
+ FUSE_ABORT_ERROR | FUSE_CACHE_SYMLINKS;
#ifdef CONFIG_FUSE_DAX
if (fc->dax)
arg->flags |= FUSE_MAP_ALIGNMENT;
diff --git a/include/uapi/linux/fuse.h b/include/uapi/linux/fuse.h
index f008a61c5506..1c6956b32597 100644
--- a/include/uapi/linux/fuse.h
+++ b/include/uapi/linux/fuse.h
@@ -119,6 +119,7 @@
*
* 7.28
* - add FOPEN_CACHE_DIR
+ * - add FUSE_CACHE_SYMLINKS
*/
#ifndef _LINUX_FUSE_H
@@ -256,6 +257,7 @@ struct fuse_file_lock {
* FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc
* FUSE_POSIX_ACL: filesystem supports posix acls
* FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED
+ * FUSE_CACHE_SYMLINKS: cache READLINK responses
* FUSE_MAP_ALIGNMENT: init_out.map_alignment contains log2(byte alignment) for
* foffset and moffset fields in struct
* fuse_setupmapping_out and fuse_removemapping_one.
@@ -282,6 +284,7 @@ struct fuse_file_lock {
#define FUSE_HANDLE_KILLPRIV (1 << 19)
#define FUSE_POSIX_ACL (1 << 20)
#define FUSE_ABORT_ERROR (1 << 21)
+#define FUSE_CACHE_SYMLINKS (1 << 23)
#define FUSE_MAP_ALIGNMENT (1 << 26)
/**
--
2.27.0