]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/fallocate.cc
Import device-tree files from Linux 6.2
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / fallocate.cc
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 }