fusefs: fix page fault triggered by async notification when unmounted
A FUSE daemon can send asynchronous notification to the kernel in order to, for example, invalidate an inode's cache. Fix a page fault that can happen if the file system isn't yet mounted, or is already unmounted, when that notification arrives. PR: 290519 MFC after: 1 week Reviewed by: emaste Differential Revision: https://reviews.freebsd.org/D53356
This commit is contained in:
@@ -550,6 +550,13 @@ fuse_device_write(struct cdev *dev, struct uio *uio, int ioflag)
|
||||
} else if (ohead.unique == 0){
|
||||
/* unique == 0 means asynchronous notification */
|
||||
SDT_PROBE1(fusefs, , device, fuse_device_write_notify, &ohead);
|
||||
if (data->mp == NULL) {
|
||||
SDT_PROBE2(fusefs, , device, trace, 1,
|
||||
"asynchronous notification before mount"
|
||||
" or after unmount");
|
||||
return (EXTERROR(ENODEV,
|
||||
"This FUSE session is not mounted"));
|
||||
}
|
||||
mp = data->mp;
|
||||
vfs_ref(mp);
|
||||
err = vfs_busy(mp, 0);
|
||||
|
||||
@@ -827,10 +827,12 @@ void MockFS::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen)
|
||||
int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen,
|
||||
int expected_errno)
|
||||
{
|
||||
std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
|
||||
|
||||
out->expected_errno = expected_errno;
|
||||
out->header.unique = 0; /* 0 means asynchronous notification */
|
||||
out->header.error = FUSE_NOTIFY_INVAL_ENTRY;
|
||||
out->body.inval_entry.parent = parent;
|
||||
|
||||
@@ -390,8 +390,10 @@ class MockFS {
|
||||
* @param parent Parent directory's inode number
|
||||
* @param name name of dirent to invalidate
|
||||
* @param namelen size of name, including the NUL
|
||||
* @param expected_errno The error that write() should return
|
||||
*/
|
||||
int notify_inval_entry(ino_t parent, const char *name, size_t namelen);
|
||||
int notify_inval_entry(ino_t parent, const char *name, size_t namelen,
|
||||
int expected_errno = 0);
|
||||
|
||||
/*
|
||||
* Send an asynchronous notification to invalidate an inode's cached
|
||||
|
||||
@@ -385,6 +385,27 @@ TEST_F(Notify, inval_inode_with_clean_cache)
|
||||
leak(fd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempting to invalidate an entry or inode after unmounting should fail, but
|
||||
* nothing bad should happen.
|
||||
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290519
|
||||
*/
|
||||
TEST_F(Notify, notify_after_unmount)
|
||||
{
|
||||
const static char *name = "foo";
|
||||
struct inval_entry_args iea;
|
||||
|
||||
expect_destroy(0);
|
||||
|
||||
m_mock->unmount();
|
||||
|
||||
iea.mock = m_mock;
|
||||
iea.parent = FUSE_ROOT_ID;
|
||||
iea.name = name;
|
||||
iea.namelen = strlen(name);
|
||||
iea.mock->notify_inval_entry(iea.parent, iea.name, iea.namelen, ENODEV);
|
||||
}
|
||||
|
||||
/* 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)
|
||||
@@ -544,3 +565,38 @@ TEST_F(NotifyWriteback, inval_inode_attrs_only)
|
||||
|
||||
leak(fd);
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempting asynchronous invalidation of an Entry before mounting the file
|
||||
* system should fail, but nothing bad should happen.
|
||||
*
|
||||
* Note that invalidating an inode before mount goes through the same path, and
|
||||
* is not separately tested.
|
||||
*
|
||||
* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=290519
|
||||
*/
|
||||
TEST(PreMount, inval_entry_before_mount)
|
||||
{
|
||||
const static char name[] = "foo";
|
||||
size_t namelen = strlen(name);
|
||||
struct mockfs_buf_out *out;
|
||||
int r;
|
||||
int fuse_fd;
|
||||
|
||||
fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
|
||||
ASSERT_GE(fuse_fd, 0) << strerror(errno);
|
||||
|
||||
out = new mockfs_buf_out;
|
||||
out->header.unique = 0; /* 0 means asynchronous notification */
|
||||
out->header.error = FUSE_NOTIFY_INVAL_ENTRY;
|
||||
out->body.inval_entry.parent = FUSE_ROOT_ID;
|
||||
out->body.inval_entry.namelen = namelen;
|
||||
strlcpy((char*)&out->body.bytes + sizeof(out->body.inval_entry),
|
||||
name, sizeof(out->body.bytes) - sizeof(out->body.inval_entry));
|
||||
out->header.len = sizeof(out->header) + sizeof(out->body.inval_entry) +
|
||||
namelen;
|
||||
r = write(fuse_fd, out, out->header.len);
|
||||
EXPECT_EQ(-1, r);
|
||||
EXPECT_EQ(ENODEV, errno);
|
||||
delete out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user