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