xref: /freebsd/tests/sys/fs/fusefs/fallocate.cc (revision 55224280e2f20474f83001cbc402b21fba8f1c4b)
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 class Fallocate: public FuseTest{};
49 
50 class PosixFallocate: public Fallocate {
51 public:
52 static sig_atomic_t s_sigxfsz;
53 
54 void SetUp() {
55 	s_sigxfsz = 0;
56 	FuseTest::SetUp();
57 }
58 
59 void TearDown() {
60 	struct sigaction sa;
61 
62 	bzero(&sa, sizeof(sa));
63 	sa.sa_handler = SIG_DFL;
64 	sigaction(SIGXFSZ, &sa, NULL);
65 
66 	Fallocate::TearDown();
67 }
68 
69 };
70 
71 sig_atomic_t PosixFallocate::s_sigxfsz = 0;
72 
73 void sigxfsz_handler(int __unused sig) {
74 	PosixFallocate::s_sigxfsz = 1;
75 }
76 
77 class PosixFallocate_7_18: public PosixFallocate {
78 public:
79 virtual void SetUp() {
80 	m_kernel_minor_version = 18;
81 	PosixFallocate::SetUp();
82 }
83 };
84 
85 
86 /*
87  * If the server returns ENOSYS, it indicates that the server does not support
88  * FUSE_FALLOCATE.  This and future calls should return EINVAL.
89  */
90 TEST_F(PosixFallocate, enosys)
91 {
92 	const char FULLPATH[] = "mountpoint/some_file.txt";
93 	const char RELPATH[] = "some_file.txt";
94 	uint64_t ino = 42;
95 	uint64_t offset = 0;
96 	uint64_t length = 1000;
97 	int fd;
98 
99 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
100 	expect_open(ino, 0, 1);
101 	expect_fallocate(ino, offset, length, 0, ENOSYS);
102 
103 	fd = open(FULLPATH, O_RDWR);
104 	ASSERT_LE(0, fd) << strerror(errno);
105 	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
106 
107 	/* Subsequent calls shouldn't query the daemon*/
108 	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
109 
110 	leak(fd);
111 }
112 
113 /*
114  * EOPNOTSUPP means either "the file system does not support fallocate" or "the
115  * file system does not support fallocate with the supplied mode".  fusefs
116  * should conservatively assume the latter, and not issue any more fallocate
117  * operations with the same mode.
118  */
119 TEST_F(PosixFallocate, eopnotsupp)
120 {
121 	const char FULLPATH[] = "mountpoint/some_file.txt";
122 	const char RELPATH[] = "some_file.txt";
123 	uint64_t ino = 42;
124 	uint64_t offset = 0;
125 	uint64_t length = 1000;
126 	int fd;
127 
128 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
129 	expect_open(ino, 0, 1);
130 	expect_fallocate(ino, offset, length, 0, EOPNOTSUPP);
131 
132 	fd = open(FULLPATH, O_RDWR);
133 	ASSERT_LE(0, fd) << strerror(errno);
134 	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
135 
136 	/* Subsequent calls shouldn't query the daemon*/
137 	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
138 
139 	leak(fd);
140 }
141 
142 /* EIO is not a permanent error, and may be retried */
143 TEST_F(PosixFallocate, eio)
144 {
145 	const char FULLPATH[] = "mountpoint/some_file.txt";
146 	const char RELPATH[] = "some_file.txt";
147 	uint64_t ino = 42;
148 	uint64_t offset = 0;
149 	uint64_t length = 1000;
150 	int fd;
151 
152 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
153 	expect_open(ino, 0, 1);
154 	expect_fallocate(ino, offset, length, 0, EIO);
155 
156 	fd = open(FULLPATH, O_RDWR);
157 	ASSERT_LE(0, fd) << strerror(errno);
158 	EXPECT_EQ(EIO, posix_fallocate(fd, offset, length));
159 
160 	expect_fallocate(ino, offset, length, 0, 0);
161 
162 	EXPECT_EQ(0, posix_fallocate(fd, offset, length));
163 
164 	leak(fd);
165 }
166 
167 TEST_F(PosixFallocate, erofs)
168 {
169 	const char FULLPATH[] = "mountpoint/some_file.txt";
170 	const char RELPATH[] = "some_file.txt";
171 	struct statfs statbuf;
172 	struct iovec *iov = NULL;
173 	int iovlen = 0;
174 	uint64_t ino = 42;
175 	uint64_t offset = 0;
176 	uint64_t length = 1000;
177 	int fd;
178 	int newflags;
179 
180 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
181 	expect_open(ino, 0, 1);
182 	EXPECT_CALL(*m_mock, process(
183 		ResultOf([](auto in) {
184 			return (in.header.opcode == FUSE_STATFS);
185 		}, Eq(true)),
186 		_)
187 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out)
188 	{
189 		/*
190 		 * All of the fields except f_flags are don't care, and f_flags
191 		 * is set by the VFS
192 		 */
193 		SET_OUT_HEADER_LEN(out, statfs);
194 	})));
195 
196 	fd = open(FULLPATH, O_RDWR);
197 	ASSERT_LE(0, fd) << strerror(errno);
198 
199 	/* Remount read-only */
200 	ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno);
201 	newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY;
202 	build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1);
203 	build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1);
204 	build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
205 	ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno);
206 
207 	EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length));
208 
209 	leak(fd);
210 }
211 
212 TEST_F(PosixFallocate, ok)
213 {
214 	const char FULLPATH[] = "mountpoint/some_file.txt";
215 	const char RELPATH[] = "some_file.txt";
216 	struct stat sb0, sb1;
217 	uint64_t ino = 42;
218 	uint64_t offset = 0;
219 	uint64_t length = 1000;
220 	int fd;
221 
222 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
223 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
224 		SET_OUT_HEADER_LEN(out, entry);
225 		out.body.entry.attr.mode = S_IFREG | 0644;
226 		out.body.entry.nodeid = ino;
227 		out.body.entry.entry_valid = UINT64_MAX;
228 		out.body.entry.attr_valid = UINT64_MAX;
229 	})));
230 	expect_open(ino, 0, 1);
231 	expect_fallocate(ino, offset, length, 0, 0);
232 
233 	fd = open(FULLPATH, O_RDWR);
234 	ASSERT_LE(0, fd) << strerror(errno);
235 	ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno);
236 	EXPECT_EQ(0, posix_fallocate(fd, offset, length));
237 	/*
238 	 * Despite the originally cached file size of zero, stat should now
239 	 * return either the new size or requery the daemon.
240 	 */
241 	EXPECT_EQ(0, stat(FULLPATH, &sb1));
242 	EXPECT_EQ(length, (uint64_t)sb1.st_size);
243 
244 	/* mtime and ctime should be updated */
245 	EXPECT_EQ(sb0.st_atime, sb1.st_atime);
246 	EXPECT_NE(sb0.st_mtime, sb1.st_mtime);
247 	EXPECT_NE(sb0.st_ctime, sb1.st_ctime);
248 
249 	leak(fd);
250 }
251 
252 /* fusefs should respect RLIMIT_FSIZE */
253 TEST_F(PosixFallocate, rlimit_fsize)
254 {
255 	const char FULLPATH[] = "mountpoint/some_file.txt";
256 	const char RELPATH[] = "some_file.txt";
257 	struct rlimit rl;
258 	uint64_t ino = 42;
259 	uint64_t offset = 0;
260 	uint64_t length = 1'000'000;
261 	int fd;
262 
263 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
264 	expect_open(ino, 0, 1);
265 
266 	rl.rlim_cur = length / 2;
267 	rl.rlim_max = 10 * length;
268 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
269 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
270 
271 	fd = open(FULLPATH, O_RDWR);
272 	ASSERT_LE(0, fd) << strerror(errno);
273 	EXPECT_EQ(EFBIG, posix_fallocate(fd, offset, length));
274 	EXPECT_EQ(1, s_sigxfsz);
275 
276 	leak(fd);
277 }
278 
279 /* With older servers, no FUSE_FALLOCATE should be attempted */
280 TEST_F(PosixFallocate_7_18, einval)
281 {
282 	const char FULLPATH[] = "mountpoint/some_file.txt";
283 	const char RELPATH[] = "some_file.txt";
284 	uint64_t ino = 42;
285 	uint64_t offset = 0;
286 	uint64_t length = 1000;
287 	int fd;
288 
289 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
290 	expect_open(ino, 0, 1);
291 
292 	fd = open(FULLPATH, O_RDWR);
293 	ASSERT_LE(0, fd) << strerror(errno);
294 	EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length));
295 
296 	leak(fd);
297 }
298