1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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/mount.h> 36 #include <sys/sysctl.h> 37 38 #include <fcntl.h> 39 #include <semaphore.h> 40 #include <unistd.h> 41 } 42 43 #include "mockfs.hh" 44 #include "utils.hh" 45 46 using namespace testing; 47 48 class Forget: public FuseTest { 49 public: 50 void SetUp() { 51 if (geteuid() != 0) 52 GTEST_SKIP() << "Only root may use " << reclaim_mib; 53 54 FuseTest::SetUp(); 55 } 56 57 }; 58 59 /* 60 * When a fusefs vnode is reclaimed, it should send a FUSE_FORGET operation. 61 */ 62 TEST_F(Forget, ok) 63 { 64 const char FULLPATH[] = "mountpoint/some_file.txt"; 65 const char RELPATH[] = "some_file.txt"; 66 uint64_t ino = 42; 67 mode_t mode = S_IFREG | 0755; 68 sem_t sem; 69 70 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 71 72 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 73 .Times(3) 74 .WillRepeatedly(Invoke( 75 ReturnImmediate([=](auto in __unused, auto& out) { 76 SET_OUT_HEADER_LEN(out, entry); 77 out.body.entry.attr.mode = mode; 78 out.body.entry.nodeid = ino; 79 out.body.entry.attr.nlink = 1; 80 out.body.entry.attr_valid = UINT64_MAX; 81 }))); 82 expect_forget(ino, 3, &sem); 83 84 /* 85 * access(2) the file to force a lookup. Access it twice to double its 86 * lookup count. 87 */ 88 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 89 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 90 91 reclaim_vnode(FULLPATH); 92 93 sem_wait(&sem); 94 sem_destroy(&sem); 95 } 96 97 /* 98 * When a directory is reclaimed, the names of its entries vanish from the 99 * namecache 100 */ 101 TEST_F(Forget, invalidate_names) 102 { 103 const char FULLFPATH[] = "mountpoint/some_dir/some_file.txt"; 104 const char FULLDPATH[] = "mountpoint/some_dir"; 105 const char DNAME[] = "some_dir"; 106 const char FNAME[] = "some_file.txt"; 107 uint64_t dir_ino = 42; 108 uint64_t file_ino = 43; 109 110 EXPECT_LOOKUP(FUSE_ROOT_ID, DNAME) 111 .Times(2) 112 .WillRepeatedly(Invoke( 113 ReturnImmediate([=](auto in __unused, auto& out) { 114 SET_OUT_HEADER_LEN(out, entry); 115 out.body.entry.attr.mode = S_IFDIR | 0755; 116 out.body.entry.nodeid = dir_ino; 117 out.body.entry.attr.nlink = 2; 118 out.body.entry.attr_valid = UINT64_MAX; 119 out.body.entry.entry_valid = UINT64_MAX; 120 }))); 121 122 /* 123 * Even though we don't reclaim FNAME and its entry is cacheable, we 124 * should get two lookups because the reclaim of DNAME will invalidate 125 * the cached FNAME entry. 126 */ 127 EXPECT_LOOKUP(dir_ino, FNAME) 128 .Times(2) 129 .WillRepeatedly(Invoke( 130 ReturnImmediate([=](auto in __unused, auto& out) { 131 SET_OUT_HEADER_LEN(out, entry); 132 out.body.entry.attr.mode = S_IFREG | 0644; 133 out.body.entry.nodeid = file_ino; 134 out.body.entry.attr.nlink = 1; 135 out.body.entry.attr_valid = UINT64_MAX; 136 out.body.entry.entry_valid = UINT64_MAX; 137 }))); 138 expect_forget(dir_ino, 1); 139 140 /* Access the file to cache its name */ 141 ASSERT_EQ(0, access(FULLFPATH, F_OK)) << strerror(errno); 142 143 /* Reclaim the directory, invalidating its children from namecache */ 144 reclaim_vnode(FULLDPATH); 145 146 /* Access the file again, causing another lookup */ 147 ASSERT_EQ(0, access(FULLFPATH, F_OK)) << strerror(errno); 148 } 149 150 /* 151 * Reclaiming the root inode should not send a FUSE_FORGET request, nor should 152 * it interfere with further lookup operations. 153 */ 154 TEST_F(Forget, root) 155 { 156 const char FULLPATH[] = "mountpoint/some_file.txt"; 157 const char RELPATH[] = "some_file.txt"; 158 uint64_t ino = 42; 159 mode_t mode = S_IFREG | 0755; 160 161 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 162 .WillRepeatedly(Invoke( 163 ReturnImmediate([=](auto in __unused, auto& out) { 164 SET_OUT_HEADER_LEN(out, entry); 165 out.body.entry.attr.mode = mode; 166 out.body.entry.nodeid = ino; 167 out.body.entry.attr.nlink = 1; 168 out.body.entry.attr_valid = UINT64_MAX; 169 out.body.entry.entry_valid = UINT64_MAX; 170 }))); 171 172 /* access(2) the file to force a lookup. */ 173 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 174 175 reclaim_vnode("mountpoint"); 176 nap(); 177 178 /* Access it again, to make sure it's still possible. */ 179 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 180 } 181