]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - fs/fuse/file.c
Merge tag 'v2.6.38' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[mv-sheeva.git] / fs / fuse / file.c
index 8b984a2cebbda185af01a6d4d5f8c31c89fc6fbe..9e0832dbb1e3dfb8e0872273fc53800f60a9251d 100644 (file)
@@ -86,18 +86,52 @@ struct fuse_file *fuse_file_get(struct fuse_file *ff)
        return ff;
 }
 
+static void fuse_release_async(struct work_struct *work)
+{
+       struct fuse_req *req;
+       struct fuse_conn *fc;
+       struct path path;
+
+       req = container_of(work, struct fuse_req, misc.release.work);
+       path = req->misc.release.path;
+       fc = get_fuse_conn(path.dentry->d_inode);
+
+       fuse_put_request(fc, req);
+       path_put(&path);
+}
+
 static void fuse_release_end(struct fuse_conn *fc, struct fuse_req *req)
 {
-       path_put(&req->misc.release.path);
+       if (fc->destroy_req) {
+               /*
+                * If this is a fuseblk mount, then it's possible that
+                * releasing the path will result in releasing the
+                * super block and sending the DESTROY request.  If
+                * the server is single threaded, this would hang.
+                * For this reason do the path_put() in a separate
+                * thread.
+                */
+               atomic_inc(&req->count);
+               INIT_WORK(&req->misc.release.work, fuse_release_async);
+               schedule_work(&req->misc.release.work);
+       } else {
+               path_put(&req->misc.release.path);
+       }
 }
 
-static void fuse_file_put(struct fuse_file *ff)
+static void fuse_file_put(struct fuse_file *ff, bool sync)
 {
        if (atomic_dec_and_test(&ff->count)) {
                struct fuse_req *req = ff->reserved_req;
 
-               req->end = fuse_release_end;
-               fuse_request_send_background(ff->fc, req);
+               if (sync) {
+                       fuse_request_send(ff->fc, req);
+                       path_put(&req->misc.release.path);
+                       fuse_put_request(ff->fc, req);
+               } else {
+                       req->end = fuse_release_end;
+                       fuse_request_send_background(ff->fc, req);
+               }
                kfree(ff);
        }
 }
@@ -219,8 +253,12 @@ void fuse_release_common(struct file *file, int opcode)
         * Normally this will send the RELEASE request, however if
         * some asynchronous READ or WRITE requests are outstanding,
         * the sending will be delayed.
+        *
+        * Make the release synchronous if this is a fuseblk mount,
+        * synchronous RELEASE is allowed (and desirable) in this case
+        * because the server can be trusted not to screw up.
         */
-       fuse_file_put(ff);
+       fuse_file_put(ff, ff->fc->destroy_req != NULL);
 }
 
 static int fuse_open(struct inode *inode, struct file *file)
@@ -558,7 +596,7 @@ static void fuse_readpages_end(struct fuse_conn *fc, struct fuse_req *req)
                page_cache_release(page);
        }
        if (req->ff)
-               fuse_file_put(req->ff);
+               fuse_file_put(req->ff, false);
 }
 
 static void fuse_send_readpages(struct fuse_req *req, struct file *file)
@@ -1137,7 +1175,7 @@ static ssize_t fuse_direct_write(struct file *file, const char __user *buf,
 static void fuse_writepage_free(struct fuse_conn *fc, struct fuse_req *req)
 {
        __free_page(req->pages[0]);
-       fuse_file_put(req->ff);
+       fuse_file_put(req->ff, false);
 }
 
 static void fuse_writepage_finish(struct fuse_conn *fc, struct fuse_req *req)
@@ -1634,9 +1672,9 @@ static int fuse_ioctl_copy_user(struct page **pages, struct iovec *iov,
  * and 64bit.  Fortunately we can determine which structure the server
  * used from the size of the reply.
  */
-static int fuse_copy_ioctl_iovec(struct iovec *dst, void *src,
-                                size_t transferred, unsigned count,
-                                bool is_compat)
+static int fuse_copy_ioctl_iovec_old(struct iovec *dst, void *src,
+                                    size_t transferred, unsigned count,
+                                    bool is_compat)
 {
 #ifdef CONFIG_COMPAT
        if (count * sizeof(struct compat_iovec) == transferred) {
@@ -1680,6 +1718,42 @@ static int fuse_verify_ioctl_iov(struct iovec *iov, size_t count)
        return 0;
 }
 
+static int fuse_copy_ioctl_iovec(struct fuse_conn *fc, struct iovec *dst,
+                                void *src, size_t transferred, unsigned count,
+                                bool is_compat)
+{
+       unsigned i;
+       struct fuse_ioctl_iovec *fiov = src;
+
+       if (fc->minor < 16) {
+               return fuse_copy_ioctl_iovec_old(dst, src, transferred,
+                                                count, is_compat);
+       }
+
+       if (count * sizeof(struct fuse_ioctl_iovec) != transferred)
+               return -EIO;
+
+       for (i = 0; i < count; i++) {
+               /* Did the server supply an inappropriate value? */
+               if (fiov[i].base != (unsigned long) fiov[i].base ||
+                   fiov[i].len != (unsigned long) fiov[i].len)
+                       return -EIO;
+
+               dst[i].iov_base = (void __user *) (unsigned long) fiov[i].base;
+               dst[i].iov_len = (size_t) fiov[i].len;
+
+#ifdef CONFIG_COMPAT
+               if (is_compat &&
+                   (ptr_to_compat(dst[i].iov_base) != fiov[i].base ||
+                    (compat_size_t) dst[i].iov_len != fiov[i].len))
+                       return -EIO;
+#endif
+       }
+
+       return 0;
+}
+
+
 /*
  * For ioctls, there is no generic way to determine how much memory
  * needs to be read and/or written.  Furthermore, ioctls are allowed
@@ -1740,18 +1814,25 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
        struct fuse_ioctl_out outarg;
        struct fuse_req *req = NULL;
        struct page **pages = NULL;
-       struct page *iov_page = NULL;
+       struct iovec *iov_page = NULL;
        struct iovec *in_iov = NULL, *out_iov = NULL;
        unsigned int in_iovs = 0, out_iovs = 0, num_pages = 0, max_pages;
        size_t in_size, out_size, transferred;
        int err;
 
+#if BITS_PER_LONG == 32
+       inarg.flags |= FUSE_IOCTL_32BIT;
+#else
+       if (flags & FUSE_IOCTL_COMPAT)
+               inarg.flags |= FUSE_IOCTL_32BIT;
+#endif
+
        /* assume all the iovs returned by client always fits in a page */
-       BUILD_BUG_ON(sizeof(struct iovec) * FUSE_IOCTL_MAX_IOV > PAGE_SIZE);
+       BUILD_BUG_ON(sizeof(struct fuse_ioctl_iovec) * FUSE_IOCTL_MAX_IOV > PAGE_SIZE);
 
        err = -ENOMEM;
        pages = kzalloc(sizeof(pages[0]) * FUSE_MAX_PAGES_PER_REQ, GFP_KERNEL);
-       iov_page = alloc_page(GFP_KERNEL);
+       iov_page = (struct iovec *) __get_free_page(GFP_KERNEL);
        if (!pages || !iov_page)
                goto out;
 
@@ -1760,7 +1841,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
         * RETRY from server is not allowed.
         */
        if (!(flags & FUSE_IOCTL_UNRESTRICTED)) {
-               struct iovec *iov = page_address(iov_page);
+               struct iovec *iov = iov_page;
 
                iov->iov_base = (void __user *)arg;
                iov->iov_len = _IOC_SIZE(cmd);
@@ -1841,7 +1922,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
 
        /* did it ask for retry? */
        if (outarg.flags & FUSE_IOCTL_RETRY) {
-               char *vaddr;
+               void *vaddr;
 
                /* no retry if in restricted mode */
                err = -EIO;
@@ -1862,14 +1943,14 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
                        goto out;
 
                vaddr = kmap_atomic(pages[0], KM_USER0);
-               err = fuse_copy_ioctl_iovec(page_address(iov_page), vaddr,
+               err = fuse_copy_ioctl_iovec(fc, iov_page, vaddr,
                                            transferred, in_iovs + out_iovs,
                                            (flags & FUSE_IOCTL_COMPAT) != 0);
                kunmap_atomic(vaddr, KM_USER0);
                if (err)
                        goto out;
 
-               in_iov = page_address(iov_page);
+               in_iov = iov_page;
                out_iov = in_iov + in_iovs;
 
                err = fuse_verify_ioctl_iov(in_iov, in_iovs);
@@ -1891,8 +1972,7 @@ long fuse_do_ioctl(struct file *file, unsigned int cmd, unsigned long arg,
  out:
        if (req)
                fuse_put_request(fc, req);
-       if (iov_page)
-               __free_page(iov_page);
+       free_page((unsigned long) iov_page);
        while (num_pages)
                __free_page(pages[--num_pages]);
        kfree(pages);