diff options
author | Miklos Szeredi <mszeredi@suse.cz> | 2009-04-02 08:25:34 -0400 |
---|---|---|
committer | Miklos Szeredi <mszeredi@suse.cz> | 2009-04-02 08:25:34 -0400 |
commit | f4975c67dd9ad8eb47a4c77af0521a2b16ee0197 (patch) | |
tree | 4d4642e04e40f4835df9a18192da53b98b206bf8 /fs/fuse | |
parent | 833bb3046b6cb320e775ea2160ddca87d53260d5 (diff) |
fuse: allow kernel to access "direct_io" files
Allow the kernel read and write on "direct_io" files. This is
necessary for nfs export and execute support.
The implementation is simple: if an access from the kernel is
detected, don't perform get_user_pages(), just use the kernel address
provided by the requester to copy from/to the userspace filesystem.
Signed-off-by: Miklos Szeredi <mszeredi@suse.cz>
Diffstat (limited to 'fs/fuse')
-rw-r--r-- | fs/fuse/dir.c | 1 | ||||
-rw-r--r-- | fs/fuse/file.c | 42 |
2 files changed, 31 insertions, 12 deletions
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 06da05261e0..8b8eebc5614 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c | |||
@@ -1032,6 +1032,7 @@ static int fuse_readdir(struct file *file, void *dstbuf, filldir_t filldir) | |||
1032 | fuse_put_request(fc, req); | 1032 | fuse_put_request(fc, req); |
1033 | return -ENOMEM; | 1033 | return -ENOMEM; |
1034 | } | 1034 | } |
1035 | req->out.argpages = 1; | ||
1035 | req->num_pages = 1; | 1036 | req->num_pages = 1; |
1036 | req->pages[0] = page; | 1037 | req->pages[0] = page; |
1037 | fuse_read_fill(req, file, inode, file->f_pos, PAGE_SIZE, FUSE_READDIR); | 1038 | fuse_read_fill(req, file, inode, file->f_pos, PAGE_SIZE, FUSE_READDIR); |
diff --git a/fs/fuse/file.c b/fs/fuse/file.c index 4e340fedf76..78a2c833370 100644 --- a/fs/fuse/file.c +++ b/fs/fuse/file.c | |||
@@ -386,7 +386,6 @@ void fuse_read_fill(struct fuse_req *req, struct file *file, | |||
386 | req->in.numargs = 1; | 386 | req->in.numargs = 1; |
387 | req->in.args[0].size = sizeof(struct fuse_read_in); | 387 | req->in.args[0].size = sizeof(struct fuse_read_in); |
388 | req->in.args[0].value = inarg; | 388 | req->in.args[0].value = inarg; |
389 | req->out.argpages = 1; | ||
390 | req->out.argvar = 1; | 389 | req->out.argvar = 1; |
391 | req->out.numargs = 1; | 390 | req->out.numargs = 1; |
392 | req->out.args[0].size = count; | 391 | req->out.args[0].size = count; |
@@ -453,6 +452,7 @@ static int fuse_readpage(struct file *file, struct page *page) | |||
453 | attr_ver = fuse_get_attr_version(fc); | 452 | attr_ver = fuse_get_attr_version(fc); |
454 | 453 | ||
455 | req->out.page_zeroing = 1; | 454 | req->out.page_zeroing = 1; |
455 | req->out.argpages = 1; | ||
456 | req->num_pages = 1; | 456 | req->num_pages = 1; |
457 | req->pages[0] = page; | 457 | req->pages[0] = page; |
458 | num_read = fuse_send_read(req, file, inode, pos, count, NULL); | 458 | num_read = fuse_send_read(req, file, inode, pos, count, NULL); |
@@ -510,6 +510,8 @@ static void fuse_send_readpages(struct fuse_req *req, struct file *file, | |||
510 | struct fuse_conn *fc = get_fuse_conn(inode); | 510 | struct fuse_conn *fc = get_fuse_conn(inode); |
511 | loff_t pos = page_offset(req->pages[0]); | 511 | loff_t pos = page_offset(req->pages[0]); |
512 | size_t count = req->num_pages << PAGE_CACHE_SHIFT; | 512 | size_t count = req->num_pages << PAGE_CACHE_SHIFT; |
513 | |||
514 | req->out.argpages = 1; | ||
513 | req->out.page_zeroing = 1; | 515 | req->out.page_zeroing = 1; |
514 | fuse_read_fill(req, file, inode, pos, count, FUSE_READ); | 516 | fuse_read_fill(req, file, inode, pos, count, FUSE_READ); |
515 | req->misc.read.attr_ver = fuse_get_attr_version(fc); | 517 | req->misc.read.attr_ver = fuse_get_attr_version(fc); |
@@ -621,7 +623,6 @@ static void fuse_write_fill(struct fuse_req *req, struct file *file, | |||
621 | inarg->flags = file ? file->f_flags : 0; | 623 | inarg->flags = file ? file->f_flags : 0; |
622 | req->in.h.opcode = FUSE_WRITE; | 624 | req->in.h.opcode = FUSE_WRITE; |
623 | req->in.h.nodeid = get_node_id(inode); | 625 | req->in.h.nodeid = get_node_id(inode); |
624 | req->in.argpages = 1; | ||
625 | req->in.numargs = 2; | 626 | req->in.numargs = 2; |
626 | if (fc->minor < 9) | 627 | if (fc->minor < 9) |
627 | req->in.args[0].size = FUSE_COMPAT_WRITE_IN_SIZE; | 628 | req->in.args[0].size = FUSE_COMPAT_WRITE_IN_SIZE; |
@@ -695,6 +696,7 @@ static int fuse_buffered_write(struct file *file, struct inode *inode, | |||
695 | if (IS_ERR(req)) | 696 | if (IS_ERR(req)) |
696 | return PTR_ERR(req); | 697 | return PTR_ERR(req); |
697 | 698 | ||
699 | req->in.argpages = 1; | ||
698 | req->num_pages = 1; | 700 | req->num_pages = 1; |
699 | req->pages[0] = page; | 701 | req->pages[0] = page; |
700 | req->page_offset = offset; | 702 | req->page_offset = offset; |
@@ -771,6 +773,7 @@ static ssize_t fuse_fill_write_pages(struct fuse_req *req, | |||
771 | size_t count = 0; | 773 | size_t count = 0; |
772 | int err; | 774 | int err; |
773 | 775 | ||
776 | req->in.argpages = 1; | ||
774 | req->page_offset = offset; | 777 | req->page_offset = offset; |
775 | 778 | ||
776 | do { | 779 | do { |
@@ -935,21 +938,28 @@ static void fuse_release_user_pages(struct fuse_req *req, int write) | |||
935 | } | 938 | } |
936 | 939 | ||
937 | static int fuse_get_user_pages(struct fuse_req *req, const char __user *buf, | 940 | static int fuse_get_user_pages(struct fuse_req *req, const char __user *buf, |
938 | unsigned nbytes, int write) | 941 | unsigned *nbytesp, int write) |
939 | { | 942 | { |
943 | unsigned nbytes = *nbytesp; | ||
940 | unsigned long user_addr = (unsigned long) buf; | 944 | unsigned long user_addr = (unsigned long) buf; |
941 | unsigned offset = user_addr & ~PAGE_MASK; | 945 | unsigned offset = user_addr & ~PAGE_MASK; |
942 | int npages; | 946 | int npages; |
943 | 947 | ||
944 | /* This doesn't work with nfsd */ | 948 | /* Special case for kernel I/O: can copy directly into the buffer */ |
945 | if (!current->mm) | 949 | if (segment_eq(get_fs(), KERNEL_DS)) { |
946 | return -EPERM; | 950 | if (write) |
951 | req->in.args[1].value = (void *) user_addr; | ||
952 | else | ||
953 | req->out.args[0].value = (void *) user_addr; | ||
954 | |||
955 | return 0; | ||
956 | } | ||
947 | 957 | ||
948 | nbytes = min(nbytes, (unsigned) FUSE_MAX_PAGES_PER_REQ << PAGE_SHIFT); | 958 | nbytes = min(nbytes, (unsigned) FUSE_MAX_PAGES_PER_REQ << PAGE_SHIFT); |
949 | npages = (nbytes + offset + PAGE_SIZE - 1) >> PAGE_SHIFT; | 959 | npages = (nbytes + offset + PAGE_SIZE - 1) >> PAGE_SHIFT; |
950 | npages = clamp(npages, 1, FUSE_MAX_PAGES_PER_REQ); | 960 | npages = clamp(npages, 1, FUSE_MAX_PAGES_PER_REQ); |
951 | down_read(¤t->mm->mmap_sem); | 961 | down_read(¤t->mm->mmap_sem); |
952 | npages = get_user_pages(current, current->mm, user_addr, npages, write, | 962 | npages = get_user_pages(current, current->mm, user_addr, npages, !write, |
953 | 0, req->pages, NULL); | 963 | 0, req->pages, NULL); |
954 | up_read(¤t->mm->mmap_sem); | 964 | up_read(¤t->mm->mmap_sem); |
955 | if (npages < 0) | 965 | if (npages < 0) |
@@ -957,6 +967,15 @@ static int fuse_get_user_pages(struct fuse_req *req, const char __user *buf, | |||
957 | 967 | ||
958 | req->num_pages = npages; | 968 | req->num_pages = npages; |
959 | req->page_offset = offset; | 969 | req->page_offset = offset; |
970 | |||
971 | if (write) | ||
972 | req->in.argpages = 1; | ||
973 | else | ||
974 | req->out.argpages = 1; | ||
975 | |||
976 | nbytes = (req->num_pages << PAGE_SHIFT) - req->page_offset; | ||
977 | *nbytesp = min(*nbytesp, nbytes); | ||
978 | |||
960 | return 0; | 979 | return 0; |
961 | } | 980 | } |
962 | 981 | ||
@@ -979,15 +998,13 @@ static ssize_t fuse_direct_io(struct file *file, const char __user *buf, | |||
979 | 998 | ||
980 | while (count) { | 999 | while (count) { |
981 | size_t nres; | 1000 | size_t nres; |
982 | size_t nbytes_limit = min(count, nmax); | 1001 | size_t nbytes = min(count, nmax); |
983 | size_t nbytes; | 1002 | int err = fuse_get_user_pages(req, buf, &nbytes, write); |
984 | int err = fuse_get_user_pages(req, buf, nbytes_limit, !write); | ||
985 | if (err) { | 1003 | if (err) { |
986 | res = err; | 1004 | res = err; |
987 | break; | 1005 | break; |
988 | } | 1006 | } |
989 | nbytes = (req->num_pages << PAGE_SHIFT) - req->page_offset; | 1007 | |
990 | nbytes = min(nbytes_limit, nbytes); | ||
991 | if (write) | 1008 | if (write) |
992 | nres = fuse_send_write(req, file, inode, pos, nbytes, | 1009 | nres = fuse_send_write(req, file, inode, pos, nbytes, |
993 | current->files); | 1010 | current->files); |
@@ -1163,6 +1180,7 @@ static int fuse_writepage_locked(struct page *page) | |||
1163 | fuse_write_fill(req, NULL, ff, inode, page_offset(page), 0, 1); | 1180 | fuse_write_fill(req, NULL, ff, inode, page_offset(page), 0, 1); |
1164 | 1181 | ||
1165 | copy_highpage(tmp_page, page); | 1182 | copy_highpage(tmp_page, page); |
1183 | req->in.argpages = 1; | ||
1166 | req->num_pages = 1; | 1184 | req->num_pages = 1; |
1167 | req->pages[0] = tmp_page; | 1185 | req->pages[0] = tmp_page; |
1168 | req->page_offset = 0; | 1186 | req->page_offset = 0; |