xref: /freebsd/tests/sys/fs/fusefs/cache.cc (revision b0ecfb42d1705bc1423c726a16a182c81047f854)
1*b0ecfb42SAlan Somers /*-
2*b0ecfb42SAlan Somers  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3*b0ecfb42SAlan Somers  *
4*b0ecfb42SAlan Somers  * Copyright (c) 2020 Alan Somers
5*b0ecfb42SAlan Somers  *
6*b0ecfb42SAlan Somers  * Redistribution and use in source and binary forms, with or without
7*b0ecfb42SAlan Somers  * modification, are permitted provided that the following conditions
8*b0ecfb42SAlan Somers  * are met:
9*b0ecfb42SAlan Somers  * 1. Redistributions of source code must retain the above copyright
10*b0ecfb42SAlan Somers  *    notice, this list of conditions and the following disclaimer.
11*b0ecfb42SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
12*b0ecfb42SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
13*b0ecfb42SAlan Somers  *    documentation and/or other materials provided with the distribution.
14*b0ecfb42SAlan Somers  *
15*b0ecfb42SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16*b0ecfb42SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17*b0ecfb42SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18*b0ecfb42SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19*b0ecfb42SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20*b0ecfb42SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21*b0ecfb42SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22*b0ecfb42SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23*b0ecfb42SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24*b0ecfb42SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25*b0ecfb42SAlan Somers  * SUCH DAMAGE.
26*b0ecfb42SAlan Somers  *
27*b0ecfb42SAlan Somers  * $FreeBSD$
28*b0ecfb42SAlan Somers  */
29*b0ecfb42SAlan Somers 
30*b0ecfb42SAlan Somers extern "C" {
31*b0ecfb42SAlan Somers #include <sys/param.h>
32*b0ecfb42SAlan Somers #include <fcntl.h>
33*b0ecfb42SAlan Somers }
34*b0ecfb42SAlan Somers 
35*b0ecfb42SAlan Somers #include "mockfs.hh"
36*b0ecfb42SAlan Somers #include "utils.hh"
37*b0ecfb42SAlan Somers 
38*b0ecfb42SAlan Somers /*
39*b0ecfb42SAlan Somers  * Tests for thorny cache problems not specific to any one opcode
40*b0ecfb42SAlan Somers  */
41*b0ecfb42SAlan Somers 
42*b0ecfb42SAlan Somers using namespace testing;
43*b0ecfb42SAlan Somers 
44*b0ecfb42SAlan Somers /*
45*b0ecfb42SAlan Somers  * Parameters
46*b0ecfb42SAlan Somers  * - reopen file	- If true, close and reopen the file between reads
47*b0ecfb42SAlan Somers  * - cache lookups	- If true, allow lookups to be cached
48*b0ecfb42SAlan Somers  * - cache attrs	- If true, allow file attributes to be cached
49*b0ecfb42SAlan Somers  * - cache_mode		- uncached, writeback, or writethrough
50*b0ecfb42SAlan Somers  * - initial size	- File size before truncation
51*b0ecfb42SAlan Somers  * - truncated size	- File size after truncation
52*b0ecfb42SAlan Somers  */
53*b0ecfb42SAlan Somers typedef tuple<tuple<bool, bool, bool>, cache_mode, ssize_t, ssize_t> CacheParam;
54*b0ecfb42SAlan Somers 
55*b0ecfb42SAlan Somers class Cache: public FuseTest, public WithParamInterface<CacheParam> {
56*b0ecfb42SAlan Somers public:
57*b0ecfb42SAlan Somers bool m_direct_io;
58*b0ecfb42SAlan Somers 
59*b0ecfb42SAlan Somers Cache(): m_direct_io(false) {};
60*b0ecfb42SAlan Somers 
61*b0ecfb42SAlan Somers virtual void SetUp() {
62*b0ecfb42SAlan Somers 	int cache_mode = get<1>(GetParam());
63*b0ecfb42SAlan Somers 	switch (cache_mode) {
64*b0ecfb42SAlan Somers 		case Uncached:
65*b0ecfb42SAlan Somers 			m_direct_io = true;
66*b0ecfb42SAlan Somers 			break;
67*b0ecfb42SAlan Somers 		case WritebackAsync:
68*b0ecfb42SAlan Somers 			m_async = true;
69*b0ecfb42SAlan Somers 			/* FALLTHROUGH */
70*b0ecfb42SAlan Somers 		case Writeback:
71*b0ecfb42SAlan Somers 			m_init_flags |= FUSE_WRITEBACK_CACHE;
72*b0ecfb42SAlan Somers 			/* FALLTHROUGH */
73*b0ecfb42SAlan Somers 		case Writethrough:
74*b0ecfb42SAlan Somers 			break;
75*b0ecfb42SAlan Somers 		default:
76*b0ecfb42SAlan Somers 			FAIL() << "Unknown cache mode";
77*b0ecfb42SAlan Somers 	}
78*b0ecfb42SAlan Somers 
79*b0ecfb42SAlan Somers 	FuseTest::SetUp();
80*b0ecfb42SAlan Somers 	if (IsSkipped())
81*b0ecfb42SAlan Somers 		return;
82*b0ecfb42SAlan Somers }
83*b0ecfb42SAlan Somers 
84*b0ecfb42SAlan Somers void expect_getattr(uint64_t ino, int times, uint64_t size, uint64_t attr_valid)
85*b0ecfb42SAlan Somers {
86*b0ecfb42SAlan Somers 	EXPECT_CALL(*m_mock, process(
87*b0ecfb42SAlan Somers 		ResultOf([=](auto in) {
88*b0ecfb42SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
89*b0ecfb42SAlan Somers 				in.header.nodeid == ino);
90*b0ecfb42SAlan Somers 		}, Eq(true)),
91*b0ecfb42SAlan Somers 		_)
92*b0ecfb42SAlan Somers 	).Times(times)
93*b0ecfb42SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
94*b0ecfb42SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
95*b0ecfb42SAlan Somers 		out.body.attr.attr_valid = attr_valid;
96*b0ecfb42SAlan Somers 		out.body.attr.attr.ino = ino;
97*b0ecfb42SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
98*b0ecfb42SAlan Somers 		out.body.attr.attr.size = size;
99*b0ecfb42SAlan Somers 	})));
100*b0ecfb42SAlan Somers }
101*b0ecfb42SAlan Somers 
102*b0ecfb42SAlan Somers void expect_lookup(const char *relpath, uint64_t ino,
103*b0ecfb42SAlan Somers 	uint64_t size, uint64_t entry_valid, uint64_t attr_valid)
104*b0ecfb42SAlan Somers {
105*b0ecfb42SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
106*b0ecfb42SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
107*b0ecfb42SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
108*b0ecfb42SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
109*b0ecfb42SAlan Somers 		out.body.entry.nodeid = ino;
110*b0ecfb42SAlan Somers 		out.body.entry.attr.nlink = 1;
111*b0ecfb42SAlan Somers 		out.body.entry.attr_valid = attr_valid;
112*b0ecfb42SAlan Somers 		out.body.entry.attr.size = size;
113*b0ecfb42SAlan Somers 		out.body.entry.entry_valid = entry_valid;
114*b0ecfb42SAlan Somers 	})));
115*b0ecfb42SAlan Somers }
116*b0ecfb42SAlan Somers 
117*b0ecfb42SAlan Somers void expect_open(uint64_t ino, int times)
118*b0ecfb42SAlan Somers {
119*b0ecfb42SAlan Somers 	FuseTest::expect_open(ino, m_direct_io ? FOPEN_DIRECT_IO: 0, times);
120*b0ecfb42SAlan Somers }
121*b0ecfb42SAlan Somers 
122*b0ecfb42SAlan Somers void expect_release(uint64_t ino, ProcessMockerT r)
123*b0ecfb42SAlan Somers {
124*b0ecfb42SAlan Somers 	EXPECT_CALL(*m_mock, process(
125*b0ecfb42SAlan Somers 		ResultOf([=](auto in) {
126*b0ecfb42SAlan Somers 			return (in.header.opcode == FUSE_RELEASE &&
127*b0ecfb42SAlan Somers 				in.header.nodeid == ino);
128*b0ecfb42SAlan Somers 		}, Eq(true)),
129*b0ecfb42SAlan Somers 		_)
130*b0ecfb42SAlan Somers 	).WillRepeatedly(Invoke(r));
131*b0ecfb42SAlan Somers }
132*b0ecfb42SAlan Somers 
133*b0ecfb42SAlan Somers };
134*b0ecfb42SAlan Somers 
135*b0ecfb42SAlan Somers // If the server truncates the file behind the kernel's back, the kernel should
136*b0ecfb42SAlan Somers // invalidate cached pages beyond the new EOF
137*b0ecfb42SAlan Somers TEST_P(Cache, truncate_by_surprise_invalidates_cache)
138*b0ecfb42SAlan Somers {
139*b0ecfb42SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
140*b0ecfb42SAlan Somers 	const char RELPATH[] = "some_file.txt";
141*b0ecfb42SAlan Somers 	const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz";
142*b0ecfb42SAlan Somers 	uint64_t ino = 42;
143*b0ecfb42SAlan Somers 	uint64_t attr_valid, entry_valid;
144*b0ecfb42SAlan Somers 	int fd;
145*b0ecfb42SAlan Somers 	ssize_t bufsize = strlen(CONTENTS);
146*b0ecfb42SAlan Somers 	uint8_t buf[bufsize];
147*b0ecfb42SAlan Somers 	bool reopen = get<0>(get<0>(GetParam()));
148*b0ecfb42SAlan Somers 	bool cache_lookups = get<1>(get<0>(GetParam()));
149*b0ecfb42SAlan Somers 	bool cache_attrs = get<2>(get<0>(GetParam()));
150*b0ecfb42SAlan Somers 	ssize_t osize = get<2>(GetParam());
151*b0ecfb42SAlan Somers 	ssize_t nsize = get<3>(GetParam());
152*b0ecfb42SAlan Somers 
153*b0ecfb42SAlan Somers 	ASSERT_LE(osize, bufsize);
154*b0ecfb42SAlan Somers 	ASSERT_LE(nsize, bufsize);
155*b0ecfb42SAlan Somers 	if (cache_attrs)
156*b0ecfb42SAlan Somers 		attr_valid = UINT64_MAX;
157*b0ecfb42SAlan Somers 	else
158*b0ecfb42SAlan Somers 		attr_valid = 0;
159*b0ecfb42SAlan Somers 	if (cache_lookups)
160*b0ecfb42SAlan Somers 		entry_valid = UINT64_MAX;
161*b0ecfb42SAlan Somers 	else
162*b0ecfb42SAlan Somers 		entry_valid = 0;
163*b0ecfb42SAlan Somers 
164*b0ecfb42SAlan Somers 	expect_lookup(RELPATH, ino, osize, entry_valid, attr_valid);
165*b0ecfb42SAlan Somers 	expect_open(ino, 1);
166*b0ecfb42SAlan Somers 	if (!cache_attrs)
167*b0ecfb42SAlan Somers 		expect_getattr(ino, 2, osize, attr_valid);
168*b0ecfb42SAlan Somers 	expect_read(ino, 0, osize, osize, CONTENTS);
169*b0ecfb42SAlan Somers 
170*b0ecfb42SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
171*b0ecfb42SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
172*b0ecfb42SAlan Somers 
173*b0ecfb42SAlan Somers 	ASSERT_EQ(osize, read(fd, buf, bufsize)) << strerror(errno);
174*b0ecfb42SAlan Somers 	ASSERT_EQ(0, memcmp(buf, CONTENTS, osize));
175*b0ecfb42SAlan Somers 
176*b0ecfb42SAlan Somers 	// Now truncate the file behind the kernel's back.  The next read
177*b0ecfb42SAlan Somers 	// should discard cache and fetch from disk again.
178*b0ecfb42SAlan Somers 	if (reopen) {
179*b0ecfb42SAlan Somers 		// Close and reopen the file
180*b0ecfb42SAlan Somers 		expect_flush(ino, 1, ReturnErrno(ENOSYS));
181*b0ecfb42SAlan Somers 		expect_release(ino, ReturnErrno(0));
182*b0ecfb42SAlan Somers 		ASSERT_EQ(0, close(fd));
183*b0ecfb42SAlan Somers 		expect_lookup(RELPATH, ino, nsize, entry_valid, attr_valid);
184*b0ecfb42SAlan Somers 		expect_open(ino, 1);
185*b0ecfb42SAlan Somers 		fd = open(FULLPATH, O_RDONLY);
186*b0ecfb42SAlan Somers 		ASSERT_LE(0, fd) << strerror(errno);
187*b0ecfb42SAlan Somers 	}
188*b0ecfb42SAlan Somers 
189*b0ecfb42SAlan Somers 	if (!cache_attrs)
190*b0ecfb42SAlan Somers 		expect_getattr(ino, 1, nsize, attr_valid);
191*b0ecfb42SAlan Somers 	expect_read(ino, 0, nsize, nsize, CONTENTS);
192*b0ecfb42SAlan Somers 	ASSERT_EQ(0, lseek(fd, 0, SEEK_SET));
193*b0ecfb42SAlan Somers 	ASSERT_EQ(nsize, read(fd, buf, bufsize)) << strerror(errno);
194*b0ecfb42SAlan Somers 	ASSERT_EQ(0, memcmp(buf, CONTENTS, nsize));
195*b0ecfb42SAlan Somers 
196*b0ecfb42SAlan Somers 	leak(fd);
197*b0ecfb42SAlan Somers }
198*b0ecfb42SAlan Somers 
199*b0ecfb42SAlan Somers INSTANTIATE_TEST_CASE_P(Cache, Cache,
200*b0ecfb42SAlan Somers 	Combine(
201*b0ecfb42SAlan Somers 		/* Test every combination that:
202*b0ecfb42SAlan Somers 		 * - does not cache at least one of entries and attrs
203*b0ecfb42SAlan Somers 		 * - either doesn't cache attrs, or reopens the file
204*b0ecfb42SAlan Somers 		 * In the other combinations, the kernel will never learn that
205*b0ecfb42SAlan Somers 		 * the file's size has changed.
206*b0ecfb42SAlan Somers 		 */
207*b0ecfb42SAlan Somers 		Values(
208*b0ecfb42SAlan Somers 			std::make_tuple(false, false, false),
209*b0ecfb42SAlan Somers 			std::make_tuple(false, true, false),
210*b0ecfb42SAlan Somers 			std::make_tuple(true, false, false),
211*b0ecfb42SAlan Somers 			std::make_tuple(true, false, true),
212*b0ecfb42SAlan Somers 			std::make_tuple(true, true, false)
213*b0ecfb42SAlan Somers 		),
214*b0ecfb42SAlan Somers 		Values(Writethrough, Writeback),
215*b0ecfb42SAlan Somers 		/* Test both reductions and extensions to file size */
216*b0ecfb42SAlan Somers 		Values(20),
217*b0ecfb42SAlan Somers 		Values(10, 25)
218*b0ecfb42SAlan Somers 	)
219*b0ecfb42SAlan Somers );
220