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 <fcntl.h> 33 #include <unistd.h> 34 } 35 36 #include "mockfs.hh" 37 #include "utils.hh" 38 39 using namespace testing; 40 41 class Release: public FuseTest { 42 43 public: 44 void expect_lookup(const char *relpath, uint64_t ino, int times) 45 { 46 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, times); 47 } 48 49 void expect_release(uint64_t ino, uint64_t lock_owner, 50 uint32_t flags, int error) 51 { 52 EXPECT_CALL(*m_mock, process( 53 ResultOf([=](auto in) { 54 return (in.header.opcode == FUSE_RELEASE && 55 in.header.nodeid == ino && 56 in.body.release.lock_owner == lock_owner && 57 in.body.release.fh == FH && 58 in.body.release.flags == flags); 59 }, Eq(true)), 60 _) 61 ).WillOnce(Invoke(ReturnErrno(error))) 62 .RetiresOnSaturation(); 63 } 64 }; 65 66 class ReleaseWithLocks: public Release { 67 virtual void SetUp() { 68 m_init_flags = FUSE_POSIX_LOCKS; 69 Release::SetUp(); 70 } 71 }; 72 73 74 /* If a file descriptor is duplicated, only the last close causes RELEASE */ 75 TEST_F(Release, dup) 76 { 77 const char FULLPATH[] = "mountpoint/some_file.txt"; 78 const char RELPATH[] = "some_file.txt"; 79 uint64_t ino = 42; 80 int fd, fd2; 81 82 expect_lookup(RELPATH, ino, 1); 83 expect_open(ino, 0, 1); 84 expect_flush(ino, 1, ReturnErrno(0)); 85 expect_release(ino, getpid(), O_RDONLY, 0); 86 87 fd = open(FULLPATH, O_RDONLY); 88 ASSERT_LE(0, fd) << strerror(errno); 89 90 fd2 = dup(fd); 91 ASSERT_LE(0, fd2) << strerror(errno); 92 93 ASSERT_EQ(0, close(fd2)) << strerror(errno); 94 ASSERT_EQ(0, close(fd)) << strerror(errno); 95 } 96 97 /* 98 * Some FUSE filesystem cache data internally and flush it on release. Such 99 * filesystems may generate errors during release. On Linux, these get 100 * returned by close(2). However, POSIX does not require close(2) to return 101 * this error. FreeBSD's fuse(4) should return EIO if it returns an error at 102 * all. 103 */ 104 /* http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html */ 105 TEST_F(Release, eio) 106 { 107 const char FULLPATH[] = "mountpoint/some_file.txt"; 108 const char RELPATH[] = "some_file.txt"; 109 uint64_t ino = 42; 110 int fd; 111 112 expect_lookup(RELPATH, ino, 1); 113 expect_open(ino, 0, 1); 114 expect_flush(ino, 1, ReturnErrno(0)); 115 expect_release(ino, getpid(), O_WRONLY, EIO); 116 117 fd = open(FULLPATH, O_WRONLY); 118 ASSERT_LE(0, fd) << strerror(errno); 119 120 ASSERT_TRUE(0 == close(fd) || errno == EIO) << strerror(errno); 121 } 122 123 /* 124 * FUSE_RELEASE should contain the same flags used for FUSE_OPEN 125 */ 126 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 127 TEST_F(Release, DISABLED_flags) 128 { 129 const char FULLPATH[] = "mountpoint/some_file.txt"; 130 const char RELPATH[] = "some_file.txt"; 131 uint64_t ino = 42; 132 int fd; 133 134 expect_lookup(RELPATH, ino, 1); 135 expect_open(ino, 0, 1); 136 expect_flush(ino, 1, ReturnErrno(0)); 137 expect_release(ino, getpid(), O_RDWR | O_APPEND, 0); 138 139 fd = open(FULLPATH, O_RDWR | O_APPEND); 140 ASSERT_LE(0, fd) << strerror(errno); 141 142 ASSERT_EQ(0, close(fd)) << strerror(errno); 143 } 144 145 /* 146 * fuse(4) will issue multiple FUSE_OPEN operations for the same file if it's 147 * opened with different modes. Each FUSE_OPEN should get its own 148 * FUSE_RELEASE. 149 */ 150 TEST_F(Release, multiple_opens) 151 { 152 const char FULLPATH[] = "mountpoint/some_file.txt"; 153 const char RELPATH[] = "some_file.txt"; 154 uint64_t ino = 42; 155 int fd, fd2; 156 157 expect_lookup(RELPATH, ino, 2); 158 expect_open(ino, 0, 2); 159 expect_flush(ino, 2, ReturnErrno(0)); 160 expect_release(ino, getpid(), O_RDONLY, 0); 161 162 fd = open(FULLPATH, O_RDONLY); 163 ASSERT_LE(0, fd) << strerror(errno); 164 165 expect_release(ino, getpid(), O_WRONLY, 0); 166 fd2 = open(FULLPATH, O_WRONLY); 167 ASSERT_LE(0, fd2) << strerror(errno); 168 169 ASSERT_EQ(0, close(fd2)) << strerror(errno); 170 ASSERT_EQ(0, close(fd)) << strerror(errno); 171 } 172 173 TEST_F(Release, ok) 174 { 175 const char FULLPATH[] = "mountpoint/some_file.txt"; 176 const char RELPATH[] = "some_file.txt"; 177 uint64_t ino = 42; 178 int fd; 179 180 expect_lookup(RELPATH, ino, 1); 181 expect_open(ino, 0, 1); 182 expect_flush(ino, 1, ReturnErrno(0)); 183 expect_release(ino, getpid(), O_RDONLY, 0); 184 185 fd = open(FULLPATH, O_RDONLY); 186 ASSERT_LE(0, fd) << strerror(errno); 187 188 ASSERT_EQ(0, close(fd)) << strerror(errno); 189 } 190 191 /* When closing a file with a POSIX file lock, release should release the lock*/ 192 TEST_F(ReleaseWithLocks, unlock_on_close) 193 { 194 const char FULLPATH[] = "mountpoint/some_file.txt"; 195 const char RELPATH[] = "some_file.txt"; 196 uint64_t ino = 42; 197 int fd; 198 struct flock fl; 199 pid_t pid = getpid(); 200 201 expect_lookup(RELPATH, ino, 1); 202 expect_open(ino, 0, 1); 203 EXPECT_CALL(*m_mock, process( 204 ResultOf([=](auto in) { 205 return (in.header.opcode == FUSE_SETLK && 206 in.header.nodeid == ino && 207 in.body.setlk.lk.type == F_RDLCK && 208 in.body.setlk.fh == FH); 209 }, Eq(true)), 210 _) 211 ).WillOnce(Invoke(ReturnErrno(0))); 212 EXPECT_CALL(*m_mock, process( 213 ResultOf([=](auto in) { 214 return (in.header.opcode == FUSE_SETLK && 215 in.header.nodeid == ino && 216 in.body.setlk.lk.type == F_UNLCK && 217 in.body.setlk.fh == FH); 218 }, Eq(true)), 219 _) 220 ).WillOnce(Invoke(ReturnErrno(0))); 221 expect_flush(ino, 1, ReturnErrno(0)); 222 expect_release(ino, static_cast<uint64_t>(pid), O_RDWR, 0); 223 224 fd = open(FULLPATH, O_RDWR); 225 ASSERT_LE(0, fd) << strerror(errno); 226 fl.l_start = 0; 227 fl.l_len = 0; 228 fl.l_pid = pid; 229 fl.l_type = F_RDLCK; 230 fl.l_whence = SEEK_SET; 231 fl.l_sysid = 0; 232 ASSERT_NE(-1, fcntl(fd, F_SETLK, &fl)) << strerror(errno); 233 234 ASSERT_EQ(0, close(fd)) << strerror(errno); 235 } 236