Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F153659894
D27804.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
24 KB
Referenced Files
None
Subscribers
None
D27804.id.diff
View Options
diff --git a/sys/fs/fuse/fuse_internal.c b/sys/fs/fuse/fuse_internal.c
--- a/sys/fs/fuse/fuse_internal.c
+++ b/sys/fs/fuse/fuse_internal.c
@@ -219,7 +219,7 @@
SDT_PROBE0(fusefs, , internal, access_vadmin);
}
- if (!fsess_isimpl(mp, FUSE_ACCESS))
+ if (fsess_not_impl(mp, FUSE_ACCESS))
return 0;
if ((mode & (VWRITE | VAPPEND)) != 0)
@@ -337,14 +337,14 @@
int op = FUSE_FSYNC;
int err = 0;
- if (!fsess_isimpl(vnode_mount(vp),
+ if (fsess_not_impl(vnode_mount(vp),
(vnode_vtype(vp) == VDIR ? FUSE_FSYNCDIR : FUSE_FSYNC))) {
return 0;
}
if (vnode_isdir(vp))
op = FUSE_FSYNCDIR;
- if (!fsess_isimpl(mp, op))
+ if (fsess_not_impl(mp, op))
return 0;
fdisp_init(&fdi, sizeof(*ffsi));
@@ -1051,6 +1051,9 @@
else
data->cache_mode = FUSE_CACHE_WT;
+ if (!fuse_libabi_geq(data, 7, 24))
+ fsess_set_notimpl(data->mp, FUSE_LSEEK);
+
out:
if (err) {
fdata_set_dead(data);
diff --git a/sys/fs/fuse/fuse_ipc.h b/sys/fs/fuse/fuse_ipc.h
--- a/sys/fs/fuse/fuse_ipc.h
+++ b/sys/fs/fuse/fuse_ipc.h
@@ -212,7 +212,15 @@
int daemon_timeout;
int linux_errnos;
unsigned time_gran;
+ /* A bitmask of FUSE RPCs that are not implemented by the server */
uint64_t notimpl;
+ /*
+ * A bitmask of FUSE RPCs that are implemented by the server.
+ * If an operation is not present in either notimpl or isimpl, then it
+ * may be implemented by the server, but the kernel doesn't know for
+ * sure.
+ */
+ uint64_t isimpl;
uint64_t mnt_flag;
enum fuse_data_cache_mode cache_mode;
};
@@ -240,13 +248,40 @@
}
static inline bool
-fsess_isimpl(struct mount *mp, int opcode)
+fsess_is_impl(struct mount *mp, int opcode)
+{
+ struct fuse_data *data = fuse_get_mpdata(mp);
+
+ return ((data->isimpl & (1ULL << opcode)) != 0);
+
+}
+
+static inline bool
+fsess_maybe_impl(struct mount *mp, int opcode)
{
struct fuse_data *data = fuse_get_mpdata(mp);
return ((data->notimpl & (1ULL << opcode)) == 0);
}
+
+static inline bool
+fsess_not_impl(struct mount *mp, int opcode)
+{
+ struct fuse_data *data = fuse_get_mpdata(mp);
+
+ return ((data->notimpl & (1ULL << opcode)) != 0);
+
+}
+
+static inline void
+fsess_set_impl(struct mount *mp, int opcode)
+{
+ struct fuse_data *data = fuse_get_mpdata(mp);
+
+ data->isimpl |= (1ULL << opcode);
+}
+
static inline void
fsess_set_notimpl(struct mount *mp, int opcode)
{
diff --git a/sys/fs/fuse/fuse_ipc.c b/sys/fs/fuse/fuse_ipc.c
--- a/sys/fs/fuse/fuse_ipc.c
+++ b/sys/fs/fuse/fuse_ipc.c
@@ -230,7 +230,7 @@
* If the fuse daemon doesn't support interrupts, then there's
* nothing more that we can do
*/
- if (!fsess_isimpl(data->mp, FUSE_INTERRUPT))
+ if (fsess_not_impl(data->mp, FUSE_INTERRUPT))
return;
/*
@@ -423,7 +423,7 @@
struct fuse_data *data = ftick->tk_data;
bool interrupted = false;
- if (fsess_isimpl(ftick->tk_data->mp, FUSE_INTERRUPT) &&
+ if (fsess_maybe_impl(ftick->tk_data->mp, FUSE_INTERRUPT) &&
data->dataflags & FSESS_INTR) {
SIGEMPTYSET(blockedset);
} else {
@@ -851,6 +851,10 @@
err = (blen == 0) ? 0 : EINVAL;
break;
+ case FUSE_LSEEK:
+ err = (blen == sizeof(struct fuse_lseek_out)) ? 0 : EINVAL;
+ break;
+
default:
panic("FUSE: opcodes out of sync (%d)\n", opcode);
}
diff --git a/sys/fs/fuse/fuse_kernel.h b/sys/fs/fuse/fuse_kernel.h
--- a/sys/fs/fuse/fuse_kernel.h
+++ b/sys/fs/fuse/fuse_kernel.h
@@ -102,6 +102,9 @@
* - add ctime and ctimensec to fuse_setattr_in
* - add FUSE_RENAME2 request
* - add FUSE_NO_OPEN_SUPPORT flag
+ *
+ * 7.24
+ * - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
*/
#ifndef _FUSE_FUSE_KERNEL_H
@@ -117,7 +120,7 @@
#define FUSE_KERNEL_VERSION 7
/** Minor version number of this interface */
-#define FUSE_KERNEL_MINOR_VERSION 23
+#define FUSE_KERNEL_MINOR_VERSION 24
/** The node ID of the root inode */
#define FUSE_ROOT_ID 1
@@ -340,6 +343,7 @@
FUSE_FALLOCATE = 43,
FUSE_READDIRPLUS = 44,
FUSE_RENAME2 = 45,
+ FUSE_LSEEK = 46,
#ifdef linux
/* CUSE specific operations */
@@ -751,4 +755,15 @@
uint64_t dummy4;
};
+struct fuse_lseek_in {
+ uint64_t fh;
+ uint64_t offset;
+ uint32_t whence;
+ uint32_t padding;
+};
+
+struct fuse_lseek_out {
+ uint64_t offset;
+};
+
#endif /* _FUSE_FUSE_KERNEL_H */
diff --git a/sys/fs/fuse/fuse_vfsops.c b/sys/fs/fuse/fuse_vfsops.c
--- a/sys/fs/fuse/fuse_vfsops.c
+++ b/sys/fs/fuse/fuse_vfsops.c
@@ -501,7 +501,7 @@
if (fdata_get_dead(data)) {
goto alreadydead;
}
- if (fsess_isimpl(mp, FUSE_DESTROY)) {
+ if (fsess_maybe_impl(mp, FUSE_DESTROY)) {
fdisp_init(&fdi, 0);
fdisp_make(&fdi, FUSE_DESTROY, mp, 0, td, NULL);
diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -69,6 +69,7 @@
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/conf.h>
+#include <sys/filio.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/queue.h>
@@ -136,6 +137,7 @@
static vop_getattr_t fuse_vnop_getattr;
static vop_getextattr_t fuse_vnop_getextattr;
static vop_inactive_t fuse_vnop_inactive;
+static vop_ioctl_t fuse_vnop_ioctl;
static vop_link_t fuse_vnop_link;
static vop_listextattr_t fuse_vnop_listextattr;
static vop_lookup_t fuse_vnop_lookup;
@@ -190,11 +192,7 @@
.vop_getattr = fuse_vnop_getattr,
.vop_getextattr = fuse_vnop_getextattr,
.vop_inactive = fuse_vnop_inactive,
- /*
- * TODO: implement vop_ioctl after upgrading to protocol 7.16.
- * FUSE_IOCTL was added in 7.11, but 32-bit compat is broken until
- * 7.16.
- */
+ .vop_ioctl = fuse_vnop_ioctl,
.vop_link = fuse_vnop_link,
.vop_listextattr = fuse_vnop_listextattr,
.vop_lookup = fuse_vnop_lookup,
@@ -284,7 +282,7 @@
struct mount *mp = vnode_mount(vp);
int err;
- if (!fsess_isimpl(vnode_mount(vp), FUSE_FLUSH))
+ if (fsess_not_impl(vnode_mount(vp), FUSE_FLUSH))
return 0;
err = fuse_filehandle_getrw(vp, fflag, &fufh, cred, pid);
@@ -318,6 +316,42 @@
return (fifo_specops.vop_close(ap));
}
+/* Send FUSE_LSEEK for this node */
+static int
+fuse_vnop_do_lseek(struct vnode *vp, struct thread *td, struct ucred *cred,
+ pid_t pid, off_t *offp, int whence)
+{
+ struct fuse_dispatcher fdi;
+ struct fuse_filehandle *fufh;
+ struct fuse_lseek_in *flsi;
+ struct fuse_lseek_out *flso;
+ struct mount *mp = vnode_mount(vp);
+ int err;
+
+ MPASS(VOP_ISLOCKED(vp));
+
+ err = fuse_filehandle_getrw(vp, FREAD, &fufh, cred, pid);
+ if (err)
+ return (err);
+ fdisp_init(&fdi, sizeof(*flsi));
+ fdisp_make_vp(&fdi, FUSE_LSEEK, vp, td, cred);
+ flsi = fdi.indata;
+ flsi->fh = fufh->fh_id;
+ flsi->offset = *offp;
+ flsi->whence = whence;
+ err = fdisp_wait_answ(&fdi);
+ if (err == ENOSYS) {
+ fsess_set_notimpl(mp, FUSE_LSEEK);
+ } else if (err == 0) {
+ fsess_set_impl(mp, FUSE_LSEEK);
+ flso = fdi.answ;
+ *offp = flso->offset;
+ }
+ fdisp_destroy(&fdi);
+
+ return (err);
+}
+
/*
struct vnop_access_args {
struct vnode *a_vp;
@@ -516,7 +550,7 @@
*runp = 0;
}
- if (fsess_isimpl(mp, FUSE_BMAP)) {
+ if (fsess_maybe_impl(mp, FUSE_BMAP)) {
fdisp_init(&fdi, sizeof(*fbi));
fdisp_make_vp(&fdi, FUSE_BMAP, vp, td, td->td_ucred);
fbi = fdi.indata;
@@ -652,7 +686,7 @@
if (vap->va_type != VREG)
return (EINVAL);
- if (!fsess_isimpl(mp, FUSE_CREATE) || vap->va_type == VSOCK) {
+ if (fsess_not_impl(mp, FUSE_CREATE) || vap->va_type == VSOCK) {
/* Fallback to FUSE_MKNOD/FUSE_OPEN */
fdisp_make_mknod_for_fallback(fdip, cnp, dvp, parentnid, td,
cred, mode, &op);
@@ -883,6 +917,56 @@
return 0;
}
+/*
+ struct vnop_ioctl_args {
+ struct vnode *a_vp;
+ u_long a_command;
+ caddr_t a_data;
+ int a_fflag;
+ struct ucred *a_cred;
+ struct thread *a_td;
+ };
+*/
+static int
+fuse_vnop_ioctl(struct vop_ioctl_args *ap)
+{
+ struct vnode *vp = ap->a_vp;
+ struct mount *mp = vnode_mount(vp);
+ struct ucred *cred = ap->a_cred;
+ off_t *offp;
+ pid_t pid = ap->a_td->td_proc->p_pid;
+ int err;
+
+ switch (ap->a_command) {
+ case FIOSEEKDATA:
+ case FIOSEEKHOLE:
+ /* Call FUSE_LSEEK, if we can, or fall back to vop_stdioctl */
+ if (fsess_maybe_impl(mp, FUSE_LSEEK)) {
+ int whence;
+
+ offp = ap->a_data;
+ if (ap->a_command == FIOSEEKDATA)
+ whence = SEEK_DATA;
+ else
+ whence = SEEK_HOLE;
+
+ vn_lock(vp, LK_SHARED | LK_RETRY);
+ err = fuse_vnop_do_lseek(vp, ap->a_td, cred, pid, offp,
+ whence);
+ VOP_UNLOCK(vp);
+ }
+ if (fsess_not_impl(mp, FUSE_LSEEK))
+ err = vop_stdioctl(ap);
+ break;
+ default:
+ /* TODO: implement FUSE_IOCTL */
+ err = ENOTTY;
+ break;
+ }
+ return (err);
+}
+
+
/*
struct vnop_link_args {
struct vnode *a_tdvp;
@@ -1337,6 +1421,8 @@
static int
fuse_vnop_pathconf(struct vop_pathconf_args *ap)
{
+ struct vnode *vp = ap->a_vp;
+ struct mount *mp;
switch (ap->a_name) {
case _PC_FILESIZEBITS:
@@ -1354,6 +1440,35 @@
case _PC_NO_TRUNC:
*ap->a_retval = 1;
return (0);
+ case _PC_MIN_HOLE_SIZE:
+ /*
+ * The FUSE protocol provides no mechanism for a server to
+ * report _PC_MIN_HOLE_SIZE. It's a protocol bug. Instead,
+ * return EINVAL if the server does not support FUSE_LSEEK, or
+ * 1 if it does.
+ */
+ mp = vnode_mount(vp);
+ if (!fsess_is_impl(mp, FUSE_LSEEK) &&
+ !fsess_not_impl(mp, FUSE_LSEEK)) {
+ off_t offset = 0;
+
+ /* Issue a FUSE_LSEEK to find out if it's implemented */
+ fuse_vnop_do_lseek(vp, curthread, curthread->td_ucred,
+ curthread->td_proc->p_pid, &offset, SEEK_DATA);
+ }
+
+ if (fsess_is_impl(mp, FUSE_LSEEK)) {
+ *ap->a_retval = 1;
+ return (0);
+ } else {
+ /*
+ * Probably FUSE_LSEEK is not implemented. It might
+ * be, if the FUSE_LSEEK above returned an error like
+ * EACCES, but in that case we can't tell, so it's
+ * safest to report EINVAL anyway.
+ */
+ return (EINVAL);
+ }
default:
return (vop_stdpathconf(ap));
}
@@ -2035,7 +2150,7 @@
if (fuse_isdeadfs(vp))
return (ENXIO);
- if (!fsess_isimpl(mp, FUSE_GETXATTR))
+ if (fsess_not_impl(mp, FUSE_GETXATTR))
return EOPNOTSUPP;
err = fuse_extattr_check_cred(vp, ap->a_attrnamespace, cred, td, VREAD);
@@ -2121,7 +2236,7 @@
if (fuse_isdeadfs(vp))
return (ENXIO);
- if (!fsess_isimpl(mp, FUSE_SETXATTR))
+ if (fsess_not_impl(mp, FUSE_SETXATTR))
return EOPNOTSUPP;
if (vfs_isrdonly(mp))
@@ -2133,7 +2248,7 @@
* If we got here as fallback from VOP_DELETEEXTATTR, then
* return EOPNOTSUPP.
*/
- if (!fsess_isimpl(mp, FUSE_REMOVEXATTR))
+ if (fsess_not_impl(mp, FUSE_REMOVEXATTR))
return (EOPNOTSUPP);
else
return (EINVAL);
@@ -2286,7 +2401,7 @@
if (fuse_isdeadfs(vp))
return (ENXIO);
- if (!fsess_isimpl(mp, FUSE_LISTXATTR))
+ if (fsess_not_impl(mp, FUSE_LISTXATTR))
return EOPNOTSUPP;
err = fuse_extattr_check_cred(vp, ap->a_attrnamespace, cred, td, VREAD);
@@ -2409,7 +2524,7 @@
if (fuse_isdeadfs(vp))
return (ENXIO);
- if (!fsess_isimpl(mp, FUSE_REMOVEXATTR))
+ if (fsess_not_impl(mp, FUSE_REMOVEXATTR))
return EOPNOTSUPP;
if (vfs_isrdonly(mp))
diff --git a/tests/sys/fs/fusefs/Makefile b/tests/sys/fs/fusefs/Makefile
--- a/tests/sys/fs/fusefs/Makefile
+++ b/tests/sys/fs/fusefs/Makefile
@@ -29,6 +29,7 @@
GTESTS+= link
GTESTS+= locks
GTESTS+= lookup
+GTESTS+= lseek
GTESTS+= mkdir
GTESTS+= mknod
GTESTS+= mount
diff --git a/tests/sys/fs/fusefs/lseek.cc b/tests/sys/fs/fusefs/lseek.cc
new file mode 100644
--- /dev/null
+++ b/tests/sys/fs/fusefs/lseek.cc
@@ -0,0 +1,360 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2020 Alan Somers
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+extern "C" {
+#include <sys/param.h>
+
+#include <fcntl.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+class Lseek: public FuseTest {};
+class LseekPathconf: public Lseek {};
+class LseekPathconf_7_23: public LseekPathconf {
+public:
+virtual void SetUp() {
+ m_kernel_minor_version = 23;
+ FuseTest::SetUp();
+}
+};
+class LseekSeekHole: public Lseek {};
+class LseekSeekData: public Lseek {};
+
+/*
+ * If a previous lseek operation has already returned enosys, then pathconf can
+ * return EINVAL immediately.
+ */
+TEST_F(LseekPathconf, already_enosys)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ off_t offset_in = 1 << 28;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
+
+ fd = open(FULLPATH, O_RDONLY);
+
+ EXPECT_EQ(offset_in, lseek(fd, offset_in, SEEK_DATA));
+ EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * If a previous lseek operation has already returned successfully, then
+ * pathconf can return 1 immediately. 1 means "holes are reported, but size is
+ * not specified".
+ */
+TEST_F(LseekPathconf, already_seeked)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ off_t offset = 1 << 28;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto i, auto& out) {
+ SET_OUT_HEADER_LEN(out, lseek);
+ out.body.lseek.offset = i.body.lseek.offset;
+ })));
+ fd = open(FULLPATH, O_RDONLY);
+ EXPECT_EQ(offset, lseek(fd, offset, SEEK_DATA));
+
+ EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+}
+
+/*
+ * If no FUSE_LSEEK operation has been attempted since mount, try once as soon
+ * as a pathconf request comes in.
+ */
+TEST_F(LseekPathconf, enosys_now)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
+
+ fd = open(FULLPATH, O_RDONLY);
+
+ EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * If no FUSE_LSEEK operation has been attempted since mount, try one as soon
+ * as a pathconf request comes in. This is the typical pattern of bsdtar. It
+ * will only try SEEK_HOLE/SEEK_DATA if fpathconf says they're supported.
+ */
+TEST_F(LseekPathconf, seek_now)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ off_t offset_initial = 1 << 27;
+ off_t offset_out = 1 << 29;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, lseek);
+ out.body.lseek.offset = offset_out;
+ })));
+
+ fd = open(FULLPATH, O_RDONLY);
+ EXPECT_EQ(offset_initial, lseek(fd, offset_initial, SEEK_SET));
+ EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ /* And check that the file pointer hasn't changed */
+ EXPECT_EQ(offset_initial, lseek(fd, 0, SEEK_CUR));
+}
+
+/*
+ * For servers using older protocol versions, no FUSE_LSEEK should be attempted
+ */
+TEST_F(LseekPathconf_7_23, already_enosys)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK);
+ }, Eq(true)),
+ _)
+ ).Times(0);
+
+ fd = open(FULLPATH, O_RDONLY);
+ EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(LseekSeekData, ok)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ off_t offset_in = 1 << 28;
+ off_t offset_out = 1 << 29;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK &&
+ in.header.nodeid == ino &&
+ in.body.lseek.fh == FH &&
+ (off_t)in.body.lseek.offset == offset_in &&
+ in.body.lseek.whence == SEEK_DATA);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, lseek);
+ out.body.lseek.offset = offset_out;
+ })));
+ fd = open(FULLPATH, O_RDONLY);
+ EXPECT_EQ(offset_out, lseek(fd, offset_in, SEEK_DATA));
+ EXPECT_EQ(offset_out, lseek(fd, 0, SEEK_CUR));
+}
+
+/*
+ * If the server returns ENOSYS, fusefs should fall back to the default
+ * behavior, and never query the server again.
+ */
+TEST_F(LseekSeekData, enosys)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ off_t offset_in = 1 << 28;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK &&
+ in.header.nodeid == ino &&
+ in.body.lseek.fh == FH &&
+ (off_t)in.body.lseek.offset == offset_in &&
+ in.body.lseek.whence == SEEK_DATA);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
+ fd = open(FULLPATH, O_RDONLY);
+
+ /*
+ * Default behavior: ENXIO if offset is < 0 or >= fsize, offset
+ * otherwise.
+ */
+ EXPECT_EQ(offset_in, lseek(fd, offset_in, SEEK_DATA));
+ EXPECT_EQ(-1, lseek(fd, -1, SEEK_HOLE));
+ EXPECT_EQ(ENXIO, errno);
+ EXPECT_EQ(-1, lseek(fd, fsize, SEEK_HOLE));
+ EXPECT_EQ(ENXIO, errno);
+}
+
+TEST_F(LseekSeekHole, ok)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ off_t offset_in = 1 << 28;
+ off_t offset_out = 1 << 29;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK &&
+ in.header.nodeid == ino &&
+ in.body.lseek.fh == FH &&
+ (off_t)in.body.lseek.offset == offset_in &&
+ in.body.lseek.whence == SEEK_HOLE);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, lseek);
+ out.body.lseek.offset = offset_out;
+ })));
+ fd = open(FULLPATH, O_RDONLY);
+ EXPECT_EQ(offset_out, lseek(fd, offset_in, SEEK_HOLE));
+ EXPECT_EQ(offset_out, lseek(fd, 0, SEEK_CUR));
+}
+
+/*
+ * If the server returns ENOSYS, fusefs should fall back to the default
+ * behavior, and never query the server again.
+ */
+TEST_F(LseekSeekHole, enosys)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ off_t offset_in = 1 << 28;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK &&
+ in.header.nodeid == ino &&
+ in.body.lseek.fh == FH &&
+ (off_t)in.body.lseek.offset == offset_in &&
+ in.body.lseek.whence == SEEK_HOLE);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
+ fd = open(FULLPATH, O_RDONLY);
+
+ /*
+ * Default behavior: ENXIO if offset is < 0 or >= fsize, fsize
+ * otherwise.
+ */
+ EXPECT_EQ(fsize, lseek(fd, offset_in, SEEK_HOLE));
+ EXPECT_EQ(-1, lseek(fd, -1, SEEK_HOLE));
+ EXPECT_EQ(ENXIO, errno);
+ EXPECT_EQ(-1, lseek(fd, fsize, SEEK_HOLE));
+ EXPECT_EQ(ENXIO, errno);
+}
+
+/* lseek should return ENXIO when offset points to EOF */
+TEST_F(LseekSeekHole, enxio)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 1 << 30; /* 1 GiB */
+ off_t offset_in = fsize;
+ int fd;
+
+ expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
+ expect_open(ino, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_LSEEK &&
+ in.header.nodeid == ino &&
+ in.body.lseek.fh == FH &&
+ (off_t)in.body.lseek.offset == offset_in &&
+ in.body.lseek.whence == SEEK_HOLE);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(ENXIO)));
+ fd = open(FULLPATH, O_RDONLY);
+ EXPECT_EQ(-1, lseek(fd, offset_in, SEEK_HOLE));
+ EXPECT_EQ(ENXIO, errno);
+}
diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh
--- a/tests/sys/fs/fusefs/mockfs.hh
+++ b/tests/sys/fs/fusefs/mockfs.hh
@@ -170,6 +170,7 @@
fuse_link_in link;
fuse_listxattr_in listxattr;
char lookup[0];
+ fuse_lseek_in lseek;
fuse_mkdir_in mkdir;
fuse_mknod_in mknod;
fuse_open_in open;
@@ -210,6 +211,7 @@
fuse_getxattr_out getxattr;
fuse_init_out init;
fuse_init_out_7_22 init_7_22;
+ fuse_lseek_out lseek;
/* 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;
diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc
--- a/tests/sys/fs/fusefs/mockfs.cc
+++ b/tests/sys/fs/fusefs/mockfs.cc
@@ -62,8 +62,7 @@
const char* opcode2opname(uint32_t opcode)
{
- const int NUM_OPS = 39;
- const char* table[NUM_OPS] = {
+ const char* table[] = {
"Unknown (opcode 0)",
"LOOKUP",
"FORGET",
@@ -102,9 +101,17 @@
"CREATE",
"INTERRUPT",
"BMAP",
- "DESTROY"
+ "DESTROY",
+ "IOCTL",
+ "POLL",
+ "NOTIFY_REPLY",
+ "BATCH_FORGET",
+ "FALLOCATE",
+ "READDIRPLUS",
+ "RENAME2",
+ "LSEEK",
};
- if (opcode >= NUM_OPS)
+ if (opcode >= nitems(table))
return ("Unknown (opcode > max)");
else
return (table[opcode]);
@@ -211,6 +218,22 @@
case FUSE_LOOKUP:
printf(" %s", in.body.lookup);
break;
+ case FUSE_LSEEK:
+ switch (in.body.lseek.whence) {
+ case SEEK_HOLE:
+ printf(" SEEK_HOLE offset=%ld",
+ in.body.lseek.offset);
+ break;
+ case SEEK_DATA:
+ printf(" SEEK_DATA offset=%ld",
+ in.body.lseek.offset);
+ break;
+ default:
+ printf(" whence=%u offset=%ld",
+ in.body.lseek.whence, in.body.lseek.offset);
+ break;
+ }
+ break;
case FUSE_MKDIR:
name = (const char*)in.body.bytes +
sizeof(fuse_mkdir_in);
@@ -636,6 +659,10 @@
EXPECT_EQ(inlen, fih + sizeof(in.body.bmap));
EXPECT_EQ((size_t)buflen, inlen);
break;
+ case FUSE_LSEEK:
+ EXPECT_EQ(inlen, fih + sizeof(in.body.lseek));
+ EXPECT_EQ((size_t)buflen, inlen);
+ break;
case FUSE_NOTIFY_REPLY:
case FUSE_BATCH_FORGET:
case FUSE_FALLOCATE:
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Apr 23, 5:37 PM (5 h, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
32009370
Default Alt Text
D27804.id.diff (24 KB)
Attached To
Mode
D27804: fusefs: update FUSE protocol to 7.24 and implement FUSE_LSEEK
Attached
Detach File
Event Timeline
Log In to Comment