xref: /freebsd/tests/sys/fs/fusefs/fallocate.cc (revision a123502ef781ea052e842af25c7aeb64fd9fe217)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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 
28 extern "C" {
29 #include <sys/param.h>
30 #include <sys/mount.h>
31 #include <sys/resource.h>
32 #include <sys/time.h>
33 
34 #include <fcntl.h>
35 #include <mntopts.h>	// for build_iovec
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 /* Is buf all zero? */
46 static bool
is_zero(const char * buf,uint64_t size)47 is_zero(const char *buf, uint64_t size)
48 {
49     return buf[0] == 0 && !memcmp(buf, buf + 1, size - 1);
50 }
51 
52 class Fallocate: public FuseTest {
53 public:
54 /*
55  * expect VOP_DEALLOCATE to be implemented by vop_stddeallocate.
56  */
expect_vop_stddeallocate(uint64_t ino,uint64_t off,uint64_t length)57 void expect_vop_stddeallocate(uint64_t ino, uint64_t off, uint64_t length)
58 {
59 	/* XXX read offset and size may depend on cache mode */
60 	EXPECT_CALL(*m_mock, process(
61 		ResultOf([=](auto in) {
62 			return (in.header.opcode == FUSE_READ &&
63 				in.header.nodeid == ino &&
64 				in.body.read.offset <= off &&
65 				in.body.read.offset + in.body.read.size >=
66 					off + length);
67 		}, Eq(true)),
68 		_)
69 	).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
70 		assert(in.body.read.size <= sizeof(out.body.bytes));
71 		out.header.len = sizeof(struct fuse_out_header) +
72 			in.body.read.size;
73 		memset(out.body.bytes, 'X', in.body.read.size);
74 	}))).RetiresOnSaturation();
75 	EXPECT_CALL(*m_mock, process(
76 		ResultOf([=](auto in) {
77 			const char *buf = (const char*)in.body.bytes +
78 				sizeof(struct fuse_write_in);
79 
80 			assert(length <= sizeof(in.body.bytes) -
81 				sizeof(struct fuse_write_in));
82 			return (in.header.opcode == FUSE_WRITE &&
83 				in.header.nodeid == ino &&
84 				in.body.write.offset == off  &&
85 				in.body.write.size == length &&
86 				is_zero(buf, length));
87 		}, Eq(true)),
88 		_)
89 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
90 		SET_OUT_HEADER_LEN(out, write);
91 		out.body.write.size = length;
92 	})));
93 }
94 };
95 
96 class Fspacectl: public Fallocate {};
97 
98 class Fspacectl_7_18: public Fspacectl {
99 public:
SetUp()100 virtual void SetUp() {
101 	m_kernel_minor_version = 18;
102 	Fspacectl::SetUp();
103 }
104 };
105 
106 class FspacectlCache: public Fspacectl, public WithParamInterface<cache_mode> {
107 public:
108 bool m_direct_io;
109 
FspacectlCache()110 FspacectlCache(): m_direct_io(false) {};
111 
SetUp()112 virtual void SetUp() {
113 	int cache_mode = GetParam();
114 	switch (cache_mode) {
115 		case Uncached:
116 			m_direct_io = true;
117 			break;
118 		case WritebackAsync:
119 			m_async = true;
120 			/* FALLTHROUGH */
121 		case Writeback:
122 			m_init_flags |= FUSE_WRITEBACK_CACHE;
123 			/* FALLTHROUGH */
124 		case Writethrough:
125 			break;
126 		default:
127 			FAIL() << "Unknown cache mode";
128 	}
129 
130 	FuseTest::SetUp();
131 	if (IsSkipped())
132 		return;
133 }
134 };
135 
136 class PosixFallocate: public Fallocate {
137 public:
138 static sig_atomic_t s_sigxfsz;
139 
SetUp()140 void SetUp() {
141 	s_sigxfsz = 0;
142 	FuseTest::SetUp();
143 }
144 
TearDown()145 void TearDown() {
146 	struct sigaction sa;
147 
148 	bzero(&sa, sizeof(sa));
149 	sa.sa_handler = SIG_DFL;
150 	sigaction(SIGXFSZ, &sa, NULL);
151 
152 	Fallocate::TearDown();
153 }
154 
155 };
156 
157 sig_atomic_t PosixFallocate::s_sigxfsz = 0;
158 
sigxfsz_handler(int __unused sig)159 void sigxfsz_handler(int __unused sig) {
160 	PosixFallocate::s_sigxfsz = 1;
161 }
162 
163 class PosixFallocate_7_18: public PosixFallocate {
164 public:
SetUp()165 virtual void SetUp() {
166 	m_kernel_minor_version = 18;
167 	PosixFallocate::SetUp();
168 }
169 };
170 
171 
172 /*
173  * If the server returns ENOSYS, it indicates that the server does not support
174  * FUSE_FALLOCATE.  This and future calls should fall back to vop_stddeallocate.
175  */
TEST_F(Fspacectl,enosys)176 TEST_F(Fspacectl, enosys)
177 {
178 	const char FULLPATH[] = "mountpoint/some_file.txt";
179 	const char RELPATH[] = "some_file.txt";
180 	off_t fsize = 1 << 20;
181 	off_t off0 = 100;
182 	off_t len0 = 500;
183 	struct spacectl_range rqsr = { .r_offset = off0, .r_len = len0 };
184 	uint64_t ino = 42;
185 	uint64_t off1 = fsize;
186 	uint64_t len1 = 1000;
187 	off_t off2 = fsize / 2;
188 	off_t len2 = 500;
189 	int fd;
190 
191 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
192 	expect_open(ino, 0, 1);
193 	expect_fallocate(ino, off0, len0,
194 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, ENOSYS);
195 	expect_vop_stddeallocate(ino, off0, len0);
196 	expect_vop_stddeallocate(ino, off2, len2);
197 
198 	fd = open(FULLPATH, O_RDWR);
199 	ASSERT_LE(0, fd) << strerror(errno);
200 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
201 
202 	/* Subsequent calls shouldn't query the daemon either */
203 	rqsr.r_offset = off2;
204 	rqsr.r_len = len2;
205 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
206 
207 	/* Neither should posix_fallocate query the daemon */
208 	EXPECT_EQ(EINVAL, posix_fallocate(fd, off1, len1));
209 
210 	leak(fd);
211 }
212 
213 /*
214  * EOPNOTSUPP means "the file system does not support fallocate with the
215  * supplied mode on this particular file".  So we should fallback, but not
216  * assume anything about whether the operation will fail on a different file or
217  * with a different mode.
218  */
TEST_F(Fspacectl,eopnotsupp)219 TEST_F(Fspacectl, eopnotsupp)
220 {
221 	const char FULLPATH[] = "mountpoint/some_file.txt";
222 	const char RELPATH[] = "some_file.txt";
223 	struct spacectl_range rqsr;
224 	uint64_t ino = 42;
225 	uint64_t fsize = 1 << 20;
226 	uint64_t off0 = 500;
227 	uint64_t len = 1000;
228 	uint64_t off1 = fsize / 2;
229 	int fd;
230 
231 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
232 	expect_open(ino, 0, 1);
233 	expect_fallocate(ino, off0, len,
234 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE,
235 	                EOPNOTSUPP);
236 	expect_vop_stddeallocate(ino, off0, len);
237 	expect_fallocate(ino, off1, len,
238 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE,
239 	                EOPNOTSUPP);
240 	expect_vop_stddeallocate(ino, off1, len);
241 	expect_fallocate(ino, fsize, len, 0, 0);
242 
243 	fd = open(FULLPATH, O_RDWR);
244 	ASSERT_LE(0, fd) << strerror(errno);
245 
246 	/*
247 	 * Though the FUSE daemon will reject the call, the kernel should fall
248 	 * back to a read-modify-write approach.
249 	 */
250 	rqsr.r_offset = off0;
251 	rqsr.r_len = len;
252 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
253 
254 	/* Subsequent calls should still query the daemon */
255 	rqsr.r_offset = off1;
256 	rqsr.r_len = len;
257 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
258 
259 	/* But subsequent posix_fallocate calls _should_ query the daemon */
260 	EXPECT_EQ(0, posix_fallocate(fd, fsize, len));
261 
262 	leak(fd);
263 }
264 
TEST_F(Fspacectl,erofs)265 TEST_F(Fspacectl, erofs)
266 {
267 	const char FULLPATH[] = "mountpoint/some_file.txt";
268 	const char RELPATH[] = "some_file.txt";
269 	struct statfs statbuf;
270 	uint64_t fsize = 2000;
271 	struct spacectl_range rqsr = { .r_offset = 0, .r_len = 1 };
272 	struct iovec *iov = NULL;
273 	int iovlen = 0;
274 	uint64_t ino = 42;
275 	int fd;
276 	int newflags;
277 
278 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
279 	expect_open(ino, 0, 1);
280 	EXPECT_CALL(*m_mock, process(
281 		ResultOf([](auto in) {
282 			return (in.header.opcode == FUSE_STATFS);
283 		}, Eq(true)),
284 		_)
285 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
286 	{
287 		/*
288 		 * All of the fields except f_flags are don't care, and f_flags
289 		 * is set by the VFS
290 		 */
291 		SET_OUT_HEADER_LEN(out, statfs);
292 	})));
293 
294 	fd = open(FULLPATH, O_RDWR);
295 	ASSERT_LE(0, fd) << strerror(errno);
296 
297 	/* Remount read-only */
298 	ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
299 	newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY;
300 	build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1);
301 	build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1);
302 	build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
303 	ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno);
304 	free_iovec(&iov, &iovlen);
305 
306 	EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
307 	EXPECT_EQ(EROFS, errno);
308 
309 	leak(fd);
310 }
311 
312 /*
313  * If FUSE_GETATTR fails when determining the size of the file, fspacectl
314  * should fail gracefully.  This failure mode is easiest to trigger when
315  * attribute caching is disabled.
316  */
TEST_F(Fspacectl,getattr_fails)317 TEST_F(Fspacectl, getattr_fails)
318 {
319 	const char FULLPATH[] = "mountpoint/some_file.txt";
320 	const char RELPATH[] = "some_file.txt";
321 	Sequence seq;
322 	struct spacectl_range rqsr;
323 	const uint64_t ino = 42;
324 	const uint64_t fsize = 2000;
325 	int fd;
326 
327 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1, 0);
328 	expect_open(ino, 0, 1);
329 	EXPECT_CALL(*m_mock, process(
330 		ResultOf([](auto in) {
331 			return (in.header.opcode == FUSE_GETATTR &&
332 				in.header.nodeid == ino);
333 		}, Eq(true)),
334 		_)
335 	).Times(1)
336 	.InSequence(seq)
337 	.WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
338 		SET_OUT_HEADER_LEN(out, attr);
339 		out.body.attr.attr.ino = ino;
340 		out.body.attr.attr.mode = S_IFREG | 0644;
341 		out.body.attr.attr.size = fsize;
342 		out.body.attr.attr_valid = 0;
343 	})));
344 	EXPECT_CALL(*m_mock, process(
345 		ResultOf([](auto in) {
346 			return (in.header.opcode == FUSE_GETATTR &&
347 				in.header.nodeid == ino);
348 		}, Eq(true)),
349 		_)
350 	).InSequence(seq)
351 	.WillOnce(ReturnErrno(EIO));
352 
353 	fd = open(FULLPATH, O_RDWR);
354 	ASSERT_LE(0, fd) << strerror(errno);
355 	rqsr.r_offset = 500;
356 	rqsr.r_len = 1000;
357 	EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
358 	EXPECT_EQ(EIO, errno);
359 
360 	leak(fd);
361 }
362 
TEST_F(Fspacectl,ok)363 TEST_F(Fspacectl, ok)
364 {
365 	const char FULLPATH[] = "mountpoint/some_file.txt";
366 	const char RELPATH[] = "some_file.txt";
367 	struct spacectl_range rqsr, rmsr;
368 	struct stat sb0, sb1;
369 	uint64_t ino = 42;
370 	uint64_t fsize = 2000;
371 	uint64_t offset = 500;
372 	uint64_t length = 1000;
373 	int fd;
374 
375 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
376 	expect_open(ino, 0, 1);
377 	expect_fallocate(ino, offset, length,
378 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
379 
380 	fd = open(FULLPATH, O_RDWR);
381 	ASSERT_LE(0, fd) << strerror(errno);
382 	ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno);
383 	rqsr.r_offset = offset;
384 	rqsr.r_len = length;
385 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
386 	EXPECT_EQ(0, rmsr.r_len);
387 	EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
388 
389 	/*
390 	 * The file's attributes should not have been invalidated, so this fstat
391 	 * will not requery the daemon.
392 	 */
393 	EXPECT_EQ(0, fstat(fd, &sb1));
394 	EXPECT_EQ(fsize, (uint64_t)sb1.st_size);
395 
396 	/* mtime and ctime should be updated */
397 	EXPECT_EQ(sb0.st_atime, sb1.st_atime);
398 	EXPECT_NE(sb0.st_mtime, sb1.st_mtime);
399 	EXPECT_NE(sb0.st_ctime, sb1.st_ctime);
400 
401 	leak(fd);
402 }
403 
404 /* The returned rqsr.r_off should be clipped at EoF */
TEST_F(Fspacectl,past_eof)405 TEST_F(Fspacectl, past_eof)
406 {
407 	const char FULLPATH[] = "mountpoint/some_file.txt";
408 	const char RELPATH[] = "some_file.txt";
409 	struct spacectl_range rqsr, rmsr;
410 	uint64_t ino = 42;
411 	uint64_t fsize = 1000;
412 	uint64_t offset = 1500;
413 	uint64_t length = 1000;
414 	int fd;
415 
416 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
417 	expect_open(ino, 0, 1);
418 	expect_fallocate(ino, offset, length,
419 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
420 
421 	fd = open(FULLPATH, O_RDWR);
422 	ASSERT_LE(0, fd) << strerror(errno);
423 	rqsr.r_offset = offset;
424 	rqsr.r_len = length;
425 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
426 	EXPECT_EQ(0, rmsr.r_len);
427 	EXPECT_EQ((off_t)fsize, rmsr.r_offset);
428 
429 	leak(fd);
430 }
431 
432 /* The returned rqsr.r_off should be clipped at EoF */
TEST_F(Fspacectl,spans_eof)433 TEST_F(Fspacectl, spans_eof)
434 {
435 	const char FULLPATH[] = "mountpoint/some_file.txt";
436 	const char RELPATH[] = "some_file.txt";
437 	struct spacectl_range rqsr, rmsr;
438 	uint64_t ino = 42;
439 	uint64_t fsize = 1000;
440 	uint64_t offset = 500;
441 	uint64_t length = 1000;
442 	int fd;
443 
444 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
445 	expect_open(ino, 0, 1);
446 	expect_fallocate(ino, offset, length,
447 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
448 
449 	fd = open(FULLPATH, O_RDWR);
450 	ASSERT_LE(0, fd) << strerror(errno);
451 	rqsr.r_offset = offset;
452 	rqsr.r_len = length;
453 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
454 	EXPECT_EQ(0, rmsr.r_len);
455 	EXPECT_EQ((off_t)fsize, rmsr.r_offset);
456 
457 	leak(fd);
458 }
459 
460 /*
461  * With older servers, no FUSE_FALLOCATE should be attempted.  The kernel
462  * should fall back to vop_stddeallocate.
463  */
TEST_F(Fspacectl_7_18,ok)464 TEST_F(Fspacectl_7_18, ok)
465 {
466 	const char FULLPATH[] = "mountpoint/some_file.txt";
467 	const char RELPATH[] = "some_file.txt";
468 	struct spacectl_range rqsr, rmsr;
469 	char *buf;
470 	uint64_t ino = 42;
471 	uint64_t fsize = 2000;
472 	uint64_t offset = 500;
473 	uint64_t length = 1000;
474 	int fd;
475 
476 	buf = new char[length];
477 
478 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
479 	expect_open(ino, 0, 1);
480 	expect_vop_stddeallocate(ino, offset, length);
481 
482 	fd = open(FULLPATH, O_RDWR);
483 	ASSERT_LE(0, fd) << strerror(errno);
484 	rqsr.r_offset = offset;
485 	rqsr.r_len = length;
486 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
487 	EXPECT_EQ(0, rmsr.r_len);
488 	EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
489 
490 	leak(fd);
491 	delete[] buf;
492 }
493 
494 /*
495  * A successful fspacectl should clear the zeroed data from the kernel cache.
496  */
TEST_P(FspacectlCache,clears_cache)497 TEST_P(FspacectlCache, clears_cache)
498 {
499 	const char FULLPATH[] = "mountpoint/some_file.txt";
500 	const char RELPATH[] = "some_file.txt";
501 	const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz";
502 	struct spacectl_range rqsr, rmsr;
503 	uint64_t ino = 42;
504 	ssize_t bufsize = strlen(CONTENTS);
505 	uint64_t fsize = bufsize;
506 	uint8_t buf[bufsize];
507 	char zbuf[bufsize];
508 	uint64_t offset = 0;
509 	uint64_t length = bufsize;
510 	int fd;
511 
512 	bzero(zbuf, bufsize);
513 
514 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
515 	expect_open(ino, 0, 1);
516 	/* NB: expectations are applied in LIFO order */
517 	expect_read(ino, 0, fsize, fsize, zbuf);
518 	expect_read(ino, 0, fsize, fsize, CONTENTS);
519 	expect_fallocate(ino, offset, length,
520 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
521 
522 	fd = open(FULLPATH, O_RDWR);
523 	ASSERT_LE(0, fd) << strerror(errno);
524 
525 	/* Populate the cache */
526 	ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0))
527 		<< strerror(errno);
528 	ASSERT_EQ(0, memcmp(buf, CONTENTS, fsize));
529 
530 	/* Zero the file */
531 	rqsr.r_offset = offset;
532 	rqsr.r_len = length;
533 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
534 	EXPECT_EQ(0, rmsr.r_len);
535 	EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
536 
537 	/* Read again.  This should query the daemon */
538 	ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0))
539 		<< strerror(errno);
540 	ASSERT_EQ(0, memcmp(buf, zbuf, fsize));
541 
542 	leak(fd);
543 }
544 
545 INSTANTIATE_TEST_SUITE_P(FspacectlCache, FspacectlCache,
546 	Values(Uncached, Writethrough, Writeback)
547 );
548 
549 /*
550  * If the server returns ENOSYS, it indicates that the server does not support
551  * FUSE_FALLOCATE.  This and future calls should return EINVAL.
552  */
TEST_F(PosixFallocate,enosys)553 TEST_F(PosixFallocate, enosys)
554 {
555 	const char FULLPATH[] = "mountpoint/some_file.txt";
556 	const char RELPATH[] = "some_file.txt";
557 	uint64_t ino = 42;
558 	uint64_t off0 = 0;
559 	uint64_t len0 = 1000;
560 	off_t off1 = 100;
561 	off_t len1 = 200;
562 	uint64_t fsize = 500;
563 	struct spacectl_range rqsr = { .r_offset = off1, .r_len = len1 };
564 	int fd;
565 
566 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
567 	expect_open(ino, 0, 1);
568 	expect_fallocate(ino, off0, len0, 0, ENOSYS);
569 	expect_vop_stddeallocate(ino, off1, len1);
570 
571 	fd = open(FULLPATH, O_RDWR);
572 	ASSERT_LE(0, fd) << strerror(errno);
573 	EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0));
574 
575 	/* Subsequent calls shouldn't query the daemon*/
576 	EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0));
577 
578 	/* Neither should VOP_DEALLOCATE query the daemon */
579 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
580 
581 	leak(fd);
582 }
583 
584 /*
585  * EOPNOTSUPP means "the file system does not support fallocate with the
586  * supplied mode on this particular file".  So we should fallback, but not
587  * assume anything about whether the operation will fail on a different file or
588  * with a different mode.
589  */
TEST_F(PosixFallocate,eopnotsupp)590 TEST_F(PosixFallocate, eopnotsupp)
591 {
592 	const char FULLPATH[] = "mountpoint/some_file.txt";
593 	const char RELPATH[] = "some_file.txt";
594 	struct spacectl_range rqsr;
595 	uint64_t ino = 42;
596 	uint64_t fsize = 2000;
597 	uint64_t offset = 0;
598 	uint64_t length = 1000;
599 	int fd;
600 
601 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
602 	expect_open(ino, 0, 1);
603 	expect_fallocate(ino, fsize, length, 0, EOPNOTSUPP);
604 	expect_fallocate(ino, offset, length, 0, EOPNOTSUPP);
605 	expect_fallocate(ino, offset, length,
606 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
607 
608 	fd = open(FULLPATH, O_RDWR);
609 	ASSERT_LE(0, fd) << strerror(errno);
610 	EXPECT_EQ(EINVAL, posix_fallocate(fd, fsize, length));
611 
612 	/* Subsequent calls should still query the daemon*/
613 	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
614 
615 	/* And subsequent VOP_DEALLOCATE calls should also query the daemon */
616 	rqsr.r_len = length;
617 	rqsr.r_offset = offset;
618 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
619 
620 	leak(fd);
621 }
622 
623 /* EIO is not a permanent error, and may be retried */
TEST_F(PosixFallocate,eio)624 TEST_F(PosixFallocate, eio)
625 {
626 	const char FULLPATH[] = "mountpoint/some_file.txt";
627 	const char RELPATH[] = "some_file.txt";
628 	uint64_t ino = 42;
629 	uint64_t offset = 0;
630 	uint64_t length = 1000;
631 	int fd;
632 
633 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
634 	expect_open(ino, 0, 1);
635 	expect_fallocate(ino, offset, length, 0, EIO);
636 
637 	fd = open(FULLPATH, O_RDWR);
638 	ASSERT_LE(0, fd) << strerror(errno);
639 	EXPECT_EQ(EIO, posix_fallocate(fd, offset, length));
640 
641 	expect_fallocate(ino, offset, length, 0, 0);
642 
643 	EXPECT_EQ(0, posix_fallocate(fd, offset, length));
644 
645 	leak(fd);
646 }
647 
TEST_F(PosixFallocate,erofs)648 TEST_F(PosixFallocate, erofs)
649 {
650 	const char FULLPATH[] = "mountpoint/some_file.txt";
651 	const char RELPATH[] = "some_file.txt";
652 	struct statfs statbuf;
653 	struct iovec *iov = NULL;
654 	int iovlen = 0;
655 	uint64_t ino = 42;
656 	uint64_t offset = 0;
657 	uint64_t length = 1000;
658 	int fd;
659 	int newflags;
660 
661 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
662 	expect_open(ino, 0, 1);
663 	EXPECT_CALL(*m_mock, process(
664 		ResultOf([](auto in) {
665 			return (in.header.opcode == FUSE_STATFS);
666 		}, Eq(true)),
667 		_)
668 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
669 	{
670 		/*
671 		 * All of the fields except f_flags are don't care, and f_flags
672 		 * is set by the VFS
673 		 */
674 		SET_OUT_HEADER_LEN(out, statfs);
675 	})));
676 
677 	fd = open(FULLPATH, O_RDWR);
678 	ASSERT_LE(0, fd) << strerror(errno);
679 
680 	/* Remount read-only */
681 	ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
682 	newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY;
683 	build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1);
684 	build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1);
685 	build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
686 	ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno);
687 	free_iovec(&iov, &iovlen);
688 
689 	EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length));
690 
691 	leak(fd);
692 }
693 
TEST_F(PosixFallocate,ok)694 TEST_F(PosixFallocate, ok)
695 {
696 	const char FULLPATH[] = "mountpoint/some_file.txt";
697 	const char RELPATH[] = "some_file.txt";
698 	struct stat sb0, sb1;
699 	uint64_t ino = 42;
700 	uint64_t offset = 0;
701 	uint64_t length = 1000;
702 	int fd;
703 
704 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
705 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
706 		SET_OUT_HEADER_LEN(out, entry);
707 		out.body.entry.attr.mode = S_IFREG | 0644;
708 		out.body.entry.nodeid = ino;
709 		out.body.entry.entry_valid = UINT64_MAX;
710 		out.body.entry.attr_valid = UINT64_MAX;
711 	})));
712 	expect_open(ino, 0, 1);
713 	expect_fallocate(ino, offset, length, 0, 0);
714 
715 	fd = open(FULLPATH, O_RDWR);
716 	ASSERT_LE(0, fd) << strerror(errno);
717 	ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno);
718 	EXPECT_EQ(0, posix_fallocate(fd, offset, length));
719 	/*
720 	 * Despite the originally cached file size of zero, stat should now
721 	 * return either the new size or requery the daemon.
722 	 */
723 	EXPECT_EQ(0, stat(FULLPATH, &sb1));
724 	EXPECT_EQ(length, (uint64_t)sb1.st_size);
725 
726 	/* mtime and ctime should be updated */
727 	EXPECT_EQ(sb0.st_atime, sb1.st_atime);
728 	EXPECT_NE(sb0.st_mtime, sb1.st_mtime);
729 	EXPECT_NE(sb0.st_ctime, sb1.st_ctime);
730 
731 	leak(fd);
732 }
733 
734 /* fusefs should respect RLIMIT_FSIZE */
TEST_F(PosixFallocate,rlimit_fsize)735 TEST_F(PosixFallocate, rlimit_fsize)
736 {
737 	const char FULLPATH[] = "mountpoint/some_file.txt";
738 	const char RELPATH[] = "some_file.txt";
739 	struct rlimit rl;
740 	uint64_t ino = 42;
741 	uint64_t offset = 0;
742 	uint64_t length = 1'000'000;
743 	int fd;
744 
745 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
746 	expect_open(ino, 0, 1);
747 
748 	rl.rlim_cur = length / 2;
749 	rl.rlim_max = 10 * length;
750 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
751 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
752 
753 	fd = open(FULLPATH, O_RDWR);
754 	ASSERT_LE(0, fd) << strerror(errno);
755 	EXPECT_EQ(EFBIG, posix_fallocate(fd, offset, length));
756 	EXPECT_EQ(1, s_sigxfsz);
757 
758 	leak(fd);
759 }
760 
761 /* With older servers, no FUSE_FALLOCATE should be attempted */
TEST_F(PosixFallocate_7_18,einval)762 TEST_F(PosixFallocate_7_18, einval)
763 {
764 	const char FULLPATH[] = "mountpoint/some_file.txt";
765 	const char RELPATH[] = "some_file.txt";
766 	uint64_t ino = 42;
767 	uint64_t offset = 0;
768 	uint64_t length = 1000;
769 	int fd;
770 
771 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
772 	expect_open(ino, 0, 1);
773 
774 	fd = open(FULLPATH, O_RDWR);
775 	ASSERT_LE(0, fd) << strerror(errno);
776 	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
777 
778 	leak(fd);
779 }
780