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 31 extern "C" { 32 #include <fcntl.h> 33 } 34 35 #include "mockfs.hh" 36 #include "utils.hh" 37 38 using namespace testing; 39 40 class Mkdir: public FuseTest {}; 41 class Mkdir_7_8: public FuseTest { 42 public: 43 virtual void SetUp() { 44 m_kernel_minor_version = 8; 45 FuseTest::SetUp(); 46 } 47 }; 48 49 /* 50 * EMLINK is possible on filesystems that limit the number of hard links to a 51 * single file, like early versions of BtrFS 52 */ 53 TEST_F(Mkdir, emlink) 54 { 55 const char FULLPATH[] = "mountpoint/some_dir"; 56 const char RELPATH[] = "some_dir"; 57 mode_t mode = 0755; 58 59 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 60 .WillOnce(Invoke(ReturnErrno(ENOENT))); 61 62 EXPECT_CALL(*m_mock, process( 63 ResultOf([=](auto in) { 64 const char *name = (const char*)in.body.bytes + 65 sizeof(fuse_mkdir_in); 66 return (in.header.opcode == FUSE_MKDIR && 67 in.body.mkdir.mode == (S_IFDIR | mode) && 68 (0 == strcmp(RELPATH, name))); 69 }, Eq(true)), 70 _) 71 ).WillOnce(Invoke(ReturnErrno(EMLINK))); 72 73 ASSERT_NE(1, mkdir(FULLPATH, mode)); 74 ASSERT_EQ(EMLINK, errno); 75 } 76 77 /* 78 * Creating a new directory after FUSE_LOOKUP returned a negative cache entry 79 */ 80 TEST_F(Mkdir, entry_cache_negative) 81 { 82 const char FULLPATH[] = "mountpoint/some_file.txt"; 83 const char RELPATH[] = "some_file.txt"; 84 mode_t mode = 0755; 85 uint64_t ino = 42; 86 /* 87 * Set entry_valid = 0 because this test isn't concerned with whether 88 * or not we actually cache negative entries, only with whether we 89 * interpret negative cache responses correctly. 90 */ 91 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 92 93 /* mkdir will first do a LOOKUP, adding a negative cache entry */ 94 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 95 .WillOnce(ReturnNegativeCache(&entry_valid)); 96 97 EXPECT_CALL(*m_mock, process( 98 ResultOf([=](auto in) { 99 const char *name = (const char*)in.body.bytes + 100 sizeof(fuse_open_in); 101 return (in.header.opcode == FUSE_MKDIR && 102 in.body.mkdir.mode == (S_IFDIR | mode) && 103 (0 == strcmp(RELPATH, name))); 104 }, Eq(true)), 105 _) 106 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 107 SET_OUT_HEADER_LEN(out, entry); 108 out.body.create.entry.attr.mode = S_IFDIR | mode; 109 out.body.create.entry.nodeid = ino; 110 out.body.create.entry.entry_valid = UINT64_MAX; 111 out.body.create.entry.attr_valid = UINT64_MAX; 112 }))); 113 114 ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); 115 } 116 117 /* 118 * Creating a new directory should purge any negative namecache entries 119 */ 120 TEST_F(Mkdir, entry_cache_negative_purge) 121 { 122 const char FULLPATH[] = "mountpoint/some_file.txt"; 123 const char RELPATH[] = "some_file.txt"; 124 mode_t mode = 0755; 125 uint64_t ino = 42; 126 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 127 128 /* mkdir will first do a LOOKUP, adding a negative cache entry */ 129 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 130 .Times(1) 131 .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 132 .RetiresOnSaturation(); 133 134 /* Then the MKDIR should purge the negative cache entry */ 135 EXPECT_CALL(*m_mock, process( 136 ResultOf([=](auto in) { 137 const char *name = (const char*)in.body.bytes + 138 sizeof(fuse_open_in); 139 return (in.header.opcode == FUSE_MKDIR && 140 in.body.mkdir.mode == (S_IFDIR | mode) && 141 (0 == strcmp(RELPATH, name))); 142 }, Eq(true)), 143 _) 144 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 145 SET_OUT_HEADER_LEN(out, entry); 146 out.body.entry.attr.mode = S_IFDIR | mode; 147 out.body.entry.nodeid = ino; 148 out.body.entry.attr_valid = UINT64_MAX; 149 }))); 150 151 ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); 152 153 /* Finally, a subsequent lookup should query the daemon */ 154 expect_lookup(RELPATH, ino, S_IFDIR | mode, 0, 1); 155 156 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 157 } 158 159 TEST_F(Mkdir, ok) 160 { 161 const char FULLPATH[] = "mountpoint/some_dir"; 162 const char RELPATH[] = "some_dir"; 163 mode_t mode = 0755; 164 uint64_t ino = 42; 165 mode_t mask; 166 167 mask = umask(0); 168 (void)umask(mask); 169 170 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 171 .WillOnce(Invoke(ReturnErrno(ENOENT))); 172 173 EXPECT_CALL(*m_mock, process( 174 ResultOf([=](auto in) { 175 const char *name = (const char*)in.body.bytes + 176 sizeof(fuse_mkdir_in); 177 return (in.header.opcode == FUSE_MKDIR && 178 in.body.mkdir.mode == (S_IFDIR | mode) && 179 in.body.mkdir.umask == mask && 180 (0 == strcmp(RELPATH, name))); 181 }, Eq(true)), 182 _) 183 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 184 SET_OUT_HEADER_LEN(out, entry); 185 out.body.create.entry.attr.mode = S_IFDIR | mode; 186 out.body.create.entry.nodeid = ino; 187 out.body.create.entry.entry_valid = UINT64_MAX; 188 out.body.create.entry.attr_valid = UINT64_MAX; 189 }))); 190 191 ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); 192 } 193 194 /* 195 * Nothing bad should happen if the server returns the parent's inode number 196 * for the newly created directory. Regression test for bug 263662. 197 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263662 198 */ 199 TEST_F(Mkdir, parent_inode) 200 { 201 const char FULLPATH[] = "mountpoint/parent/some_dir"; 202 const char PPATH[] = "parent"; 203 const char RELPATH[] = "some_dir"; 204 mode_t mode = 0755; 205 uint64_t ino = 42; 206 mode_t mask; 207 208 mask = umask(0); 209 (void)umask(mask); 210 211 expect_lookup(PPATH, ino, S_IFDIR | 0755, 0, 1); 212 EXPECT_LOOKUP(ino, RELPATH) 213 .WillOnce(Invoke(ReturnErrno(ENOENT))); 214 215 EXPECT_CALL(*m_mock, process( 216 ResultOf([=](auto in) { 217 const char *name = (const char*)in.body.bytes + 218 sizeof(fuse_mkdir_in); 219 return (in.header.opcode == FUSE_MKDIR && 220 in.body.mkdir.mode == (S_IFDIR | mode) && 221 in.body.mkdir.umask == mask && 222 (0 == strcmp(RELPATH, name))); 223 }, Eq(true)), 224 _) 225 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 226 SET_OUT_HEADER_LEN(out, entry); 227 out.body.create.entry.attr.mode = S_IFDIR | mode; 228 out.body.create.entry.nodeid = ino; 229 out.body.create.entry.entry_valid = UINT64_MAX; 230 out.body.create.entry.attr_valid = UINT64_MAX; 231 }))); 232 // FUSE_FORGET happens asynchronously, so it may or may not arrive 233 // before the test completes. 234 EXPECT_CALL(*m_mock, process( 235 ResultOf([=](auto in) { 236 return (in.header.opcode == FUSE_FORGET); 237 }, Eq(true)), 238 _) 239 ).Times(AtMost(1)) 240 .WillOnce(Invoke([=](auto in __unused, auto &out __unused) { })); 241 242 ASSERT_EQ(-1, mkdir(FULLPATH, mode)); 243 ASSERT_EQ(EIO, errno); 244 } 245 246 TEST_F(Mkdir_7_8, ok) 247 { 248 const char FULLPATH[] = "mountpoint/some_dir"; 249 const char RELPATH[] = "some_dir"; 250 mode_t mode = 0755; 251 uint64_t ino = 42; 252 253 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 254 .WillOnce(Invoke(ReturnErrno(ENOENT))); 255 256 EXPECT_CALL(*m_mock, process( 257 ResultOf([=](auto in) { 258 const char *name = (const char*)in.body.bytes + 259 sizeof(fuse_mkdir_in); 260 return (in.header.opcode == FUSE_MKDIR && 261 in.body.mkdir.mode == (S_IFDIR | mode) && 262 (0 == strcmp(RELPATH, name))); 263 }, Eq(true)), 264 _) 265 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 266 SET_OUT_HEADER_LEN(out, entry_7_8); 267 out.body.create.entry.attr.mode = S_IFDIR | mode; 268 out.body.create.entry.nodeid = ino; 269 out.body.create.entry.entry_valid = UINT64_MAX; 270 out.body.create.entry.attr_valid = UINT64_MAX; 271 }))); 272 273 ASSERT_EQ(0, mkdir(FULLPATH, mode)) << strerror(errno); 274 } 275