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 * $FreeBSD$ 31 */ 32 33 extern "C" { 34 #include <aio.h> 35 #include <fcntl.h> 36 #include <unistd.h> 37 } 38 39 #include "mockfs.hh" 40 #include "utils.hh" 41 42 using namespace testing; 43 44 /* 45 * TODO: remove FUSE_FSYNC_FDATASYNC definition when upgrading to protocol 7.28. 46 * This bit was actually part of kernel protocol version 5.2, but never 47 * documented until after 7.28 48 */ 49 #ifndef FUSE_FSYNC_FDATASYNC 50 #define FUSE_FSYNC_FDATASYNC 1 51 #endif 52 53 class Fsync: public FuseTest { 54 public: 55 void expect_fsync(uint64_t ino, uint32_t flags, int error, int times = 1) 56 { 57 EXPECT_CALL(*m_mock, process( 58 ResultOf([=](auto in) { 59 return (in.header.opcode == FUSE_FSYNC && 60 in.header.nodeid == ino && 61 /* 62 * TODO: reenable pid check after fixing 63 * bug 236379 64 */ 65 //(pid_t)in.header.pid == getpid() && 66 in.body.fsync.fh == FH && 67 in.body.fsync.fsync_flags == flags); 68 }, Eq(true)), 69 _) 70 ).Times(times) 71 .WillRepeatedly(Invoke(ReturnErrno(error))); 72 } 73 74 void expect_lookup(const char *relpath, uint64_t ino, int times = 1) 75 { 76 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 0, times); 77 } 78 79 void expect_write(uint64_t ino, uint64_t size, const void *contents) 80 { 81 FuseTest::expect_write(ino, 0, size, size, 0, 0, contents); 82 } 83 84 }; 85 86 class AioFsync: public Fsync { 87 virtual void SetUp() { 88 if (!is_unsafe_aio_enabled()) 89 GTEST_SKIP() << 90 "vfs.aio.enable_unsafe must be set for this test"; 91 FuseTest::SetUp(); 92 } 93 }; 94 95 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236379 */ 96 TEST_F(AioFsync, aio_fsync) 97 { 98 const char FULLPATH[] = "mountpoint/some_file.txt"; 99 const char RELPATH[] = "some_file.txt"; 100 const char *CONTENTS = "abcdefgh"; 101 ssize_t bufsize = strlen(CONTENTS); 102 uint64_t ino = 42; 103 struct aiocb iocb, *piocb; 104 int fd; 105 106 expect_lookup(RELPATH, ino); 107 expect_open(ino, 0, 1); 108 expect_write(ino, bufsize, CONTENTS); 109 expect_fsync(ino, 0, 0); 110 111 fd = open(FULLPATH, O_RDWR); 112 ASSERT_LE(0, fd) << strerror(errno); 113 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 114 115 bzero(&iocb, sizeof(iocb)); 116 iocb.aio_fildes = fd; 117 118 ASSERT_EQ(0, aio_fsync(O_SYNC, &iocb)) << strerror(errno); 119 ASSERT_EQ(0, aio_waitcomplete(&piocb, NULL)) << strerror(errno); 120 121 leak(fd); 122 } 123 124 /* 125 * fuse(4) should NOT fsync during VOP_RELEASE or VOP_INACTIVE 126 * 127 * This test only really make sense in writeback caching mode, but it should 128 * still pass in any cache mode. 129 */ 130 TEST_F(Fsync, close) 131 { 132 const char FULLPATH[] = "mountpoint/some_file.txt"; 133 const char RELPATH[] = "some_file.txt"; 134 const char *CONTENTS = "abcdefgh"; 135 ssize_t bufsize = strlen(CONTENTS); 136 uint64_t ino = 42; 137 int fd; 138 139 expect_lookup(RELPATH, ino); 140 expect_open(ino, 0, 1); 141 expect_write(ino, bufsize, CONTENTS); 142 EXPECT_CALL(*m_mock, process( 143 ResultOf([=](auto in) { 144 return (in.header.opcode == FUSE_SETATTR); 145 }, Eq(true)), 146 _) 147 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 148 SET_OUT_HEADER_LEN(out, attr); 149 out.body.attr.attr.ino = ino; // Must match nodeid 150 }))); 151 EXPECT_CALL(*m_mock, process( 152 ResultOf([=](auto in) { 153 return (in.header.opcode == FUSE_FSYNC); 154 }, Eq(true)), 155 _) 156 ).Times(0); 157 expect_flush(ino, 1, ReturnErrno(0)); 158 expect_release(ino, FH); 159 160 fd = open(FULLPATH, O_RDWR); 161 ASSERT_LE(0, fd) << strerror(errno); 162 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 163 close(fd); 164 } 165 166 TEST_F(Fsync, eio) 167 { 168 const char FULLPATH[] = "mountpoint/some_file.txt"; 169 const char RELPATH[] = "some_file.txt"; 170 const char *CONTENTS = "abcdefgh"; 171 ssize_t bufsize = strlen(CONTENTS); 172 uint64_t ino = 42; 173 int fd; 174 175 expect_lookup(RELPATH, ino); 176 expect_open(ino, 0, 1); 177 expect_write(ino, bufsize, CONTENTS); 178 expect_fsync(ino, FUSE_FSYNC_FDATASYNC, EIO); 179 180 fd = open(FULLPATH, O_RDWR); 181 ASSERT_LE(0, fd) << strerror(errno); 182 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 183 ASSERT_NE(0, fdatasync(fd)); 184 ASSERT_EQ(EIO, errno); 185 186 leak(fd); 187 } 188 189 /* 190 * If the filesystem returns ENOSYS, it will be treated as success and 191 * subsequent calls to VOP_FSYNC will succeed automatically without being sent 192 * to the filesystem daemon 193 */ 194 TEST_F(Fsync, enosys) 195 { 196 const char FULLPATH[] = "mountpoint/some_file.txt"; 197 const char RELPATH[] = "some_file.txt"; 198 const char *CONTENTS = "abcdefgh"; 199 ssize_t bufsize = strlen(CONTENTS); 200 uint64_t ino = 42; 201 int fd; 202 203 expect_lookup(RELPATH, ino); 204 expect_open(ino, 0, 1); 205 expect_write(ino, bufsize, CONTENTS); 206 expect_fsync(ino, FUSE_FSYNC_FDATASYNC, ENOSYS); 207 208 fd = open(FULLPATH, O_RDWR); 209 ASSERT_LE(0, fd) << strerror(errno); 210 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 211 EXPECT_EQ(0, fdatasync(fd)); 212 213 /* Subsequent calls shouldn't query the daemon*/ 214 EXPECT_EQ(0, fdatasync(fd)); 215 leak(fd); 216 } 217 218 219 TEST_F(Fsync, fdatasync) 220 { 221 const char FULLPATH[] = "mountpoint/some_file.txt"; 222 const char RELPATH[] = "some_file.txt"; 223 const char *CONTENTS = "abcdefgh"; 224 ssize_t bufsize = strlen(CONTENTS); 225 uint64_t ino = 42; 226 int fd; 227 228 expect_lookup(RELPATH, ino); 229 expect_open(ino, 0, 1); 230 expect_write(ino, bufsize, CONTENTS); 231 expect_fsync(ino, FUSE_FSYNC_FDATASYNC, 0); 232 233 fd = open(FULLPATH, O_RDWR); 234 ASSERT_LE(0, fd) << strerror(errno); 235 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 236 ASSERT_EQ(0, fdatasync(fd)) << strerror(errno); 237 238 leak(fd); 239 } 240 241 TEST_F(Fsync, fsync) 242 { 243 const char FULLPATH[] = "mountpoint/some_file.txt"; 244 const char RELPATH[] = "some_file.txt"; 245 const char *CONTENTS = "abcdefgh"; 246 ssize_t bufsize = strlen(CONTENTS); 247 uint64_t ino = 42; 248 int fd; 249 250 expect_lookup(RELPATH, ino); 251 expect_open(ino, 0, 1); 252 expect_write(ino, bufsize, CONTENTS); 253 expect_fsync(ino, 0, 0); 254 255 fd = open(FULLPATH, O_RDWR); 256 ASSERT_LE(0, fd) << strerror(errno); 257 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 258 ASSERT_EQ(0, fsync(fd)) << strerror(errno); 259 260 leak(fd); 261 } 262 263 /* If multiple FUSE file handles are active, we must fsync them all */ 264 TEST_F(Fsync, two_handles) 265 { 266 const char FULLPATH[] = "mountpoint/some_file.txt"; 267 const char RELPATH[] = "some_file.txt"; 268 const char *CONTENTS = "abcdefgh"; 269 ssize_t bufsize = strlen(CONTENTS); 270 uint64_t ino = 42; 271 int fd1, fd2; 272 273 expect_lookup(RELPATH, ino, 2); 274 expect_open(ino, 0, 2); 275 expect_write(ino, bufsize, CONTENTS); 276 expect_fsync(ino, 0, 0, 2); 277 278 fd1 = open(FULLPATH, O_WRONLY); 279 ASSERT_LE(0, fd1) << strerror(errno); 280 fd2 = open(FULLPATH, O_RDONLY); 281 ASSERT_LE(0, fd2) << strerror(errno); 282 ASSERT_EQ(bufsize, write(fd1, CONTENTS, bufsize)) << strerror(errno); 283 ASSERT_EQ(0, fsync(fd1)) << strerror(errno); 284 285 leak(fd1); 286 leak(fd2); 287 } 288