diff options
Diffstat (limited to 'fs/fuse/file.c')
-rw-r--r-- | fs/fuse/file.c | 133 |
1 files changed, 131 insertions, 2 deletions
diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 86ffb6db5fe7..6bc3fb26de39 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c | |||
@@ -363,6 +363,118 @@ static int fuse_commit_write(struct file *file, struct page *page, | |||
363 | return err; | 363 | return err; |
364 | } | 364 | } |
365 | 365 | ||
366 | static void fuse_release_user_pages(struct fuse_req *req, int write) | ||
367 | { | ||
368 | unsigned i; | ||
369 | |||
370 | for (i = 0; i < req->num_pages; i++) { | ||
371 | struct page *page = req->pages[i]; | ||
372 | if (write) | ||
373 | set_page_dirty_lock(page); | ||
374 | put_page(page); | ||
375 | } | ||
376 | } | ||
377 | |||
378 | static int fuse_get_user_pages(struct fuse_req *req, const char __user *buf, | ||
379 | unsigned nbytes, int write) | ||
380 | { | ||
381 | unsigned long user_addr = (unsigned long) buf; | ||
382 | unsigned offset = user_addr & ~PAGE_MASK; | ||
383 | int npages; | ||
384 | |||
385 | /* This doesn't work with nfsd */ | ||
386 | if (!current->mm) | ||
387 | return -EPERM; | ||
388 | |||
389 | nbytes = min(nbytes, (unsigned) FUSE_MAX_PAGES_PER_REQ << PAGE_SHIFT); | ||
390 | npages = (nbytes + offset + PAGE_SIZE - 1) >> PAGE_SHIFT; | ||
391 | npages = min(npages, FUSE_MAX_PAGES_PER_REQ); | ||
392 | down_read(¤t->mm->mmap_sem); | ||
393 | npages = get_user_pages(current, current->mm, user_addr, npages, write, | ||
394 | 0, req->pages, NULL); | ||
395 | up_read(¤t->mm->mmap_sem); | ||
396 | if (npages < 0) | ||
397 | return npages; | ||
398 | |||
399 | req->num_pages = npages; | ||
400 | req->page_offset = offset; | ||
401 | return 0; | ||
402 | } | ||
403 | |||
404 | static ssize_t fuse_direct_io(struct file *file, const char __user *buf, | ||
405 | size_t count, loff_t *ppos, int write) | ||
406 | { | ||
407 | struct inode *inode = file->f_dentry->d_inode; | ||
408 | struct fuse_conn *fc = get_fuse_conn(inode); | ||
409 | size_t nmax = write ? fc->max_write : fc->max_read; | ||
410 | loff_t pos = *ppos; | ||
411 | ssize_t res = 0; | ||
412 | struct fuse_req *req = fuse_get_request(fc); | ||
413 | if (!req) | ||
414 | return -ERESTARTSYS; | ||
415 | |||
416 | while (count) { | ||
417 | size_t tmp; | ||
418 | size_t nres; | ||
419 | size_t nbytes = min(count, nmax); | ||
420 | int err = fuse_get_user_pages(req, buf, nbytes, !write); | ||
421 | if (err) { | ||
422 | res = err; | ||
423 | break; | ||
424 | } | ||
425 | tmp = (req->num_pages << PAGE_SHIFT) - req->page_offset; | ||
426 | nbytes = min(nbytes, tmp); | ||
427 | if (write) | ||
428 | nres = fuse_send_write(req, file, inode, pos, nbytes); | ||
429 | else | ||
430 | nres = fuse_send_read(req, file, inode, pos, nbytes); | ||
431 | fuse_release_user_pages(req, !write); | ||
432 | if (req->out.h.error) { | ||
433 | if (!res) | ||
434 | res = req->out.h.error; | ||
435 | break; | ||
436 | } else if (nres > nbytes) { | ||
437 | res = -EIO; | ||
438 | break; | ||
439 | } | ||
440 | count -= nres; | ||
441 | res += nres; | ||
442 | pos += nres; | ||
443 | buf += nres; | ||
444 | if (nres != nbytes) | ||
445 | break; | ||
446 | if (count) | ||
447 | fuse_reset_request(req); | ||
448 | } | ||
449 | fuse_put_request(fc, req); | ||
450 | if (res > 0) { | ||
451 | if (write && pos > i_size_read(inode)) | ||
452 | i_size_write(inode, pos); | ||
453 | *ppos = pos; | ||
454 | } else if (write && (res == -EINTR || res == -EIO)) | ||
455 | fuse_invalidate_attr(inode); | ||
456 | |||
457 | return res; | ||
458 | } | ||
459 | |||
460 | static ssize_t fuse_direct_read(struct file *file, char __user *buf, | ||
461 | size_t count, loff_t *ppos) | ||
462 | { | ||
463 | return fuse_direct_io(file, buf, count, ppos, 0); | ||
464 | } | ||
465 | |||
466 | static ssize_t fuse_direct_write(struct file *file, const char __user *buf, | ||
467 | size_t count, loff_t *ppos) | ||
468 | { | ||
469 | struct inode *inode = file->f_dentry->d_inode; | ||
470 | ssize_t res; | ||
471 | /* Don't allow parallel writes to the same file */ | ||
472 | down(&inode->i_sem); | ||
473 | res = fuse_direct_io(file, buf, count, ppos, 1); | ||
474 | up(&inode->i_sem); | ||
475 | return res; | ||
476 | } | ||
477 | |||
366 | static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma) | 478 | static int fuse_file_mmap(struct file *file, struct vm_area_struct *vma) |
367 | { | 479 | { |
368 | if ((vma->vm_flags & VM_SHARED)) { | 480 | if ((vma->vm_flags & VM_SHARED)) { |
@@ -393,6 +505,17 @@ static struct file_operations fuse_file_operations = { | |||
393 | .sendfile = generic_file_sendfile, | 505 | .sendfile = generic_file_sendfile, |
394 | }; | 506 | }; |
395 | 507 | ||
508 | static struct file_operations fuse_direct_io_file_operations = { | ||
509 | .llseek = generic_file_llseek, | ||
510 | .read = fuse_direct_read, | ||
511 | .write = fuse_direct_write, | ||
512 | .open = fuse_open, | ||
513 | .flush = fuse_flush, | ||
514 | .release = fuse_release, | ||
515 | .fsync = fuse_fsync, | ||
516 | /* no mmap and sendfile */ | ||
517 | }; | ||
518 | |||
396 | static struct address_space_operations fuse_file_aops = { | 519 | static struct address_space_operations fuse_file_aops = { |
397 | .readpage = fuse_readpage, | 520 | .readpage = fuse_readpage, |
398 | .prepare_write = fuse_prepare_write, | 521 | .prepare_write = fuse_prepare_write, |
@@ -403,6 +526,12 @@ static struct address_space_operations fuse_file_aops = { | |||
403 | 526 | ||
404 | void fuse_init_file_inode(struct inode *inode) | 527 | void fuse_init_file_inode(struct inode *inode) |
405 | { | 528 | { |
406 | inode->i_fop = &fuse_file_operations; | 529 | struct fuse_conn *fc = get_fuse_conn(inode); |
407 | inode->i_data.a_ops = &fuse_file_aops; | 530 | |
531 | if (fc->flags & FUSE_DIRECT_IO) | ||
532 | inode->i_fop = &fuse_direct_io_file_operations; | ||
533 | else { | ||
534 | inode->i_fop = &fuse_file_operations; | ||
535 | inode->i_data.a_ops = &fuse_file_aops; | ||
536 | } | ||
408 | } | 537 | } |