mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 21:51:40 +00:00
fuse: split out readdir.c
Directory reading code is about to grow larger, so split it out from dir.c into a new source file. Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
This commit is contained in:
parent
be2ff42c5d
commit
d123d8e183
@ -5,4 +5,4 @@
|
|||||||
obj-$(CONFIG_FUSE_FS) += fuse.o
|
obj-$(CONFIG_FUSE_FS) += fuse.o
|
||||||
obj-$(CONFIG_CUSE) += cuse.o
|
obj-$(CONFIG_CUSE) += cuse.o
|
||||||
|
|
||||||
fuse-objs := dev.o dir.o file.o inode.o control.o xattr.o acl.o
|
fuse-objs := dev.o dir.o file.o inode.o control.o xattr.o acl.o readdir.o
|
||||||
|
259
fs/fuse/dir.c
259
fs/fuse/dir.c
@ -16,22 +16,6 @@
|
|||||||
#include <linux/xattr.h>
|
#include <linux/xattr.h>
|
||||||
#include <linux/posix_acl.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)
|
static void fuse_advise_use_readdirplus(struct inode *dir)
|
||||||
{
|
{
|
||||||
struct fuse_inode *fi = get_fuse_inode(dir);
|
struct fuse_inode *fi = get_fuse_inode(dir);
|
||||||
@ -80,8 +64,7 @@ static u64 time_to_jiffies(u64 sec, u32 nsec)
|
|||||||
* Set dentry and possibly attribute timeouts from the lookup/mk*
|
* Set dentry and possibly attribute timeouts from the lookup/mk*
|
||||||
* replies
|
* replies
|
||||||
*/
|
*/
|
||||||
static void fuse_change_entry_timeout(struct dentry *entry,
|
void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o)
|
||||||
struct fuse_entry_out *o)
|
|
||||||
{
|
{
|
||||||
fuse_dentry_settime(entry,
|
fuse_dentry_settime(entry,
|
||||||
time_to_jiffies(o->entry_valid, o->entry_valid_nsec));
|
time_to_jiffies(o->entry_valid, o->entry_valid_nsec));
|
||||||
@ -92,7 +75,7 @@ static u64 attr_timeout(struct fuse_attr_out *o)
|
|||||||
return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
|
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);
|
return time_to_jiffies(o->attr_valid, o->attr_valid_nsec);
|
||||||
}
|
}
|
||||||
@ -262,11 +245,6 @@ invalid:
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int invalid_nodeid(u64 nodeid)
|
|
||||||
{
|
|
||||||
return !nodeid || nodeid == FUSE_ROOT_ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int fuse_dentry_init(struct dentry *dentry)
|
static int fuse_dentry_init(struct dentry *dentry)
|
||||||
{
|
{
|
||||||
dentry->d_fsdata = kzalloc(sizeof(union fuse_dentry), GFP_KERNEL);
|
dentry->d_fsdata = kzalloc(sizeof(union fuse_dentry), GFP_KERNEL);
|
||||||
@ -1165,239 +1143,6 @@ static int fuse_permission(struct inode *inode, int mask)
|
|||||||
return err;
|
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)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
|
|
||||||
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.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);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__free_page(page);
|
|
||||||
fuse_invalidate_atime(inode);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char *fuse_get_link(struct dentry *dentry,
|
static const char *fuse_get_link(struct dentry *dentry,
|
||||||
struct inode *inode,
|
struct inode *inode,
|
||||||
struct delayed_call *done)
|
struct delayed_call *done)
|
||||||
|
@ -704,6 +704,11 @@ static inline u64 get_node_id(struct inode *inode)
|
|||||||
return get_fuse_inode(inode)->nodeid;
|
return get_fuse_inode(inode)->nodeid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int invalid_nodeid(u64 nodeid)
|
||||||
|
{
|
||||||
|
return !nodeid || nodeid == FUSE_ROOT_ID;
|
||||||
|
}
|
||||||
|
|
||||||
/** Device operations */
|
/** Device operations */
|
||||||
extern const struct file_operations fuse_dev_operations;
|
extern const struct file_operations fuse_dev_operations;
|
||||||
|
|
||||||
@ -878,6 +883,9 @@ void fuse_invalidate_entry_cache(struct dentry *entry);
|
|||||||
|
|
||||||
void fuse_invalidate_atime(struct inode *inode);
|
void fuse_invalidate_atime(struct inode *inode);
|
||||||
|
|
||||||
|
u64 entry_attr_timeout(struct fuse_entry_out *o);
|
||||||
|
void fuse_change_entry_timeout(struct dentry *entry, struct fuse_entry_out *o);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Acquire reference to fuse_conn
|
* Acquire reference to fuse_conn
|
||||||
*/
|
*/
|
||||||
@ -997,4 +1005,8 @@ struct posix_acl;
|
|||||||
struct posix_acl *fuse_get_acl(struct inode *inode, int type);
|
struct posix_acl *fuse_get_acl(struct inode *inode, int type);
|
||||||
int fuse_set_acl(struct inode *inode, struct posix_acl *acl, int type);
|
int fuse_set_acl(struct inode *inode, struct posix_acl *acl, int type);
|
||||||
|
|
||||||
|
|
||||||
|
/* readdir.c */
|
||||||
|
int fuse_readdir(struct file *file, struct dir_context *ctx);
|
||||||
|
|
||||||
#endif /* _FS_FUSE_I_H */
|
#endif /* _FS_FUSE_I_H */
|
||||||
|
259
fs/fuse/readdir.c
Normal file
259
fs/fuse/readdir.c
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
FUSE: Filesystem in Userspace
|
||||||
|
Copyright (C) 2001-2018 Miklos Szeredi <miklos@szeredi.hu>
|
||||||
|
|
||||||
|
This program can be distributed under the terms of the GNU GPL.
|
||||||
|
See the file COPYING.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "fuse_i.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 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)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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.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);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__free_page(page);
|
||||||
|
fuse_invalidate_atime(inode);
|
||||||
|
return err;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user