aboutsummaryrefslogtreecommitdiffstats
path: root/fs/fuse/file.c
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@suse.cz>2010-05-25 09:06:07 -0400
committerMiklos Szeredi <mszeredi@suse.cz>2010-05-25 09:06:07 -0400
commitce534fb052928ce556639d7ecf01cbf4e01321e1 (patch)
treec09f7c592a41d635d7f2f54fc7fe10594f332b69 /fs/fuse/file.c
parenta52116aba5b3eed0ee41f70b794cc1937acd5cb8 (diff)
fuse: allow splice to move pages
When splicing buffers to the fuse device with SPLICE_F_MOVE, try to move pages from the pipe buffer into the page cache. This allows populating the fuse filesystem's cache without ever touching the page contents, i.e. zero copy read capability. The following steps are performed when trying to move a page into the page cache: - buf->ops->confirm() to make sure the new page is uptodate - buf->ops->steal() to try to remove the new page from it's previous place - remove_from_page_cache() on the old page - add_to_page_cache_locked() on the new page If any of the above steps fail (non fatally) then the code falls back to copying the page. In particular ->steal() will fail if there are external references (other than the page cache and the pipe buffer) to the page. Also since the remove_from_page_cache() + add_to_page_cache_locked() are non-atomic it is possible that the page cache is repopulated in between the two and add_to_page_cache_locked() will fail. This could be fixed by creating a new atomic replace_page_cache_page() function. fuse_readpages_end() needed to be reworked so it works even if page->mapping is NULL for some or all pages which can happen if the add_to_page_cache_locked() failed. A number of sanity checks were added to make sure the stolen pages don't have weird flags set, etc... These could be moved into generic splice/steal code. Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Diffstat (limited to 'fs/fuse/file.c')
-rw-r--r--fs/fuse/file.c28
1 files changed, 19 insertions, 9 deletions
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index 9ca68edcbdbe..06e3775b2282 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -517,17 +517,26 @@ static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req)
517 int i; 517 int i;
518 size_t count = req->misc.read.in.size; 518 size_t count = req->misc.read.in.size;
519 size_t num_read = req->out.args[0].size; 519 size_t num_read = req->out.args[0].size;
520 struct inode *inode = req->pages[0]->mapping->host; 520 struct address_space *mapping = NULL;
521 521
522 /* 522 for (i = 0; mapping == NULL && i < req->num_pages; i++)
523 * Short read means EOF. If file size is larger, truncate it 523 mapping = req->pages[i]->mapping;
524 */
525 if (!req->out.h.error && num_read < count) {
526 loff_t pos = page_offset(req->pages[0]) + num_read;
527 fuse_read_update_size(inode, pos, req->misc.read.attr_ver);
528 }
529 524
530 fuse_invalidate_attr(inode); /* atime changed */ 525 if (mapping) {
526 struct inode *inode = mapping->host;
527
528 /*
529 * Short read means EOF. If file size is larger, truncate it
530 */
531 if (!req->out.h.error && num_read < count) {
532 loff_t pos;
533
534 pos = page_offset(req->pages[0]) + num_read;
535 fuse_read_update_size(inode, pos,
536 req->misc.read.attr_ver);
537 }
538 fuse_invalidate_attr(inode); /* atime changed */
539 }
531 540
532 for (i = 0; i < req->num_pages; i++) { 541 for (i = 0; i < req->num_pages; i++) {
533 struct page *page = req->pages[i]; 542 struct page *page = req->pages[i];
@@ -551,6 +560,7 @@ static void fuse_send_readpages(struct fuse_req *req, struct file *file)
551 560
552 req->out.argpages = 1; 561 req->out.argpages = 1;
553 req->out.page_zeroing = 1; 562 req->out.page_zeroing = 1;
563 req->out.page_replace = 1;
554 fuse_read_fill(req, file, pos, count, FUSE_READ); 564 fuse_read_fill(req, file, pos, count, FUSE_READ);
555 req->misc.read.attr_ver = fuse_get_attr_version(fc); 565 req->misc.read.attr_ver = fuse_get_attr_version(fc);
556 if (fc->async_read) { 566 if (fc->async_read) {