|
@@ -14,24 +14,9 @@
|
|
|
#include <linux/namei.h>
|
|
|
#include <linux/slab.h>
|
|
|
#include <linux/xattr.h>
|
|
|
+#include <linux/iversion.h>
|
|
|
#include <linux/posix_acl.h>
|
|
|
|
|
|
-static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
|
|
|
-{
|
|
|
- struct fuse_conn *fc = get_fuse_conn(dir);
|
|
|
- struct fuse_inode *fi = get_fuse_inode(dir);
|
|
|
-
|
|
|
- if (!fc->do_readdirplus)
|
|
|
- return false;
|
|
|
- if (!fc->readdirplus_auto)
|
|
|
- return true;
|
|
|
- if (test_and_clear_bit(FUSE_I_ADVISE_RDPLUS, &fi->state))
|
|
|
- return true;
|
|
|
- if (ctx->pos == 0)
|
|
|
- return true;
|
|
|
- return false;
|
|
|
-}
|
|
|
-
|
|
|
static void fuse_advise_use_readdirplus(struct inode *dir)
|
|
|
{
|
|
|
struct fuse_inode *fi = get_fuse_inode(dir);
|
|
@@ -80,8 +65,7 @@ static u64 time_to_jiffies(u64 sec, u32 nsec)
|
|
|
* Set dentry and possibly attribute timeouts from the lookup/mk*
|
|
|
* replies
|
|
|
*/
|
|
|
-static void fuse_change_entry_timeout(struct dentry *entry,
|
|
|
- struct fuse_entry_out *o)
|
|
|
+void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o)
|
|
|
{
|
|
|
fuse_dentry_settime(entry,
|
|
|
time_to_jiffies(o->entry_valid, o->entry_valid_nsec));
|
|
@@ -92,18 +76,29 @@ static u64 attr_timeout(struct fuse_attr_out *o)
|
|
|
return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
|
|
|
}
|
|
|
|
|
|
-static u64 entry_attr_timeout(struct fuse_entry_out *o)
|
|
|
+u64 entry_attr_timeout(struct fuse_entry_out *o)
|
|
|
{
|
|
|
return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
|
|
|
}
|
|
|
|
|
|
+static void fuse_invalidate_attr_mask(struct inode *inode, u32 mask)
|
|
|
+{
|
|
|
+ set_mask_bits(&get_fuse_inode(inode)->inval_mask, 0, mask);
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Mark the attributes as stale, so that at the next call to
|
|
|
* ->getattr() they will be fetched from userspace
|
|
|
*/
|
|
|
void fuse_invalidate_attr(struct inode *inode)
|
|
|
{
|
|
|
- get_fuse_inode(inode)->i_time = 0;
|
|
|
+ fuse_invalidate_attr_mask(inode, STATX_BASIC_STATS);
|
|
|
+}
|
|
|
+
|
|
|
+static void fuse_dir_changed(struct inode *dir)
|
|
|
+{
|
|
|
+ fuse_invalidate_attr(dir);
|
|
|
+ inode_maybe_inc_iversion(dir, false);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -113,7 +108,7 @@ void fuse_invalidate_attr(struct inode *inode)
|
|
|
void fuse_invalidate_atime(struct inode *inode)
|
|
|
{
|
|
|
if (!IS_RDONLY(inode))
|
|
|
- fuse_invalidate_attr(inode);
|
|
|
+ fuse_invalidate_attr_mask(inode, STATX_ATIME);
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -262,11 +257,6 @@ invalid:
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
-static int invalid_nodeid(u64 nodeid)
|
|
|
-{
|
|
|
- return !nodeid || nodeid == FUSE_ROOT_ID;
|
|
|
-}
|
|
|
-
|
|
|
static int fuse_dentry_init(struct dentry *dentry)
|
|
|
{
|
|
|
dentry->d_fsdata = kzalloc(sizeof(union fuse_dentry), GFP_KERNEL);
|
|
@@ -469,7 +459,7 @@ static int fuse_create_open(struct inode *dir, struct dentry *entry,
|
|
|
kfree(forget);
|
|
|
d_instantiate(entry, inode);
|
|
|
fuse_change_entry_timeout(entry, &outentry);
|
|
|
- fuse_invalidate_attr(dir);
|
|
|
+ fuse_dir_changed(dir);
|
|
|
err = finish_open(file, entry, generic_file_open);
|
|
|
if (err) {
|
|
|
fuse_sync_release(ff, flags);
|
|
@@ -583,7 +573,7 @@ static int create_new_entry(struct fuse_conn *fc, struct fuse_args *args,
|
|
|
} else {
|
|
|
fuse_change_entry_timeout(entry, &outarg);
|
|
|
}
|
|
|
- fuse_invalidate_attr(dir);
|
|
|
+ fuse_dir_changed(dir);
|
|
|
return 0;
|
|
|
|
|
|
out_put_forget_req:
|
|
@@ -693,7 +683,7 @@ static int fuse_unlink(struct inode *dir, struct dentry *entry)
|
|
|
drop_nlink(inode);
|
|
|
spin_unlock(&fc->lock);
|
|
|
fuse_invalidate_attr(inode);
|
|
|
- fuse_invalidate_attr(dir);
|
|
|
+ fuse_dir_changed(dir);
|
|
|
fuse_invalidate_entry_cache(entry);
|
|
|
fuse_update_ctime(inode);
|
|
|
} else if (err == -EINTR)
|
|
@@ -715,7 +705,7 @@ static int fuse_rmdir(struct inode *dir, struct dentry *entry)
|
|
|
err = fuse_simple_request(fc, &args);
|
|
|
if (!err) {
|
|
|
clear_nlink(d_inode(entry));
|
|
|
- fuse_invalidate_attr(dir);
|
|
|
+ fuse_dir_changed(dir);
|
|
|
fuse_invalidate_entry_cache(entry);
|
|
|
} else if (err == -EINTR)
|
|
|
fuse_invalidate_entry(entry);
|
|
@@ -754,9 +744,9 @@ static int fuse_rename_common(struct inode *olddir, struct dentry *oldent,
|
|
|
fuse_update_ctime(d_inode(newent));
|
|
|
}
|
|
|
|
|
|
- fuse_invalidate_attr(olddir);
|
|
|
+ fuse_dir_changed(olddir);
|
|
|
if (olddir != newdir)
|
|
|
- fuse_invalidate_attr(newdir);
|
|
|
+ fuse_dir_changed(newdir);
|
|
|
|
|
|
/* newent will end up negative */
|
|
|
if (!(flags & RENAME_EXCHANGE) && d_really_is_positive(newent)) {
|
|
@@ -932,7 +922,8 @@ static int fuse_do_getattr(struct inode *inode, struct kstat *stat,
|
|
|
}
|
|
|
|
|
|
static int fuse_update_get_attr(struct inode *inode, struct file *file,
|
|
|
- struct kstat *stat, unsigned int flags)
|
|
|
+ struct kstat *stat, u32 request_mask,
|
|
|
+ unsigned int flags)
|
|
|
{
|
|
|
struct fuse_inode *fi = get_fuse_inode(inode);
|
|
|
int err = 0;
|
|
@@ -942,6 +933,8 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
|
|
|
sync = true;
|
|
|
else if (flags & AT_STATX_DONT_SYNC)
|
|
|
sync = false;
|
|
|
+ else if (request_mask & READ_ONCE(fi->inval_mask))
|
|
|
+ sync = true;
|
|
|
else
|
|
|
sync = time_before64(fi->i_time, get_jiffies_64());
|
|
|
|
|
@@ -959,7 +952,9 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file,
|
|
|
|
|
|
int fuse_update_attributes(struct inode *inode, struct file *file)
|
|
|
{
|
|
|
- return fuse_update_get_attr(inode, file, NULL, 0);
|
|
|
+ /* Do *not* need to get atime for internal purposes */
|
|
|
+ return fuse_update_get_attr(inode, file, NULL,
|
|
|
+ STATX_BASIC_STATS & ~STATX_ATIME, 0);
|
|
|
}
|
|
|
|
|
|
int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid,
|
|
@@ -989,7 +984,7 @@ int fuse_reverse_inval_entry(struct super_block *sb, u64 parent_nodeid,
|
|
|
if (!entry)
|
|
|
goto unlock;
|
|
|
|
|
|
- fuse_invalidate_attr(parent);
|
|
|
+ fuse_dir_changed(parent);
|
|
|
fuse_invalidate_entry(entry);
|
|
|
|
|
|
if (child_nodeid != 0 && d_really_is_positive(entry)) {
|
|
@@ -1165,271 +1160,78 @@ static int fuse_permission(struct inode *inode, int mask)
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
-static int parse_dirfile(char *buf, size_t nbytes, struct file *file,
|
|
|
- struct dir_context *ctx)
|
|
|
-{
|
|
|
- while (nbytes >= FUSE_NAME_OFFSET) {
|
|
|
- struct fuse_dirent *dirent = (struct fuse_dirent *) buf;
|
|
|
- size_t reclen = FUSE_DIRENT_SIZE(dirent);
|
|
|
- if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX)
|
|
|
- return -EIO;
|
|
|
- if (reclen > nbytes)
|
|
|
- break;
|
|
|
- if (memchr(dirent->name, '/', dirent->namelen) != NULL)
|
|
|
- return -EIO;
|
|
|
-
|
|
|
- if (!dir_emit(ctx, dirent->name, dirent->namelen,
|
|
|
- dirent->ino, dirent->type))
|
|
|
- break;
|
|
|
-
|
|
|
- buf += reclen;
|
|
|
- nbytes -= reclen;
|
|
|
- ctx->pos = dirent->off;
|
|
|
- }
|
|
|
-
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static int fuse_direntplus_link(struct file *file,
|
|
|
- struct fuse_direntplus *direntplus,
|
|
|
- u64 attr_version)
|
|
|
-{
|
|
|
- struct fuse_entry_out *o = &direntplus->entry_out;
|
|
|
- struct fuse_dirent *dirent = &direntplus->dirent;
|
|
|
- struct dentry *parent = file->f_path.dentry;
|
|
|
- struct qstr name = QSTR_INIT(dirent->name, dirent->namelen);
|
|
|
- struct dentry *dentry;
|
|
|
- struct dentry *alias;
|
|
|
- struct inode *dir = d_inode(parent);
|
|
|
- struct fuse_conn *fc;
|
|
|
- struct inode *inode;
|
|
|
- DECLARE_WAIT_QUEUE_HEAD_ONSTACK(wq);
|
|
|
-
|
|
|
- if (!o->nodeid) {
|
|
|
- /*
|
|
|
- * Unlike in the case of fuse_lookup, zero nodeid does not mean
|
|
|
- * ENOENT. Instead, it only means the userspace filesystem did
|
|
|
- * not want to return attributes/handle for this entry.
|
|
|
- *
|
|
|
- * So do nothing.
|
|
|
- */
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (name.name[0] == '.') {
|
|
|
- /*
|
|
|
- * We could potentially refresh the attributes of the directory
|
|
|
- * and its parent?
|
|
|
- */
|
|
|
- if (name.len == 1)
|
|
|
- return 0;
|
|
|
- if (name.name[1] == '.' && name.len == 2)
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- if (invalid_nodeid(o->nodeid))
|
|
|
- return -EIO;
|
|
|
- if (!fuse_valid_type(o->attr.mode))
|
|
|
- return -EIO;
|
|
|
-
|
|
|
- fc = get_fuse_conn(dir);
|
|
|
-
|
|
|
- name.hash = full_name_hash(parent, name.name, name.len);
|
|
|
- dentry = d_lookup(parent, &name);
|
|
|
- if (!dentry) {
|
|
|
-retry:
|
|
|
- dentry = d_alloc_parallel(parent, &name, &wq);
|
|
|
- if (IS_ERR(dentry))
|
|
|
- return PTR_ERR(dentry);
|
|
|
- }
|
|
|
- if (!d_in_lookup(dentry)) {
|
|
|
- struct fuse_inode *fi;
|
|
|
- inode = d_inode(dentry);
|
|
|
- if (!inode ||
|
|
|
- get_node_id(inode) != o->nodeid ||
|
|
|
- ((o->attr.mode ^ inode->i_mode) & S_IFMT)) {
|
|
|
- d_invalidate(dentry);
|
|
|
- dput(dentry);
|
|
|
- goto retry;
|
|
|
- }
|
|
|
- if (is_bad_inode(inode)) {
|
|
|
- dput(dentry);
|
|
|
- return -EIO;
|
|
|
- }
|
|
|
-
|
|
|
- fi = get_fuse_inode(inode);
|
|
|
- spin_lock(&fc->lock);
|
|
|
- fi->nlookup++;
|
|
|
- spin_unlock(&fc->lock);
|
|
|
-
|
|
|
- forget_all_cached_acls(inode);
|
|
|
- fuse_change_attributes(inode, &o->attr,
|
|
|
- entry_attr_timeout(o),
|
|
|
- attr_version);
|
|
|
- /*
|
|
|
- * The other branch comes via fuse_iget()
|
|
|
- * which bumps nlookup inside
|
|
|
- */
|
|
|
- } else {
|
|
|
- inode = fuse_iget(dir->i_sb, o->nodeid, o->generation,
|
|
|
- &o->attr, entry_attr_timeout(o),
|
|
|
- attr_version);
|
|
|
- if (!inode)
|
|
|
- inode = ERR_PTR(-ENOMEM);
|
|
|
-
|
|
|
- alias = d_splice_alias(inode, dentry);
|
|
|
- d_lookup_done(dentry);
|
|
|
- if (alias) {
|
|
|
- dput(dentry);
|
|
|
- dentry = alias;
|
|
|
- }
|
|
|
- if (IS_ERR(dentry))
|
|
|
- return PTR_ERR(dentry);
|
|
|
- }
|
|
|
- if (fc->readdirplus_auto)
|
|
|
- set_bit(FUSE_I_INIT_RDPLUS, &get_fuse_inode(inode)->state);
|
|
|
- fuse_change_entry_timeout(dentry, o);
|
|
|
-
|
|
|
- dput(dentry);
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file,
|
|
|
- struct dir_context *ctx, u64 attr_version)
|
|
|
+static int fuse_readlink_page(struct inode *inode, struct page *page)
|
|
|
{
|
|
|
- struct fuse_direntplus *direntplus;
|
|
|
- struct fuse_dirent *dirent;
|
|
|
- size_t reclen;
|
|
|
- int over = 0;
|
|
|
- int ret;
|
|
|
-
|
|
|
- while (nbytes >= FUSE_NAME_OFFSET_DIRENTPLUS) {
|
|
|
- direntplus = (struct fuse_direntplus *) buf;
|
|
|
- dirent = &direntplus->dirent;
|
|
|
- reclen = FUSE_DIRENTPLUS_SIZE(direntplus);
|
|
|
-
|
|
|
- if (!dirent->namelen || dirent->namelen > FUSE_NAME_MAX)
|
|
|
- return -EIO;
|
|
|
- if (reclen > nbytes)
|
|
|
- break;
|
|
|
- if (memchr(dirent->name, '/', dirent->namelen) != NULL)
|
|
|
- return -EIO;
|
|
|
-
|
|
|
- if (!over) {
|
|
|
- /* We fill entries into dstbuf only as much as
|
|
|
- it can hold. But we still continue iterating
|
|
|
- over remaining entries to link them. If not,
|
|
|
- we need to send a FORGET for each of those
|
|
|
- which we did not link.
|
|
|
- */
|
|
|
- over = !dir_emit(ctx, dirent->name, dirent->namelen,
|
|
|
- dirent->ino, dirent->type);
|
|
|
- if (!over)
|
|
|
- ctx->pos = dirent->off;
|
|
|
- }
|
|
|
-
|
|
|
- buf += reclen;
|
|
|
- nbytes -= reclen;
|
|
|
-
|
|
|
- ret = fuse_direntplus_link(file, direntplus, attr_version);
|
|
|
- if (ret)
|
|
|
- fuse_force_forget(file, direntplus->entry_out.nodeid);
|
|
|
- }
|
|
|
-
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
-static int fuse_readdir(struct file *file, struct dir_context *ctx)
|
|
|
-{
|
|
|
- int plus, err;
|
|
|
- size_t nbytes;
|
|
|
- struct page *page;
|
|
|
- struct inode *inode = file_inode(file);
|
|
|
struct fuse_conn *fc = get_fuse_conn(inode);
|
|
|
struct fuse_req *req;
|
|
|
- u64 attr_version = 0;
|
|
|
- bool locked;
|
|
|
-
|
|
|
- if (is_bad_inode(inode))
|
|
|
- return -EIO;
|
|
|
+ int err;
|
|
|
|
|
|
req = fuse_get_req(fc, 1);
|
|
|
if (IS_ERR(req))
|
|
|
return PTR_ERR(req);
|
|
|
|
|
|
- page = alloc_page(GFP_KERNEL);
|
|
|
- if (!page) {
|
|
|
- fuse_put_request(fc, req);
|
|
|
- return -ENOMEM;
|
|
|
- }
|
|
|
-
|
|
|
- plus = fuse_use_readdirplus(inode, ctx);
|
|
|
+ req->out.page_zeroing = 1;
|
|
|
req->out.argpages = 1;
|
|
|
req->num_pages = 1;
|
|
|
req->pages[0] = page;
|
|
|
- req->page_descs[0].length = PAGE_SIZE;
|
|
|
- if (plus) {
|
|
|
- attr_version = fuse_get_attr_version(fc);
|
|
|
- fuse_read_fill(req, file, ctx->pos, PAGE_SIZE,
|
|
|
- FUSE_READDIRPLUS);
|
|
|
- } else {
|
|
|
- fuse_read_fill(req, file, ctx->pos, PAGE_SIZE,
|
|
|
- FUSE_READDIR);
|
|
|
- }
|
|
|
- locked = fuse_lock_inode(inode);
|
|
|
+ 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);
|
|
|
- fuse_unlock_inode(inode, locked);
|
|
|
- nbytes = req->out.args[0].size;
|
|
|
err = req->out.h.error;
|
|
|
- fuse_put_request(fc, req);
|
|
|
+
|
|
|
if (!err) {
|
|
|
- if (plus) {
|
|
|
- err = parse_dirplusfile(page_address(page), nbytes,
|
|
|
- file, ctx,
|
|
|
- attr_version);
|
|
|
- } else {
|
|
|
- err = parse_dirfile(page_address(page), nbytes, file,
|
|
|
- ctx);
|
|
|
- }
|
|
|
+ char *link = page_address(page);
|
|
|
+ size_t len = req->out.args[0].size;
|
|
|
+
|
|
|
+ BUG_ON(len >= PAGE_SIZE);
|
|
|
+ link[len] = '\0';
|
|
|
}
|
|
|
|
|
|
- __free_page(page);
|
|
|
+ fuse_put_request(fc, req);
|
|
|
fuse_invalidate_atime(inode);
|
|
|
+
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
-static const char *fuse_get_link(struct dentry *dentry,
|
|
|
- struct inode *inode,
|
|
|
- struct delayed_call *done)
|
|
|
+static const char *fuse_get_link(struct dentry *dentry, struct inode *inode,
|
|
|
+ struct delayed_call *callback)
|
|
|
{
|
|
|
struct fuse_conn *fc = get_fuse_conn(inode);
|
|
|
- FUSE_ARGS(args);
|
|
|
- char *link;
|
|
|
- ssize_t ret;
|
|
|
+ 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)
|
|
|
- return ERR_PTR(-ECHILD);
|
|
|
+ goto out_err;
|
|
|
|
|
|
- link = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
|
|
- if (!link)
|
|
|
- return ERR_PTR(-ENOMEM);
|
|
|
+ page = alloc_page(GFP_KERNEL);
|
|
|
+ err = -ENOMEM;
|
|
|
+ if (!page)
|
|
|
+ goto out_err;
|
|
|
|
|
|
- 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);
|
|
|
+ err = fuse_readlink_page(inode, page);
|
|
|
+ if (err) {
|
|
|
+ __free_page(page);
|
|
|
+ goto out_err;
|
|
|
}
|
|
|
- fuse_invalidate_atime(inode);
|
|
|
- return link;
|
|
|
+
|
|
|
+ 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)
|
|
@@ -1662,8 +1464,11 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr,
|
|
|
file = NULL;
|
|
|
}
|
|
|
|
|
|
- if (attr->ia_valid & ATTR_SIZE)
|
|
|
+ if (attr->ia_valid & ATTR_SIZE) {
|
|
|
+ if (WARN_ON(!S_ISREG(inode->i_mode)))
|
|
|
+ return -EIO;
|
|
|
is_truncate = true;
|
|
|
+ }
|
|
|
|
|
|
if (is_truncate) {
|
|
|
fuse_set_nowrite(inode);
|
|
@@ -1811,7 +1616,7 @@ static int fuse_getattr(const struct path *path, struct kstat *stat,
|
|
|
if (!fuse_allow_current_process(fc))
|
|
|
return -EACCES;
|
|
|
|
|
|
- return fuse_update_get_attr(inode, NULL, stat, flags);
|
|
|
+ return fuse_update_get_attr(inode, NULL, stat, request_mask, flags);
|
|
|
}
|
|
|
|
|
|
static const struct inode_operations fuse_dir_inode_operations = {
|
|
@@ -1867,11 +1672,37 @@ void fuse_init_common(struct inode *inode)
|
|
|
|
|
|
void fuse_init_dir(struct inode *inode)
|
|
|
{
|
|
|
+ struct fuse_inode *fi = get_fuse_inode(inode);
|
|
|
+
|
|
|
inode->i_op = &fuse_dir_inode_operations;
|
|
|
inode->i_fop = &fuse_dir_operations;
|
|
|
+
|
|
|
+ spin_lock_init(&fi->rdc.lock);
|
|
|
+ fi->rdc.cached = false;
|
|
|
+ fi->rdc.size = 0;
|
|
|
+ fi->rdc.pos = 0;
|
|
|
+ fi->rdc.version = 0;
|
|
|
}
|
|
|
|
|
|
+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);
|
|
|
}
|