]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/fuse/dev.c
[PATCH] fuse: ensure progress in read and write
[karo-tx-linux.git] / fs / fuse / dev.c
index ca6fc0e96d7c77762e26dd96a106541f07e3c529..e08ab4702d97ca1099474c2833778d9e72c879b4 100644 (file)
@@ -103,19 +103,8 @@ static struct fuse_req *do_get_request(struct fuse_conn *fc)
        return req;
 }
 
+/* This can return NULL, but only in case it's interrupted by a SIGKILL */
 struct fuse_req *fuse_get_request(struct fuse_conn *fc)
-{
-       if (down_interruptible(&fc->outstanding_sem))
-               return NULL;
-       return do_get_request(fc);
-}
-
-/*
- * Non-interruptible version of the above function is for operations
- * which can't legally return -ERESTART{SYS,NOINTR}.  This can still
- * return NULL, but only in case the signal is SIGKILL.
- */
-struct fuse_req *fuse_get_request_nonint(struct fuse_conn *fc)
 {
        int intr;
        sigset_t oldset;
@@ -159,12 +148,32 @@ void fuse_release_background(struct fuse_req *req)
        spin_unlock(&fuse_lock);
 }
 
+static void process_init_reply(struct fuse_conn *fc, struct fuse_req *req)
+{
+       int i;
+       struct fuse_init_out *arg = &req->misc.init_out;
+
+       if (arg->major != FUSE_KERNEL_VERSION)
+               fc->conn_error = 1;
+       else {
+               fc->minor = arg->minor;
+               fc->max_write = arg->minor < 5 ? 4096 : arg->max_write;
+       }
+
+       /* After INIT reply is received other requests can go
+          out.  So do (FUSE_MAX_OUTSTANDING - 1) number of
+          up()s on outstanding_sem.  The last up() is done in
+          fuse_putback_request() */
+       for (i = 1; i < FUSE_MAX_OUTSTANDING; i++)
+               up(&fc->outstanding_sem);
+}
+
 /*
  * This function is called when a request is finished.  Either a reply
  * has arrived or it was interrupted (and not yet sent) or some error
- * occured during communication with userspace, or the device file was
- * closed.  It decreases the referece count for the request.  In case
- * of a background request the referece to the stored objects are
+ * occurred during communication with userspace, or the device file was
+ * closed.  It decreases the reference count for the request.  In case
+ * of a background request the reference to the stored objects are
  * released.  The requester thread is woken up (if still waiting), and
  * finally the request is either freed or put on the unused_list
  *
@@ -183,18 +192,15 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
                up_read(&fc->sbput_sem);
        }
        wake_up(&req->waitq);
-       if (req->in.h.opcode == FUSE_INIT) {
-               int i;
-
-               if (req->misc.init_in_out.major != FUSE_KERNEL_VERSION)
-                       fc->conn_error = 1;
-
-               /* After INIT reply is received other requests can go
-                  out.  So do (FUSE_MAX_OUTSTANDING - 1) number of
-                  up()s on outstanding_sem.  The last up() is done in
-                  fuse_putback_request() */
-               for (i = 1; i < FUSE_MAX_OUTSTANDING; i++)
-                       up(&fc->outstanding_sem);
+       if (req->in.h.opcode == FUSE_INIT)
+               process_init_reply(fc, req);
+       else if (req->in.h.opcode == FUSE_RELEASE && req->inode == NULL) {
+               /* Special case for failed iget in CREATE */
+               u64 nodeid = req->in.h.nodeid;
+               __fuse_get_request(req);
+               fuse_reset_request(req);
+               fuse_send_forget(fc, req, nodeid, 1);
+               putback = 0;
        }
        if (putback)
                fuse_putback_request(fc, req);
@@ -241,43 +247,20 @@ static void background_request(struct fuse_conn *fc, struct fuse_req *req)
                get_file(req->file);
 }
 
-static int request_wait_answer_nonint(struct fuse_req *req)
-{
-       int err;
-       sigset_t oldset;
-       block_sigs(&oldset);
-       err = wait_event_interruptible(req->waitq, req->finished);
-       restore_sigs(&oldset);
-       return err;
-}
-
 /* Called with fuse_lock held.  Releases, and then reacquires it. */
-static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req,
-                               int interruptible)
+static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req)
 {
-       int intr;
+       sigset_t oldset;
 
        spin_unlock(&fuse_lock);
-       if (interruptible)
-               intr = wait_event_interruptible(req->waitq, req->finished);
-       else
-               intr = request_wait_answer_nonint(req);
+       block_sigs(&oldset);
+       wait_event_interruptible(req->waitq, req->finished);
+       restore_sigs(&oldset);
        spin_lock(&fuse_lock);
-       if (intr && interruptible && req->sent) {
-               /* If request is already in userspace, only allow KILL
-                  signal to interrupt */
-               spin_unlock(&fuse_lock);
-               intr = request_wait_answer_nonint(req);
-               spin_lock(&fuse_lock);
-       }
-       if (!intr)
+       if (req->finished)
                return;
 
-       if (!interruptible || req->sent)
-               req->out.h.error = -EINTR;
-       else
-               req->out.h.error = -ERESTARTNOINTR;
-
+       req->out.h.error = -EINTR;
        req->interrupted = 1;
        if (req->locked) {
                /* This is uninterruptible sleep, because data is
@@ -330,8 +313,10 @@ static void queue_request(struct fuse_conn *fc, struct fuse_req *req)
        wake_up(&fc->waitq);
 }
 
-static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req,
-                             int interruptible)
+/*
+ * This can only be interrupted by a SIGKILL
+ */
+void request_send(struct fuse_conn *fc, struct fuse_req *req)
 {
        req->isreply = 1;
        spin_lock(&fuse_lock);
@@ -345,26 +330,11 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req,
                   after request_end() */
                __fuse_get_request(req);
 
-               request_wait_answer(fc, req, interruptible);
+               request_wait_answer(fc, req);
        }
        spin_unlock(&fuse_lock);
 }
 
-void request_send(struct fuse_conn *fc, struct fuse_req *req)
-{
-       request_send_wait(fc, req, 1);
-}
-
-/*
- * Non-interruptible version of the above function is for operations
- * which can't legally return -ERESTART{SYS,NOINTR}.  This can still
- * be interrupted but only with SIGKILL.
- */
-void request_send_nonint(struct fuse_conn *fc, struct fuse_req *req)
-{
-       request_send_wait(fc, req, 0);
-}
-
 static void request_send_nowait(struct fuse_conn *fc, struct fuse_req *req)
 {
        spin_lock(&fuse_lock);
@@ -397,7 +367,7 @@ void fuse_send_init(struct fuse_conn *fc)
        /* This is called from fuse_read_super() so there's guaranteed
           to be a request available */
        struct fuse_req *req = do_get_request(fc);
-       struct fuse_init_in_out *arg = &req->misc.init_in_out;
+       struct fuse_init_in *arg = &req->misc.init_in;
        arg->major = FUSE_KERNEL_VERSION;
        arg->minor = FUSE_KERNEL_MINOR_VERSION;
        req->in.h.opcode = FUSE_INIT;
@@ -405,8 +375,12 @@ void fuse_send_init(struct fuse_conn *fc)
        req->in.args[0].size = sizeof(*arg);
        req->in.args[0].value = arg;
        req->out.numargs = 1;
-       req->out.args[0].size = sizeof(*arg);
-       req->out.args[0].value = arg;
+       /* Variable length arguement used for backward compatibility
+          with interface version < 7.5.  Rest of init_out is zeroed
+          by do_get_request(), so a short reply is not a problem */
+       req->out.argvar = 1;
+       req->out.args[0].size = sizeof(struct fuse_init_out);
+       req->out.args[0].value = &req->misc.init_out;
        request_send_background(fc, req);
 }
 
@@ -655,6 +629,7 @@ static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov,
        struct fuse_copy_state cs;
        unsigned reqsize;
 
+ restart:
        spin_lock(&fuse_lock);
        fc = file->private_data;
        err = -EPERM;
@@ -670,20 +645,25 @@ static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov,
 
        req = list_entry(fc->pending.next, struct fuse_req, list);
        list_del_init(&req->list);
-       spin_unlock(&fuse_lock);
 
        in = &req->in;
-       reqsize = req->in.h.len;
-       fuse_copy_init(&cs, 1, req, iov, nr_segs);
-       err = -EINVAL;
-       if (iov_length(iov, nr_segs) >= reqsize) {
-               err = fuse_copy_one(&cs, &in->h, sizeof(in->h));
-               if (!err)
-                       err = fuse_copy_args(&cs, in->numargs, in->argpages,
-                                            (struct fuse_arg *) in->args, 0);
+       reqsize = in->h.len;
+       /* If request is too large, reply with an error and restart the read */
+       if (iov_length(iov, nr_segs) < reqsize) {
+               req->out.h.error = -EIO;
+               /* SETXATTR is special, since it may contain too large data */
+               if (in->h.opcode == FUSE_SETXATTR)
+                       req->out.h.error = -E2BIG;
+               request_end(fc, req);
+               goto restart;
        }
+       spin_unlock(&fuse_lock);
+       fuse_copy_init(&cs, 1, req, iov, nr_segs);
+       err = fuse_copy_one(&cs, &in->h, sizeof(in->h));
+       if (!err)
+               err = fuse_copy_args(&cs, in->numargs, in->argpages,
+                                    (struct fuse_arg *) in->args, 0);
        fuse_copy_finish(&cs);
-
        spin_lock(&fuse_lock);
        req->locked = 0;
        if (!err && req->interrupted)
@@ -731,13 +711,6 @@ static struct fuse_req *request_find(struct fuse_conn *fc, u64 unique)
        return NULL;
 }
 
-/* fget() needs to be done in this context */
-static void process_getdir(struct fuse_req *req)
-{
-       struct fuse_getdir_out_i *arg = req->out.args[0].value;
-       arg->file = fget(arg->fd);
-}
-
 static int copy_out_args(struct fuse_copy_state *cs, struct fuse_out *out,
                         unsigned nbytes)
 {
@@ -817,8 +790,6 @@ static ssize_t fuse_dev_writev(struct file *file, const struct iovec *iov,
        if (!err) {
                if (req->interrupted)
                        err = -ENOENT;
-               else if (req->in.h.opcode == FUSE_GETDIR && !oh.error)
-                       process_getdir(req);
        } else if (!req->interrupted)
                req->out.h.error = -EIO;
        request_end(fc, req);