diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c index e56de69d6ea6..721a169dd3a3 100644 --- a/sys/fs/fuse/fuse_internal.c +++ b/sys/fs/fuse/fuse_internal.c @@ -764,6 +764,8 @@ fuse_internal_init_callback(struct fuse_ticket *tick, struct uio *uio) if (fuse_libabi_geq(data, 7, 5)) { if (fticket_resp(tick)->len == sizeof(struct fuse_init_out)) { data->max_write = fiio->max_write; + if (fiio->flags & FUSE_ASYNC_READ) + data->dataflags |= FSESS_ASYNC_READ; if (fiio->flags & FUSE_POSIX_LOCKS) data->dataflags |= FSESS_POSIX_LOCKS; if (fiio->flags & FUSE_EXPORT_SUPPORT) @@ -805,7 +807,7 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td) * the size of a buffer cache block. */ fiii->max_readahead = maxbcachebuf; - fiii->flags = FUSE_EXPORT_SUPPORT | FUSE_POSIX_LOCKS; + fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT ; fuse_insert_callback(fdi.tick, fuse_internal_init_callback); fuse_insert_message(fdi.tick, false); diff --git a/sys/fs/fuse/fuse_ipc.h b/sys/fs/fuse/fuse_ipc.h index b040b671f59f..89a17d7f7e23 100644 --- a/sys/fs/fuse/fuse_ipc.h +++ b/sys/fs/fuse/fuse_ipc.h @@ -221,6 +221,7 @@ struct fuse_data { #define FSESS_NO_DATACACHE 0x0200 /* disable buffer cache */ #define FSESS_NO_NAMECACHE 0x0400 /* disable name cache */ #define FSESS_NO_MMAP 0x0800 /* disable mmap */ +#define FSESS_ASYNC_READ 0x1000 /* allow multiple reads of some file */ #define FSESS_POSIX_LOCKS 0x2000 /* daemon supports POSIX locks */ #define FSESS_EXPORT_SUPPORT 0x10000 /* daemon supports NFS-style lookups */ #define FSESS_MNTOPTS_MASK ( \ diff --git a/sys/fs/fuse/fuse_node.c b/sys/fs/fuse/fuse_node.c index 0f83098264c2..20cf27b16326 100644 --- a/sys/fs/fuse/fuse_node.c +++ b/sys/fs/fuse/fuse_node.c @@ -179,10 +179,12 @@ fuse_vnode_alloc(struct mount *mp, enum vtype vtyp, struct vnode **vpp) { + struct fuse_data *data; struct fuse_vnode_data *fvdat; struct vnode *vp2; int err = 0; + data = fuse_get_mpdata(mp); if (vtyp == VNON) { return EINVAL; } @@ -235,6 +237,10 @@ fuse_vnode_alloc(struct mount *mp, *vpp = NULL; return (err); } + /* Disallow async reads for fifos because UFS does. I don't know why */ + if (data->dataflags & FSESS_ASYNC_READ && vtyp != VFIFO) + VN_LOCK_ASHARE(*vpp); + err = vfs_hash_insert(*vpp, fuse_vnode_hash(nodeid), LK_EXCLUSIVE, td, &vp2, fuse_vnode_cmp, &nodeid); if (err) { diff --git a/tests/sys/fs/fusefs/read.cc b/tests/sys/fs/fusefs/read.cc index 7200f50ab2f7..8cf40144fb47 100644 --- a/tests/sys/fs/fusefs/read.cc +++ b/tests/sys/fs/fusefs/read.cc @@ -37,6 +37,7 @@ extern "C" { #include #include +#include #include } @@ -159,21 +160,22 @@ TEST_F(AioRead, async_read_disabled) ssize_t bufsize = 50; char buf0[bufsize], buf1[bufsize]; off_t off0 = 0; - off_t off1 = 4096; + off_t off1 = 65536; struct aiocb iocb0, iocb1; + volatile sig_atomic_t read_count = 0; - expect_lookup(RELPATH, ino, bufsize); + expect_lookup(RELPATH, ino, 131072); expect_open(ino, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READ && in->header.nodeid == ino && in->body.read.fh == FH && - in->body.read.offset == (uint64_t)off0 && - in->body.read.size == bufsize); + in->body.read.offset == (uint64_t)off0); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) { + read_count++; /* Filesystem is slow to respond */ })); EXPECT_CALL(*m_mock, process( @@ -181,11 +183,13 @@ TEST_F(AioRead, async_read_disabled) return (in->header.opcode == FUSE_READ && in->header.nodeid == ino && in->body.read.fh == FH && - in->body.read.offset == (uint64_t)off1 && - in->body.read.size == bufsize); + in->body.read.offset == (uint64_t)off1); }, Eq(true)), _) - ).Times(0); + ).WillRepeatedly(Invoke([&](auto in __unused, auto &out __unused) { + read_count++; + /* Filesystem is slow to respond */ + })); fd = open(FULLPATH, O_RDONLY); ASSERT_LE(0, fd) << strerror(errno); @@ -214,8 +218,12 @@ TEST_F(AioRead, async_read_disabled) * the second read, even though the first has not yet returned */ nap(); + EXPECT_EQ(read_count, 1); - /* Deliberately leak iocbs */ + m_mock->kill_daemon(); + /* Wait for AIO activity to complete, but ignore errors */ + (void)aio_waitcomplete(NULL, NULL); + /* Deliberately leak fd. close(2) will be tested in release.cc */ } @@ -223,11 +231,7 @@ TEST_F(AioRead, async_read_disabled) * With the FUSE_ASYNC_READ mount option, fuse(4) may issue multiple * simultaneous read requests on the same file handle. */ -/* - * Disabled because we don't yet implement FUSE_ASYNC_READ. No bugzilla - * entry, because that's a feature request, not a bug. - */ -TEST_F(AsyncRead, DISABLED_async_read) +TEST_F(AsyncRead, async_read) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; @@ -236,21 +240,24 @@ TEST_F(AsyncRead, DISABLED_async_read) ssize_t bufsize = 50; char buf0[bufsize], buf1[bufsize]; off_t off0 = 0; - off_t off1 = 4096; + off_t off1 = 65536; struct aiocb iocb0, iocb1; + sem_t sem; - expect_lookup(RELPATH, ino, bufsize); + ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); + + expect_lookup(RELPATH, ino, 131072); expect_open(ino, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in->header.opcode == FUSE_READ && in->header.nodeid == ino && in->body.read.fh == FH && - in->body.read.offset == (uint64_t)off0 && - in->body.read.size == bufsize); + in->body.read.offset == (uint64_t)off0); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { + sem_post(&sem); /* Filesystem is slow to respond */ })); EXPECT_CALL(*m_mock, process( @@ -258,11 +265,11 @@ TEST_F(AsyncRead, DISABLED_async_read) return (in->header.opcode == FUSE_READ && in->header.nodeid == ino && in->body.read.fh == FH && - in->body.read.offset == (uint64_t)off1 && - in->body.read.size == bufsize); + in->body.read.offset == (uint64_t)off1); }, Eq(true)), _) - ).WillOnce(Invoke([](auto in __unused, auto &out __unused) { + ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { + sem_post(&sem); /* Filesystem is slow to respond */ })); @@ -287,13 +294,14 @@ TEST_F(AsyncRead, DISABLED_async_read) iocb1.aio_sigevent.sigev_notify = SIGEV_NONE; ASSERT_EQ(0, aio_read(&iocb1)) << strerror(errno); - /* - * Sleep for awhile to make sure the kernel has had a chance to issue - * both reads. - */ - nap(); + /* Wait until both reads have reached the daemon */ + ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno); + ASSERT_EQ(0, sem_wait(&sem)) << strerror(errno); + + m_mock->kill_daemon(); + /* Wait for AIO activity to complete, but ignore errors */ + (void)aio_waitcomplete(NULL, NULL); - /* Deliberately leak iocbs */ /* Deliberately leak fd. close(2) will be tested in release.cc */ }