1 /*- 2 * Copyright (c) 2019 The FreeBSD Foundation 3 * All rights reserved. 4 * 5 * This software was developed by BFF Storage Systems, LLC under sponsorship 6 * from the FreeBSD Foundation. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 extern "C" { 31 #include <fcntl.h> 32 #include <semaphore.h> 33 } 34 35 #include "mockfs.hh" 36 #include "utils.hh" 37 38 using namespace testing; 39 40 class Unlink: public FuseTest { 41 public: 42 void expect_lookup(const char *relpath, uint64_t ino, int times, int nlink=1) 43 { 44 EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) 45 .Times(times) 46 .WillRepeatedly(Invoke( 47 ReturnImmediate([=](auto in __unused, auto& out) { 48 SET_OUT_HEADER_LEN(out, entry); 49 out.body.entry.attr.mode = S_IFREG | 0644; 50 out.body.entry.nodeid = ino; 51 out.body.entry.attr.nlink = nlink; 52 out.body.entry.attr_valid = UINT64_MAX; 53 out.body.entry.attr.size = 0; 54 }))); 55 } 56 57 }; 58 59 /* 60 * Unlinking a multiply linked file should update its ctime and nlink. This 61 * could be handled simply by invalidating the attributes, necessitating a new 62 * GETATTR, but we implement it in-kernel for efficiency's sake. 63 */ 64 TEST_F(Unlink, attr_cache) 65 { 66 const char FULLPATH0[] = "mountpoint/some_file.txt"; 67 const char RELPATH0[] = "some_file.txt"; 68 const char FULLPATH1[] = "mountpoint/other_file.txt"; 69 const char RELPATH1[] = "other_file.txt"; 70 uint64_t ino = 42; 71 struct stat sb_old, sb_new; 72 int fd1; 73 74 expect_lookup(RELPATH0, ino, 1, 2); 75 expect_lookup(RELPATH1, ino, 1, 2); 76 expect_open(ino, 0, 1); 77 expect_unlink(1, RELPATH0, 0); 78 79 fd1 = open(FULLPATH1, O_RDONLY); 80 ASSERT_LE(0, fd1) << strerror(errno); 81 82 ASSERT_EQ(0, fstat(fd1, &sb_old)) << strerror(errno); 83 ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno); 84 ASSERT_EQ(0, fstat(fd1, &sb_new)) << strerror(errno); 85 EXPECT_NE(sb_old.st_ctime, sb_new.st_ctime); 86 EXPECT_EQ(1u, sb_new.st_nlink); 87 88 leak(fd1); 89 } 90 91 /* 92 * A successful unlink should clear the parent directory's attribute cache, 93 * because the fuse daemon should update its mtime and ctime 94 */ 95 TEST_F(Unlink, parent_attr_cache) 96 { 97 const char FULLPATH[] = "mountpoint/some_file.txt"; 98 const char RELPATH[] = "some_file.txt"; 99 struct stat sb; 100 uint64_t ino = 42; 101 Sequence seq; 102 103 /* Use nlink=2 so we don't get a FUSE_FORGET */ 104 expect_lookup(RELPATH, ino, 1, 2); 105 EXPECT_CALL(*m_mock, process( 106 ResultOf([=](auto in) { 107 return (in.header.opcode == FUSE_UNLINK && 108 0 == strcmp(RELPATH, in.body.unlink) && 109 in.header.nodeid == FUSE_ROOT_ID); 110 }, Eq(true)), 111 _) 112 ).InSequence(seq) 113 .WillOnce(Invoke(ReturnErrno(0))); 114 EXPECT_CALL(*m_mock, process( 115 ResultOf([=](auto in) { 116 return (in.header.opcode == FUSE_GETATTR && 117 in.header.nodeid == FUSE_ROOT_ID); 118 }, Eq(true)), 119 _) 120 ).InSequence(seq) 121 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 122 SET_OUT_HEADER_LEN(out, attr); 123 out.body.attr.attr.ino = FUSE_ROOT_ID; 124 out.body.attr.attr.mode = S_IFDIR | 0755; 125 out.body.attr.attr_valid = UINT64_MAX; 126 }))); 127 128 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 129 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 130 } 131 132 TEST_F(Unlink, eperm) 133 { 134 const char FULLPATH[] = "mountpoint/some_file.txt"; 135 const char RELPATH[] = "some_file.txt"; 136 uint64_t ino = 42; 137 138 expect_lookup(RELPATH, ino, 1); 139 expect_unlink(1, RELPATH, EPERM); 140 141 ASSERT_NE(0, unlink(FULLPATH)); 142 ASSERT_EQ(EPERM, errno); 143 } 144 145 /* 146 * Unlinking a file should expire its entry cache, even if it's multiply linked 147 */ 148 TEST_F(Unlink, entry_cache) 149 { 150 const char FULLPATH[] = "mountpoint/some_file.txt"; 151 const char RELPATH[] = "some_file.txt"; 152 uint64_t ino = 42; 153 154 expect_lookup(RELPATH, ino, 2, 2); 155 expect_unlink(1, RELPATH, 0); 156 157 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 158 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 159 } 160 161 /* 162 * Unlink a multiply-linked file. There should be no FUSE_FORGET because the 163 * file is still linked. 164 */ 165 TEST_F(Unlink, multiply_linked) 166 { 167 const char FULLPATH0[] = "mountpoint/some_file.txt"; 168 const char RELPATH0[] = "some_file.txt"; 169 const char FULLPATH1[] = "mountpoint/other_file.txt"; 170 const char RELPATH1[] = "other_file.txt"; 171 uint64_t ino = 42; 172 173 expect_lookup(RELPATH0, ino, 1, 2); 174 expect_unlink(1, RELPATH0, 0); 175 EXPECT_CALL(*m_mock, process( 176 ResultOf([=](auto in) { 177 return (in.header.opcode == FUSE_FORGET && 178 in.header.nodeid == ino); 179 }, Eq(true)), 180 _) 181 ).Times(0); 182 expect_lookup(RELPATH1, ino, 1, 1); 183 184 ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno); 185 186 /* 187 * The final syscall simply ensures that no FUSE_FORGET was ever sent, 188 * by scheduling an arbitrary different operation after a FUSE_FORGET 189 * would've been sent. 190 */ 191 ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno); 192 } 193 194 TEST_F(Unlink, ok) 195 { 196 const char FULLPATH[] = "mountpoint/some_file.txt"; 197 const char RELPATH[] = "some_file.txt"; 198 uint64_t ino = 42; 199 sem_t sem; 200 201 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 202 203 expect_lookup(RELPATH, ino, 1); 204 expect_unlink(1, RELPATH, 0); 205 expect_forget(ino, 1, &sem); 206 207 ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno); 208 sem_wait(&sem); 209 sem_destroy(&sem); 210 } 211 212 /* Unlink an open file */ 213 TEST_F(Unlink, open_but_deleted) 214 { 215 const char FULLPATH0[] = "mountpoint/some_file.txt"; 216 const char RELPATH0[] = "some_file.txt"; 217 const char FULLPATH1[] = "mountpoint/other_file.txt"; 218 const char RELPATH1[] = "other_file.txt"; 219 uint64_t ino = 42; 220 int fd; 221 222 expect_lookup(RELPATH0, ino, 2); 223 expect_open(ino, 0, 1); 224 expect_unlink(1, RELPATH0, 0); 225 expect_lookup(RELPATH1, ino, 1, 1); 226 227 fd = open(FULLPATH0, O_RDWR); 228 ASSERT_LE(0, fd) << strerror(errno); 229 ASSERT_EQ(0, unlink(FULLPATH0)) << strerror(errno); 230 231 /* 232 * The final syscall simply ensures that no FUSE_FORGET was ever sent, 233 * by scheduling an arbitrary different operation after a FUSE_FORGET 234 * would've been sent. 235 */ 236 ASSERT_EQ(0, access(FULLPATH1, F_OK)) << strerror(errno); 237 leak(fd); 238 } 239