xref: /freebsd/tests/sys/fs/fusefs/io.cc (revision a87e0831)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 The FreeBSD Foundation
5  *
6  * This software was developed by BFF Storage Systems, LLC under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 extern "C" {
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 }
36 
37 #include "mockfs.hh"
38 #include "utils.hh"
39 
40 /*
41  * For testing I/O like fsx does, but deterministically and without a real
42  * underlying file system
43  *
44  * TODO: after fusefs gains the options to select cache mode for each mount
45  * point, run each of these tests for all cache modes.
46  */
47 
48 using namespace testing;
49 
50 const char FULLPATH[] = "mountpoint/some_file.txt";
51 const char RELPATH[] = "some_file.txt";
52 const uint64_t ino = 42;
53 
54 class Io: public FuseTest {
55 public:
56 int m_backing_fd, m_control_fd, m_test_fd;
57 
58 Io(): m_backing_fd(-1), m_control_fd(-1) {};
59 
60 void SetUp()
61 {
62 	m_backing_fd = open("backing_file", O_RDWR | O_CREAT | O_TRUNC);
63 	if (m_backing_fd < 0)
64 		FAIL() << strerror(errno);
65 	m_control_fd = open("control", O_RDWR | O_CREAT | O_TRUNC);
66 	if (m_control_fd < 0)
67 		FAIL() << strerror(errno);
68 	srandom(22'9'1982);	// Seed with my birthday
69 	FuseTest::SetUp();
70 	if (IsSkipped())
71 		return;
72 
73 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1);
74 	expect_open(ino, 0, 1);
75 	EXPECT_CALL(*m_mock, process(
76 		ResultOf([=](auto in) {
77 			return (in.header.opcode == FUSE_WRITE &&
78 				in.header.nodeid == ino);
79 		}, Eq(true)),
80 		_)
81 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto& out) {
82 		const char *buf = (const char*)in.body.bytes +
83 			sizeof(struct fuse_write_in);
84 		ssize_t isize = in.body.write.size;
85 		off_t iofs = in.body.write.offset;
86 
87 		ASSERT_EQ(isize, pwrite(m_backing_fd, buf, isize, iofs))
88 			<< strerror(errno);
89 		SET_OUT_HEADER_LEN(out, write);
90 		out.body.write.size = isize;
91 	})));
92 	EXPECT_CALL(*m_mock, process(
93 		ResultOf([=](auto in) {
94 			return (in.header.opcode == FUSE_READ &&
95 				in.header.nodeid == ino);
96 		}, Eq(true)),
97 		_)
98 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto& out) {
99 		ssize_t isize = in.body.write.size;
100 		off_t iofs = in.body.write.offset;
101 		void *buf = out.body.bytes;
102 
103 		ASSERT_LE(0, pread(m_backing_fd, buf, isize, iofs))
104 			<< strerror(errno);
105 		out.header.len = sizeof(struct fuse_out_header) + isize;
106 	})));
107 	EXPECT_CALL(*m_mock, process(
108 		ResultOf([=](auto in) {
109 			uint32_t valid = FATTR_SIZE | FATTR_FH;
110 			return (in.header.opcode == FUSE_SETATTR &&
111 				in.header.nodeid == ino &&
112 				in.body.setattr.valid == valid);
113 		}, Eq(true)),
114 		_)
115 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto& out) {
116 		ASSERT_EQ(0, ftruncate(m_backing_fd, in.body.setattr.size))
117 			<< strerror(errno);
118 		SET_OUT_HEADER_LEN(out, attr);
119 		out.body.attr.attr.ino = ino;
120 		out.body.attr.attr.mode = S_IFREG | 0755;
121 		out.body.attr.attr.size = in.body.setattr.size;
122 		out.body.attr.attr_valid = UINT64_MAX;
123 	})));
124 	/* Any test that close()s will send FUSE_FLUSH and FUSE_RELEASE */
125 	EXPECT_CALL(*m_mock, process(
126 		ResultOf([=](auto in) {
127 			return (in.header.opcode == FUSE_FLUSH &&
128 				in.header.nodeid == ino);
129 		}, Eq(true)),
130 		_)
131 	).WillRepeatedly(Invoke(ReturnErrno(0)));
132 	EXPECT_CALL(*m_mock, process(
133 		ResultOf([=](auto in) {
134 			return (in.header.opcode == FUSE_RELEASE &&
135 				in.header.nodeid == ino);
136 		}, Eq(true)),
137 		_)
138 	).WillRepeatedly(Invoke(ReturnErrno(0)));
139 
140 	m_test_fd = open(FULLPATH, O_RDWR );
141 	EXPECT_LE(0, m_test_fd) << strerror(errno);
142 }
143 
144 void TearDown()
145 {
146 	if (m_backing_fd >= 0)
147 		close(m_backing_fd);
148 	if (m_control_fd >= 0)
149 		close(m_control_fd);
150 	FuseTest::TearDown();
151 	/* Deliberately leak test_fd */
152 }
153 
154 void do_ftruncate(off_t offs)
155 {
156 	ASSERT_EQ(0, ftruncate(m_test_fd, offs)) << strerror(errno);
157 	ASSERT_EQ(0, ftruncate(m_control_fd, offs)) << strerror(errno);
158 }
159 
160 void do_read(ssize_t size, off_t offs)
161 {
162 	void *test_buf, *control_buf;
163 
164 	test_buf = malloc(size);
165 	ASSERT_NE(NULL, test_buf) << strerror(errno);
166 	control_buf = malloc(size);
167 	ASSERT_NE(NULL, control_buf) << strerror(errno);
168 
169 	ASSERT_EQ(size, pread(m_test_fd, test_buf, size, offs))
170 		<< strerror(errno);
171 	ASSERT_EQ(size, pread(m_control_fd, control_buf, size, offs))
172 		<< strerror(errno);
173 
174 	ASSERT_EQ(0, memcmp(test_buf, control_buf, size));
175 
176 	free(control_buf);
177 	free(test_buf);
178 }
179 
180 void do_write(ssize_t size, off_t offs)
181 {
182 	char *buf;
183 	long i;
184 
185 	buf = (char*)malloc(size);
186 	ASSERT_NE(NULL, buf) << strerror(errno);
187 	for (i=0; i < size; i++)
188 		buf[i] = random();
189 
190 	ASSERT_EQ(size, pwrite(m_test_fd, buf, size, offs ))
191 		<< strerror(errno);
192 	ASSERT_EQ(size, pwrite(m_control_fd, buf, size, offs))
193 		<< strerror(errno);
194 }
195 
196 };
197 
198 /*
199  * Extend a file with dirty data in the last page of the last block.
200  *
201  * fsx -WR -P /tmp -S8 -N3 fsx.bin
202  */
203 TEST_F(Io, extend_from_dirty_page)
204 {
205 	off_t wofs = 0x21a0;
206 	ssize_t wsize = 0xf0a8;
207 	off_t rofs = 0xb284;
208 	ssize_t rsize = 0x9b22;
209 	off_t truncsize = 0x28702;
210 
211 	do_write(wsize, wofs);
212 	do_ftruncate(truncsize);
213 	do_read(rsize, rofs);
214 }
215 
216 /*
217  * When writing the last page of a file, it must be written synchronously.
218  * Otherwise the cached page can become invalid by a subsequent extend
219  * operation.
220  *
221  * fsx -WR -P /tmp -S642 -N3 fsx.bin
222  */
223 TEST_F(Io, last_page)
224 {
225 	off_t wofs0 = 0x1134f;
226 	ssize_t wsize0 = 0xcc77;
227 	off_t wofs1 = 0x2096a;
228 	ssize_t wsize1 = 0xdfa7;
229 	off_t rofs = 0x1a3aa;
230 	ssize_t rsize = 0xb5b7;
231 
232 	do_write(wsize0, wofs0);
233 	do_write(wsize1, wofs1);
234 	do_read(rsize, rofs);
235 }
236 
237 /*
238  * Read a hole from a block that contains some cached data.
239  *
240  * fsx -WR -P /tmp -S55  fsx.bin
241  */
242 TEST_F(Io, read_hole_from_cached_block)
243 {
244 	off_t wofs = 0x160c5;
245 	ssize_t wsize = 0xa996;
246 	off_t rofs = 0x472e;
247 	ssize_t rsize = 0xd8d5;
248 
249 	do_write(wsize, wofs);
250 	do_read(rsize, rofs);
251 }
252 
253 /*
254  * Reliable panic; I don't yet know why.
255  * Disabled because it panics.
256  *
257  * fsx -WR -P /tmp -S839 -d -N6 fsx.bin
258  */
259 TEST_F(Io, DISABLED_fault_on_nofault_entry)
260 {
261 	off_t wofs0 = 0x3bad7;
262 	ssize_t wsize0 = 0x4529;
263 	off_t wofs1 = 0xc30d;
264 	ssize_t wsize1 = 0x5f77;
265 	off_t truncsize0 = 0x10916;
266 	off_t rofs = 0xdf17;
267 	ssize_t rsize = 0x29ff;
268 	off_t truncsize1 = 0x152b4;
269 
270 	do_write(wsize0, wofs0);
271 	do_write(wsize1, wofs1);
272 	do_ftruncate(truncsize0);
273 	do_read(rsize, rofs);
274 	do_ftruncate(truncsize1);
275 	close(m_test_fd);
276 }
277