]> err.no Git - linux-2.6/blobdiff - fs/fuse/dev.c
[PATCH] fuse: add O_ASYNC support to FUSE device
[linux-2.6] / fs / fuse / dev.c
index 4526da8907c6d384fefee03b18bd6894558bf459..438770f8867fc2fe1289472f30fe52103251ab36 100644 (file)
@@ -66,6 +66,12 @@ static void restore_sigs(sigset_t *oldset)
        sigprocmask(SIG_SETMASK, oldset, NULL);
 }
 
+/*
+ * Reset request, so that it can be reused
+ *
+ * The caller must be _very_ careful to make sure, that it is holding
+ * the only reference to req
+ */
 void fuse_reset_request(struct fuse_req *req)
 {
        int preallocated = req->preallocated;
@@ -120,9 +126,9 @@ struct fuse_req *fuse_get_request(struct fuse_conn *fc)
        return do_get_request(fc);
 }
 
+/* Must be called with fuse_lock held */
 static void fuse_putback_request(struct fuse_conn *fc, struct fuse_req *req)
 {
-       spin_lock(&fuse_lock);
        if (req->preallocated) {
                atomic_dec(&fc->num_waiting);
                list_add(&req->list, &fc->unused_list);
@@ -134,10 +140,18 @@ static void fuse_putback_request(struct fuse_conn *fc, struct fuse_req *req)
                fc->outstanding_debt--;
        else
                up(&fc->outstanding_sem);
-       spin_unlock(&fuse_lock);
 }
 
 void fuse_put_request(struct fuse_conn *fc, struct fuse_req *req)
+{
+       if (atomic_dec_and_test(&req->count)) {
+               spin_lock(&fuse_lock);
+               fuse_putback_request(fc, req);
+               spin_unlock(&fuse_lock);
+       }
+}
+
+static void fuse_put_request_locked(struct fuse_conn *fc, struct fuse_req *req)
 {
        if (atomic_dec_and_test(&req->count))
                fuse_putback_request(fc, req);
@@ -163,26 +177,36 @@ void fuse_release_background(struct fuse_req *req)
  * still waiting), the 'end' callback is called if given, else the
  * reference to the request is released
  *
+ * Releasing extra reference for foreground requests must be done
+ * within the same locked region as setting state to finished.  This
+ * is because fuse_reset_request() may be called after request is
+ * finished and it must be the sole possessor.  If request is
+ * interrupted and put in the background, it will return with an error
+ * and hence never be reset and reused.
+ *
  * Called with fuse_lock, unlocks it
  */
 static void request_end(struct fuse_conn *fc, struct fuse_req *req)
 {
-       void (*end) (struct fuse_conn *, struct fuse_req *) = req->end;
-       req->end = NULL;
        list_del(&req->list);
        req->state = FUSE_REQ_FINISHED;
-       spin_unlock(&fuse_lock);
-       if (req->background) {
+       if (!req->background) {
+               wake_up(&req->waitq);
+               fuse_put_request_locked(fc, req);
+               spin_unlock(&fuse_lock);
+       } else {
+               void (*end) (struct fuse_conn *, struct fuse_req *) = req->end;
+               req->end = NULL;
+               spin_unlock(&fuse_lock);
                down_read(&fc->sbput_sem);
                if (fc->mounted)
                        fuse_release_background(req);
                up_read(&fc->sbput_sem);
+               if (end)
+                       end(fc, req);
+               else
+                       fuse_put_request(fc, req);
        }
-       wake_up(&req->waitq);
-       if (end)
-               end(fc, req);
-       else
-               fuse_put_request(fc, req);
 }
 
 /*
@@ -293,6 +317,7 @@ static void queue_request(struct fuse_conn *fc, struct fuse_req *req)
        list_add_tail(&req->list, &fc->pending);
        req->state = FUSE_REQ_PENDING;
        wake_up(&fc->waitq);
+       kill_fasync(&fc->fasync, SIGIO, POLL_IN);
 }
 
 /*
@@ -780,17 +805,18 @@ static ssize_t fuse_dev_write(struct file *file, const char __user *buf,
 
 static unsigned fuse_dev_poll(struct file *file, poll_table *wait)
 {
-       struct fuse_conn *fc = fuse_get_conn(file);
        unsigned mask = POLLOUT | POLLWRNORM;
-
+       struct fuse_conn *fc = fuse_get_conn(file);
        if (!fc)
-               return -ENODEV;
+               return POLLERR;
 
        poll_wait(file, &fc->waitq, wait);
 
        spin_lock(&fuse_lock);
-       if (!list_empty(&fc->pending))
-                mask |= POLLIN | POLLRDNORM;
+       if (!fc->connected)
+               mask = POLLERR;
+       else if (!list_empty(&fc->pending))
+               mask |= POLLIN | POLLRDNORM;
        spin_unlock(&fuse_lock);
 
        return mask;
@@ -876,6 +902,7 @@ void fuse_abort_conn(struct fuse_conn *fc)
                end_requests(fc, &fc->pending);
                end_requests(fc, &fc->processing);
                wake_up_all(&fc->waitq);
+               kill_fasync(&fc->fasync, SIGIO, POLL_IN);
        }
        spin_unlock(&fuse_lock);
 }
@@ -892,13 +919,25 @@ static int fuse_dev_release(struct inode *inode, struct file *file)
                end_requests(fc, &fc->processing);
        }
        spin_unlock(&fuse_lock);
-       if (fc)
+       if (fc) {
+               fasync_helper(-1, file, 0, &fc->fasync);
                kobject_put(&fc->kobj);
+       }
 
        return 0;
 }
 
-struct file_operations fuse_dev_operations = {
+static int fuse_dev_fasync(int fd, struct file *file, int on)
+{
+       struct fuse_conn *fc = fuse_get_conn(file);
+       if (!fc)
+               return -ENODEV;
+
+       /* No locking - fasync_helper does its own locking */
+       return fasync_helper(fd, file, on, &fc->fasync);
+}
+
+const struct file_operations fuse_dev_operations = {
        .owner          = THIS_MODULE,
        .llseek         = no_llseek,
        .read           = fuse_dev_read,
@@ -907,6 +946,7 @@ struct file_operations fuse_dev_operations = {
        .writev         = fuse_dev_writev,
        .poll           = fuse_dev_poll,
        .release        = fuse_dev_release,
+       .fasync         = fuse_dev_fasync,
 };
 
 static struct miscdevice fuse_miscdevice = {