Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F166302033
D33239.id100798.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
9 KB
Referenced Files
None
Subscribers
None
D33239.id100798.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
@@ -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
Details
Attached
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)
Attached To
Mode
D33239: fusefs: fix .. lookups when the parent has been reclaimed.
Attached
Detach File
Event Timeline
Log In to Comment