From 7cbb8e8a06904f6bf4a7547c2a5d40b53f0041ff Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Thu, 20 Jun 2019 23:32:25 +0000 Subject: [PATCH] fusefs: raise protocol level to 7.15 This protocol level adds two new features: the ability for the server to store or retrieve data into/from the client's cache. But the messages aren't defined soundly since they identify the file only by its inode, without the generation number. So it's possible for them to modify the wrong file's cache. Also, I don't know of any file systems in ports that use these messages. So I'm not implementing them. I did add a (disabled) test for the store message, however. Sponsored by: The FreeBSD Foundation --- sys/fs/fuse/fuse_device.c | 12 ++++++ sys/fs/fuse/fuse_kernel.h | 34 ++++++++++++++- tests/sys/fs/fusefs/mockfs.cc | 23 +++++++++++ tests/sys/fs/fusefs/mockfs.hh | 16 +++++++ tests/sys/fs/fusefs/notify.cc | 78 +++++++++++++++++++++++++++++++++++ 5 files changed, 162 insertions(+), 1 deletion(-) diff --git a/sys/fs/fuse/fuse_device.c b/sys/fs/fuse/fuse_device.c index 1ab5fa22e694..6a66df80e89b 100644 --- a/sys/fs/fuse/fuse_device.c +++ b/sys/fs/fuse/fuse_device.c @@ -518,7 +518,19 @@ fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag) case FUSE_NOTIFY_INVAL_INODE: err = fuse_internal_invalidate_inode(mp, uio); break; + case FUSE_NOTIFY_RETRIEVE: + case FUSE_NOTIFY_STORE: + /* + * Unimplemented. I don't know of any file systems + * that use them, and the protocol isn't sound anyway, + * since the notification messages don't include the + * inode's generation number. Without that, it's + * possible to manipulate the cache of the wrong vnode. + * Finally, it's not defined what this message should + * do for a file with dirty cache. + */ case FUSE_NOTIFY_POLL: + /* Unimplemented. See comments in fuse_vnops */ default: /* Not implemented */ err = ENOSYS; diff --git a/sys/fs/fuse/fuse_kernel.h b/sys/fs/fuse/fuse_kernel.h index 5a283df92828..903fb17fc719 100644 --- a/sys/fs/fuse/fuse_kernel.h +++ b/sys/fs/fuse/fuse_kernel.h @@ -64,6 +64,10 @@ * * 7.14 * - add splice support to fuse device + * + * 7.15 + * - add store notify + * - add retrieve notify */ #ifndef _FUSE_FUSE_KERNEL_H @@ -84,7 +88,7 @@ #define FUSE_KERNEL_VERSION 7 /** Minor version number of this interface */ -#define FUSE_KERNEL_MINOR_VERSION 14 +#define FUSE_KERNEL_MINOR_VERSION 15 /** The node ID of the root inode */ #define FUSE_ROOT_ID 1 @@ -269,6 +273,7 @@ enum fuse_opcode { FUSE_DESTROY = 38, FUSE_IOCTL = 39, FUSE_POLL = 40, + FUSE_NOTIFY_REPLY = 41, #ifdef linux /* CUSE specific operations */ @@ -280,6 +285,8 @@ enum fuse_notify_code { FUSE_NOTIFY_POLL = 1, FUSE_NOTIFY_INVAL_INODE = 2, FUSE_NOTIFY_INVAL_ENTRY = 3, + FUSE_NOTIFY_STORE = 4, + FUSE_NOTIFY_RETRIEVE = 5, FUSE_NOTIFY_CODE_MAX, }; @@ -558,6 +565,31 @@ struct fuse_notify_poll_wakeup_out { __u64 kh; }; +struct fuse_notify_store_out { + __u64 nodeid; + __u64 offset; + __u32 size; + __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; +}; + struct fuse_in_header { __u32 len; __u32 opcode; diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc index 7a9e8b66a5c4..d0dc5c1ef095 100644 --- a/tests/sys/fs/fusefs/mockfs.cc +++ b/tests/sys/fs/fusefs/mockfs.cc @@ -333,6 +333,13 @@ void MockFS::debug_response(const mockfs_buf_out &out) { out.body.inval_inode.off, out.body.inval_inode.len); break; + case FUSE_NOTIFY_STORE: + printf("<- STORE ino=%" PRIu64 " off=%" PRIu64 + " size=%" PRIu32 "\n", + out.body.store.nodeid, + out.body.store.offset, + out.body.store.size); + break; default: break; } @@ -544,6 +551,22 @@ int MockFS::notify_inval_inode(ino_t ino, off_t off, ssize_t len) return 0; } +int MockFS::notify_store(ino_t ino, off_t off, void* data, ssize_t size) +{ + std::unique_ptr out(new mockfs_buf_out); + + out->header.unique = 0; /* 0 means asynchronous notification */ + out->header.error = FUSE_NOTIFY_STORE; + out->body.store.nodeid = ino; + out->body.store.offset = off; + out->body.store.size = size; + bcopy(data, (char*)&out->body.bytes + sizeof(out->body.store), size); + out->header.len = sizeof(out->header) + sizeof(out->body.store) + size; + debug_response(*out); + write_response(*out); + return 0; +} + bool MockFS::pid_ok(pid_t pid) { if (pid == m_pid) { return (true); diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh index 162df6281932..94029748107e 100644 --- a/tests/sys/fs/fusefs/mockfs.hh +++ b/tests/sys/fs/fusefs/mockfs.hh @@ -181,6 +181,8 @@ union fuse_payloads_out { /* The inval_entry structure should be followed by the entry's name */ fuse_notify_inval_entry_out inval_entry; fuse_notify_inval_inode_out inval_inode; + /* The store structure should be followed by the data to store */ + fuse_notify_store_out store; fuse_listxattr_out listxattr; fuse_open_out open; fuse_statfs_out statfs; @@ -346,6 +348,20 @@ class MockFS { */ int notify_inval_inode(ino_t ino, off_t off, ssize_t len); + /* + * Send an asynchronous notification to store data directly into an + * inode's cache. Similar to libfuse's fuse_lowlevel_notify_store. + * + * This method will block until the client has responded, so it should + * generally be run in a separate thread from request processing. + * + * @param ino File's inode number + * @param off Offset at which to store data + * @param data Pointer to the data to cache + * @param len Size of data + */ + int notify_store(ino_t ino, off_t off, void* data, ssize_t size); + /* * Request handler * diff --git a/tests/sys/fs/fusefs/notify.cc b/tests/sys/fs/fusefs/notify.cc index 4198c182178c..8131e1f69636 100644 --- a/tests/sys/fs/fusefs/notify.cc +++ b/tests/sys/fs/fusefs/notify.cc @@ -133,6 +133,14 @@ struct inval_inode_args { ssize_t len; }; +struct store_args { + MockFS *mock; + ino_t nodeid; + off_t offset; + ssize_t size; + void* data; +}; + static void* inval_inode(void* arg) { const struct inval_inode_args *iia = (struct inval_inode_args*)arg; ssize_t r; @@ -144,6 +152,17 @@ static void* inval_inode(void* arg) { return (void*)(intptr_t)errno; } +static void* store(void* arg) { + const struct store_args *sa = (struct store_args*)arg; + ssize_t r; + + r = sa->mock->notify_store(sa->nodeid, sa->offset, sa->data, sa->size); + if (r >= 0) + return 0; + else + return (void*)(intptr_t)errno; +} + /* Invalidate a nonexistent entry */ TEST_F(Notify, inval_entry_nonexistent) { @@ -376,6 +395,65 @@ TEST_F(Notify, inval_inode_with_clean_cache) /* Deliberately leak fd. close(2) will be tested in release.cc */ } +/* FUSE_NOTIFY_STORE with a file that's not in the entry cache */ +/* disabled because FUSE_NOTIFY_STORE is not yet implemented */ +TEST_F(Notify, DISABLED_store_nonexistent) +{ + struct store_args sa; + ino_t ino = 42; + void *thr0_value; + pthread_t th0; + + sa.mock = m_mock; + sa.nodeid = ino; + sa.offset = 0; + sa.size = 0; + ASSERT_EQ(0, pthread_create(&th0, NULL, store, &sa)) << strerror(errno); + pthread_join(th0, &thr0_value); + /* It's not an error for a file to be unknown to the kernel */ + EXPECT_EQ(0, (intptr_t)thr0_value); +} + +/* Store data into for a file that does not yet have anything cached */ +/* disabled because FUSE_NOTIFY_STORE is not yet implemented */ +TEST_F(Notify, DISABLED_store_with_blank_cache) +{ + const static char FULLPATH[] = "mountpoint/foo"; + const static char RELPATH[] = "foo"; + const char CONTENTS1[] = "ijklmnopqrstuvwxyz"; + struct store_args sa; + ino_t ino = 42; + void *thr0_value; + Sequence seq; + pthread_t th0; + ssize_t size1 = sizeof(CONTENTS1); + char buf[80]; + int fd; + + expect_lookup(FUSE_ROOT_ID, RELPATH, ino, size1, seq); + expect_open(ino, 0, 1); + + /* Fill the data cache */ + fd = open(FULLPATH, O_RDWR); + ASSERT_LE(0, fd) << strerror(errno); + + /* Evict the data cache */ + sa.mock = m_mock; + sa.nodeid = ino; + sa.offset = 0; + sa.size = size1; + sa.data = (void*)CONTENTS1; + ASSERT_EQ(0, pthread_create(&th0, NULL, store, &sa)) << strerror(errno); + pthread_join(th0, &thr0_value); + EXPECT_EQ(0, (intptr_t)thr0_value); + + /* This read should be serviced by cache */ + ASSERT_EQ(size1, read(fd, buf, size1)) << strerror(errno); + EXPECT_EQ(0, memcmp(buf, CONTENTS1, size1)); + + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} + TEST_F(NotifyWriteback, inval_inode_with_dirty_cache) { const static char FULLPATH[] = "mountpoint/foo";