Skip to content

Commit

Permalink
fuse: add retrieve request
Browse files Browse the repository at this point in the history
Userspace filesystem can request data to be retrieved from the inode's
mapping.  This request is synchronous and the retrieved data is queued
as a new request.  If the write to the fuse device returns an error
then the retrieve request was not completed and a reply will not be
sent.

Only present pages are returned in the retrieve reply.  Retrieving
stops when it finds a non-present page and only data prior to that is
returned.

This request doesn't change the dirty state of pages.

Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
  • Loading branch information
Miklos Szeredi committed Jul 12, 2010
1 parent a1d75f2 commit 2d45ba3
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 1 deletion.
131 changes: 130 additions & 1 deletion fs/fuse/dev.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ static u64 fuse_get_unique(struct fuse_conn *fc)

static void queue_request(struct fuse_conn *fc, struct fuse_req *req)
{
req->in.h.unique = fuse_get_unique(fc);
req->in.h.len = sizeof(struct fuse_in_header) +
len_args(req->in.numargs, (struct fuse_arg *) req->in.args);
list_add_tail(&req->list, &fc->pending);
Expand All @@ -261,6 +260,7 @@ static void flush_bg_queue(struct fuse_conn *fc)
req = list_entry(fc->bg_queue.next, struct fuse_req, list);
list_del(&req->list);
fc->active_background++;
req->in.h.unique = fuse_get_unique(fc);
queue_request(fc, req);
}
}
Expand Down Expand Up @@ -398,6 +398,7 @@ void fuse_request_send(struct fuse_conn *fc, struct fuse_req *req)
else if (fc->conn_error)
req->out.h.error = -ECONNREFUSED;
else {
req->in.h.unique = fuse_get_unique(fc);
queue_request(fc, req);
/* acquire extra reference, since request is still needed
after request_end() */
Expand Down Expand Up @@ -450,6 +451,23 @@ void fuse_request_send_background(struct fuse_conn *fc, struct fuse_req *req)
}
EXPORT_SYMBOL_GPL(fuse_request_send_background);

static int fuse_request_send_notify_reply(struct fuse_conn *fc,
struct fuse_req *req, u64 unique)
{
int err = -ENODEV;

req->isreply = 0;
req->in.h.unique = unique;
spin_lock(&fc->lock);
if (fc->connected) {
queue_request(fc, req);
err = 0;
}
spin_unlock(&fc->lock);

return err;
}

/*
* Called under fc->lock
*
Expand Down Expand Up @@ -1316,6 +1334,114 @@ static int fuse_notify_store(struct fuse_conn *fc, unsigned int size,
return err;
}

static void fuse_retrieve_end(struct fuse_conn *fc, struct fuse_req *req)
{
int i;

for (i = 0; i < req->num_pages; i++) {
struct page *page = req->pages[i];
page_cache_release(page);
}
}

static int fuse_retrieve(struct fuse_conn *fc, struct inode *inode,
struct fuse_notify_retrieve_out *outarg)
{
int err;
struct address_space *mapping = inode->i_mapping;
struct fuse_req *req;
pgoff_t index;
loff_t file_size;
unsigned int num;
unsigned int offset;
size_t total_len;

req = fuse_get_req(fc);
if (IS_ERR(req))
return PTR_ERR(req);

offset = outarg->offset & ~PAGE_CACHE_MASK;

req->in.h.opcode = FUSE_NOTIFY_REPLY;
req->in.h.nodeid = outarg->nodeid;
req->in.numargs = 2;
req->in.argpages = 1;
req->page_offset = offset;
req->end = fuse_retrieve_end;

index = outarg->offset >> PAGE_CACHE_SHIFT;
file_size = i_size_read(inode);
num = outarg->size;
if (outarg->offset > file_size)
num = 0;
else if (outarg->offset + num > file_size)
num = file_size - outarg->offset;

while (num) {
struct page *page;
unsigned int this_num;

page = find_get_page(mapping, index);
if (!page)
break;

this_num = min_t(unsigned, num, PAGE_CACHE_SIZE - offset);
req->pages[req->num_pages] = page;
req->num_pages++;

num -= this_num;
total_len += this_num;
}
req->misc.retrieve_in.offset = outarg->offset;
req->misc.retrieve_in.size = total_len;
req->in.args[0].size = sizeof(req->misc.retrieve_in);
req->in.args[0].value = &req->misc.retrieve_in;
req->in.args[1].size = total_len;

err = fuse_request_send_notify_reply(fc, req, outarg->notify_unique);
if (err)
fuse_retrieve_end(fc, req);

return err;
}

static int fuse_notify_retrieve(struct fuse_conn *fc, unsigned int size,
struct fuse_copy_state *cs)
{
struct fuse_notify_retrieve_out outarg;
struct inode *inode;
int err;

err = -EINVAL;
if (size != sizeof(outarg))
goto copy_finish;

err = fuse_copy_one(cs, &outarg, sizeof(outarg));
if (err)
goto copy_finish;

fuse_copy_finish(cs);

down_read(&fc->killsb);
err = -ENOENT;
if (fc->sb) {
u64 nodeid = outarg.nodeid;

inode = ilookup5(fc->sb, nodeid, fuse_inode_eq, &nodeid);
if (inode) {
err = fuse_retrieve(fc, inode, &outarg);
iput(inode);
}
}
up_read(&fc->killsb);

return err;

copy_finish:
fuse_copy_finish(cs);
return err;
}

static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
unsigned int size, struct fuse_copy_state *cs)
{
Expand All @@ -1332,6 +1458,9 @@ static int fuse_notify(struct fuse_conn *fc, enum fuse_notify_code code,
case FUSE_NOTIFY_STORE:
return fuse_notify_store(fc, size, cs);

case FUSE_NOTIFY_RETRIEVE:
return fuse_notify_retrieve(fc, size, cs);

default:
fuse_copy_finish(cs);
return -EINVAL;
Expand Down
1 change: 1 addition & 0 deletions fs/fuse/fuse_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ struct fuse_req {
struct fuse_write_in in;
struct fuse_write_out out;
} write;
struct fuse_notify_retrieve_in retrieve_in;
struct fuse_lk_in lk_in;
} misc;

Expand Down
21 changes: 21 additions & 0 deletions include/linux/fuse.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
*
* 7.15
* - add store notify
* - add retrieve notify
*/

#ifndef _LINUX_FUSE_H
Expand Down Expand Up @@ -254,6 +255,7 @@ enum fuse_opcode {
FUSE_DESTROY = 38,
FUSE_IOCTL = 39,
FUSE_POLL = 40,
FUSE_NOTIFY_REPLY = 41,

/* CUSE specific operations */
CUSE_INIT = 4096,
Expand All @@ -264,6 +266,7 @@ enum fuse_notify_code {
FUSE_NOTIFY_INVAL_INODE = 2,
FUSE_NOTIFY_INVAL_ENTRY = 3,
FUSE_NOTIFY_STORE = 4,
FUSE_NOTIFY_RETRIEVE = 5,
FUSE_NOTIFY_CODE_MAX,
};

Expand Down Expand Up @@ -579,4 +582,22 @@ struct fuse_notify_store_out {
__u32 padding;
};

struct fuse_notify_retrieve_out {
__u64 notify_unique;
__u64 nodeid;
__u64 offset;
__u32 size;
__u32 padding;
};

/* Matches the size of fuse_write_in */
struct fuse_notify_retrieve_in {
__u64 dummy1;
__u64 offset;
__u32 size;
__u32 dummy2;
__u64 dummy3;
__u64 dummy4;
};

#endif /* _LINUX_FUSE_H */

0 comments on commit 2d45ba3

Please sign in to comment.