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