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