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 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 TEST_F(Link, ok) 178 { 179 const char FULLPATH[] = "mountpoint/src"; 180 const char RELPATH[] = "src"; 181 const char FULLDST[] = "mountpoint/dst"; 182 const char RELDST[] = "dst"; 183 const uint64_t ino = 42; 184 mode_t mode = S_IFREG | 0644; 185 struct stat sb; 186 187 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 188 .WillOnce(Invoke(ReturnErrno(ENOENT))); 189 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 190 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 191 SET_OUT_HEADER_LEN(out, entry); 192 out.body.entry.attr.mode = mode; 193 out.body.entry.nodeid = ino; 194 out.body.entry.attr.nlink = 1; 195 out.body.entry.attr_valid = UINT64_MAX; 196 out.body.entry.entry_valid = UINT64_MAX; 197 }))); 198 expect_link(ino, RELPATH, mode, 2); 199 200 ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno); 201 // Check that the original file's nlink count has increased. 202 ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno); 203 EXPECT_EQ(2ul, sb.st_nlink); 204 } 205 206 TEST_F(Link_7_8, ok) 207 { 208 const char FULLPATH[] = "mountpoint/src"; 209 const char RELPATH[] = "src"; 210 const char FULLDST[] = "mountpoint/dst"; 211 const char RELDST[] = "dst"; 212 const uint64_t ino = 42; 213 mode_t mode = S_IFREG | 0644; 214 struct stat sb; 215 216 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 217 .WillOnce(Invoke(ReturnErrno(ENOENT))); 218 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST) 219 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 220 SET_OUT_HEADER_LEN(out, entry_7_8); 221 out.body.entry.attr.mode = mode; 222 out.body.entry.nodeid = ino; 223 out.body.entry.attr.nlink = 1; 224 out.body.entry.attr_valid = UINT64_MAX; 225 out.body.entry.entry_valid = UINT64_MAX; 226 }))); 227 expect_link(ino, RELPATH, mode, 2); 228 229 ASSERT_EQ(0, link(FULLDST, FULLPATH)) << strerror(errno); 230 // Check that the original file's nlink count has increased. 231 ASSERT_EQ(0, stat(FULLDST, &sb)) << strerror(errno); 232 EXPECT_EQ(2ul, sb.st_nlink); 233 } 234