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