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