Skip to content

Commit 5571f1e

Browse files
dschatzbergMiklos Szeredi
authored andcommitted
fuse: enable caching of symlinks
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@redhat.com>
1 parent 9a2eb24 commit 5571f1e

File tree

4 files changed

+92
-26
lines changed

4 files changed

+92
-26
lines changed

fs/fuse/dir.c

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1160,38 +1160,78 @@ static int fuse_permission(struct inode *inode, int mask)
11601160
return err;
11611161
}
11621162

1163-
static const char *fuse_get_link(struct dentry *dentry,
1164-
struct inode *inode,
1165-
struct delayed_call *done)
1163+
static int fuse_readlink_page(struct inode *inode, struct page *page)
11661164
{
11671165
struct fuse_conn *fc = get_fuse_conn(inode);
1168-
FUSE_ARGS(args);
1169-
char *link;
1170-
ssize_t ret;
1166+
struct fuse_req *req;
1167+
int err;
11711168

1172-
if (!dentry)
1173-
return ERR_PTR(-ECHILD);
1169+
req = fuse_get_req(fc, 1);
1170+
if (IS_ERR(req))
1171+
return PTR_ERR(req);
1172+
1173+
req->out.page_zeroing = 1;
1174+
req->out.argpages = 1;
1175+
req->num_pages = 1;
1176+
req->pages[0] = page;
1177+
req->page_descs[0].length = PAGE_SIZE - 1;
1178+
req->in.h.opcode = FUSE_READLINK;
1179+
req->in.h.nodeid = get_node_id(inode);
1180+
req->out.argvar = 1;
1181+
req->out.numargs = 1;
1182+
req->out.args[0].size = PAGE_SIZE - 1;
1183+
fuse_request_send(fc, req);
1184+
err = req->out.h.error;
11741185

1175-
link = kmalloc(PAGE_SIZE, GFP_KERNEL);
1176-
if (!link)
1177-
return ERR_PTR(-ENOMEM);
1186+
if (!err) {
1187+
char *link = page_address(page);
1188+
size_t len = req->out.args[0].size;
11781189

1179-
args.in.h.opcode = FUSE_READLINK;
1180-
args.in.h.nodeid = get_node_id(inode);
1181-
args.out.argvar = 1;
1182-
args.out.numargs = 1;
1183-
args.out.args[0].size = PAGE_SIZE - 1;
1184-
args.out.args[0].value = link;
1185-
ret = fuse_simple_request(fc, &args);
1186-
if (ret < 0) {
1187-
kfree(link);
1188-
link = ERR_PTR(ret);
1189-
} else {
1190-
link[ret] = '\0';
1191-
set_delayed_call(done, kfree_link, link);
1190+
BUG_ON(len >= PAGE_SIZE);
1191+
link[len] = '\0';
11921192
}
1193+
1194+
fuse_put_request(fc, req);
11931195
fuse_invalidate_atime(inode);
1194-
return link;
1196+
1197+
return err;
1198+
}
1199+
1200+
static const char *fuse_get_link(struct dentry *dentry, struct inode *inode,
1201+
struct delayed_call *callback)
1202+
{
1203+
struct fuse_conn *fc = get_fuse_conn(inode);
1204+
struct page *page;
1205+
int err;
1206+
1207+
err = -EIO;
1208+
if (is_bad_inode(inode))
1209+
goto out_err;
1210+
1211+
if (fc->cache_symlinks)
1212+
return page_get_link(dentry, inode, callback);
1213+
1214+
err = -ECHILD;
1215+
if (!dentry)
1216+
goto out_err;
1217+
1218+
page = alloc_page(GFP_KERNEL);
1219+
err = -ENOMEM;
1220+
if (!page)
1221+
goto out_err;
1222+
1223+
err = fuse_readlink_page(inode, page);
1224+
if (err) {
1225+
__free_page(page);
1226+
goto out_err;
1227+
}
1228+
1229+
set_delayed_call(callback, page_put_link, page);
1230+
1231+
return page_address(page);
1232+
1233+
out_err:
1234+
return ERR_PTR(err);
11951235
}
11961236

11971237
static int fuse_dir_open(struct inode *inode, struct file *file)
@@ -1644,7 +1684,25 @@ void fuse_init_dir(struct inode *inode)
16441684
fi->rdc.version = 0;
16451685
}
16461686

1687+
static int fuse_symlink_readpage(struct file *null, struct page *page)
1688+
{
1689+
int err = fuse_readlink_page(page->mapping->host, page);
1690+
1691+
if (!err)
1692+
SetPageUptodate(page);
1693+
1694+
unlock_page(page);
1695+
1696+
return err;
1697+
}
1698+
1699+
static const struct address_space_operations fuse_symlink_aops = {
1700+
.readpage = fuse_symlink_readpage,
1701+
};
1702+
16471703
void fuse_init_symlink(struct inode *inode)
16481704
{
16491705
inode->i_op = &fuse_symlink_inode_operations;
1706+
inode->i_data.a_ops = &fuse_symlink_aops;
1707+
inode_nohighmem(inode);
16501708
}

fs/fuse/fuse_i.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,9 @@ struct fuse_conn {
613613
/** handle fs handles killing suid/sgid/cap on write/chown/trunc */
614614
unsigned handle_killpriv:1;
615615

616+
/** cache READLINK responses in page cache */
617+
unsigned cache_symlinks:1;
618+
616619
/*
617620
* The following bitfields are only for optimization purposes
618621
* and hence races in setting them will not cause malfunction

fs/fuse/inode.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,8 @@ static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req)
928928
fc->posix_acl = 1;
929929
fc->sb->s_xattr = fuse_acl_xattr_handlers;
930930
}
931+
if (arg->flags & FUSE_CACHE_SYMLINKS)
932+
fc->cache_symlinks = 1;
931933
if (arg->flags & FUSE_ABORT_ERROR)
932934
fc->abort_err = 1;
933935
if (arg->flags & FUSE_MAX_PAGES) {
@@ -966,7 +968,7 @@ static void fuse_send_init(struct fuse_conn *fc, struct fuse_req *req)
966968
FUSE_DO_READDIRPLUS | FUSE_READDIRPLUS_AUTO | FUSE_ASYNC_DIO |
967969
FUSE_WRITEBACK_CACHE | FUSE_NO_OPEN_SUPPORT |
968970
FUSE_PARALLEL_DIROPS | FUSE_HANDLE_KILLPRIV | FUSE_POSIX_ACL |
969-
FUSE_ABORT_ERROR | FUSE_MAX_PAGES;
971+
FUSE_ABORT_ERROR | FUSE_MAX_PAGES | FUSE_CACHE_SYMLINKS;
970972
req->in.h.opcode = FUSE_INIT;
971973
req->in.numargs = 1;
972974
req->in.args[0].size = sizeof(*arg);

include/uapi/linux/fuse.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
* - add FUSE_COPY_FILE_RANGE
122122
* - add FOPEN_CACHE_DIR
123123
* - add FUSE_MAX_PAGES, add max_pages to init_out
124+
* - add FUSE_CACHE_SYMLINKS
124125
*/
125126

126127
#ifndef _LINUX_FUSE_H
@@ -257,6 +258,7 @@ struct fuse_file_lock {
257258
* FUSE_POSIX_ACL: filesystem supports posix acls
258259
* FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED
259260
* FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages
261+
* FUSE_CACHE_SYMLINKS: cache READLINK responses
260262
*/
261263
#define FUSE_ASYNC_READ (1 << 0)
262264
#define FUSE_POSIX_LOCKS (1 << 1)
@@ -281,6 +283,7 @@ struct fuse_file_lock {
281283
#define FUSE_POSIX_ACL (1 << 20)
282284
#define FUSE_ABORT_ERROR (1 << 21)
283285
#define FUSE_MAX_PAGES (1 << 22)
286+
#define FUSE_CACHE_SYMLINKS (1 << 23)
284287

285288
/**
286289
* CUSE INIT request/reply flags

0 commit comments

Comments
 (0)