]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/fuse/dev.c
[PATCH] fuse: support caching negative dentries
[karo-tx-linux.git] / fs / fuse / dev.c
index e8f3170946f1f6b2cd513b280856b4cc7cfcf5ce..e5bc3f8eebd06a092f2f88343995fc13c870fcf4 100644 (file)
@@ -26,7 +26,7 @@ static inline struct fuse_conn *fuse_get_conn(struct file *file)
        struct fuse_conn *fc;
        spin_lock(&fuse_lock);
        fc = file->private_data;
-       if (fc && !fc->sb)
+       if (fc && !fc->mounted)
                fc = NULL;
        spin_unlock(&fuse_lock);
        return fc;
@@ -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;
@@ -148,12 +137,23 @@ void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req)
                fuse_putback_request(fc, req);
 }
 
+void fuse_release_background(struct fuse_req *req)
+{
+       iput(req->inode);
+       iput(req->inode2);
+       if (req->file)
+               fput(req->file);
+       spin_lock(&fuse_lock);
+       list_del(&req->bg_entry);
+       spin_unlock(&fuse_lock);
+}
+
 /*
  * 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
  *
@@ -166,12 +166,10 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
        putback = atomic_dec_and_test(&req->count);
        spin_unlock(&fuse_lock);
        if (req->background) {
-               if (req->inode)
-                       iput(req->inode);
-               if (req->inode2)
-                       iput(req->inode2);
-               if (req->file)
-                       fput(req->file);
+               down_read(&fc->sbput_sem);
+               if (fc->mounted)
+                       fuse_release_background(req);
+               up_read(&fc->sbput_sem);
        }
        wake_up(&req->waitq);
        if (req->in.h.opcode == FUSE_INIT) {
@@ -180,22 +178,59 @@ static void request_end(struct fuse_conn *fc, struct fuse_req *req)
                if (req->misc.init_in_out.major != FUSE_KERNEL_VERSION)
                        fc->conn_error = 1;
 
+               fc->minor = req->misc.init_in_out.minor;
+
                /* 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);
+       } 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);
 }
 
-static void background_request(struct fuse_req *req)
+/*
+ * Unfortunately request interruption not just solves the deadlock
+ * problem, it causes problems too.  These stem from the fact, that an
+ * interrupted request is continued to be processed in userspace,
+ * while all the locks and object references (inode and file) held
+ * during the operation are released.
+ *
+ * To release the locks is exactly why there's a need to interrupt the
+ * request, so there's not a lot that can be done about this, except
+ * introduce additional locking in userspace.
+ *
+ * More important is to keep inode and file references until userspace
+ * has replied, otherwise FORGET and RELEASE could be sent while the
+ * inode/file is still used by the filesystem.
+ *
+ * For this reason the concept of "background" request is introduced.
+ * An interrupted request is backgrounded if it has been already sent
+ * to userspace.  Backgrounding involves getting an extra reference to
+ * inode(s) or file used in the request, and adding the request to
+ * fc->background list.  When a reply is received for a background
+ * request, the object references are released, and the request is
+ * removed from the list.  If the filesystem is unmounted while there
+ * are still background requests, the list is walked and references
+ * are released as if a reply was received.
+ *
+ * There's one more use for a background request.  The RELEASE message is
+ * always sent as background, since it doesn't return an error or
+ * data.
+ */
+static void background_request(struct fuse_conn *fc, struct fuse_req *req)
 {
-       /* Need to get hold of the inode(s) and/or file used in the
-          request, so FORGET and RELEASE are not sent too early */
        req->background = 1;
+       list_add(&req->bg_entry, &fc->background);
        if (req->inode)
                req->inode = igrab(req->inode);
        if (req->inode2)
@@ -204,42 +239,20 @@ static void background_request(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_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
@@ -255,7 +268,7 @@ static void request_wait_answer(struct fuse_req *req, int interruptible)
                list_del(&req->list);
                __fuse_put_request(req);
        } else if (!req->finished && req->sent)
-               background_request(req);
+               background_request(fc, req);
 }
 
 static unsigned len_args(unsigned numargs, struct fuse_arg *args)
@@ -292,12 +305,14 @@ 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);
-       if (!fc->file)
+       if (!fc->connected)
                req->out.h.error = -ENOTCONN;
        else if (fc->conn_error)
                req->out.h.error = -ECONNREFUSED;
@@ -307,30 +322,15 @@ static void request_send_wait(struct fuse_conn *fc, struct fuse_req *req,
                   after request_end() */
                __fuse_get_request(req);
 
-               request_wait_answer(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);
-       if (fc->file) {
+       if (fc->connected) {
                queue_request(fc, req);
                spin_unlock(&fuse_lock);
        } else {
@@ -348,7 +348,9 @@ void request_send_noreply(struct fuse_conn *fc, struct fuse_req *req)
 void request_send_background(struct fuse_conn *fc, struct fuse_req *req)
 {
        req->isreply = 1;
-       background_request(req);
+       spin_lock(&fuse_lock);
+       background_request(fc, req);
+       spin_unlock(&fuse_lock);
        request_send_nowait(fc, req);
 }
 
@@ -583,7 +585,7 @@ static void request_wait(struct fuse_conn *fc)
        DECLARE_WAITQUEUE(wait, current);
 
        add_wait_queue_exclusive(&fc->waitq, &wait);
-       while (fc->sb && list_empty(&fc->pending)) {
+       while (fc->mounted && list_empty(&fc->pending)) {
                set_current_state(TASK_INTERRUPTIBLE);
                if (signal_pending(current))
                        break;
@@ -622,7 +624,7 @@ static ssize_t fuse_dev_readv(struct file *file, const struct iovec *iov,
                goto err_unlock;
        request_wait(fc);
        err = -ENODEV;
-       if (!fc->sb)
+       if (!fc->mounted)
                goto err_unlock;
        err = -ERESTARTSYS;
        if (list_empty(&fc->pending))
@@ -691,13 +693,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)
 {
@@ -777,8 +772,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);
@@ -839,7 +832,7 @@ static int fuse_dev_release(struct inode *inode, struct file *file)
        spin_lock(&fuse_lock);
        fc = file->private_data;
        if (fc) {
-               fc->file = NULL;
+               fc->connected = 0;
                end_requests(fc, &fc->pending);
                end_requests(fc, &fc->processing);
                fuse_release_conn(fc);