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 <unistd.h> 33 } 34 35 #include "mockfs.hh" 36 #include "utils.hh" 37 38 using namespace testing; 39 40 class Link: public FuseTest { 41 public: 42 void expect_link(uint64_t ino, const char *relpath, mode_t mode, uint32_t nlink) 43 { 44 EXPECT_CALL(*m_mock, process( 45 ResultOf([=](auto in) { 46 const char *name = (const char*)in.body.bytes 47 + sizeof(struct fuse_link_in); 48 return (in.header.opcode == FUSE_LINK && 49 in.body.link.oldnodeid == ino && 50 (0 == strcmp(name, relpath))); 51 }, Eq(true)), 52 _) 53 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 54 SET_OUT_HEADER_LEN(out, entry); 55 out.body.entry.nodeid = ino; 56 out.body.entry.attr.mode = mode; 57 out.body.entry.attr.nlink = nlink; 58 out.body.entry.attr_valid = UINT64_MAX; 59 out.body.entry.entry_valid = UINT64_MAX; 60 }))); 61 } 62 63 void expect_lookup(const char *relpath, uint64_t ino) 64 { 65 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, 1); 66 } 67 }; 68 69 class Link_7_8: public FuseTest { 70 public: 71 virtual void SetUp() { 72 m_kernel_minor_version = 8; 73 FuseTest::SetUp(); 74 } 75 76 void expect_link(uint64_t ino, const char *relpath, mode_t mode, uint32_t nlink) 77 { 78 EXPECT_CALL(*m_mock, process( 79 ResultOf([=](auto in) { 80 const char *name = (const char*)in.body.bytes 81 + sizeof(struct fuse_link_in); 82 return (in.header.opcode == FUSE_LINK && 83 in.body.link.oldnodeid == ino && 84 (0 == strcmp(name, relpath))); 85 }, Eq(true)), 86 _) 87 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 88 SET_OUT_HEADER_LEN(out, entry_7_8); 89 out.body.entry.nodeid = ino; 90 out.body.entry.attr.mode = mode; 91 out.body.entry.attr.nlink = nlink; 92 out.body.entry.attr_valid = UINT64_MAX; 93 out.body.entry.entry_valid = UINT64_MAX; 94 }))); 95 } 96 97 void expect_lookup(const char *relpath, uint64_t ino) 98 { 99 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, 0, 1); 100 } 101 }; 102 103 /* 104 * A successful link should clear the parent directory's attribute cache, 105 * because the fuse daemon should update its mtime and ctime 106 */ 107 TEST_F(Link, clear_attr_cache) 108 { 109 const char FULLPATH[] = "mountpoint/src"; 110 const char RELPATH[] = "src"; 111 const char FULLDST[] = "mountpoint/dst"; 112 const char RELDST[] = "dst"; 113 const uint64_t ino = 42; 114 mode_t mode = S_IFREG | 0644; 115 struct stat sb; 116 117 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 118 .WillOnce(Invoke(ReturnErrno(ENOENT))); 119 EXPECT_CALL(*m_mock, process( 120 ResultOf([=](auto in) { 121 return (in.header.opcode == FUSE_GETATTR && 122 in.header.nodeid == FUSE_ROOT_ID); 123 }, Eq(true)), 124 _) 125 ).Times(2) 126 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 127 SET_OUT_HEADER_LEN(out, attr); 128 out.body.attr.attr.ino = FUSE_ROOT_ID; 129 out.body.attr.attr.mode = S_IFDIR | 0755; 130 out.body.attr.attr_valid = UINT64_MAX; 131 }))); 132 133 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 134 135 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 136 SET_OUT_HEADER_LEN(out, entry); 137 out.body.entry.attr.mode = mode; 138 out.body.entry.nodeid = ino; 139 out.body.entry.attr.nlink = 1; 140 out.body.entry.attr_valid = UINT64_MAX; 141 out.body.entry.entry_valid = UINT64_MAX; 142 }))); 143 expect_link(ino, RELPATH, mode, 2); 144 145 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 146 EXPECT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno); 147 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 148 } 149 150 TEST_F(Link, emlink) 151 { 152 const char FULLPATH[] = "mountpoint/lnk"; 153 const char RELPATH[] = "lnk"; 154 const char FULLDST[] = "mountpoint/dst"; 155 const char RELDST[] = "dst"; 156 uint64_t dst_ino = 42; 157 158 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 159 .WillOnce(Invoke(ReturnErrno(ENOENT))); 160 expect_lookup(RELDST, dst_ino); 161 162 EXPECT_CALL(*m_mock, process( 163 ResultOf([=](auto in) { 164 const char *name = (const char*)in.body.bytes 165 + sizeof(struct fuse_link_in); 166 return (in.header.opcode == FUSE_LINK && 167 in.body.link.oldnodeid == dst_ino && 168 (0 == strcmp(name, RELPATH))); 169 }, Eq(true)), 170 _) 171 ).WillOnce(Invoke(ReturnErrno(EMLINK))); 172 173 EXPECT_EQ(-1, link(FULLDST, FULLPATH)); 174 EXPECT_EQ(EMLINK, errno); 175 } 176 177 /* 178 * A hard link should always have the same inode as its source. If it doesn't, 179 * then it's not a hard link. 180 */ 181 TEST_F(Link, bad_inode) 182 { 183 const char FULLPATH[] = "mountpoint/src"; 184 const char RELPATH[] = "src"; 185 const char FULLDST[] = "mountpoint/dst"; 186 const char RELDST[] = "dst"; 187 const uint64_t src_ino = 42; 188 const uint64_t dst_ino = 43; 189 mode_t mode = S_IFREG | 0644; 190 191 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 192 .WillOnce(Invoke(ReturnErrno(ENOENT))); 193 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 194 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 195 SET_OUT_HEADER_LEN(out, entry); 196 out.body.entry.attr.mode = mode; 197 out.body.entry.nodeid = dst_ino; 198 out.body.entry.attr.nlink = 1; 199 out.body.entry.attr_valid = UINT64_MAX; 200 out.body.entry.entry_valid = UINT64_MAX; 201 }))); 202 EXPECT_CALL(*m_mock, process( 203 ResultOf([=](auto in) { 204 const char *name = (const char*)in.body.bytes 205 + sizeof(struct fuse_link_in); 206 return (in.header.opcode == FUSE_LINK && 207 in.body.link.oldnodeid == dst_ino && 208 (0 == strcmp(name, RELPATH))); 209 }, Eq(true)), 210 _) 211 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 212 SET_OUT_HEADER_LEN(out, entry); 213 out.body.entry.nodeid = src_ino; 214 out.body.entry.attr.mode = mode; 215 out.body.entry.attr.nlink = 2; 216 out.body.entry.attr_valid = UINT64_MAX; 217 out.body.entry.entry_valid = UINT64_MAX; 218 }))); 219 220 ASSERT_EQ(-1, link(FULLDST, FULLPATH)); 221 ASSERT_EQ(EIO, errno); 222 } 223 224 TEST_F(Link, ok) 225 { 226 const char FULLPATH[] = "mountpoint/src"; 227 const char RELPATH[] = "src"; 228 const char FULLDST[] = "mountpoint/dst"; 229 const char RELDST[] = "dst"; 230 const uint64_t ino = 42; 231 mode_t mode = S_IFREG | 0644; 232 struct stat sb; 233 234 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 235 .WillOnce(Invoke(ReturnErrno(ENOENT))); 236 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 237 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 238 SET_OUT_HEADER_LEN(out, entry); 239 out.body.entry.attr.mode = mode; 240 out.body.entry.nodeid = ino; 241 out.body.entry.attr.nlink = 1; 242 out.body.entry.attr_valid = UINT64_MAX; 243 out.body.entry.entry_valid = UINT64_MAX; 244 }))); 245 expect_link(ino, RELPATH, mode, 2); 246 247 ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno); 248 // Check that the original file's nlink count has increased. 249 ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno); 250 EXPECT_EQ(2ul, sb.st_nlink); 251 } 252 253 TEST_F(Link_7_8, ok) 254 { 255 const char FULLPATH[] = "mountpoint/src"; 256 const char RELPATH[] = "src"; 257 const char FULLDST[] = "mountpoint/dst"; 258 const char RELDST[] = "dst"; 259 const uint64_t ino = 42; 260 mode_t mode = S_IFREG | 0644; 261 struct stat sb; 262 263 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 264 .WillOnce(Invoke(ReturnErrno(ENOENT))); 265 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 266 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 267 SET_OUT_HEADER_LEN(out, entry_7_8); 268 out.body.entry.attr.mode = mode; 269 out.body.entry.nodeid = ino; 270 out.body.entry.attr.nlink = 1; 271 out.body.entry.attr_valid = UINT64_MAX; 272 out.body.entry.entry_valid = UINT64_MAX; 273 }))); 274 expect_link(ino, RELPATH, mode, 2); 275 276 ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno); 277 // Check that the original file's nlink count has increased. 278 ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno); 279 EXPECT_EQ(2ul, sb.st_nlink); 280 } 281