Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F115549872
D44618.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
6 KB
Referenced Files
None
Subscribers
None
D44618.diff
View Options
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
@@ -395,6 +395,9 @@
err = fdisp_wait_answ(&fdi);
if (err == ENOSYS) {
fsess_set_notimpl(mp, FUSE_LSEEK);
+ } else if (err == ENXIO) {
+ /* Note: ENXIO means "no more hole/data regions until EOF" */
+ fsess_set_impl(mp, FUSE_LSEEK);
} else if (err == 0) {
fsess_set_impl(mp, FUSE_LSEEK);
flso = fdi.answ;
@@ -1754,6 +1757,9 @@
{
struct vnode *vp = ap->a_vp;
struct mount *mp;
+ struct fuse_filehandle *fufh;
+ int err;
+ bool closefufh = false;
switch (ap->a_name) {
case _PC_FILESIZEBITS:
@@ -1783,22 +1789,44 @@
!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);
+ /*
+ * Issue a FUSE_LSEEK to find out if it's supported.
+ * Use SEEK_DATA instead of SEEK_HOLE, because the
+ * latter generally requires sequential scans of file
+ * metadata, which can be slow.
+ */
+ err = fuse_vnop_do_lseek(vp, curthread,
+ curthread->td_ucred, curthread->td_proc->p_pid,
+ &offset, SEEK_DATA);
+ if (err == EBADF) {
+ /*
+ * pathconf() doesn't necessarily open the
+ * file. So we may need to do it here.
+ */
+ err = fuse_filehandle_open(vp, FREAD, &fufh,
+ curthread, curthread->td_ucred);
+ if (err == 0) {
+ closefufh = true;
+ err = fuse_vnop_do_lseek(vp, curthread,
+ curthread->td_ucred,
+ curthread->td_proc->p_pid, &offset,
+ SEEK_DATA);
+ }
+ if (closefufh)
+ fuse_filehandle_close(vp, fufh,
+ curthread, curthread->td_ucred);
+ }
+
}
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.
- */
+ } else if (fsess_not_impl(mp, FUSE_LSEEK)) {
+ /* FUSE_LSEEK is not implemented */
return (EINVAL);
+ } else {
+ return (err);
}
default:
return (vop_stdpathconf(ap));
diff --git a/tests/sys/fs/fusefs/lseek.cc b/tests/sys/fs/fusefs/lseek.cc
--- a/tests/sys/fs/fusefs/lseek.cc
+++ b/tests/sys/fs/fusefs/lseek.cc
@@ -112,6 +112,75 @@
leak(fd);
}
+/*
+ * Use pathconf on a file not already opened. The server returns EACCES when
+ * the kernel tries to open it. The kernel should return EACCES, and make no
+ * judgement about whether the server does or does not support FUSE_LSEEK.
+ */
+TEST_F(LseekPathconf, eacces)
+{
+ 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 */
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.entry_valid = UINT64_MAX;
+ out.body.entry.attr.mode = S_IFREG | 0644;
+ out.body.entry.nodeid = ino;
+ out.body.entry.attr.size = fsize;
+ })));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPEN &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).Times(2)
+ .WillRepeatedly(Invoke(ReturnErrno(EACCES)));
+
+ EXPECT_EQ(-1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EACCES, errno);
+ /* Check again, to ensure that the kernel didn't record the response */
+ EXPECT_EQ(-1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EACCES, errno);
+}
+
+/*
+ * If the server returns some weird error when we try FUSE_LSEEK, send that to
+ * the caller but don't record the answer.
+ */
+TEST_F(LseekPathconf, eio)
+{
+ 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(2)
+ .WillRepeatedly(Invoke(ReturnErrno(EIO)));
+
+ fd = open(FULLPATH, O_RDONLY);
+
+ EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EIO, errno);
+ /* Check again, to ensure that the kernel didn't record the response */
+ EXPECT_EQ(-1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ EXPECT_EQ(EIO, errno);
+
+ leak(fd);
+}
+
/*
* If no FUSE_LSEEK operation has been attempted since mount, try once as soon
* as a pathconf request comes in.
@@ -141,6 +210,34 @@
leak(fd);
}
+/*
+ * Use pathconf, rather than fpathconf, on a file not already opened.
+ * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=278135
+ */
+TEST_F(LseekPathconf, pathconf)
+{
+ 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_out = 1 << 29;
+
+ 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;
+ })));
+ expect_release(ino, FuseTest::FH);
+
+ EXPECT_EQ(1, pathconf(FULLPATH, _PC_MIN_HOLE_SIZE)) << strerror(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
@@ -177,6 +274,38 @@
leak(fd);
}
+/*
+ * If the user calls pathconf(_, _PC_MIN_HOLE_SIZE) on a fully sparse or
+ * zero-length file, then SEEK_DATA will return ENXIO. That should be
+ * interpreted as success.
+ */
+TEST_F(LseekPathconf, zerolength)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const uint64_t ino = 42;
+ off_t fsize = 0;
+ 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.whence == SEEK_DATA);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnErrno(ENXIO)));
+
+ fd = open(FULLPATH, O_RDONLY);
+ EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+ /* Check again, to ensure that the kernel recorded the response */
+ EXPECT_EQ(1, fpathconf(fd, _PC_MIN_HOLE_SIZE));
+
+ leak(fd);
+}
+
/*
* For servers using older protocol versions, no FUSE_LSEEK should be attempted
*/
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 26, 5:02 AM (15 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
17777221
Default Alt Text
D44618.diff (6 KB)
Attached To
Mode
D44618: fusefs: fix two bugs regarding _PC_MIN_HOLE_SIZE
Attached
Detach File
Event Timeline
Log In to Comment