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";