1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2020 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 28 extern "C" { 29 #include <sys/param.h> 30 #include <fcntl.h> 31 } 32 33 #include "mockfs.hh" 34 #include "utils.hh" 35 36 /* 37 * Tests for thorny cache problems not specific to any one opcode 38 */ 39 40 using namespace testing; 41 42 /* 43 * Parameters 44 * - reopen file - If true, close and reopen the file between reads 45 * - cache lookups - If true, allow lookups to be cached 46 * - cache attrs - If true, allow file attributes to be cached 47 * - cache_mode - uncached, writeback, or writethrough 48 * - initial size - File size before truncation 49 * - truncated size - File size after truncation 50 */ 51 typedef tuple<tuple<bool, bool, bool>, cache_mode, ssize_t, ssize_t> CacheParam; 52 53 class Cache: public FuseTest, public WithParamInterface<CacheParam> { 54 public: 55 bool m_direct_io; 56 57 Cache(): m_direct_io(false) {}; 58 59 virtual void SetUp() { 60 int cache_mode = get<1>(GetParam()); 61 switch (cache_mode) { 62 case Uncached: 63 m_direct_io = true; 64 break; 65 case WritebackAsync: 66 m_async = true; 67 /* FALLTHROUGH */ 68 case Writeback: 69 m_init_flags |= FUSE_WRITEBACK_CACHE; 70 /* FALLTHROUGH */ 71 case Writethrough: 72 break; 73 default: 74 FAIL() << "Unknown cache mode"; 75 } 76 m_noatime = true; // To prevent SETATTR for atime on close 77 78 FuseTest::SetUp(); 79 if (IsSkipped()) 80 return; 81 } 82 83 void expect_getattr(uint64_t ino, int times, uint64_t size, uint64_t attr_valid) 84 { 85 EXPECT_CALL(*m_mock, process( 86 ResultOf([=](auto in) { 87 return (in.header.opcode == FUSE_GETATTR && 88 in.header.nodeid == ino); 89 }, Eq(true)), 90 _) 91 ).Times(times) 92 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 93 SET_OUT_HEADER_LEN(out, attr); 94 out.body.attr.attr_valid = attr_valid; 95 out.body.attr.attr.ino = ino; 96 out.body.attr.attr.mode = S_IFREG | 0644; 97 out.body.attr.attr.size = size; 98 }))); 99 } 100 101 void expect_lookup(const char *relpath, uint64_t ino, 102 uint64_t size, uint64_t entry_valid, uint64_t attr_valid) 103 { 104 EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) 105 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 106 SET_OUT_HEADER_LEN(out, entry); 107 out.body.entry.attr.mode = S_IFREG | 0644; 108 out.body.entry.nodeid = ino; 109 out.body.entry.attr.nlink = 1; 110 out.body.entry.attr_valid = attr_valid; 111 out.body.entry.attr.size = size; 112 out.body.entry.entry_valid = entry_valid; 113 }))); 114 } 115 116 void expect_open(uint64_t ino, int times) 117 { 118 FuseTest::expect_open(ino, m_direct_io ? FOPEN_DIRECT_IO: 0, times); 119 } 120 121 void expect_release(uint64_t ino, ProcessMockerT r) 122 { 123 EXPECT_CALL(*m_mock, process( 124 ResultOf([=](auto in) { 125 return (in.header.opcode == FUSE_RELEASE && 126 in.header.nodeid == ino); 127 }, Eq(true)), 128 _) 129 ).WillRepeatedly(Invoke(r)); 130 } 131 132 }; 133 134 // If the server truncates the file behind the kernel's back, the kernel should 135 // invalidate cached pages beyond the new EOF 136 TEST_P(Cache, truncate_by_surprise_invalidates_cache) 137 { 138 const char FULLPATH[] = "mountpoint/some_file.txt"; 139 const char RELPATH[] = "some_file.txt"; 140 const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz"; 141 uint64_t ino = 42; 142 uint64_t attr_valid, entry_valid; 143 int fd; 144 ssize_t bufsize = strlen(CONTENTS); 145 uint8_t buf[bufsize]; 146 bool reopen = get<0>(get<0>(GetParam())); 147 bool cache_lookups = get<1>(get<0>(GetParam())); 148 bool cache_attrs = get<2>(get<0>(GetParam())); 149 ssize_t osize = get<2>(GetParam()); 150 ssize_t nsize = get<3>(GetParam()); 151 152 ASSERT_LE(osize, bufsize); 153 ASSERT_LE(nsize, bufsize); 154 if (cache_attrs) 155 attr_valid = UINT64_MAX; 156 else 157 attr_valid = 0; 158 if (cache_lookups) 159 entry_valid = UINT64_MAX; 160 else 161 entry_valid = 0; 162 163 expect_lookup(RELPATH, ino, osize, entry_valid, attr_valid); 164 expect_open(ino, 1); 165 if (!cache_attrs) 166 expect_getattr(ino, 2, osize, attr_valid); 167 expect_read(ino, 0, osize, osize, CONTENTS); 168 169 fd = open(FULLPATH, O_RDONLY); 170 ASSERT_LE(0, fd) << strerror(errno); 171 172 ASSERT_EQ(osize, read(fd, buf, bufsize)) << strerror(errno); 173 ASSERT_EQ(0, memcmp(buf, CONTENTS, osize)); 174 175 // Now truncate the file behind the kernel's back. The next read 176 // should discard cache and fetch from disk again. 177 if (reopen) { 178 // Close and reopen the file 179 expect_flush(ino, 1, ReturnErrno(ENOSYS)); 180 expect_release(ino, ReturnErrno(0)); 181 ASSERT_EQ(0, close(fd)); 182 expect_lookup(RELPATH, ino, nsize, entry_valid, attr_valid); 183 expect_open(ino, 1); 184 fd = open(FULLPATH, O_RDONLY); 185 ASSERT_LE(0, fd) << strerror(errno); 186 } 187 188 if (!cache_attrs) 189 expect_getattr(ino, 1, nsize, attr_valid); 190 expect_read(ino, 0, nsize, nsize, CONTENTS); 191 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)); 192 ASSERT_EQ(nsize, read(fd, buf, bufsize)) << strerror(errno); 193 ASSERT_EQ(0, memcmp(buf, CONTENTS, nsize)); 194 195 leak(fd); 196 } 197 198 INSTANTIATE_TEST_SUITE_P(Cache, Cache, 199 Combine( 200 /* Test every combination that: 201 * - does not cache at least one of entries and attrs 202 * - either doesn't cache attrs, or reopens the file 203 * In the other combinations, the kernel will never learn that 204 * the file's size has changed. 205 */ 206 Values( 207 std::make_tuple(false, false, false), 208 std::make_tuple(false, true, false), 209 std::make_tuple(true, false, false), 210 std::make_tuple(true, false, true), 211 std::make_tuple(true, true, false) 212 ), 213 Values(Writethrough, Writeback), 214 /* Test both reductions and extensions to file size */ 215 Values(20), 216 Values(10, 25) 217 ) 218 ); 219