From f448ac69d1791f4b5c1ce2771624e261cb1797b4 Mon Sep 17 00:00:00 2001 From: Feng Shuo Date: Fri, 4 Jan 2013 16:23:31 +0800 Subject: [PATCH] libfuse: add readdirplus support in fuse_lowlevel_ops This patch implements readdirplus support in FUSE usersapce. It adds a new fuse lowlevel operations fuse_lowleve_ops::readdir_plus, corespoding mount options and helper functions to maintain buffer. [From: Eric Wong ] This makes our terminology consistent with NFS and our kernel module, as well as reducing user/developer confusion in the command-line. Note: I'm keeping "fuse_add_direntry_plus" since that is less standardized in its use than "readdirplus" for now. Signed-off-by: Feng Shuo --- ChangeLog | 5 +++++ include/fuse_common.h | 1 + include/fuse_kernel.h | 17 +++++++++++++++- include/fuse_lowlevel.h | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/fuse_i.h | 1 + lib/fuse_lowlevel.c | 48 ++++++++++++++++++++++++++++++++++++++++++- lib/fuse_versionscript | 1 + test/stracedecode.c | 1 + 8 files changed, 126 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 7c9a2e2..d3e629c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +2013-02-07 Miklos Szeredi + + * libfuse: add readdirplus support in fuse_lowlevel_ops. Patch by + Feng Shuo + 2013-02-06 Miklos Szeredi * libfuse: set close-on-exec flag on pipe file descriptors. Patch diff --git a/include/fuse_common.h b/include/fuse_common.h index 58458ab..c8a2409 100644 --- a/include/fuse_common.h +++ b/include/fuse_common.h @@ -112,6 +112,7 @@ struct fuse_file_info { #define FUSE_CAP_FLOCK_LOCKS (1 << 10) #define FUSE_CAP_IOCTL_DIR (1 << 11) #define FUSE_CAP_AUTO_INVAL_DATA (1 << 12) +#define FUSE_CAP_READDIRPLUS (1 << 13) /** * Ioctl flags diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h index df8e9b9..28fefbb 100644 --- a/include/fuse_kernel.h +++ b/include/fuse_kernel.h @@ -86,6 +86,9 @@ * * 7.20 * - add FUSE_AUTO_INVAL_DATA + * + * 7.21 + * - add FUSE_READDIRPLUS */ #ifndef _LINUX_FUSE_H @@ -126,7 +129,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 20 +#define FUSE_KERNEL_MINOR_VERSION 21 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -228,6 +231,7 @@ struct fuse_file_lock { #define FUSE_FLOCK_LOCKS (1 << 10) #define FUSE_HAS_IOCTL_DIR (1 << 11) #define FUSE_AUTO_INVAL_DATA (1 << 12) +#define FUSE_DO_READDIRPLUS (1 << 13) /** * CUSE INIT request/reply flags @@ -334,6 +338,7 @@ enum fuse_opcode { FUSE_NOTIFY_REPLY = 41, FUSE_BATCH_FORGET = 42, FUSE_FALLOCATE = 43, + FUSE_READDIRPLUS = 44, /* CUSE specific operations */ CUSE_INIT = 4096, @@ -665,6 +670,16 @@ struct fuse_dirent { #define FUSE_DIRENT_SIZE(d) \ FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen) +struct fuse_direntplus { + struct fuse_entry_out entry_out; + struct fuse_dirent dirent; +}; + +#define FUSE_NAME_OFFSET_DIRENTPLUS \ + offsetof(struct fuse_direntplus, dirent.name) +#define FUSE_DIRENTPLUS_SIZE(d) \ + FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen) + struct fuse_notify_inval_inode_out { __u64 ino; __s64 off; diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h index 9a24bcc..22c2e5a 100644 --- a/include/fuse_lowlevel.h +++ b/include/fuse_lowlevel.h @@ -1016,6 +1016,32 @@ struct fuse_lowlevel_ops { */ void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode, off_t offset, off_t length, struct fuse_file_info *fi); + + /** + * Read directory with attributes + * + * Send a buffer filled using fuse_add_direntry_plus(), with size not + * exceeding the requested size. Send an empty buffer on end of + * stream. + * + * fi->fh will contain the value set by the opendir method, or + * will be undefined if the opendir method didn't set any value. + * + * Introduced in version 3.0 + * + * Valid replies: + * fuse_reply_buf + * fuse_reply_data + * fuse_reply_err + * + * @param req request handle + * @param ino the inode number + * @param size maximum number of bytes to send + * @param off offset to continue reading the directory stream + * @param fi file information + */ + void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi); }; /** @@ -1252,6 +1278,34 @@ size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, off_t off); /** + * Add a directory entry to the buffer with the attributes + * + * Buffer needs to be large enough to hold the entry. If it's not, + * then the entry is not filled in but the size of the entry is still + * returned. The caller can check this by comparing the bufsize + * parameter with the returned entry size. If the entry size is + * larger than the buffer size, the operation failed. + * + * From the 'stbuf' argument the st_ino field and bits 12-15 of the + * st_mode field are used. The other fields are ignored. + * + * Note: offsets do not necessarily represent physical offsets, and + * could be any marker, that enables the implementation to find a + * specific point in the directory stream. + * + * @param req request handle + * @param buf the point where the new entry will be added to the buffer + * @param bufsize remaining size of the buffer + * @param name the name of the entry + * @param e the directory entry + * @param off the offset of the next entry + * @return the space needed for the entry + */ +size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, + const char *name, + const struct fuse_entry_param *e, off_t off); + +/** * Reply to ask for data fetch and output buffer preparation. ioctl * will be retried with the specified input data fetched and output * buffer prepared. diff --git a/lib/fuse_i.h b/lib/fuse_i.h index 225ff7d..02e7af1 100644 --- a/lib/fuse_i.h +++ b/lib/fuse_i.h @@ -73,6 +73,7 @@ struct fuse_ll { int no_splice_read; int auto_inval_data; int no_auto_inval_data; + int no_readdirplus; struct fuse_lowlevel_ops op; int got_init; struct cuse_data *cuse_data; diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c index 568cf47..feaa076 100644 --- a/lib/fuse_lowlevel.c +++ b/lib/fuse_lowlevel.c @@ -339,6 +339,25 @@ static void fill_entry(struct fuse_entry_out *arg, convert_stat(&e->attr, &arg->attr); } +size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize, + const char *name, + const struct fuse_entry_param *e, off_t off) +{ + struct fuse_entry_out *argp; + size_t entsize; + + (void) req; + entsize = FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + + fuse_dirent_size(strlen(name))); + if (entsize <= bufsize && buf){ + argp = (struct fuse_entry_out *)buf; + memset(argp, 0, sizeof(*argp)); + fill_entry(argp, e); + fuse_add_dirent(buf + sizeof(*argp), name, &(e->attr), off); + } + return entsize; +} + static void fill_open(struct fuse_open_out *arg, const struct fuse_file_info *f) { @@ -1406,6 +1425,21 @@ static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) fuse_reply_err(req, ENOSYS); } +static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) +{ + struct fuse_read_in *arg = (struct fuse_read_in *) inarg; + struct fuse_file_info fi; + + memset(&fi, 0, sizeof(fi)); + fi.fh = arg->fh; + fi.fh_old = fi.fh; + + if (req->f->op.readdirplus) + req->f->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi); + else + fuse_reply_err(req, ENOSYS); +} + static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) { struct fuse_release_in *arg = (struct fuse_release_in *) inarg; @@ -1806,6 +1840,8 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) f->conn.capable |= FUSE_CAP_FLOCK_LOCKS; if (arg->flags & FUSE_AUTO_INVAL_DATA) f->conn.capable |= FUSE_CAP_AUTO_INVAL_DATA; + if (arg->flags & FUSE_DO_READDIRPLUS) + f->conn.capable |= FUSE_CAP_READDIRPLUS; } else { f->conn.async_read = 0; f->conn.max_readahead = 0; @@ -1838,6 +1874,8 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) f->conn.want |= FUSE_CAP_BIG_WRITES; if (f->auto_inval_data) f->conn.want |= FUSE_CAP_AUTO_INVAL_DATA; + if (f->op.readdirplus && !f->no_readdirplus) + f->conn.want |= FUSE_CAP_READDIRPLUS; if (bufsize < FUSE_MIN_READ_BUFFER) { fprintf(stderr, "fuse: warning: buffer size too small: %zu\n", @@ -1861,6 +1899,8 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) f->conn.want &= ~FUSE_CAP_SPLICE_MOVE; if (f->no_auto_inval_data) f->conn.want &= ~FUSE_CAP_AUTO_INVAL_DATA; + if (f->no_readdirplus) + f->conn.want &= ~FUSE_CAP_READDIRPLUS; if (f->conn.async_read || (f->conn.want & FUSE_CAP_ASYNC_READ)) outarg.flags |= FUSE_ASYNC_READ; @@ -1878,6 +1918,8 @@ static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg) outarg.flags |= FUSE_FLOCK_LOCKS; if (f->conn.want & FUSE_CAP_AUTO_INVAL_DATA) outarg.flags |= FUSE_AUTO_INVAL_DATA; + if (f->conn.want & FUSE_CAP_READDIRPLUS) + outarg.flags |= FUSE_DO_READDIRPLUS; outarg.max_readahead = f->conn.max_readahead; outarg.max_write = f->conn.max_write; if (f->conn.proto_minor >= 13) { @@ -2294,6 +2336,7 @@ static struct { [FUSE_DESTROY] = { do_destroy, "DESTROY" }, [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" }, [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" }, + [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"}, [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" }, }; @@ -2402,7 +2445,8 @@ static void fuse_ll_process_buf(void *data, const struct fuse_buf *buf, 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_NOTIFY_REPLY) + in->opcode != FUSE_NOTIFY_REPLY && + in->opcode != FUSE_READDIRPLUS) goto reply_err; err = ENOSYS; @@ -2501,6 +2545,7 @@ static const struct fuse_opt fuse_ll_opts[] = { { "no_splice_read", offsetof(struct fuse_ll, no_splice_read), 1}, { "auto_inval_data", offsetof(struct fuse_ll, auto_inval_data), 1}, { "no_auto_inval_data", offsetof(struct fuse_ll, no_auto_inval_data), 1}, + { "no_readdirplus", offsetof(struct fuse_ll, no_readdirplus), 1}, FUSE_OPT_KEY("max_read=", FUSE_OPT_KEY_DISCARD), FUSE_OPT_KEY("-h", KEY_HELP), FUSE_OPT_KEY("--help", KEY_HELP), @@ -2533,6 +2578,7 @@ static void fuse_ll_help(void) " -o [no_]splice_move move data while splicing to the fuse device\n" " -o [no_]splice_read use splice to read from the fuse device\n" " -o [no_]auto_inval_data use automatic kernel cache invalidation logic\n" +" -o [no_]readdirplus use readdirplus if possible.\n" ); } diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript index ea2a1c8..ad5a940 100644 --- a/lib/fuse_versionscript +++ b/lib/fuse_versionscript @@ -51,6 +51,7 @@ FUSE_3.0 { fuse_reply_statfs; fuse_set_signal_handlers; fuse_add_direntry; + fuse_add_direntry_plus; fuse_chan_new; fuse_chan_recv; fuse_daemonize; diff --git a/test/stracedecode.c b/test/stracedecode.c index 27b883c..940438a 100644 --- a/test/stracedecode.c +++ b/test/stracedecode.c @@ -41,6 +41,7 @@ static struct { [FUSE_INTERRUPT] = { "INTERRUPT" }, [FUSE_BMAP] = { "BMAP" }, [FUSE_DESTROY] = { "DESTROY" }, + [FUSE_READDIRPLUS] = { "READDIRPLUS" }, }; #define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0])) -- 2.11.4.GIT