From: Darrick J. Wong <djwong@xxxxxxxxxx> Implement inline data file IO by issuing FUSE_READ/FUSE_WRITE commands in response to an inline data mapping. Signed-off-by: "Darrick J. Wong" <djwong@xxxxxxxxxx> --- fs/fuse/fuse_trace.h | 57 +++++++++++++++ fs/fuse/file_iomap.c | 188 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 234 insertions(+), 11 deletions(-) diff --git a/fs/fuse/fuse_trace.h b/fs/fuse/fuse_trace.h index 0078a9ad2a2871..20257aed0cd89f 100644 --- a/fs/fuse/fuse_trace.h +++ b/fs/fuse/fuse_trace.h @@ -1232,6 +1232,63 @@ TRACE_EVENT(fuse_iomap_config, __entry->time_min, __entry->time_max, __entry->maxbytes, __entry->uuid_len) ); + +DECLARE_EVENT_CLASS(fuse_iomap_inline_class, + TP_PROTO(const struct inode *inode, loff_t pos, uint64_t count, + const struct iomap *map), + TP_ARGS(inode, pos, count, map), + + TP_STRUCT__entry( + __field(dev_t, connection) + __field(uint64_t, ino) + __field(uint64_t, nodeid) + __field(loff_t, isize) + __field(loff_t, pos) + __field(uint64_t, count) + __field(loff_t, offset) + __field(uint64_t, length) + __field(uint16_t, maptype) + __field(uint16_t, mapflags) + __field(bool, has_buf) + __field(uint64_t, validity_cookie) + ), + + TP_fast_assign( + const struct fuse_inode *fi = get_fuse_inode_c(inode); + const struct fuse_mount *fm = get_fuse_mount_c(inode); + + __entry->connection = fm->fc->dev; + __entry->ino = fi->orig_ino; + __entry->nodeid = fi->nodeid; + __entry->isize = i_size_read(inode); + __entry->pos = pos; + __entry->count = count; + __entry->offset = map->offset; + __entry->length = map->length; + __entry->maptype = map->type; + __entry->mapflags = map->flags; + __entry->has_buf = map->inline_data != NULL; + __entry->validity_cookie= map->validity_cookie; + ), + + TP_printk("connection %u ino %llu nodeid %llu isize 0x%llx pos 0x%llx count 0x%llx offset 0x%llx length 0x%llx type %s mapflags (%s) has_buf? %d cookie 0x%llx", + __entry->connection, __entry->ino, __entry->nodeid, + __entry->isize, + __entry->pos, __entry->count, + __entry->offset, __entry->length, + __print_symbolic(__entry->maptype, FUSE_IOMAP_TYPE_STRINGS), + __print_flags(__entry->mapflags, "|", FUSE_IOMAP_F_STRINGS), + __entry->has_buf, __entry->validity_cookie) +); +#define DEFINE_FUSE_IOMAP_INLINE_EVENT(name) \ +DEFINE_EVENT(fuse_iomap_inline_class, name, \ + TP_PROTO(const struct inode *inode, loff_t pos, uint64_t count, \ + const struct iomap *map), \ + TP_ARGS(inode, pos, count, map)) +DEFINE_FUSE_IOMAP_INLINE_EVENT(fuse_iomap_inline_read); +DEFINE_FUSE_IOMAP_INLINE_EVENT(fuse_iomap_inline_write); +DEFINE_FUSE_IOMAP_INLINE_EVENT(fuse_iomap_set_inline_iomap); +DEFINE_FUSE_IOMAP_INLINE_EVENT(fuse_iomap_set_inline_srcmap); #endif /* CONFIG_FUSE_IOMAP */ #endif /* _TRACE_FUSE_H */ diff --git a/fs/fuse/file_iomap.c b/fs/fuse/file_iomap.c index 3f6e0496c4744b..5ef9fa67db807e 100644 --- a/fs/fuse/file_iomap.c +++ b/fs/fuse/file_iomap.c @@ -201,17 +201,6 @@ fuse_iomap_begin_validate(const struct fuse_iomap_begin_out *outarg, BAD_DATA(check_add_overflow(outarg->write_addr, outarg->length, &end))) return -EIO; - if (!(opflags & FUSE_IOMAP_OP_REPORT)) { - /* - * XXX inline data reads and writes are not supported, how do - * we do this? - */ - if (BAD_DATA(outarg->read_type == FUSE_IOMAP_TYPE_INLINE)) - return -EIO; - if (BAD_DATA(outarg->write_type == FUSE_IOMAP_TYPE_INLINE)) - return -EIO; - } - return 0; } @@ -312,6 +301,157 @@ fuse_iomap_set_device(struct iomap *iomap, const struct fuse_iomap_dev *fb) iomap->dax_dev = NULL; } +static inline int fuse_iomap_inline_alloc(struct iomap *iomap) +{ + ASSERT(iomap->inline_data == NULL); + ASSERT(iomap->length > 0); + + iomap->inline_data = kvzalloc(iomap->length, GFP_KERNEL); + return iomap->inline_data ? 0 : -ENOMEM; +} + +static inline void fuse_iomap_inline_free(struct iomap *iomap) +{ + kvfree(iomap->inline_data); + iomap->inline_data = NULL; +} + +/* + * Use the FUSE_READ command to read inline file data from the fuse server. + * Note that there's no file handle attached, so the fuse server must be able + * to reconnect to the inode via the nodeid. + */ +static int fuse_iomap_inline_read(struct inode *inode, loff_t pos, + loff_t count, struct iomap *iomap) +{ + struct fuse_read_in in = { + .offset = pos, + .size = count, + }; + struct fuse_inode *fi = get_fuse_inode(inode); + struct fuse_mount *fm = get_fuse_mount(inode); + FUSE_ARGS(args); + ssize_t ret; + + if (BAD_DATA(!iomap_inline_data_valid(iomap))) + return -EIO; + + trace_fuse_iomap_inline_read(inode, pos, count, iomap); + + args.opcode = FUSE_READ; + args.nodeid = fi->nodeid; + args.in_numargs = 1; + args.in_args[0].size = sizeof(in); + args.in_args[0].value = ∈ + args.out_argvar = true; + args.out_numargs = 1; + args.out_args[0].size = count; + args.out_args[0].value = iomap_inline_data(iomap, pos); + + ret = fuse_simple_request(fm, &args); + if (ret < 0) { + fuse_iomap_inline_free(iomap); + return ret; + } + /* no readahead means something bad happened */ + if (ret == 0) { + fuse_iomap_inline_free(iomap); + return -EIO; + } + + return 0; +} + +/* + * Use the FUSE_WRITE command to write inline file data from the fuse server. + * Note that there's no file handle attached, so the fuse server must be able + * to reconnect to the inode via the nodeid. + */ +static int fuse_iomap_inline_write(struct inode *inode, loff_t pos, + loff_t count, struct iomap *iomap) +{ + struct fuse_write_in in = { + .offset = pos, + .size = count, + }; + struct fuse_write_out out = { }; + struct fuse_inode *fi = get_fuse_inode(inode); + struct fuse_mount *fm = get_fuse_mount(inode); + FUSE_ARGS(args); + ssize_t ret; + + if (BAD_DATA(!iomap_inline_data_valid(iomap))) + return -EIO; + + trace_fuse_iomap_inline_write(inode, pos, count, iomap); + + args.opcode = FUSE_WRITE; + args.nodeid = fi->nodeid; + args.in_numargs = 2; + args.in_args[0].size = sizeof(in); + args.in_args[0].value = ∈ + args.in_args[1].size = count; + args.in_args[1].value = iomap_inline_data(iomap, pos); + args.out_numargs = 1; + args.out_args[0].size = sizeof(out); + args.out_args[0].value = &out; + + ret = fuse_simple_request(fm, &args); + if (ret < 0) { + fuse_iomap_inline_free(iomap); + return ret; + } + /* short write means something bad happened */ + if (out.size < count) { + fuse_iomap_inline_free(iomap); + return -EIO; + } + + return 0; +} + +/* Set up inline data buffers for iomap_begin */ +static int fuse_iomap_set_inline(struct inode *inode, unsigned opflags, + loff_t pos, loff_t count, + struct iomap *iomap, struct iomap *srcmap) +{ + int err; + + if (opflags & FUSE_IOMAP_OP_REPORT) + return 0; + + if (fuse_is_iomap_file_write(opflags)) { + if (iomap->type == IOMAP_INLINE) { + err = fuse_iomap_inline_alloc(iomap); + if (err) + return err; + } + + if (srcmap->type == IOMAP_INLINE) { + err = fuse_iomap_inline_alloc(srcmap); + if (!err) + err = fuse_iomap_inline_read(inode, pos, count, + srcmap); + if (err) { + fuse_iomap_inline_free(iomap); + return err; + } + } + } else if (iomap->type == IOMAP_INLINE) { + /* inline data read */ + err = fuse_iomap_inline_alloc(iomap); + if (!err) + err = fuse_iomap_inline_read(inode, pos, count, iomap); + if (err) + return err; + } + + trace_fuse_iomap_set_inline_iomap(inode, pos, count, iomap); + trace_fuse_iomap_set_inline_srcmap(inode, pos, count, srcmap); + + return 0; +} + static int fuse_iomap_begin(struct inode *inode, loff_t pos, loff_t count, unsigned opflags, struct iomap *iomap, struct iomap *srcmap) @@ -399,12 +539,20 @@ static int fuse_iomap_begin(struct inode *inode, loff_t pos, loff_t count, fuse_iomap_set_device(iomap, read_dev); } + if (iomap->type == IOMAP_INLINE || srcmap->type == IOMAP_INLINE) { + err = fuse_iomap_set_inline(inode, opflags, pos, count, iomap, + srcmap); + if (err) + goto out_write_dev; + } + /* * XXX: if we ever want to support closing devices, we need a way to * track the fuse_iomap_dev refcount all the way through bio endios. * For now we put the refcount here because you can't remove an iomap * device until unmount time. */ +out_write_dev: fuse_iomap_dev_put(write_dev); out_read_dev: fuse_iomap_dev_put(read_dev); @@ -448,9 +596,26 @@ static int fuse_iomap_end(struct inode *inode, loff_t pos, loff_t count, .map_flags = iomap->flags, }; struct fuse_mount *fm = get_fuse_mount(inode); + struct iomap_iter *iter = container_of(iomap, struct iomap_iter, iomap); + struct iomap *srcmap = &iter->srcmap; FUSE_ARGS(args); int err; + if (srcmap->inline_data) + fuse_iomap_inline_free(srcmap); + + if (iomap->inline_data) { + if (fuse_is_iomap_file_write(opflags) && written > 0) { + err = fuse_iomap_inline_write(inode, pos, written, + iomap); + fuse_iomap_inline_free(iomap); + if (err) + goto out_err; + } else { + fuse_iomap_inline_free(iomap); + } + } + if (!fuse_want_iomap_end(iomap, opflags, count, written)) return 0; @@ -463,6 +628,7 @@ static int fuse_iomap_end(struct inode *inode, loff_t pos, loff_t count, args.in_args[0].value = &inarg; err = fuse_simple_request(fm, &args); +out_err: trace_fuse_iomap_end_error(inode, &inarg, err); return err;