diff options
Diffstat (limited to 'tests/sys/fs/fusefs/cache.cc')
| -rw-r--r-- | tests/sys/fs/fusefs/cache.cc | 218 | 
1 files changed, 218 insertions, 0 deletions
| diff --git a/tests/sys/fs/fusefs/cache.cc b/tests/sys/fs/fusefs/cache.cc new file mode 100644 index 000000000000..ea6d87674da2 --- /dev/null +++ b/tests/sys/fs/fusefs/cache.cc @@ -0,0 +1,218 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2020 Alan Somers + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include <sys/param.h> +#include <fcntl.h> +} + +#include "mockfs.hh" +#include "utils.hh" + +/* + * Tests for thorny cache problems not specific to any one opcode + */ + +using namespace testing; + +/* + * Parameters + * - reopen file	- If true, close and reopen the file between reads + * - cache lookups	- If true, allow lookups to be cached + * - cache attrs	- If true, allow file attributes to be cached + * - cache_mode		- uncached, writeback, or writethrough + * - initial size	- File size before truncation + * - truncated size	- File size after truncation + */ +typedef tuple<tuple<bool, bool, bool>, cache_mode, ssize_t, ssize_t> CacheParam; + +class Cache: public FuseTest, public WithParamInterface<CacheParam> { +public: +bool m_direct_io; + +Cache(): m_direct_io(false) {}; + +virtual void SetUp() { +	int cache_mode = get<1>(GetParam()); +	switch (cache_mode) { +		case Uncached: +			m_direct_io = true; +			break; +		case WritebackAsync: +			m_async = true; +			/* FALLTHROUGH */ +		case Writeback: +			m_init_flags |= FUSE_WRITEBACK_CACHE; +			/* FALLTHROUGH */ +		case Writethrough: +			break; +		default: +			FAIL() << "Unknown cache mode"; +	} +	m_noatime = true;	// To prevent SETATTR for atime on close + +	FuseTest::SetUp(); +	if (IsSkipped()) +		return; +} + +void expect_getattr(uint64_t ino, int times, uint64_t size, uint64_t attr_valid) +{ +	EXPECT_CALL(*m_mock, process( +		ResultOf([=](auto in) { +			return (in.header.opcode == FUSE_GETATTR && +				in.header.nodeid == ino); +		}, Eq(true)), +		_) +	).Times(times) +	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { +		SET_OUT_HEADER_LEN(out, attr); +		out.body.attr.attr_valid = attr_valid; +		out.body.attr.attr.ino = ino; +		out.body.attr.attr.mode = S_IFREG | 0644; +		out.body.attr.attr.size = size; +	}))); +} + +void expect_lookup(const char *relpath, uint64_t ino, +	uint64_t size, uint64_t entry_valid, uint64_t attr_valid) +{ +	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) +	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { +		SET_OUT_HEADER_LEN(out, entry); +		out.body.entry.attr.mode = S_IFREG | 0644; +		out.body.entry.nodeid = ino; +		out.body.entry.attr.nlink = 1; +		out.body.entry.attr_valid = attr_valid; +		out.body.entry.attr.size = size; +		out.body.entry.entry_valid = entry_valid; +	}))); +} + +void expect_open(uint64_t ino, int times) +{ +	FuseTest::expect_open(ino, m_direct_io ? FOPEN_DIRECT_IO: 0, times); +} + +void expect_release(uint64_t ino, ProcessMockerT r) +{ +	EXPECT_CALL(*m_mock, process( +		ResultOf([=](auto in) { +			return (in.header.opcode == FUSE_RELEASE && +				in.header.nodeid == ino); +		}, Eq(true)), +		_) +	).WillRepeatedly(Invoke(r)); +} + +}; + +// If the server truncates the file behind the kernel's back, the kernel should +// invalidate cached pages beyond the new EOF +TEST_P(Cache, truncate_by_surprise_invalidates_cache) +{ +	const char FULLPATH[] = "mountpoint/some_file.txt"; +	const char RELPATH[] = "some_file.txt"; +	const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz"; +	uint64_t ino = 42; +	uint64_t attr_valid, entry_valid; +	int fd; +	ssize_t bufsize = strlen(CONTENTS); +	uint8_t buf[bufsize]; +	bool reopen = get<0>(get<0>(GetParam())); +	bool cache_lookups = get<1>(get<0>(GetParam())); +	bool cache_attrs = get<2>(get<0>(GetParam())); +	ssize_t osize = get<2>(GetParam()); +	ssize_t nsize = get<3>(GetParam()); + +	ASSERT_LE(osize, bufsize); +	ASSERT_LE(nsize, bufsize); +	if (cache_attrs) +		attr_valid = UINT64_MAX; +	else +		attr_valid = 0; +	if (cache_lookups) +		entry_valid = UINT64_MAX; +	else +		entry_valid = 0; + +	expect_lookup(RELPATH, ino, osize, entry_valid, attr_valid); +	expect_open(ino, 1); +	if (!cache_attrs) +		expect_getattr(ino, 2, osize, attr_valid); +	expect_read(ino, 0, osize, osize, CONTENTS); + +	fd = open(FULLPATH, O_RDONLY); +	ASSERT_LE(0, fd) << strerror(errno); + +	ASSERT_EQ(osize, read(fd, buf, bufsize)) << strerror(errno); +	ASSERT_EQ(0, memcmp(buf, CONTENTS, osize)); + +	// Now truncate the file behind the kernel's back.  The next read +	// should discard cache and fetch from disk again. +	if (reopen) { +		// Close and reopen the file +		expect_flush(ino, 1, ReturnErrno(ENOSYS)); +		expect_release(ino, ReturnErrno(0)); +		ASSERT_EQ(0, close(fd)); +		expect_lookup(RELPATH, ino, nsize, entry_valid, attr_valid); +		expect_open(ino, 1); +		fd = open(FULLPATH, O_RDONLY); +		ASSERT_LE(0, fd) << strerror(errno); +	} + +	if (!cache_attrs) +		expect_getattr(ino, 1, nsize, attr_valid); +	expect_read(ino, 0, nsize, nsize, CONTENTS); +	ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)); +	ASSERT_EQ(nsize, read(fd, buf, bufsize)) << strerror(errno); +	ASSERT_EQ(0, memcmp(buf, CONTENTS, nsize)); + +	leak(fd); +} + +INSTANTIATE_TEST_SUITE_P(Cache, Cache, +	Combine( +		/* Test every combination that: +		 * - does not cache at least one of entries and attrs +		 * - either doesn't cache attrs, or reopens the file +		 * In the other combinations, the kernel will never learn that +		 * the file's size has changed. +		 */ +		Values( +			std::make_tuple(false, false, false), +			std::make_tuple(false, true, false), +			std::make_tuple(true, false, false), +			std::make_tuple(true, false, true), +			std::make_tuple(true, true, false) +		), +		Values(Writethrough, Writeback), +		/* Test both reductions and extensions to file size */ +		Values(20), +		Values(10, 25) +	) +); | 
