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
This commit is contained in:
Alan Somers 2019-06-20 23:32:25 +00:00
parent bb23d43901
commit 7cbb8e8a06
5 changed files with 162 additions and 1 deletions

View File

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

View File

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

View File

@ -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<mockfs_buf_out> 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);

View File

@ -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
*

View File

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