libfuse: add retrieve request
authorMiklos Szeredi <miklos@szeredi.hu>
Mon, 8 Nov 2010 20:13:32 +0000 (21:13 +0100)
committerMiklos Szeredi <mszeredi@suse.cz>
Mon, 8 Nov 2010 20:13:32 +0000 (21:13 +0100)
Retrieve data stored in the kernel buffers for a given inode.

ChangeLog
include/fuse_kernel.h
include/fuse_lowlevel.h
lib/fuse_i.h
lib/fuse_lowlevel.c
lib/fuse_versionscript

index 010bcbebc7ed2c6057a1899241f2aee0f6267a15..52a000a9ffe3b7ae3b84499a3a2829415cdd26d5 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -37,6 +37,9 @@
        * libfuse: add store request.  Request data to be stored in the
        kernel buffers for a given inode.
 
+       * libfuse: add retrieve request.  Retrieve data stored in the
+       kernel buffers for a given inode.
+
 2010-10-14  Miklos Szeredi <miklos@szeredi.hu>
 
        * Use LTLIBICONV when linking libfuse.  This fixes building against
index 25d3555aec64a35fcd1b832f543ef0b31fb72d6e..c7c99a5e0838611148ac88d4e714bfd9e89e1748 100644 (file)
@@ -66,6 +66,7 @@
  *
  * 7.15
  *  - add store notify
+ *  - add retrieve notify
  */
 
 #ifndef _LINUX_FUSE_H
@@ -285,6 +286,7 @@ enum fuse_opcode {
        FUSE_DESTROY       = 38,
        FUSE_IOCTL         = 39,
        FUSE_POLL          = 40,
+       FUSE_NOTIFY_REPLY  = 41,
 
        /* CUSE specific operations */
        CUSE_INIT          = 4096,
@@ -295,6 +297,7 @@ enum fuse_notify_code {
        FUSE_NOTIFY_INVAL_INODE = 2,
        FUSE_NOTIFY_INVAL_ENTRY = 3,
        FUSE_NOTIFY_STORE = 4,
+       FUSE_NOTIFY_RETRIEVE = 5,
        FUSE_NOTIFY_CODE_MAX,
 };
 
@@ -610,4 +613,22 @@ struct fuse_notify_store_out {
        __u32   padding;
 };
 
+struct fuse_notify_retrieve_out {
+       __u64   notify_unique;
+       __u64   nodeid;
+       __u64   offset;
+       __u32   size;
+       __u32   padding;
+};
+
+/* Matches the size of fuse_write_in */
+struct fuse_notify_retrieve_in {
+       __u64   dummy1;
+       __u64   offset;
+       __u32   size;
+       __u32   dummy2;
+       __u64   dummy3;
+       __u64   dummy4;
+};
+
 #endif /* _LINUX_FUSE_H */
index 0a8fdf174b0904e158844ef50476715e94c60e54..8f31471333ece57a441b05951fa66da9cb0575d1 100644 (file)
@@ -902,6 +902,17 @@ struct fuse_lowlevel_ops {
        void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
                           struct fuse_bufvec *bufv, off_t off,
                           struct fuse_file_info *fi);
+
+       /**
+        * Callback function for the retrieve request
+        *
+        * @param cookie user data supplied to fuse_lowlevel_notify_retrieve()
+        * @param ino the inode number supplied to fuse_lowlevel_notify_retrieve()
+        * @param offset the offset supplied to fuse_lowlevel_notify_retrieve()
+        * @param bufv the buffer containing the returned data
+        */
+       void (*retrieve_reply) (void *cookie, fuse_ino_t ino, off_t offset,
+                               struct fuse_bufvec *bufv);
 };
 
 /**
@@ -1248,6 +1259,34 @@ int fuse_lowlevel_notify_inval_entry(struct fuse_chan *ch, fuse_ino_t parent,
 int fuse_lowlevel_notify_store(struct fuse_chan *ch, fuse_ino_t ino,
                               off_t offset, struct fuse_bufvec *bufv,
                               enum fuse_buf_copy_flags flags);
+/**
+ * Retrieve data from the kernel buffers
+ *
+ * Retrieve data in the kernel buffers belonging to the given inode.
+ * If successful then the retrieve_reply() method will be called with
+ * the returned data.
+ *
+ * Only present pages are returned in the retrieve reply.  Retrieving
+ * stops when it finds a non-present page and only data prior to that is
+ * returned.
+ *
+ * If this function returns an error, then the retrieve will not be
+ * completed and no reply will be sent.
+ *
+ * This function doesn't change the dirty state of pages in the kernel
+ * buffer.  For dirty pages the write() method will be called
+ * regardless of having been retrieved previously.
+ *
+ * @param ch the channel through which to send the invalidation
+ * @param ino the inode number
+ * @param size the number of bytes to retrieve
+ * @param offset the starting offset into the file to retrieve from
+ * @param cookie user data to supply to the reply callback
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_retrieve(struct fuse_chan *ch, fuse_ino_t ino,
+                                 size_t size, off_t offset, void *cookie);
+
 
 /* ----------------------------------------------------------- *
  * Utility functions                                          *
index 6d10b1c21d29128fde3c590088ab97827abb77d9..9d0de5804f225e6ed38dac61ca6a85afd58c3490 100644 (file)
@@ -49,6 +49,14 @@ struct fuse_req {
        struct fuse_req *prev;
 };
 
+struct fuse_notify_req {
+       uint64_t unique;
+       void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+                     const void *, const struct fuse_buf *);
+       struct fuse_notify_req *next;
+       struct fuse_notify_req *prev;
+};
+
 struct fuse_ll {
        int debug;
        int allow_root;
@@ -70,6 +78,8 @@ struct fuse_ll {
        int got_destroy;
        pthread_key_t pipe_key;
        int broken_splice_nonblock;
+       uint64_t notify_ctr;
+       struct fuse_notify_req notify_list;
 };
 
 struct fuse_cmd {
index 2a5e8d26358eaa737775ac4048b7b3fc0b942fab..02de394b122e58ff81424227152cfb6fffbed37f 100644 (file)
 #define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
 #define OFFSET_MAX 0x7fffffffffffffffLL
 
+#define container_of(ptr, type, member) ({                             \
+                       const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+                       (type *)( (char *)__mptr - offsetof(type,member) );})
+
 struct fuse_pollhandle {
        uint64_t kh;
        struct fuse_chan *ch;
@@ -1612,7 +1616,8 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
                        f->conn.want |= FUSE_CAP_SPLICE_WRITE;
                if (!f->no_splice_move)
                        f->conn.want |= FUSE_CAP_SPLICE_MOVE;
-               if (f->op.write_buf && !f->no_splice_read)
+               if (!f->no_splice_read &&
+                   (f->op.write_buf || f->op.retrieve_reply))
                        f->conn.want |= FUSE_CAP_SPLICE_READ;
        }
 
@@ -1694,11 +1699,59 @@ static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
        send_reply_ok(req, NULL, 0);
 }
 
+static void list_del_nreq(struct fuse_notify_req *nreq)
+{
+       struct fuse_notify_req *prev = nreq->prev;
+       struct fuse_notify_req *next = nreq->next;
+       prev->next = next;
+       next->prev = prev;
+}
+
+static void list_add_nreq(struct fuse_notify_req *nreq,
+                         struct fuse_notify_req *next)
+{
+       struct fuse_notify_req *prev = next->prev;
+       nreq->next = next;
+       nreq->prev = prev;
+       prev->next = nreq;
+       next->prev = nreq;
+}
+
+static void list_init_nreq(struct fuse_notify_req *nreq)
+{
+       nreq->next = nreq;
+       nreq->prev = nreq;
+}
+
+static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+                           const void *inarg, const struct fuse_buf *buf)
+{
+       struct fuse_ll *f = req->f;
+       struct fuse_notify_req *nreq;
+       struct fuse_notify_req *head;
+
+       pthread_mutex_lock(&f->lock);
+       head = &f->notify_list;
+       for (nreq = head->next; nreq != head; nreq = nreq->next) {
+               if (nreq->unique == req->unique) {
+                       list_del_nreq(nreq);
+                       break;
+               }
+       }
+       pthread_mutex_unlock(&f->lock);
+
+       if (nreq != head)
+               nreq->reply(nreq, req, nodeid, inarg, buf);
+}
+
 static int send_notify_iov(struct fuse_ll *f, struct fuse_chan *ch,
                           int notify_code, struct iovec *iov, int count)
 {
        struct fuse_out_header out;
 
+       if (!f->got_init)
+               return -ENOTCONN;
+
        out.unique = 0;
        out.error = notify_code;
        iov[0].iov_base = &out;
@@ -1811,6 +1864,95 @@ int fuse_lowlevel_notify_store(struct fuse_chan *ch, fuse_ino_t ino,
        return res;
 }
 
+struct fuse_retrieve_req {
+       struct fuse_notify_req nreq;
+       void *cookie;
+};
+
+static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+                                  fuse_req_t req, fuse_ino_t ino,
+                                  const void *inarg,
+                                  const struct fuse_buf *ibuf)
+{
+       struct fuse_ll *f = req->f;
+       struct fuse_retrieve_req *rreq =
+               container_of(nreq, struct fuse_retrieve_req, nreq);
+       const struct fuse_notify_retrieve_in *arg = inarg;
+       struct fuse_buf buf = *ibuf;
+       struct fuse_bufvec bufv = {
+               .buf = &buf,
+               .count = 1,
+       };
+
+       if (!(buf.flags & FUSE_BUF_IS_FD))
+               buf.mem = PARAM(arg);
+
+       buf.size -= sizeof(struct fuse_in_header) +
+               sizeof(struct fuse_notify_retrieve_in);
+
+       if (buf.size < arg->size) {
+               fprintf(stderr, "fuse: retrieve reply: buffer size too small\n");
+               fuse_reply_none(req);
+               goto out;
+       }
+       buf.size = arg->size;
+
+       if (req->f->op.retrieve_reply)
+               req->f->op.retrieve_reply(rreq->cookie, ino, arg->offset, &bufv);
+       fuse_reply_none(req);
+       free(rreq);
+
+out:
+       if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+               fuse_ll_clear_pipe(f);
+}
+
+int fuse_lowlevel_notify_retrieve(struct fuse_chan *ch, fuse_ino_t ino,
+                                 size_t size, off_t offset, void *cookie)
+{
+       struct fuse_notify_retrieve_out outarg;
+       struct fuse_ll *f;
+       struct iovec iov[2];
+       struct fuse_retrieve_req *rreq;
+       int err;
+
+       if (!ch)
+               return -EINVAL;
+
+       f = (struct fuse_ll *)fuse_session_data(fuse_chan_session(ch));
+       if (!f)
+               return -ENODEV;
+
+       rreq = malloc(sizeof(*rreq));
+       if (rreq == NULL)
+               return -ENOMEM;
+
+       pthread_mutex_lock(&f->lock);
+       rreq->cookie = cookie;
+       rreq->nreq.unique = f->notify_ctr++;
+       rreq->nreq.reply = fuse_ll_retrieve_reply;
+       list_add_nreq(&rreq->nreq, &f->notify_list);
+       pthread_mutex_unlock(&f->lock);
+
+       outarg.notify_unique = rreq->nreq.unique;
+       outarg.nodeid = ino;
+       outarg.offset = offset;
+       outarg.size = size;
+
+       iov[1].iov_base = &outarg;
+       iov[1].iov_len = sizeof(outarg);
+
+       err = send_notify_iov(f, ch, FUSE_NOTIFY_RETRIEVE, iov, 2);
+       if (err) {
+               pthread_mutex_lock(&f->lock);
+               list_del_nreq(&rreq->nreq);
+               pthread_mutex_unlock(&f->lock);
+               free(rreq);
+       }
+
+       return err;
+}
+
 void *fuse_req_userdata(fuse_req_t req)
 {
        return req->f->userdata;
@@ -1902,6 +2044,7 @@ static struct {
        [FUSE_IOCTL]       = { do_ioctl,       "IOCTL"       },
        [FUSE_POLL]        = { do_poll,        "POLL"        },
        [FUSE_DESTROY]     = { do_destroy,     "DESTROY"     },
+       [FUSE_NOTIFY_REPLY] = { (void *) 1,    "NOTIFY_REPLY" },
        [CUSE_INIT]        = { cuse_lowlevel_init, "CUSE_INIT"   },
 };
 
@@ -2007,7 +2150,8 @@ static void fuse_ll_process_buf(void *data, const struct fuse_buf *buf,
                 in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
                 in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
                 in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
-                in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR)
+                in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+                in->opcode != FUSE_NOTIFY_REPLY)
                goto reply_err;
 
        err = ENOSYS;
@@ -2024,7 +2168,8 @@ static void fuse_ll_process_buf(void *data, const struct fuse_buf *buf,
        }
 
        if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
-           (in->opcode != FUSE_WRITE || !f->op.write_buf)) {
+           (in->opcode != FUSE_WRITE || !f->op.write_buf) &&
+           in->opcode != FUSE_NOTIFY_REPLY) {
                void *newmbuf;
 
                err = ENOMEM;
@@ -2048,6 +2193,8 @@ static void fuse_ll_process_buf(void *data, const struct fuse_buf *buf,
        inarg = (void *) &in[1];
        if (in->opcode == FUSE_WRITE && f->op.write_buf)
                do_write_buf(req, in->nodeid, inarg, buf);
+       else if (in->opcode == FUSE_NOTIFY_REPLY)
+               do_notify_reply(req, in->nodeid, inarg, buf);
        else
                fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
 
@@ -2310,6 +2457,8 @@ struct fuse_session *fuse_lowlevel_new_common(struct fuse_args *args,
        f->atomic_o_trunc = 0;
        list_init_req(&f->list);
        list_init_req(&f->interrupts);
+       list_init_nreq(&f->notify_list);
+       f->notify_ctr = 1;
        fuse_mutex_init(&f->lock);
 
        err = pthread_key_create(&f->pipe_key, fuse_ll_pipe_destructor);
index f2718610ce610a28cdfb6eb8ba8178c0b1912dfc..da1cd90893f833dd8a7589d865ef1073485d2035 100644 (file)
@@ -184,6 +184,7 @@ FUSE_2.9 {
        global:
                fuse_buf_copy;
                fuse_buf_size;
+               fuse_lowlevel_notify_retrieve;
                fuse_lowlevel_notify_store;
                fuse_reply_data;
                fuse_session_process_buf;