1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2019 The FreeBSD Foundation 5 * 6 * This software was developed by BFF Storage Systems, LLC under sponsorship 7 * from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 * 30 * $FreeBSD$ 31 */ 32 33 extern "C" { 34 #include <sys/types.h> 35 #include <sys/sysctl.h> 36 37 #include <fcntl.h> 38 #include <semaphore.h> 39 #include <unistd.h> 40 } 41 42 #include "mockfs.hh" 43 #include "utils.hh" 44 45 using namespace testing; 46 47 const char reclaim_mib[] = "debug.try_reclaim_vnode"; 48 49 class Forget: public FuseTest { 50 public: 51 void SetUp() { 52 if (geteuid() != 0) 53 GTEST_SKIP() << "Only root may use " << reclaim_mib; 54 55 if (-1 == sysctlbyname(reclaim_mib, NULL, 0, NULL, 0) && 56 errno == ENOENT) 57 GTEST_SKIP() << reclaim_mib << " is not available"; 58 59 FuseTest::SetUp(); 60 } 61 62 }; 63 64 /* 65 * When a fusefs vnode is reclaimed, it should send a FUSE_FORGET operation. 66 */ 67 TEST_F(Forget, ok) 68 { 69 const char FULLPATH[] = "mountpoint/some_file.txt"; 70 const char RELPATH[] = "some_file.txt"; 71 uint64_t ino = 42; 72 mode_t mode = S_IFREG | 0755; 73 sem_t sem; 74 int err; 75 76 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 77 78 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 79 .Times(3) 80 .WillRepeatedly(Invoke( 81 ReturnImmediate([=](auto in __unused, auto& out) { 82 SET_OUT_HEADER_LEN(out, entry); 83 out.body.entry.attr.mode = mode; 84 out.body.entry.nodeid = ino; 85 out.body.entry.attr.nlink = 1; 86 out.body.entry.attr_valid = UINT64_MAX; 87 }))); 88 expect_forget(ino, 3, &sem); 89 90 /* 91 * access(2) the file to force a lookup. Access it twice to double its 92 * lookup count. 93 */ 94 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 95 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 96 97 err = sysctlbyname(reclaim_mib, NULL, 0, FULLPATH, sizeof(FULLPATH)); 98 ASSERT_EQ(0, err) << strerror(errno); 99 100 sem_wait(&sem); 101 sem_destroy(&sem); 102 } 103 104 /* 105 * When a directory is reclaimed, the names of its entries vanish from the 106 * namecache 107 */ 108 TEST_F(Forget, invalidate_names) 109 { 110 const char FULLFPATH[] = "mountpoint/some_dir/some_file.txt"; 111 const char FULLDPATH[] = "mountpoint/some_dir"; 112 const char DNAME[] = "some_dir"; 113 const char FNAME[] = "some_file.txt"; 114 uint64_t dir_ino = 42; 115 uint64_t file_ino = 43; 116 int err; 117 118 EXPECT_LOOKUP(FUSE_ROOT_ID, DNAME) 119 .Times(2) 120 .WillRepeatedly(Invoke( 121 ReturnImmediate([=](auto in __unused, auto& out) { 122 SET_OUT_HEADER_LEN(out, entry); 123 out.body.entry.attr.mode = S_IFDIR | 0755; 124 out.body.entry.nodeid = dir_ino; 125 out.body.entry.attr.nlink = 2; 126 out.body.entry.attr_valid = UINT64_MAX; 127 out.body.entry.entry_valid = UINT64_MAX; 128 }))); 129 130 /* 131 * Even though we don't reclaim FNAME and its entry is cacheable, we 132 * should get two lookups because the reclaim of DNAME will invalidate 133 * the cached FNAME entry. 134 */ 135 EXPECT_LOOKUP(dir_ino, FNAME) 136 .Times(2) 137 .WillRepeatedly(Invoke( 138 ReturnImmediate([=](auto in __unused, auto& out) { 139 SET_OUT_HEADER_LEN(out, entry); 140 out.body.entry.attr.mode = S_IFREG | 0644; 141 out.body.entry.nodeid = file_ino; 142 out.body.entry.attr.nlink = 1; 143 out.body.entry.attr_valid = UINT64_MAX; 144 out.body.entry.entry_valid = UINT64_MAX; 145 }))); 146 expect_forget(dir_ino, 1); 147 148 /* Access the file to cache its name */ 149 ASSERT_EQ(0, access(FULLFPATH, F_OK)) << strerror(errno); 150 151 /* Reclaim the directory, invalidating its children from namecache */ 152 err = sysctlbyname(reclaim_mib, NULL, 0, FULLDPATH, sizeof(FULLDPATH)); 153 ASSERT_EQ(0, err) << strerror(errno); 154 155 /* Access the file again, causing another lookup */ 156 ASSERT_EQ(0, access(FULLFPATH, F_OK)) << strerror(errno); 157 } 158