1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2021 Alan Somers 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 * 27 * $FreeBSD$ 28 */ 29 30 extern "C" { 31 #include <sys/param.h> 32 #include <sys/mount.h> 33 #include <sys/resource.h> 34 #include <sys/time.h> 35 36 #include <fcntl.h> 37 #include <signal.h> 38 #include <unistd.h> 39 40 #include "mntopts.h" // for build_iovec 41 } 42 43 #include "mockfs.hh" 44 #include "utils.hh" 45 46 using namespace testing; 47 48 class Fallocate: public FuseTest{}; 49 50 class PosixFallocate: public Fallocate { 51 public: 52 static sig_atomic_t s_sigxfsz; 53 54 void SetUp() { 55 s_sigxfsz = 0; 56 FuseTest::SetUp(); 57 } 58 59 void TearDown() { 60 struct sigaction sa; 61 62 bzero(&sa, sizeof(sa)); 63 sa.sa_handler = SIG_DFL; 64 sigaction(SIGXFSZ, &sa, NULL); 65 66 Fallocate::TearDown(); 67 } 68 69 }; 70 71 sig_atomic_t PosixFallocate::s_sigxfsz = 0; 72 73 void sigxfsz_handler(int __unused sig) { 74 PosixFallocate::s_sigxfsz = 1; 75 } 76 77 class PosixFallocate_7_18: public PosixFallocate { 78 public: 79 virtual void SetUp() { 80 m_kernel_minor_version = 18; 81 PosixFallocate::SetUp(); 82 } 83 }; 84 85 86 /* 87 * If the server returns ENOSYS, it indicates that the server does not support 88 * FUSE_FALLOCATE. This and future calls should return EINVAL. 89 */ 90 TEST_F(PosixFallocate, enosys) 91 { 92 const char FULLPATH[] = "mountpoint/some_file.txt"; 93 const char RELPATH[] = "some_file.txt"; 94 uint64_t ino = 42; 95 uint64_t offset = 0; 96 uint64_t length = 1000; 97 int fd; 98 99 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 100 expect_open(ino, 0, 1); 101 expect_fallocate(ino, offset, length, 0, ENOSYS); 102 103 fd = open(FULLPATH, O_RDWR); 104 ASSERT_LE(0, fd) << strerror(errno); 105 EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); 106 107 /* Subsequent calls shouldn't query the daemon*/ 108 EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); 109 110 leak(fd); 111 } 112 113 /* 114 * EOPNOTSUPP means either "the file system does not support fallocate" or "the 115 * file system does not support fallocate with the supplied mode". fusefs 116 * should conservatively assume the latter, and not issue any more fallocate 117 * operations with the same mode. 118 */ 119 TEST_F(PosixFallocate, eopnotsupp) 120 { 121 const char FULLPATH[] = "mountpoint/some_file.txt"; 122 const char RELPATH[] = "some_file.txt"; 123 uint64_t ino = 42; 124 uint64_t offset = 0; 125 uint64_t length = 1000; 126 int fd; 127 128 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 129 expect_open(ino, 0, 1); 130 expect_fallocate(ino, offset, length, 0, EOPNOTSUPP); 131 132 fd = open(FULLPATH, O_RDWR); 133 ASSERT_LE(0, fd) << strerror(errno); 134 EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); 135 136 /* Subsequent calls shouldn't query the daemon*/ 137 EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); 138 139 leak(fd); 140 } 141 142 /* EIO is not a permanent error, and may be retried */ 143 TEST_F(PosixFallocate, eio) 144 { 145 const char FULLPATH[] = "mountpoint/some_file.txt"; 146 const char RELPATH[] = "some_file.txt"; 147 uint64_t ino = 42; 148 uint64_t offset = 0; 149 uint64_t length = 1000; 150 int fd; 151 152 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 153 expect_open(ino, 0, 1); 154 expect_fallocate(ino, offset, length, 0, EIO); 155 156 fd = open(FULLPATH, O_RDWR); 157 ASSERT_LE(0, fd) << strerror(errno); 158 EXPECT_EQ(EIO, posix_fallocate(fd, offset, length)); 159 160 expect_fallocate(ino, offset, length, 0, 0); 161 162 EXPECT_EQ(0, posix_fallocate(fd, offset, length)); 163 164 leak(fd); 165 } 166 167 TEST_F(PosixFallocate, erofs) 168 { 169 const char FULLPATH[] = "mountpoint/some_file.txt"; 170 const char RELPATH[] = "some_file.txt"; 171 struct statfs statbuf; 172 struct iovec *iov = NULL; 173 int iovlen = 0; 174 uint64_t ino = 42; 175 uint64_t offset = 0; 176 uint64_t length = 1000; 177 int fd; 178 int newflags; 179 180 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 181 expect_open(ino, 0, 1); 182 EXPECT_CALL(*m_mock, process( 183 ResultOf([](auto in) { 184 return (in.header.opcode == FUSE_STATFS); 185 }, Eq(true)), 186 _) 187 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) 188 { 189 /* 190 * All of the fields except f_flags are don't care, and f_flags 191 * is set by the VFS 192 */ 193 SET_OUT_HEADER_LEN(out, statfs); 194 }))); 195 196 fd = open(FULLPATH, O_RDWR); 197 ASSERT_LE(0, fd) << strerror(errno); 198 199 /* Remount read-only */ 200 ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); 201 newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY; 202 build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1); 203 build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1); 204 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); 205 ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno); 206 207 EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length)); 208 209 leak(fd); 210 } 211 212 TEST_F(PosixFallocate, ok) 213 { 214 const char FULLPATH[] = "mountpoint/some_file.txt"; 215 const char RELPATH[] = "some_file.txt"; 216 struct stat sb0, sb1; 217 uint64_t ino = 42; 218 uint64_t offset = 0; 219 uint64_t length = 1000; 220 int fd; 221 222 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 223 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 224 SET_OUT_HEADER_LEN(out, entry); 225 out.body.entry.attr.mode = S_IFREG | 0644; 226 out.body.entry.nodeid = ino; 227 out.body.entry.entry_valid = UINT64_MAX; 228 out.body.entry.attr_valid = UINT64_MAX; 229 }))); 230 expect_open(ino, 0, 1); 231 expect_fallocate(ino, offset, length, 0, 0); 232 233 fd = open(FULLPATH, O_RDWR); 234 ASSERT_LE(0, fd) << strerror(errno); 235 ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno); 236 EXPECT_EQ(0, posix_fallocate(fd, offset, length)); 237 /* 238 * Despite the originally cached file size of zero, stat should now 239 * return either the new size or requery the daemon. 240 */ 241 EXPECT_EQ(0, stat(FULLPATH, &sb1)); 242 EXPECT_EQ(length, (uint64_t)sb1.st_size); 243 244 /* mtime and ctime should be updated */ 245 EXPECT_EQ(sb0.st_atime, sb1.st_atime); 246 EXPECT_NE(sb0.st_mtime, sb1.st_mtime); 247 EXPECT_NE(sb0.st_ctime, sb1.st_ctime); 248 249 leak(fd); 250 } 251 252 /* fusefs should respect RLIMIT_FSIZE */ 253 TEST_F(PosixFallocate, rlimit_fsize) 254 { 255 const char FULLPATH[] = "mountpoint/some_file.txt"; 256 const char RELPATH[] = "some_file.txt"; 257 struct rlimit rl; 258 uint64_t ino = 42; 259 uint64_t offset = 0; 260 uint64_t length = 1'000'000; 261 int fd; 262 263 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 264 expect_open(ino, 0, 1); 265 266 rl.rlim_cur = length / 2; 267 rl.rlim_max = 10 * length; 268 ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 269 ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 270 271 fd = open(FULLPATH, O_RDWR); 272 ASSERT_LE(0, fd) << strerror(errno); 273 EXPECT_EQ(EFBIG, posix_fallocate(fd, offset, length)); 274 EXPECT_EQ(1, s_sigxfsz); 275 276 leak(fd); 277 } 278 279 /* With older servers, no FUSE_FALLOCATE should be attempted */ 280 TEST_F(PosixFallocate_7_18, einval) 281 { 282 const char FULLPATH[] = "mountpoint/some_file.txt"; 283 const char RELPATH[] = "some_file.txt"; 284 uint64_t ino = 42; 285 uint64_t offset = 0; 286 uint64_t length = 1000; 287 int fd; 288 289 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 290 expect_open(ino, 0, 1); 291 292 fd = open(FULLPATH, O_RDWR); 293 ASSERT_LE(0, fd) << strerror(errno); 294 EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); 295 296 leak(fd); 297 } 298