From nobody Tue Jan 18 01:12:49 2022 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id AD0941957790; Tue, 18 Jan 2022 01:12:49 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4Jd9k94MRsz4Qlw; Tue, 18 Jan 2022 01:12:49 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1642468369; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=nHfXBBWBhpAsWdGtIsMUTgBToA9Jc5ryT6nvNSwMrmE=; b=HRNd15zs3bSjYaKSCiyJ5SCuvKR6kNJaYsk8ETE5vDEmCb6m33SW91P52jCkXvF+TKwSMT C/DKlbCysMvqTU2FpmP8uGkeLSlIk52G6cO3ekgIYOOR4T/sW1GONDifLgIUfiEnL75x3t 37DmNuHqBW0IMsSMtGIsPAukfmLdBikLJNLOVPUfRE2HoY4PBK/j4+hRiHCL2iu5KvXMCP hlRcAGsy8kLIOY1t9I8zI/O1E3FucTs6NEQq68QAksJzpk3wHru1E58wxO/B1Z2XeVjeUO 9LAIRXFhxwkcYP2EfR3drIR9PmmR8WV28y3Fmq1sPz2I4fEPeeSKe622hhpq2A== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 76C2017CEB; Tue, 18 Jan 2022 01:12:49 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 20I1CnF4027961; Tue, 18 Jan 2022 01:12:49 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 20I1Cnmg027960; Tue, 18 Jan 2022 01:12:49 GMT (envelope-from git) Date: Tue, 18 Jan 2022 01:12:49 GMT Message-Id: <202201180112.20I1Cnmg027960@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Alan Somers Subject: git: bfffd3510807 - stable/13 - fusefs: in the tests, always assume debug.try_reclaim_vnode is available List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-all@freebsd.org X-BeenThere: dev-commits-src-all@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: asomers X-Git-Repository: src X-Git-Refname: refs/heads/stable/13 X-Git-Reftype: branch X-Git-Commit: bfffd351080752652c9f850d1b5540f1097bc7c8 Auto-Submitted: auto-generated ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1642468369; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=nHfXBBWBhpAsWdGtIsMUTgBToA9Jc5ryT6nvNSwMrmE=; b=htOqvunr8NzM5K3KwsgJcP8MDlJJnX6KF6QEUSqo/m/4RNO/aBiwUv5lbRAI6o5FuF2Kfb m9NTs9SkclbpPYlZr8ViYJ2dsqaCYdn7QxkX680ujqXvwntCSlt5ZtyGCWBmNs1k81GyaH CFOdmZSIufVAaGLu5rK/ykdRl2sTBLmCSmoVC8mh47UxoRDkzdQId/gF5Ta5kyIcizaHBg wydlzD2d4QhOOjwzZ+4nz2gjfJGwHsdu3IBS05ycoPWTW/CHvrvvsQQNt6hQKgWZjG/i/O oX1U2lLb+5X6M0JDRytsMo6nSNN2gSDJzx+RaziYe31i1qfXwFLe3/0Fqi1F/w== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1642468369; a=rsa-sha256; cv=none; b=j/boejXTk2l9g7UDMM5hQCZIWqs8gIu4iyz2HyHdFF0DuURZRZ2hwjIk2e58iyL2WUHHB0 JKT65qk06xFYAxDRM0axMjvwByHADRlDYt/Kkd4p6fTi2NDbate/tYeSD7yUXmISOWi3yv z//gTSiCA/TZYr9Kxsc3wDY9GaGKLjovAOH0Is94x2Q6c+OVM77FbNX2pudlVMGRoGIv7q CoJW2q0kYiNudIKM6IFI0RkYe4bUDE1O+hESBJgNvPdgJYU5eGzE/fwMpxn0BcQEjtF8Ut rTAQbpZ/gIBhK2xHFbLp/RO5G5EHbMyIOFjgzU0gSIKa9IfkzSfeDv1fZEvRow== ARC-Authentication-Results: i=1; mx1.freebsd.org; none X-ThisMailContainsUnwantedMimeParts: N The branch stable/13 has been updated by asomers: URL: https://cgit.FreeBSD.org/src/commit/?id=bfffd351080752652c9f850d1b5540f1097bc7c8 commit bfffd351080752652c9f850d1b5540f1097bc7c8 Author: Alan Somers AuthorDate: 2021-12-02 02:38:04 +0000 Commit: Alan Somers CommitDate: 2022-01-18 01:08:25 +0000 fusefs: in the tests, always assume debug.try_reclaim_vnode is available In an earlier version of the revision that created that sysctl (D20519) the sysctl was gated by INVARIANTS, so the test had to check for it. But in the committed version it is always available. (cherry picked from commit 19ab361045343bb777176bb08468f7706d7649c4) fusefs: move common code from forget.cc to utils.cc (cherry picked from commit 8d99a6b91b788b7ddf88f975f288f7c6479f4be3) fusefs: fix .. lookups when the parent has been reclaimed. By default, FUSE file systems are assumed not to support lookups for "." and "..". They must opt-in to that. To cope with this limitation, the fusefs kernel module caches every fuse vnode's parent's inode number, and uses that during VOP_LOOKUP for "..". But if the parent's vnode has been reclaimed that won't be possible. Previously we paniced in this situation. Now, we'll return ESTALE instead. Or, if the file system has opted into ".." lookups, we'll just do that instead. This commit also fixes VOP_LOOKUP to respect the cache timeout for ".." lookups, if the FUSE file system specified a finite timeout. PR: 259974 Reviewed by: pfg Differential Revision: https://reviews.freebsd.org/D33239 (cherry picked from commit 1613087a8127122b03a3730046d051adf4edd14f) --- sys/fs/fuse/fuse_vnops.c | 26 ++++-- tests/sys/fs/fusefs/forget.cc | 14 +-- tests/sys/fs/fusefs/lookup.cc | 203 ++++++++++++++++++++++++++++++++++++++++++ tests/sys/fs/fusefs/utils.cc | 9 ++ tests/sys/fs/fusefs/utils.hh | 4 + 5 files changed, 236 insertions(+), 20 deletions(-) diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c index 5cef2a7ea62a..f57f376e5685 100644 --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -1320,9 +1320,15 @@ fuse_vnop_lookup(struct vop_lookup_args *ap) 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 @@ fuse_vnop_lookup(struct vop_lookup_args *ap) 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 @@ fuse_vnop_lookup(struct vop_lookup_args *ap) 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/forget.cc b/tests/sys/fs/fusefs/forget.cc index c138b7acc4aa..84fc271df57c 100644 --- a/tests/sys/fs/fusefs/forget.cc +++ b/tests/sys/fs/fusefs/forget.cc @@ -44,18 +44,12 @@ extern "C" { using namespace testing; -const char reclaim_mib[] = "debug.try_reclaim_vnode"; - class Forget: public FuseTest { public: void SetUp() { if (geteuid() != 0) GTEST_SKIP() << "Only root may use " << reclaim_mib; - if (-1 == sysctlbyname(reclaim_mib, NULL, 0, NULL, 0) && - errno == ENOENT) - GTEST_SKIP() << reclaim_mib << " is not available"; - FuseTest::SetUp(); } @@ -71,7 +65,6 @@ TEST_F(Forget, ok) uint64_t ino = 42; mode_t mode = S_IFREG | 0755; sem_t sem; - int err; ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); @@ -94,8 +87,7 @@ TEST_F(Forget, ok) ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); - err = sysctlbyname(reclaim_mib, NULL, 0, FULLPATH, sizeof(FULLPATH)); - ASSERT_EQ(0, err) << strerror(errno); + reclaim_vnode(FULLPATH); sem_wait(&sem); sem_destroy(&sem); @@ -113,7 +105,6 @@ TEST_F(Forget, invalidate_names) const char FNAME[] = "some_file.txt"; uint64_t dir_ino = 42; uint64_t file_ino = 43; - int err; EXPECT_LOOKUP(FUSE_ROOT_ID, DNAME) .Times(2) @@ -149,8 +140,7 @@ TEST_F(Forget, invalidate_names) ASSERT_EQ(0, access(FULLFPATH, F_OK)) << strerror(errno); /* Reclaim the directory, invalidating its children from namecache */ - err = sysctlbyname(reclaim_mib, NULL, 0, FULLDPATH, sizeof(FULLDPATH)); - ASSERT_EQ(0, err) << strerror(errno); + reclaim_vnode(FULLDPATH); /* Access the file again, causing another lookup */ ASSERT_EQ(0, access(FULLFPATH, F_OK)) << strerror(errno); diff --git a/tests/sys/fs/fusefs/lookup.cc b/tests/sys/fs/fusefs/lookup.cc index d301990c2048..2dfa10730ec8 100644 --- a/tests/sys/fs/fusefs/lookup.cc +++ b/tests/sys/fs/fusefs/lookup.cc @@ -31,6 +31,10 @@ */ extern "C" { +#include +#include + +#include #include } @@ -40,6 +44,7 @@ extern "C" { using namespace testing; class Lookup: public FuseTest {}; + class Lookup_7_8: public Lookup { public: virtual void SetUp() { @@ -48,6 +53,14 @@ virtual void SetUp() { } }; +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 @@ TEST_F(Lookup, dotdot) 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 @@ TEST_F(Lookup_7_8, ok) 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); +} diff --git a/tests/sys/fs/fusefs/utils.cc b/tests/sys/fs/fusefs/utils.cc index f733fef7ebe0..fb2109e1e9c4 100644 --- a/tests/sys/fs/fusefs/utils.cc +++ b/tests/sys/fs/fusefs/utils.cc @@ -623,6 +623,15 @@ out: return; } +void +FuseTest::reclaim_vnode(const char *path) +{ + int err; + + err = sysctlbyname(reclaim_mib, NULL, 0, path, strlen(path) + 1); + ASSERT_EQ(0, err) << strerror(errno); +} + static void usage(char* progname) { fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); exit(2); diff --git a/tests/sys/fs/fusefs/utils.hh b/tests/sys/fs/fusefs/utils.hh index 6f1f91b02c97..610d2126fa52 100644 --- a/tests/sys/fs/fusefs/utils.hh +++ b/tests/sys/fs/fusefs/utils.hh @@ -73,6 +73,7 @@ class FuseTest : public ::testing::Test { unsigned m_time_gran; MockFS *m_mock = NULL; const static uint64_t FH = 0xdeadbeef1a7ebabe; + const char *reclaim_mib = "debug.try_reclaim_vnode"; public: int m_maxbcachebuf; @@ -256,4 +257,7 @@ class FuseTest : public ::testing::Test { * See comments for FuseTest::leak */ static void leakdir(DIR* dirp __unused) {} + + /* Manually reclaim a vnode. Requires root privileges. */ + void reclaim_vnode(const char *fullpath); };