xref: /freebsd/tests/sys/fs/fusefs/fallocate.cc (revision 1323ec57)
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 /* 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 		out.header.len = sizeof(struct fuse_out_header) +
74 			in.body.read.size;
75 		memset(out.body.bytes, 'X', in.body.read.size);
76 	}))).RetiresOnSaturation();
77 	EXPECT_CALL(*m_mock, process(
78 		ResultOf([=](auto in) {
79 			const char *buf = (const char*)in.body.bytes +
80 				sizeof(struct fuse_write_in);
81 
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:
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 
110 FspacectlCache(): m_direct_io(false) {};
111 
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 
140 void SetUp() {
141 	s_sigxfsz = 0;
142 	FuseTest::SetUp();
143 }
144 
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 
159 void sigxfsz_handler(int __unused sig) {
160 	PosixFallocate::s_sigxfsz = 1;
161 }
162 
163 class PosixFallocate_7_18: public PosixFallocate {
164 public:
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  */
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  */
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 
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 
305 	EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
306 	EXPECT_EQ(EROFS, errno);
307 
308 	leak(fd);
309 }
310 
311 TEST_F(Fspacectl, ok)
312 {
313 	const char FULLPATH[] = "mountpoint/some_file.txt";
314 	const char RELPATH[] = "some_file.txt";
315 	struct spacectl_range rqsr, rmsr;
316 	struct stat sb0, sb1;
317 	uint64_t ino = 42;
318 	uint64_t fsize = 2000;
319 	uint64_t offset = 500;
320 	uint64_t length = 1000;
321 	int fd;
322 
323 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
324 	expect_open(ino, 0, 1);
325 	expect_fallocate(ino, offset, length,
326 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
327 
328 	fd = open(FULLPATH, O_RDWR);
329 	ASSERT_LE(0, fd) << strerror(errno);
330 	ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno);
331 	rqsr.r_offset = offset;
332 	rqsr.r_len = length;
333 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
334 	EXPECT_EQ(0, rmsr.r_len);
335 	EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
336 
337 	/*
338 	 * The file's attributes should not have been invalidated, so this fstat
339 	 * will not requery the daemon.
340 	 */
341 	EXPECT_EQ(0, fstat(fd, &sb1));
342 	EXPECT_EQ(fsize, (uint64_t)sb1.st_size);
343 
344 	/* mtime and ctime should be updated */
345 	EXPECT_EQ(sb0.st_atime, sb1.st_atime);
346 	EXPECT_NE(sb0.st_mtime, sb1.st_mtime);
347 	EXPECT_NE(sb0.st_ctime, sb1.st_ctime);
348 
349 	leak(fd);
350 }
351 
352 /* The returned rqsr.r_off should be clipped at EoF */
353 TEST_F(Fspacectl, past_eof)
354 {
355 	const char FULLPATH[] = "mountpoint/some_file.txt";
356 	const char RELPATH[] = "some_file.txt";
357 	struct spacectl_range rqsr, rmsr;
358 	uint64_t ino = 42;
359 	uint64_t fsize = 1000;
360 	uint64_t offset = 1500;
361 	uint64_t length = 1000;
362 	int fd;
363 
364 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
365 	expect_open(ino, 0, 1);
366 	expect_fallocate(ino, offset, length,
367 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
368 
369 	fd = open(FULLPATH, O_RDWR);
370 	ASSERT_LE(0, fd) << strerror(errno);
371 	rqsr.r_offset = offset;
372 	rqsr.r_len = length;
373 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
374 	EXPECT_EQ(0, rmsr.r_len);
375 	EXPECT_EQ((off_t)fsize, rmsr.r_offset);
376 
377 	leak(fd);
378 }
379 
380 /* The returned rqsr.r_off should be clipped at EoF */
381 TEST_F(Fspacectl, spans_eof)
382 {
383 	const char FULLPATH[] = "mountpoint/some_file.txt";
384 	const char RELPATH[] = "some_file.txt";
385 	struct spacectl_range rqsr, rmsr;
386 	uint64_t ino = 42;
387 	uint64_t fsize = 1000;
388 	uint64_t offset = 500;
389 	uint64_t length = 1000;
390 	int fd;
391 
392 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
393 	expect_open(ino, 0, 1);
394 	expect_fallocate(ino, offset, length,
395 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
396 
397 	fd = open(FULLPATH, O_RDWR);
398 	ASSERT_LE(0, fd) << strerror(errno);
399 	rqsr.r_offset = offset;
400 	rqsr.r_len = length;
401 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
402 	EXPECT_EQ(0, rmsr.r_len);
403 	EXPECT_EQ((off_t)fsize, rmsr.r_offset);
404 
405 	leak(fd);
406 }
407 
408 /*
409  * With older servers, no FUSE_FALLOCATE should be attempted.  The kernel
410  * should fall back to vop_stddeallocate.
411  */
412 TEST_F(Fspacectl_7_18, ok)
413 {
414 	const char FULLPATH[] = "mountpoint/some_file.txt";
415 	const char RELPATH[] = "some_file.txt";
416 	struct spacectl_range rqsr, rmsr;
417 	void *buf;
418 	uint64_t ino = 42;
419 	uint64_t fsize = 2000;
420 	uint64_t offset = 500;
421 	uint64_t length = 1000;
422 	int fd;
423 
424 	buf = malloc(length);
425 
426 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
427 	expect_open(ino, 0, 1);
428 	expect_vop_stddeallocate(ino, offset, length);
429 
430 	fd = open(FULLPATH, O_RDWR);
431 	ASSERT_LE(0, fd) << strerror(errno);
432 	rqsr.r_offset = offset;
433 	rqsr.r_len = length;
434 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
435 	EXPECT_EQ(0, rmsr.r_len);
436 	EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
437 
438 	leak(fd);
439 	free(buf);
440 }
441 
442 /*
443  * A successful fspacectl should clear the zeroed data from the kernel cache.
444  */
445 TEST_P(FspacectlCache, clears_cache)
446 {
447 	const char FULLPATH[] = "mountpoint/some_file.txt";
448 	const char RELPATH[] = "some_file.txt";
449 	const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz";
450 	struct spacectl_range rqsr, rmsr;
451 	uint64_t ino = 42;
452 	ssize_t bufsize = strlen(CONTENTS);
453 	uint64_t fsize = bufsize;
454 	uint8_t buf[bufsize];
455 	char zbuf[bufsize];
456 	uint64_t offset = 0;
457 	uint64_t length = bufsize;
458 	int fd;
459 
460 	bzero(zbuf, bufsize);
461 
462 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
463 	expect_open(ino, 0, 1);
464 	/* NB: expectations are applied in LIFO order */
465 	expect_read(ino, 0, fsize, fsize, zbuf);
466 	expect_read(ino, 0, fsize, fsize, CONTENTS);
467 	expect_fallocate(ino, offset, length,
468 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
469 
470 	fd = open(FULLPATH, O_RDWR);
471 	ASSERT_LE(0, fd) << strerror(errno);
472 
473 	/* Populate the cache */
474 	ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0))
475 		<< strerror(errno);
476 	ASSERT_EQ(0, memcmp(buf, CONTENTS, fsize));
477 
478 	/* Zero the file */
479 	rqsr.r_offset = offset;
480 	rqsr.r_len = length;
481 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
482 	EXPECT_EQ(0, rmsr.r_len);
483 	EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
484 
485 	/* Read again.  This should query the daemon */
486 	ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0))
487 		<< strerror(errno);
488 	ASSERT_EQ(0, memcmp(buf, zbuf, fsize));
489 
490 	leak(fd);
491 }
492 
493 INSTANTIATE_TEST_CASE_P(FspacectlCache, FspacectlCache,
494 	Values(Uncached, Writethrough, Writeback),
495 );
496 
497 /*
498  * If the server returns ENOSYS, it indicates that the server does not support
499  * FUSE_FALLOCATE.  This and future calls should return EINVAL.
500  */
501 TEST_F(PosixFallocate, enosys)
502 {
503 	const char FULLPATH[] = "mountpoint/some_file.txt";
504 	const char RELPATH[] = "some_file.txt";
505 	uint64_t ino = 42;
506 	uint64_t off0 = 0;
507 	uint64_t len0 = 1000;
508 	off_t off1 = 100;
509 	off_t len1 = 200;
510 	uint64_t fsize = 500;
511 	struct spacectl_range rqsr = { .r_offset = off1, .r_len = len1 };
512 	int fd;
513 
514 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
515 	expect_open(ino, 0, 1);
516 	expect_fallocate(ino, off0, len0, 0, ENOSYS);
517 	expect_vop_stddeallocate(ino, off1, len1);
518 
519 	fd = open(FULLPATH, O_RDWR);
520 	ASSERT_LE(0, fd) << strerror(errno);
521 	EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0));
522 
523 	/* Subsequent calls shouldn't query the daemon*/
524 	EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0));
525 
526 	/* Neither should VOP_DEALLOCATE query the daemon */
527 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
528 
529 	leak(fd);
530 }
531 
532 /*
533  * EOPNOTSUPP means "the file system does not support fallocate with the
534  * supplied mode on this particular file".  So we should fallback, but not
535  * assume anything about whether the operation will fail on a different file or
536  * with a different mode.
537  */
538 TEST_F(PosixFallocate, eopnotsupp)
539 {
540 	const char FULLPATH[] = "mountpoint/some_file.txt";
541 	const char RELPATH[] = "some_file.txt";
542 	struct spacectl_range rqsr;
543 	uint64_t ino = 42;
544 	uint64_t fsize = 2000;
545 	uint64_t offset = 0;
546 	uint64_t length = 1000;
547 	int fd;
548 
549 	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
550 	expect_open(ino, 0, 1);
551 	expect_fallocate(ino, fsize, length, 0, EOPNOTSUPP);
552 	expect_fallocate(ino, offset, length, 0, EOPNOTSUPP);
553 	expect_fallocate(ino, offset, length,
554 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
555 
556 	fd = open(FULLPATH, O_RDWR);
557 	ASSERT_LE(0, fd) << strerror(errno);
558 	EXPECT_EQ(EINVAL, posix_fallocate(fd, fsize, length));
559 
560 	/* Subsequent calls should still query the daemon*/
561 	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
562 
563 	/* And subsequent VOP_DEALLOCATE calls should also query the daemon */
564 	rqsr.r_len = length;
565 	rqsr.r_offset = offset;
566 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
567 
568 	leak(fd);
569 }
570 
571 /* EIO is not a permanent error, and may be retried */
572 TEST_F(PosixFallocate, eio)
573 {
574 	const char FULLPATH[] = "mountpoint/some_file.txt";
575 	const char RELPATH[] = "some_file.txt";
576 	uint64_t ino = 42;
577 	uint64_t offset = 0;
578 	uint64_t length = 1000;
579 	int fd;
580 
581 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
582 	expect_open(ino, 0, 1);
583 	expect_fallocate(ino, offset, length, 0, EIO);
584 
585 	fd = open(FULLPATH, O_RDWR);
586 	ASSERT_LE(0, fd) << strerror(errno);
587 	EXPECT_EQ(EIO, posix_fallocate(fd, offset, length));
588 
589 	expect_fallocate(ino, offset, length, 0, 0);
590 
591 	EXPECT_EQ(0, posix_fallocate(fd, offset, length));
592 
593 	leak(fd);
594 }
595 
596 TEST_F(PosixFallocate, erofs)
597 {
598 	const char FULLPATH[] = "mountpoint/some_file.txt";
599 	const char RELPATH[] = "some_file.txt";
600 	struct statfs statbuf;
601 	struct iovec *iov = NULL;
602 	int iovlen = 0;
603 	uint64_t ino = 42;
604 	uint64_t offset = 0;
605 	uint64_t length = 1000;
606 	int fd;
607 	int newflags;
608 
609 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
610 	expect_open(ino, 0, 1);
611 	EXPECT_CALL(*m_mock, process(
612 		ResultOf([](auto in) {
613 			return (in.header.opcode == FUSE_STATFS);
614 		}, Eq(true)),
615 		_)
616 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
617 	{
618 		/*
619 		 * All of the fields except f_flags are don't care, and f_flags
620 		 * is set by the VFS
621 		 */
622 		SET_OUT_HEADER_LEN(out, statfs);
623 	})));
624 
625 	fd = open(FULLPATH, O_RDWR);
626 	ASSERT_LE(0, fd) << strerror(errno);
627 
628 	/* Remount read-only */
629 	ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
630 	newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY;
631 	build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1);
632 	build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1);
633 	build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
634 	ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno);
635 
636 	EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length));
637 
638 	leak(fd);
639 }
640 
641 TEST_F(PosixFallocate, ok)
642 {
643 	const char FULLPATH[] = "mountpoint/some_file.txt";
644 	const char RELPATH[] = "some_file.txt";
645 	struct stat sb0, sb1;
646 	uint64_t ino = 42;
647 	uint64_t offset = 0;
648 	uint64_t length = 1000;
649 	int fd;
650 
651 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
652 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
653 		SET_OUT_HEADER_LEN(out, entry);
654 		out.body.entry.attr.mode = S_IFREG | 0644;
655 		out.body.entry.nodeid = ino;
656 		out.body.entry.entry_valid = UINT64_MAX;
657 		out.body.entry.attr_valid = UINT64_MAX;
658 	})));
659 	expect_open(ino, 0, 1);
660 	expect_fallocate(ino, offset, length, 0, 0);
661 
662 	fd = open(FULLPATH, O_RDWR);
663 	ASSERT_LE(0, fd) << strerror(errno);
664 	ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno);
665 	EXPECT_EQ(0, posix_fallocate(fd, offset, length));
666 	/*
667 	 * Despite the originally cached file size of zero, stat should now
668 	 * return either the new size or requery the daemon.
669 	 */
670 	EXPECT_EQ(0, stat(FULLPATH, &sb1));
671 	EXPECT_EQ(length, (uint64_t)sb1.st_size);
672 
673 	/* mtime and ctime should be updated */
674 	EXPECT_EQ(sb0.st_atime, sb1.st_atime);
675 	EXPECT_NE(sb0.st_mtime, sb1.st_mtime);
676 	EXPECT_NE(sb0.st_ctime, sb1.st_ctime);
677 
678 	leak(fd);
679 }
680 
681 /* fusefs should respect RLIMIT_FSIZE */
682 TEST_F(PosixFallocate, rlimit_fsize)
683 {
684 	const char FULLPATH[] = "mountpoint/some_file.txt";
685 	const char RELPATH[] = "some_file.txt";
686 	struct rlimit rl;
687 	uint64_t ino = 42;
688 	uint64_t offset = 0;
689 	uint64_t length = 1'000'000;
690 	int fd;
691 
692 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
693 	expect_open(ino, 0, 1);
694 
695 	rl.rlim_cur = length / 2;
696 	rl.rlim_max = 10 * length;
697 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
698 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
699 
700 	fd = open(FULLPATH, O_RDWR);
701 	ASSERT_LE(0, fd) << strerror(errno);
702 	EXPECT_EQ(EFBIG, posix_fallocate(fd, offset, length));
703 	EXPECT_EQ(1, s_sigxfsz);
704 
705 	leak(fd);
706 }
707 
708 /* With older servers, no FUSE_FALLOCATE should be attempted */
709 TEST_F(PosixFallocate_7_18, einval)
710 {
711 	const char FULLPATH[] = "mountpoint/some_file.txt";
712 	const char RELPATH[] = "some_file.txt";
713 	uint64_t ino = 42;
714 	uint64_t offset = 0;
715 	uint64_t length = 1000;
716 	int fd;
717 
718 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
719 	expect_open(ino, 0, 1);
720 
721 	fd = open(FULLPATH, O_RDWR);
722 	ASSERT_LE(0, fd) << strerror(errno);
723 	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
724 
725 	leak(fd);
726 }
727