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