From 3f41e8f2c3d700930e1e604b2e9116f9f827283f Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Mon, 8 Nov 2010 21:13:32 +0100 Subject: [PATCH] libfuse: add retrieve request Retrieve data stored in the kernel buffers for a given inode. --- ChangeLog | 3 + include/fuse_kernel.h | 21 ++++++ include/fuse_lowlevel.h | 39 ++++++++++ lib/fuse_i.h | 10 +++ lib/fuse_lowlevel.c | 155 +++++++++++++++++++++++++++++++++++++++- lib/fuse_versionscript | 1 + 6 files changed, 226 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index 010bcbe..52a000a 100644 --- 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 * Use LTLIBICONV when linking libfuse. This fixes building against diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h index 25d3555..c7c99a5 100644 --- a/include/fuse_kernel.h +++ b/include/fuse_kernel.h @@ -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 */ diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 0a8fdf1..8f31471 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -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 * diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 6d10b1c..9d0de58 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -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 { diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 2a5e8d2..02de394 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -35,6 +35,10 @@ #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); diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index f271861..da1cd90 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -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; -- 2.30.2