1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2020 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/time.h> 33 #include <sys/resource.h> 34 35 #include <fcntl.h> 36 #include <signal.h> 37 #include <unistd.h> 38 } 39 40 #include "mockfs.hh" 41 #include "utils.hh" 42 43 using namespace testing; 44 45 class CopyFileRange: public FuseTest { 46 public: 47 static sig_atomic_t s_sigxfsz; 48 49 void SetUp() { 50 s_sigxfsz = 0; 51 FuseTest::SetUp(); 52 } 53 54 void TearDown() { 55 struct sigaction sa; 56 57 bzero(&sa, sizeof(sa)); 58 sa.sa_handler = SIG_DFL; 59 sigaction(SIGXFSZ, &sa, NULL); 60 61 FuseTest::TearDown(); 62 } 63 64 void expect_maybe_lseek(uint64_t ino) 65 { 66 EXPECT_CALL(*m_mock, process( 67 ResultOf([=](auto in) { 68 return (in.header.opcode == FUSE_LSEEK && 69 in.header.nodeid == ino); 70 }, Eq(true)), 71 _) 72 ).Times(AtMost(1)) 73 .WillRepeatedly(Invoke(ReturnErrno(ENOSYS))); 74 } 75 76 void expect_open(uint64_t ino, uint32_t flags, int times, uint64_t fh) 77 { 78 EXPECT_CALL(*m_mock, process( 79 ResultOf([=](auto in) { 80 return (in.header.opcode == FUSE_OPEN && 81 in.header.nodeid == ino); 82 }, Eq(true)), 83 _) 84 ).Times(times) 85 .WillRepeatedly(Invoke( 86 ReturnImmediate([=](auto in __unused, auto& out) { 87 out.header.len = sizeof(out.header); 88 SET_OUT_HEADER_LEN(out, open); 89 out.body.open.fh = fh; 90 out.body.open.open_flags = flags; 91 }))); 92 } 93 94 void expect_write(uint64_t ino, uint64_t offset, uint64_t isize, 95 uint64_t osize, const void *contents) 96 { 97 EXPECT_CALL(*m_mock, process( 98 ResultOf([=](auto in) { 99 const char *buf = (const char*)in.body.bytes + 100 sizeof(struct fuse_write_in); 101 102 return (in.header.opcode == FUSE_WRITE && 103 in.header.nodeid == ino && 104 in.body.write.offset == offset && 105 in.body.write.size == isize && 106 0 == bcmp(buf, contents, isize)); 107 }, Eq(true)), 108 _) 109 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 110 SET_OUT_HEADER_LEN(out, write); 111 out.body.write.size = osize; 112 }))); 113 } 114 115 }; 116 117 sig_atomic_t CopyFileRange::s_sigxfsz = 0; 118 119 void sigxfsz_handler(int __unused sig) { 120 CopyFileRange::s_sigxfsz = 1; 121 } 122 123 124 class CopyFileRange_7_27: public CopyFileRange { 125 public: 126 virtual void SetUp() { 127 m_kernel_minor_version = 27; 128 CopyFileRange::SetUp(); 129 } 130 }; 131 132 TEST_F(CopyFileRange, eio) 133 { 134 const char FULLPATH1[] = "mountpoint/src.txt"; 135 const char RELPATH1[] = "src.txt"; 136 const char FULLPATH2[] = "mountpoint/dst.txt"; 137 const char RELPATH2[] = "dst.txt"; 138 const uint64_t ino1 = 42; 139 const uint64_t ino2 = 43; 140 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 141 const uint64_t fh2 = 0xdeadc0de88c0ffee; 142 off_t fsize1 = 1 << 20; /* 1 MiB */ 143 off_t fsize2 = 1 << 19; /* 512 KiB */ 144 off_t start1 = 1 << 18; 145 off_t start2 = 3 << 17; 146 ssize_t len = 65536; 147 int fd1, fd2; 148 149 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 150 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 151 expect_open(ino1, 0, 1, fh1); 152 expect_open(ino2, 0, 1, fh2); 153 EXPECT_CALL(*m_mock, process( 154 ResultOf([=](auto in) { 155 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 156 in.header.nodeid == ino1 && 157 in.body.copy_file_range.fh_in == fh1 && 158 (off_t)in.body.copy_file_range.off_in == start1 && 159 in.body.copy_file_range.nodeid_out == ino2 && 160 in.body.copy_file_range.fh_out == fh2 && 161 (off_t)in.body.copy_file_range.off_out == start2 && 162 in.body.copy_file_range.len == (size_t)len && 163 in.body.copy_file_range.flags == 0); 164 }, Eq(true)), 165 _) 166 ).WillOnce(Invoke(ReturnErrno(EIO))); 167 168 fd1 = open(FULLPATH1, O_RDONLY); 169 fd2 = open(FULLPATH2, O_WRONLY); 170 ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 171 EXPECT_EQ(EIO, errno); 172 } 173 174 /* 175 * If the server doesn't support FUSE_COPY_FILE_RANGE, the kernel should 176 * fallback to a read/write based implementation. 177 */ 178 TEST_F(CopyFileRange, fallback) 179 { 180 const char FULLPATH1[] = "mountpoint/src.txt"; 181 const char RELPATH1[] = "src.txt"; 182 const char FULLPATH2[] = "mountpoint/dst.txt"; 183 const char RELPATH2[] = "dst.txt"; 184 const uint64_t ino1 = 42; 185 const uint64_t ino2 = 43; 186 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 187 const uint64_t fh2 = 0xdeadc0de88c0ffee; 188 off_t fsize2 = 0; 189 off_t start1 = 0; 190 off_t start2 = 0; 191 const char *contents = "Hello, world!"; 192 ssize_t len; 193 int fd1, fd2; 194 195 len = strlen(contents); 196 197 /* 198 * Ensure that we read to EOF, just so the buffer cache's read size is 199 * predictable. 200 */ 201 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 202 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 203 expect_open(ino1, 0, 1, fh1); 204 expect_open(ino2, 0, 1, fh2); 205 EXPECT_CALL(*m_mock, process( 206 ResultOf([=](auto in) { 207 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 208 in.header.nodeid == ino1 && 209 in.body.copy_file_range.fh_in == fh1 && 210 (off_t)in.body.copy_file_range.off_in == start1 && 211 in.body.copy_file_range.nodeid_out == ino2 && 212 in.body.copy_file_range.fh_out == fh2 && 213 (off_t)in.body.copy_file_range.off_out == start2 && 214 in.body.copy_file_range.len == (size_t)len && 215 in.body.copy_file_range.flags == 0); 216 }, Eq(true)), 217 _) 218 ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 219 expect_maybe_lseek(ino1); 220 expect_read(ino1, start1, len, len, contents, 0); 221 expect_write(ino2, start2, len, len, contents); 222 223 fd1 = open(FULLPATH1, O_RDONLY); 224 ASSERT_GE(fd1, 0); 225 fd2 = open(FULLPATH2, O_WRONLY); 226 ASSERT_GE(fd2, 0); 227 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 228 } 229 230 /* fusefs should respect RLIMIT_FSIZE */ 231 TEST_F(CopyFileRange, rlimit_fsize) 232 { 233 const char FULLPATH1[] = "mountpoint/src.txt"; 234 const char RELPATH1[] = "src.txt"; 235 const char FULLPATH2[] = "mountpoint/dst.txt"; 236 const char RELPATH2[] = "dst.txt"; 237 struct rlimit rl; 238 const uint64_t ino1 = 42; 239 const uint64_t ino2 = 43; 240 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 241 const uint64_t fh2 = 0xdeadc0de88c0ffee; 242 off_t fsize1 = 1 << 20; /* 1 MiB */ 243 off_t fsize2 = 1 << 19; /* 512 KiB */ 244 off_t start1 = 1 << 18; 245 off_t start2 = fsize2; 246 ssize_t len = 65536; 247 int fd1, fd2; 248 249 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 250 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 251 expect_open(ino1, 0, 1, fh1); 252 expect_open(ino2, 0, 1, fh2); 253 EXPECT_CALL(*m_mock, process( 254 ResultOf([=](auto in) { 255 return (in.header.opcode == FUSE_COPY_FILE_RANGE); 256 }, Eq(true)), 257 _) 258 ).Times(0); 259 260 rl.rlim_cur = fsize2; 261 rl.rlim_max = 10 * fsize2; 262 ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 263 ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 264 265 fd1 = open(FULLPATH1, O_RDONLY); 266 fd2 = open(FULLPATH2, O_WRONLY); 267 ASSERT_EQ(-1, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 268 EXPECT_EQ(EFBIG, errno); 269 EXPECT_EQ(1, s_sigxfsz); 270 } 271 272 TEST_F(CopyFileRange, ok) 273 { 274 const char FULLPATH1[] = "mountpoint/src.txt"; 275 const char RELPATH1[] = "src.txt"; 276 const char FULLPATH2[] = "mountpoint/dst.txt"; 277 const char RELPATH2[] = "dst.txt"; 278 const uint64_t ino1 = 42; 279 const uint64_t ino2 = 43; 280 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 281 const uint64_t fh2 = 0xdeadc0de88c0ffee; 282 off_t fsize1 = 1 << 20; /* 1 MiB */ 283 off_t fsize2 = 1 << 19; /* 512 KiB */ 284 off_t start1 = 1 << 18; 285 off_t start2 = 3 << 17; 286 ssize_t len = 65536; 287 int fd1, fd2; 288 289 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, fsize1, 1); 290 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 291 expect_open(ino1, 0, 1, fh1); 292 expect_open(ino2, 0, 1, fh2); 293 EXPECT_CALL(*m_mock, process( 294 ResultOf([=](auto in) { 295 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 296 in.header.nodeid == ino1 && 297 in.body.copy_file_range.fh_in == fh1 && 298 (off_t)in.body.copy_file_range.off_in == start1 && 299 in.body.copy_file_range.nodeid_out == ino2 && 300 in.body.copy_file_range.fh_out == fh2 && 301 (off_t)in.body.copy_file_range.off_out == start2 && 302 in.body.copy_file_range.len == (size_t)len && 303 in.body.copy_file_range.flags == 0); 304 }, Eq(true)), 305 _) 306 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 307 SET_OUT_HEADER_LEN(out, write); 308 out.body.write.size = len; 309 }))); 310 311 fd1 = open(FULLPATH1, O_RDONLY); 312 fd2 = open(FULLPATH2, O_WRONLY); 313 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 314 } 315 316 /* 317 * copy_file_range can make copies within a single file, as long as the ranges 318 * don't overlap. 319 * */ 320 TEST_F(CopyFileRange, same_file) 321 { 322 const char FULLPATH[] = "mountpoint/src.txt"; 323 const char RELPATH[] = "src.txt"; 324 const uint64_t ino = 4; 325 const uint64_t fh = 0xdeadbeefa7ebabe; 326 off_t fsize = 1 << 20; /* 1 MiB */ 327 off_t off_in = 1 << 18; 328 off_t off_out = 3 << 17; 329 ssize_t len = 65536; 330 int fd; 331 332 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 333 expect_open(ino, 0, 1, fh); 334 EXPECT_CALL(*m_mock, process( 335 ResultOf([=](auto in) { 336 return (in.header.opcode == FUSE_COPY_FILE_RANGE && 337 in.header.nodeid == ino && 338 in.body.copy_file_range.fh_in == fh && 339 (off_t)in.body.copy_file_range.off_in == off_in && 340 in.body.copy_file_range.nodeid_out == ino && 341 in.body.copy_file_range.fh_out == fh && 342 (off_t)in.body.copy_file_range.off_out == off_out && 343 in.body.copy_file_range.len == (size_t)len && 344 in.body.copy_file_range.flags == 0); 345 }, Eq(true)), 346 _) 347 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 348 SET_OUT_HEADER_LEN(out, write); 349 out.body.write.size = len; 350 }))); 351 352 fd = open(FULLPATH, O_RDWR); 353 ASSERT_EQ(len, copy_file_range(fd, &off_in, fd, &off_out, len, 0)); 354 } 355 356 /* With older protocol versions, no FUSE_COPY_FILE_RANGE should be attempted */ 357 TEST_F(CopyFileRange_7_27, fallback) 358 { 359 const char FULLPATH1[] = "mountpoint/src.txt"; 360 const char RELPATH1[] = "src.txt"; 361 const char FULLPATH2[] = "mountpoint/dst.txt"; 362 const char RELPATH2[] = "dst.txt"; 363 const uint64_t ino1 = 42; 364 const uint64_t ino2 = 43; 365 const uint64_t fh1 = 0xdeadbeef1a7ebabe; 366 const uint64_t fh2 = 0xdeadc0de88c0ffee; 367 off_t fsize2 = 0; 368 off_t start1 = 0; 369 off_t start2 = 0; 370 const char *contents = "Hello, world!"; 371 ssize_t len; 372 int fd1, fd2; 373 374 len = strlen(contents); 375 376 /* 377 * Ensure that we read to EOF, just so the buffer cache's read size is 378 * predictable. 379 */ 380 expect_lookup(RELPATH1, ino1, S_IFREG | 0644, start1 + len, 1); 381 expect_lookup(RELPATH2, ino2, S_IFREG | 0644, fsize2, 1); 382 expect_open(ino1, 0, 1, fh1); 383 expect_open(ino2, 0, 1, fh2); 384 EXPECT_CALL(*m_mock, process( 385 ResultOf([=](auto in) { 386 return (in.header.opcode == FUSE_COPY_FILE_RANGE); 387 }, Eq(true)), 388 _) 389 ).Times(0); 390 expect_maybe_lseek(ino1); 391 expect_read(ino1, start1, len, len, contents, 0); 392 expect_write(ino2, start2, len, len, contents); 393 394 fd1 = open(FULLPATH1, O_RDONLY); 395 ASSERT_GE(fd1, 0); 396 fd2 = open(FULLPATH2, O_WRONLY); 397 ASSERT_GE(fd2, 0); 398 ASSERT_EQ(len, copy_file_range(fd1, &start1, fd2, &start2, len, 0)); 399 } 400 401 402