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 Symlink: public FuseTest { 41 public: 42 43 void expect_symlink(uint64_t ino, const char *target, const char *relpath) 44 { 45 EXPECT_CALL(*m_mock, process( 46 ResultOf([=](auto in) { 47 const char *name = (const char*)in.body.bytes; 48 const char *linkname = name + strlen(name) + 1; 49 return (in.header.opcode == FUSE_SYMLINK && 50 (0 == strcmp(linkname, target)) && 51 (0 == strcmp(name, relpath))); 52 }, Eq(true)), 53 _) 54 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 55 SET_OUT_HEADER_LEN(out, entry); 56 out.body.entry.attr.mode = S_IFLNK | 0777; 57 out.body.entry.nodeid = ino; 58 out.body.entry.entry_valid = UINT64_MAX; 59 out.body.entry.attr_valid = UINT64_MAX; 60 }))); 61 } 62 63 }; 64 65 class Symlink_7_8: public FuseTest { 66 public: 67 virtual void SetUp() { 68 m_kernel_minor_version = 8; 69 FuseTest::SetUp(); 70 } 71 72 void expect_symlink(uint64_t ino, const char *target, const char *relpath) 73 { 74 EXPECT_CALL(*m_mock, process( 75 ResultOf([=](auto in) { 76 const char *name = (const char*)in.body.bytes; 77 const char *linkname = name + strlen(name) + 1; 78 return (in.header.opcode == FUSE_SYMLINK && 79 (0 == strcmp(linkname, target)) && 80 (0 == strcmp(name, relpath))); 81 }, Eq(true)), 82 _) 83 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 84 SET_OUT_HEADER_LEN(out, entry_7_8); 85 out.body.entry.attr.mode = S_IFLNK | 0777; 86 out.body.entry.nodeid = ino; 87 out.body.entry.entry_valid = UINT64_MAX; 88 out.body.entry.attr_valid = UINT64_MAX; 89 }))); 90 } 91 92 }; 93 94 /* 95 * A successful symlink should clear the parent directory's attribute cache, 96 * because the fuse daemon should update its mtime and ctime 97 */ 98 TEST_F(Symlink, clear_attr_cache) 99 { 100 const char FULLPATH[] = "mountpoint/src"; 101 const char RELPATH[] = "src"; 102 const char dst[] = "dst"; 103 const uint64_t ino = 42; 104 struct stat sb; 105 106 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 107 .WillOnce(Invoke(ReturnErrno(ENOENT))); 108 EXPECT_CALL(*m_mock, process( 109 ResultOf([=](auto in) { 110 return (in.header.opcode == FUSE_GETATTR && 111 in.header.nodeid == FUSE_ROOT_ID); 112 }, Eq(true)), 113 _) 114 ).Times(2) 115 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 116 SET_OUT_HEADER_LEN(out, attr); 117 out.body.attr.attr.ino = FUSE_ROOT_ID; 118 out.body.attr.attr.mode = S_IFDIR | 0755; 119 out.body.attr.attr_valid = UINT64_MAX; 120 }))); 121 expect_symlink(ino, dst, RELPATH); 122 123 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 124 EXPECT_EQ(0, symlink(dst, FULLPATH)) << strerror(errno); 125 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 126 } 127 128 TEST_F(Symlink, enospc) 129 { 130 const char FULLPATH[] = "mountpoint/lnk"; 131 const char RELPATH[] = "lnk"; 132 const char dst[] = "dst"; 133 134 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 135 .WillOnce(Invoke(ReturnErrno(ENOENT))); 136 137 EXPECT_CALL(*m_mock, process( 138 ResultOf([=](auto in) { 139 const char *name = (const char*)in.body.bytes; 140 const char *linkname = name + strlen(name) + 1; 141 return (in.header.opcode == FUSE_SYMLINK && 142 (0 == strcmp(linkname, dst)) && 143 (0 == strcmp(name, RELPATH))); 144 }, Eq(true)), 145 _) 146 ).WillOnce(Invoke(ReturnErrno(ENOSPC))); 147 148 EXPECT_EQ(-1, symlink(dst, FULLPATH)); 149 EXPECT_EQ(ENOSPC, errno); 150 } 151 152 TEST_F(Symlink, ok) 153 { 154 const char FULLPATH[] = "mountpoint/src"; 155 const char RELPATH[] = "src"; 156 const char dst[] = "dst"; 157 const uint64_t ino = 42; 158 159 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 160 .WillOnce(Invoke(ReturnErrno(ENOENT))); 161 expect_symlink(ino, dst, RELPATH); 162 163 EXPECT_EQ(0, symlink(dst, FULLPATH)) << strerror(errno); 164 } 165 166 /* 167 * Nothing bad should happen if the server returns the parent's inode number 168 * for the newly created symlink. Regression test for bug 263662. 169 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263662 170 */ 171 TEST_F(Symlink, parent_ino) 172 { 173 const char FULLPATH[] = "mountpoint/parent/src"; 174 const char PPATH[] = "parent"; 175 const char RELPATH[] = "src"; 176 const char dst[] = "dst"; 177 const uint64_t ino = 42; 178 179 expect_lookup(PPATH, ino, S_IFDIR | 0755, 0, 1); 180 EXPECT_LOOKUP(ino, RELPATH) 181 .WillOnce(Invoke(ReturnErrno(ENOENT))); 182 expect_symlink(ino, dst, RELPATH); 183 184 EXPECT_EQ(-1, symlink(dst, FULLPATH)); 185 EXPECT_EQ(EIO, errno); 186 } 187 188 TEST_F(Symlink_7_8, ok) 189 { 190 const char FULLPATH[] = "mountpoint/src"; 191 const char RELPATH[] = "src"; 192 const char dst[] = "dst"; 193 const uint64_t ino = 42; 194 195 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 196 .WillOnce(Invoke(ReturnErrno(ENOENT))); 197 expect_symlink(ino, dst, RELPATH); 198 199 EXPECT_EQ(0, symlink(dst, FULLPATH)) << strerror(errno); 200 } 201