Page MenuHomeFreeBSD

D33239.id100798.diff
No OneTemporary

D33239.id100798.diff

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
@@ -1320,9 +1320,15 @@
else if ((err = fuse_internal_access(dvp, VEXEC, td, cred)))
return err;
- if (flags & ISDOTDOT) {
- KASSERT(VTOFUD(dvp)->flag & FN_PARENT_NID,
- ("Looking up .. is TODO"));
+ if ((flags & ISDOTDOT) && !(data->dataflags & FSESS_EXPORT_SUPPORT))
+ {
+ if (!(VTOFUD(dvp)->flag & FN_PARENT_NID)) {
+ /*
+ * Since the file system doesn't support ".." lookups,
+ * we have no way to find this entry.
+ */
+ return ESTALE;
+ }
nid = VTOFUD(dvp)->parent_nid;
if (nid == 0)
return ENOENT;
@@ -1375,9 +1381,8 @@
return err;
}
- nid = VTOI(dvp);
fdisp_init(&fdi, cnp->cn_namelen + 1);
- fdisp_make(&fdi, FUSE_LOOKUP, mp, nid, td, cred);
+ fdisp_make(&fdi, FUSE_LOOKUP, mp, VTOI(dvp), td, cred);
memcpy(fdi.indata, cnp->cn_nameptr, cnp->cn_namelen);
((char *)fdi.indata)[cnp->cn_namelen] = '\0';
@@ -1396,11 +1401,16 @@
lookup_err = ENOENT;
if (cnp->cn_flags & MAKEENTRY) {
fuse_validity_2_timespec(feo, &timeout);
+ /* Use the same entry_time for .. as for
+ * the file itself. That doesn't honor
+ * exactly what the fuse server tells
+ * us, but to do otherwise would require
+ * another cache lookup at this point.
+ */
+ struct timespec *dtsp = NULL;
cache_enter_time(dvp, *vpp, cnp,
- &timeout, NULL);
+ &timeout, dtsp);
}
- } else if (nid == FUSE_ROOT_ID) {
- lookup_err = EINVAL;
}
vtyp = IFTOVT(feo->attr.mode);
filesize = feo->attr.size;
diff --git a/tests/sys/fs/fusefs/lookup.cc b/tests/sys/fs/fusefs/lookup.cc
--- a/tests/sys/fs/fusefs/lookup.cc
+++ b/tests/sys/fs/fusefs/lookup.cc
@@ -31,6 +31,10 @@
*/
extern "C" {
+#include <sys/param.h>
+#include <sys/mount.h>
+
+#include <fcntl.h>
#include <unistd.h>
}
@@ -40,6 +44,7 @@
using namespace testing;
class Lookup: public FuseTest {};
+
class Lookup_7_8: public Lookup {
public:
virtual void SetUp() {
@@ -48,6 +53,14 @@
}
};
+class LookupExportable: public Lookup {
+public:
+virtual void SetUp() {
+ m_init_flags = FUSE_EXPORT_SUPPORT;
+ Lookup::SetUp();
+}
+};
+
/*
* If lookup returns a non-zero cache timeout, then subsequent VOP_GETATTRs
* should use the cached attributes, rather than query the daemon
@@ -181,6 +194,89 @@
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
}
+/*
+ * Lookup ".." when that vnode's entry cache has timed out, but its child's
+ * hasn't. Since this file system doesn't set FUSE_EXPORT_SUPPORT, we have no
+ * choice but to use the cached entry, even though it expired.
+ */
+TEST_F(Lookup, dotdot_entry_cache_timeout)
+{
+ uint64_t foo_ino = 42;
+ uint64_t bar_ino = 43;
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, "foo")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = foo_ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = 0; // immediate timeout
+ })));
+ EXPECT_LOOKUP(foo_ino, "bar")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = bar_ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+ expect_opendir(bar_ino);
+
+ int fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY);
+ ASSERT_LE(0, fd) << strerror(errno);
+ EXPECT_EQ(0, faccessat(fd, "../..", F_OK, 0)) << strerror(errno);
+}
+
+/*
+ * Lookup ".." for a vnode with no valid parent nid
+ * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259974
+ * Since the file system is not exportable, we have no choice but to return an
+ * error.
+ */
+TEST_F(Lookup, dotdot_no_parent_nid)
+{
+ uint64_t foo_ino = 42;
+ uint64_t bar_ino = 43;
+ int fd;
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, "foo")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = foo_ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+ EXPECT_LOOKUP(foo_ino, "bar")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = bar_ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPENDIR);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, open);
+ })));
+ expect_forget(FUSE_ROOT_ID, 1, NULL);
+ expect_forget(foo_ino, 1, NULL);
+
+ fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY);
+ ASSERT_LE(0, fd) << strerror(errno);
+ // Try (and fail) to unmount the file system, to reclaim the mountpoint
+ // and foo vnodes.
+ ASSERT_NE(0, unmount("mountpoint", 0));
+ EXPECT_EQ(EBUSY, errno);
+ nap(); // Because vnode reclamation is asynchronous
+ EXPECT_NE(0, faccessat(fd, "../..", F_OK, 0));
+ EXPECT_EQ(ESTALE, errno);
+}
+
TEST_F(Lookup, enoent)
{
const char FULLPATH[] = "mountpoint/does_not_exist";
@@ -398,4 +494,111 @@
ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
}
+/*
+ * Lookup ".." when that vnode's entry cache has timed out, but its child's
+ * hasn't.
+ */
+TEST_F(LookupExportable, dotdot_entry_cache_timeout)
+{
+ uint64_t foo_ino = 42;
+ uint64_t bar_ino = 43;
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, "foo")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = foo_ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = 0; // immediate timeout
+ })));
+ EXPECT_LOOKUP(foo_ino, "bar")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = bar_ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+ expect_opendir(bar_ino);
+ EXPECT_LOOKUP(foo_ino, "..")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = FUSE_ROOT_ID;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+
+ int fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY);
+ ASSERT_LE(0, fd) << strerror(errno);
+ /* FreeBSD's fusefs driver always uses the same cache expiration time
+ * for ".." as for the directory itself. So we need to look up two
+ * levels to find an expired ".." cache entry.
+ */
+ EXPECT_EQ(0, faccessat(fd, "../..", F_OK, 0)) << strerror(errno);
+}
+
+/*
+ * Lookup ".." for a vnode with no valid parent nid
+ * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259974
+ * Since the file system is exportable, we should resolve the problem by
+ * sending a FUSE_LOOKUP for "..".
+ */
+TEST_F(LookupExportable, dotdot_no_parent_nid)
+{
+ uint64_t foo_ino = 42;
+ uint64_t bar_ino = 43;
+ int fd;
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, "foo")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = foo_ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+ EXPECT_LOOKUP(foo_ino, "bar")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = bar_ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_OPENDIR);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, open);
+ })));
+ expect_forget(FUSE_ROOT_ID, 1, NULL);
+ expect_forget(foo_ino, 1, NULL);
+ EXPECT_LOOKUP(bar_ino, "..")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = foo_ino;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+ EXPECT_LOOKUP(foo_ino, "..")
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | 0755;
+ out.body.entry.nodeid = FUSE_ROOT_ID;
+ out.body.entry.attr_valid = UINT64_MAX;
+ out.body.entry.entry_valid = UINT64_MAX;
+ })));
+ fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY);
+ ASSERT_LE(0, fd) << strerror(errno);
+ // Try (and fail) to unmount the file system, to reclaim the mountpoint
+ // and foo vnodes.
+ ASSERT_NE(0, unmount("mountpoint", 0));
+ EXPECT_EQ(EBUSY, errno);
+ nap(); // Because vnode reclamation is asynchronous
+ EXPECT_EQ(0, faccessat(fd, "../..", F_OK, 0)) << strerror(errno);
+}

File Metadata

Mime Type
text/plain
Expires
Thu, Aug 13, 6:39 PM (5 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
36673722
Default Alt Text
D33239.id100798.diff (9 KB)

Event Timeline