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
28extern "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
44using namespace testing;
45
46/* Is buf all zero? */
47static bool
48is_zero(const char *buf, uint64_t size)
49{
50    return buf[0] == 0 && !memcmp(buf, buf + 1, size - 1);
51}
52
53class Fallocate: public FuseTest {
54public:
55/*
56 * expect VOP_DEALLOCATE to be implemented by vop_stddeallocate.
57 */
58void 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
97class Fspacectl: public Fallocate {};
98
99class Fspacectl_7_18: public Fspacectl {
100public:
101virtual void SetUp() {
102	m_kernel_minor_version = 18;
103	Fspacectl::SetUp();
104}
105};
106
107class FspacectlCache: public Fspacectl, public WithParamInterface<cache_mode> {
108public:
109bool m_direct_io;
110
111FspacectlCache(): m_direct_io(false) {};
112
113virtual 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
137class PosixFallocate: public Fallocate {
138public:
139static sig_atomic_t s_sigxfsz;
140
141void SetUp() {
142	s_sigxfsz = 0;
143	FuseTest::SetUp();
144}
145
146void 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
158sig_atomic_t PosixFallocate::s_sigxfsz = 0;
159
160void sigxfsz_handler(int __unused sig) {
161	PosixFallocate::s_sigxfsz = 1;
162}
163
164class PosixFallocate_7_18: public PosixFallocate {
165public:
166virtual 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 */
177TEST_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 */
220TEST_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
266TEST_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
313TEST_F(Fspacectl, ok)
314{
315	const char FULLPATH[] = "mountpoint/some_file.txt";
316	const char RELPATH[] = "some_file.txt";
317	struct spacectl_range rqsr, rmsr;
318	struct stat sb0, sb1;
319	uint64_t ino = 42;
320	uint64_t fsize = 2000;
321	uint64_t offset = 500;
322	uint64_t length = 1000;
323	int fd;
324
325	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
326	expect_open(ino, 0, 1);
327	expect_fallocate(ino, offset, length,
328		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
329
330	fd = open(FULLPATH, O_RDWR);
331	ASSERT_LE(0, fd) << strerror(errno);
332	ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno);
333	rqsr.r_offset = offset;
334	rqsr.r_len = length;
335	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
336	EXPECT_EQ(0, rmsr.r_len);
337	EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
338
339	/*
340	 * The file's attributes should not have been invalidated, so this fstat
341	 * will not requery the daemon.
342	 */
343	EXPECT_EQ(0, fstat(fd, &sb1));
344	EXPECT_EQ(fsize, (uint64_t)sb1.st_size);
345
346	/* mtime and ctime should be updated */
347	EXPECT_EQ(sb0.st_atime, sb1.st_atime);
348	EXPECT_NE(sb0.st_mtime, sb1.st_mtime);
349	EXPECT_NE(sb0.st_ctime, sb1.st_ctime);
350
351	leak(fd);
352}
353
354/* The returned rqsr.r_off should be clipped at EoF */
355TEST_F(Fspacectl, past_eof)
356{
357	const char FULLPATH[] = "mountpoint/some_file.txt";
358	const char RELPATH[] = "some_file.txt";
359	struct spacectl_range rqsr, rmsr;
360	uint64_t ino = 42;
361	uint64_t fsize = 1000;
362	uint64_t offset = 1500;
363	uint64_t length = 1000;
364	int fd;
365
366	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
367	expect_open(ino, 0, 1);
368	expect_fallocate(ino, offset, length,
369		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
370
371	fd = open(FULLPATH, O_RDWR);
372	ASSERT_LE(0, fd) << strerror(errno);
373	rqsr.r_offset = offset;
374	rqsr.r_len = length;
375	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
376	EXPECT_EQ(0, rmsr.r_len);
377	EXPECT_EQ((off_t)fsize, rmsr.r_offset);
378
379	leak(fd);
380}
381
382/* The returned rqsr.r_off should be clipped at EoF */
383TEST_F(Fspacectl, spans_eof)
384{
385	const char FULLPATH[] = "mountpoint/some_file.txt";
386	const char RELPATH[] = "some_file.txt";
387	struct spacectl_range rqsr, rmsr;
388	uint64_t ino = 42;
389	uint64_t fsize = 1000;
390	uint64_t offset = 500;
391	uint64_t length = 1000;
392	int fd;
393
394	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
395	expect_open(ino, 0, 1);
396	expect_fallocate(ino, offset, length,
397		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
398
399	fd = open(FULLPATH, O_RDWR);
400	ASSERT_LE(0, fd) << strerror(errno);
401	rqsr.r_offset = offset;
402	rqsr.r_len = length;
403	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
404	EXPECT_EQ(0, rmsr.r_len);
405	EXPECT_EQ((off_t)fsize, rmsr.r_offset);
406
407	leak(fd);
408}
409
410/*
411 * With older servers, no FUSE_FALLOCATE should be attempted.  The kernel
412 * should fall back to vop_stddeallocate.
413 */
414TEST_F(Fspacectl_7_18, ok)
415{
416	const char FULLPATH[] = "mountpoint/some_file.txt";
417	const char RELPATH[] = "some_file.txt";
418	struct spacectl_range rqsr, rmsr;
419	char *buf;
420	uint64_t ino = 42;
421	uint64_t fsize = 2000;
422	uint64_t offset = 500;
423	uint64_t length = 1000;
424	int fd;
425
426	buf = new char[length];
427
428	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
429	expect_open(ino, 0, 1);
430	expect_vop_stddeallocate(ino, offset, length);
431
432	fd = open(FULLPATH, O_RDWR);
433	ASSERT_LE(0, fd) << strerror(errno);
434	rqsr.r_offset = offset;
435	rqsr.r_len = length;
436	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
437	EXPECT_EQ(0, rmsr.r_len);
438	EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
439
440	leak(fd);
441	delete[] buf;
442}
443
444/*
445 * A successful fspacectl should clear the zeroed data from the kernel cache.
446 */
447TEST_P(FspacectlCache, clears_cache)
448{
449	const char FULLPATH[] = "mountpoint/some_file.txt";
450	const char RELPATH[] = "some_file.txt";
451	const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz";
452	struct spacectl_range rqsr, rmsr;
453	uint64_t ino = 42;
454	ssize_t bufsize = strlen(CONTENTS);
455	uint64_t fsize = bufsize;
456	uint8_t buf[bufsize];
457	char zbuf[bufsize];
458	uint64_t offset = 0;
459	uint64_t length = bufsize;
460	int fd;
461
462	bzero(zbuf, bufsize);
463
464	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
465	expect_open(ino, 0, 1);
466	/* NB: expectations are applied in LIFO order */
467	expect_read(ino, 0, fsize, fsize, zbuf);
468	expect_read(ino, 0, fsize, fsize, CONTENTS);
469	expect_fallocate(ino, offset, length,
470		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
471
472	fd = open(FULLPATH, O_RDWR);
473	ASSERT_LE(0, fd) << strerror(errno);
474
475	/* Populate the cache */
476	ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0))
477		<< strerror(errno);
478	ASSERT_EQ(0, memcmp(buf, CONTENTS, fsize));
479
480	/* Zero the file */
481	rqsr.r_offset = offset;
482	rqsr.r_len = length;
483	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr));
484	EXPECT_EQ(0, rmsr.r_len);
485	EXPECT_EQ((off_t)(offset + length), rmsr.r_offset);
486
487	/* Read again.  This should query the daemon */
488	ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0))
489		<< strerror(errno);
490	ASSERT_EQ(0, memcmp(buf, zbuf, fsize));
491
492	leak(fd);
493}
494
495INSTANTIATE_TEST_SUITE_P(FspacectlCache, FspacectlCache,
496	Values(Uncached, Writethrough, Writeback)
497);
498
499/*
500 * If the server returns ENOSYS, it indicates that the server does not support
501 * FUSE_FALLOCATE.  This and future calls should return EINVAL.
502 */
503TEST_F(PosixFallocate, enosys)
504{
505	const char FULLPATH[] = "mountpoint/some_file.txt";
506	const char RELPATH[] = "some_file.txt";
507	uint64_t ino = 42;
508	uint64_t off0 = 0;
509	uint64_t len0 = 1000;
510	off_t off1 = 100;
511	off_t len1 = 200;
512	uint64_t fsize = 500;
513	struct spacectl_range rqsr = { .r_offset = off1, .r_len = len1 };
514	int fd;
515
516	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
517	expect_open(ino, 0, 1);
518	expect_fallocate(ino, off0, len0, 0, ENOSYS);
519	expect_vop_stddeallocate(ino, off1, len1);
520
521	fd = open(FULLPATH, O_RDWR);
522	ASSERT_LE(0, fd) << strerror(errno);
523	EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0));
524
525	/* Subsequent calls shouldn't query the daemon*/
526	EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0));
527
528	/* Neither should VOP_DEALLOCATE query the daemon */
529	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
530
531	leak(fd);
532}
533
534/*
535 * EOPNOTSUPP means "the file system does not support fallocate with the
536 * supplied mode on this particular file".  So we should fallback, but not
537 * assume anything about whether the operation will fail on a different file or
538 * with a different mode.
539 */
540TEST_F(PosixFallocate, eopnotsupp)
541{
542	const char FULLPATH[] = "mountpoint/some_file.txt";
543	const char RELPATH[] = "some_file.txt";
544	struct spacectl_range rqsr;
545	uint64_t ino = 42;
546	uint64_t fsize = 2000;
547	uint64_t offset = 0;
548	uint64_t length = 1000;
549	int fd;
550
551	expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1);
552	expect_open(ino, 0, 1);
553	expect_fallocate(ino, fsize, length, 0, EOPNOTSUPP);
554	expect_fallocate(ino, offset, length, 0, EOPNOTSUPP);
555	expect_fallocate(ino, offset, length,
556		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
557
558	fd = open(FULLPATH, O_RDWR);
559	ASSERT_LE(0, fd) << strerror(errno);
560	EXPECT_EQ(EINVAL, posix_fallocate(fd, fsize, length));
561
562	/* Subsequent calls should still query the daemon*/
563	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
564
565	/* And subsequent VOP_DEALLOCATE calls should also query the daemon */
566	rqsr.r_len = length;
567	rqsr.r_offset = offset;
568	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
569
570	leak(fd);
571}
572
573/* EIO is not a permanent error, and may be retried */
574TEST_F(PosixFallocate, eio)
575{
576	const char FULLPATH[] = "mountpoint/some_file.txt";
577	const char RELPATH[] = "some_file.txt";
578	uint64_t ino = 42;
579	uint64_t offset = 0;
580	uint64_t length = 1000;
581	int fd;
582
583	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
584	expect_open(ino, 0, 1);
585	expect_fallocate(ino, offset, length, 0, EIO);
586
587	fd = open(FULLPATH, O_RDWR);
588	ASSERT_LE(0, fd) << strerror(errno);
589	EXPECT_EQ(EIO, posix_fallocate(fd, offset, length));
590
591	expect_fallocate(ino, offset, length, 0, 0);
592
593	EXPECT_EQ(0, posix_fallocate(fd, offset, length));
594
595	leak(fd);
596}
597
598TEST_F(PosixFallocate, erofs)
599{
600	const char FULLPATH[] = "mountpoint/some_file.txt";
601	const char RELPATH[] = "some_file.txt";
602	struct statfs statbuf;
603	struct iovec *iov = NULL;
604	int iovlen = 0;
605	uint64_t ino = 42;
606	uint64_t offset = 0;
607	uint64_t length = 1000;
608	int fd;
609	int newflags;
610
611	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
612	expect_open(ino, 0, 1);
613	EXPECT_CALL(*m_mock, process(
614		ResultOf([](auto in) {
615			return (in.header.opcode == FUSE_STATFS);
616		}, Eq(true)),
617		_)
618	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
619	{
620		/*
621		 * All of the fields except f_flags are don't care, and f_flags
622		 * is set by the VFS
623		 */
624		SET_OUT_HEADER_LEN(out, statfs);
625	})));
626
627	fd = open(FULLPATH, O_RDWR);
628	ASSERT_LE(0, fd) << strerror(errno);
629
630	/* Remount read-only */
631	ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
632	newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY;
633	build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1);
634	build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1);
635	build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
636	ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno);
637	free_iovec(&iov, &iovlen);
638
639	EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length));
640
641	leak(fd);
642}
643
644TEST_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 */
685TEST_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 */
712TEST_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