View | Details | Raw Unified | Return to bug 244178
Collapse All | Expand All

(-)sys/fs/fuse/fuse_internal.c (+17 lines)
Lines 856-861 Link Here
856
	fdisp_destroy(&fdi);
856
	fdisp_destroy(&fdi);
857
}
857
}
858
858
859
SDT_PROBE_DEFINE2(fusefs, , internal, getattr_cache_incoherent,
860
	"struct vnode*", "struct fuse_attr_out*");
861
859
/* Fetch the vnode's attributes from the daemon*/
862
/* Fetch the vnode's attributes from the daemon*/
860
int
863
int
861
fuse_internal_do_getattr(struct vnode *vp, struct vattr *vap,
864
fuse_internal_do_getattr(struct vnode *vp, struct vattr *vap,
Lines 898-903 Link Here
898
		fao->attr.mtime = old_mtime.tv_sec;
901
		fao->attr.mtime = old_mtime.tv_sec;
899
		fao->attr.mtimensec = old_mtime.tv_nsec;
902
		fao->attr.mtimensec = old_mtime.tv_nsec;
900
	}
903
	}
904
	if (vnode_isreg(vp) &&
905
	    fvdat->cached_attrs.va_size != VNOVAL &&
906
	    fao->attr.size != fvdat->cached_attrs.va_size) {
907
		/*
908
		 * The server changed the file's size even though we had it
909
		 * cached!  That's a server bug.
910
		 */
911
		SDT_PROBE2(fusefs, , internal, getattr_cache_incoherent, vp,
912
		    fao);
913
		printf("%s: cache incoherent on %s!\n", __func__,
914
			vnode_mount(vp)->mnt_stat.f_mntonname);
915
		int iosize = fuse_iosize(vp);
916
		v_inval_buf_range(vp, 0, INT64_MAX, iosize);
917
	}
901
	fuse_internal_cache_attrs(vp, &fao->attr, fao->attr_valid,
918
	fuse_internal_cache_attrs(vp, &fao->attr, fao->attr_valid,
902
		fao->attr_valid_nsec, vap);
919
		fao->attr_valid_nsec, vap);
903
	if (vtyp != vnode_vtype(vp)) {
920
	if (vtyp != vnode_vtype(vp)) {
(-)sys/fs/fuse/fuse_node.c (-1 / +2 lines)
Lines 450-456 Link Here
450
	int error = 0;
450
	int error = 0;
451
451
452
	if (!(fvdat->flag & FN_SIZECHANGE) &&
452
	if (!(fvdat->flag & FN_SIZECHANGE) &&
453
		(VTOVA(vp) == NULL || fvdat->cached_attrs.va_size == VNOVAL)) 
453
		(!fuse_vnode_attr_cache_valid(vp) ||
454
		  fvdat->cached_attrs.va_size == VNOVAL)) 
454
		error = fuse_internal_do_getattr(vp, NULL, cred, td);
455
		error = fuse_internal_do_getattr(vp, NULL, cred, td);
455
456
456
	if (!error)
457
	if (!error)
(-)sys/fs/fuse/fuse_node.h (-3 / +8 lines)
Lines 134-146 Link Here
134
#define VTOFUD(vp) \
134
#define VTOFUD(vp) \
135
	((struct fuse_vnode_data *)((vp)->v_data))
135
	((struct fuse_vnode_data *)((vp)->v_data))
136
#define VTOI(vp)    (VTOFUD(vp)->nid)
136
#define VTOI(vp)    (VTOFUD(vp)->nid)
137
static inline struct vattr*
137
static inline bool fuse_vnode_attr_cache_valid(struct vnode *vp)
138
VTOVA(struct vnode *vp)
139
{
138
{
140
	struct bintime now;
139
	struct bintime now;
141
140
142
	getbinuptime(&now);
141
	getbinuptime(&now);
143
	if (bintime_cmp(&(VTOFUD(vp)->attr_cache_timeout), &now, >))
142
	return (bintime_cmp(&(VTOFUD(vp)->attr_cache_timeout), &now, >));
143
}
144
145
static inline struct vattr*
146
VTOVA(struct vnode *vp)
147
{
148
	if (fuse_vnode_attr_cache_valid(vp))
144
		return &(VTOFUD(vp)->cached_attrs);
149
		return &(VTOFUD(vp)->cached_attrs);
145
	else
150
	else
146
		return NULL;
151
		return NULL;
(-)sys/fs/fuse/fuse_vnops.c (-12 / +16 lines)
Lines 961-966 Link Here
961
961
962
SDT_PROBE_DEFINE3(fusefs, , vnops, cache_lookup,
962
SDT_PROBE_DEFINE3(fusefs, , vnops, cache_lookup,
963
	"int", "struct timespec*", "struct timespec*");
963
	"int", "struct timespec*", "struct timespec*");
964
SDT_PROBE_DEFINE2(fusefs, , vnops, lookup_cache_incoherent,
965
	"struct vnode*", "struct fuse_entry_out*");
964
/*
966
/*
965
    struct vnop_lookup_args {
967
    struct vnop_lookup_args {
966
	struct vnodeop_desc *a_desc;
968
	struct vnodeop_desc *a_desc;
Lines 1137-1142 Link Here
1137
			*vpp = dvp;
1139
			*vpp = dvp;
1138
		} else {
1140
		} else {
1139
			struct fuse_vnode_data *fvdat;
1141
			struct fuse_vnode_data *fvdat;
1142
			struct vattr *vap;
1140
1143
1141
			err = fuse_vnode_get(vnode_mount(dvp), feo, nid, dvp,
1144
			err = fuse_vnode_get(vnode_mount(dvp), feo, nid, dvp,
1142
			    &vp, cnp, vtyp);
1145
			    &vp, cnp, vtyp);
Lines 1157-1178 Link Here
1157
			 */
1160
			 */
1158
			fvdat = VTOFUD(vp);
1161
			fvdat = VTOFUD(vp);
1159
			if (vnode_isreg(vp) &&
1162
			if (vnode_isreg(vp) &&
1160
			    filesize != fvdat->cached_attrs.va_size &&
1163
			    ((filesize != fvdat->cached_attrs.va_size &&
1161
			    fvdat->flag & FN_SIZECHANGE) {
1164
			      fvdat->flag & FN_SIZECHANGE) ||
1165
			     ((vap = VTOVA(vp)) &&
1166
			      filesize != vap->va_size)))
1167
			{
1168
				SDT_PROBE2(fusefs, , vnops, lookup_cache_incoherent, vp, feo);
1169
				fvdat->flag &= ~FN_SIZECHANGE;
1162
				/*
1170
				/*
1163
				 * The FN_SIZECHANGE flag reflects a dirty
1171
				 * The server changed the file's size even
1164
				 * append.  If userspace lets us know our cache
1172
				 * though we had it cached, or had dirty writes
1165
				 * is invalid, that write was lost.  (Dirty
1173
				 * in the WB cache!
1166
				 * writes that do not cause append are also
1167
				 * lost, but we don't detect them here.)
1168
				 *
1169
				 * XXX: Maybe disable WB caching on this mount.
1170
				 */
1174
				 */
1171
				printf("%s: WB cache incoherent on %s!\n",
1175
				printf("%s: cache incoherent on %s!\n",
1172
				    __func__,
1176
				    __func__,
1173
				    vnode_mount(vp)->mnt_stat.f_mntonname);
1177
				    vnode_mount(vp)->mnt_stat.f_mntonname);
1174
1178
				int iosize = fuse_iosize(vp);
1175
				fvdat->flag &= ~FN_SIZECHANGE;
1179
				v_inval_buf_range(vp, 0, INT64_MAX, iosize);
1176
			}
1180
			}
1177
1181
1178
			MPASS(feo != NULL);
1182
			MPASS(feo != NULL);
(-)tests/sys/fs/fusefs/Makefile (+1 lines)
Lines 12-17 Link Here
12
GTESTS+=	access
12
GTESTS+=	access
13
GTESTS+=	allow_other
13
GTESTS+=	allow_other
14
GTESTS+=	bmap
14
GTESTS+=	bmap
15
GTESTS+=	cache
15
GTESTS+=	create
16
GTESTS+=	create
16
GTESTS+=	default_permissions
17
GTESTS+=	default_permissions
17
GTESTS+=	default_permissions_privileged
18
GTESTS+=	default_permissions_privileged
(-)tests/sys/fs/fusefs/cache.cc (+219 lines)
Line 0 Link Here
1
/*-
2
 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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
 * $FreeBSD$
28
 */
29
30
extern "C" {
31
#include <sys/param.h>
32
#include <fcntl.h>
33
}
34
35
#include "mockfs.hh"
36
#include "utils.hh"
37
38
/*
39
 * Tests for thorny cache problems not specific to any one opcode
40
 */
41
42
using namespace testing;
43
44
/*
45
 * Parameters
46
 * - reopen file	- If true, close and reopen the file between reads
47
 * - cache lookups	- If true, allow lookups to be cached
48
 * - cache attrs	- If true, allow file attributes to be cached
49
 * - cache_mode		- uncached, writeback, or writethrough
50
 * - initial size	- File size before truncation
51
 * - truncated size	- File size after truncation
52
 */
53
typedef tuple<tuple<bool, bool, bool>, cache_mode, ssize_t, ssize_t> CacheParam;
54
55
class Cache: public FuseTest, public WithParamInterface<CacheParam> {
56
public:
57
bool m_direct_io;
58
59
Cache(): m_direct_io(false) {};
60
61
virtual void SetUp() {
62
	int cache_mode = get<1>(GetParam());
63
	switch (cache_mode) {
64
		case Uncached:
65
			m_direct_io = true;
66
			break;
67
		case WritebackAsync:
68
			m_async = true;
69
			/* FALLTHROUGH */
70
		case Writeback:
71
			m_init_flags |= FUSE_WRITEBACK_CACHE;
72
			/* FALLTHROUGH */
73
		case Writethrough:
74
			break;
75
		default:
76
			FAIL() << "Unknown cache mode";
77
	}
78
79
	FuseTest::SetUp();
80
	if (IsSkipped())
81
		return;
82
}
83
84
void expect_getattr(uint64_t ino, int times, uint64_t size, uint64_t attr_valid)
85
{
86
	EXPECT_CALL(*m_mock, process(
87
		ResultOf([=](auto in) {
88
			return (in.header.opcode == FUSE_GETATTR &&
89
				in.header.nodeid == ino);
90
		}, Eq(true)),
91
		_)
92
	).Times(times)
93
	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
94
		SET_OUT_HEADER_LEN(out, attr);
95
		out.body.attr.attr_valid = attr_valid;
96
		out.body.attr.attr.ino = ino;
97
		out.body.attr.attr.mode = S_IFREG | 0644;
98
		out.body.attr.attr.size = size;
99
	})));
100
}
101
102
void expect_lookup(const char *relpath, uint64_t ino,
103
	uint64_t size, uint64_t entry_valid, uint64_t attr_valid)
104
{
105
	EXPECT_LOOKUP(FUSE_ROOT_ID, relpath)
106
	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
107
		SET_OUT_HEADER_LEN(out, entry);
108
		out.body.entry.attr.mode = S_IFREG | 0644;
109
		out.body.entry.nodeid = ino;
110
		out.body.entry.attr.nlink = 1;
111
		out.body.entry.attr_valid = attr_valid;
112
		out.body.entry.attr.size = size;
113
		out.body.entry.entry_valid = entry_valid;
114
	})));
115
}
116
117
void expect_open(uint64_t ino, int times)
118
{
119
	FuseTest::expect_open(ino, m_direct_io ? FOPEN_DIRECT_IO: 0, times);
120
}
121
122
void expect_release(uint64_t ino, ProcessMockerT r)
123
{
124
	EXPECT_CALL(*m_mock, process(
125
		ResultOf([=](auto in) {
126
			return (in.header.opcode == FUSE_RELEASE &&
127
				in.header.nodeid == ino);
128
		}, Eq(true)),
129
		_)
130
	).WillRepeatedly(Invoke(r));
131
}
132
133
};
134
135
// If the server truncates the file behind the kernel's back, the kernel should
136
// invalidate cached pages beyond the new EOF
137
TEST_P(Cache, truncate_by_surprise_invalidates_cache)
138
{
139
	const char FULLPATH[] = "mountpoint/some_file.txt";
140
	const char RELPATH[] = "some_file.txt";
141
	const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz";
142
	uint64_t ino = 42;
143
	uint64_t attr_valid, entry_valid;
144
	int fd;
145
	ssize_t bufsize = strlen(CONTENTS);
146
	uint8_t buf[bufsize];
147
	bool reopen = get<0>(get<0>(GetParam()));
148
	bool cache_lookups = get<1>(get<0>(GetParam()));
149
	bool cache_attrs = get<2>(get<0>(GetParam()));
150
	ssize_t osize = get<2>(GetParam());
151
	ssize_t nsize = get<3>(GetParam());
152
153
	ASSERT_LE(osize, bufsize);
154
	ASSERT_LE(nsize, bufsize);
155
	if (cache_attrs)
156
		attr_valid = UINT64_MAX;
157
	else
158
		attr_valid = 0;
159
	if (cache_lookups)
160
		entry_valid = UINT64_MAX;
161
	else
162
		entry_valid = 0;
163
164
	expect_lookup(RELPATH, ino, osize, entry_valid, attr_valid);
165
	expect_open(ino, 1);
166
	if (!cache_attrs)
167
		expect_getattr(ino, 2, osize, attr_valid);
168
	expect_read(ino, 0, osize, osize, CONTENTS);
169
170
	fd = open(FULLPATH, O_RDONLY);
171
	ASSERT_LE(0, fd) << strerror(errno);
172
173
	ASSERT_EQ(osize, read(fd, buf, bufsize)) << strerror(errno);
174
	ASSERT_EQ(0, memcmp(buf, CONTENTS, osize));
175
176
	// Now truncate the file behind the kernel's back.  The next read
177
	// should discard cache and fetch from disk again.
178
	if (reopen) {
179
		// Close and reopen the file
180
		expect_flush(ino, 1, ReturnErrno(ENOSYS));
181
		expect_release(ino, ReturnErrno(0));
182
		ASSERT_EQ(0, close(fd));
183
		expect_lookup(RELPATH, ino, nsize, entry_valid, attr_valid);
184
		expect_open(ino, 1);
185
		fd = open(FULLPATH, O_RDONLY);
186
		ASSERT_LE(0, fd) << strerror(errno);
187
	}
188
189
	if (!cache_attrs)
190
		expect_getattr(ino, 1, nsize, attr_valid);
191
	expect_read(ino, 0, nsize, nsize, CONTENTS);
192
	ASSERT_EQ(0, lseek(fd, 0, SEEK_SET));
193
	ASSERT_EQ(nsize, read(fd, buf, bufsize)) << strerror(errno);
194
	ASSERT_EQ(0, memcmp(buf, CONTENTS, nsize));
195
196
	leak(fd);
197
}
198
199
INSTANTIATE_TEST_CASE_P(Cache, Cache,
200
	Combine(
201
		/* Test every combination that:
202
		 * - does not cache at least one of entries and attrs
203
		 * - either doesn't cache attrs, or reopens the file
204
		 * In the other combinations, the kernel will never learn that
205
		 * the file's size has changed.
206
		 */
207
		Values(
208
			std::make_tuple(false, false, false),
209
			std::make_tuple(false, true, false),
210
			std::make_tuple(true, false, false),
211
			std::make_tuple(true, false, true),
212
			std::make_tuple(true, true, false)
213
		),
214
		Values(Writethrough, Writeback),
215
		/* Test both reductions and extensions to file size */
216
		Values(20),
217
		Values(10, 25)
218
	)
219
);
(-)tests/sys/fs/fusefs/getattr.cc (+1 lines)
Lines 159-164 Link Here
159
		out.body.attr.attr.mode = S_IFREG | 0644;
159
		out.body.attr.attr.mode = S_IFREG | 0644;
160
		out.body.attr.attr.ino = ino;	// Must match nodeid
160
		out.body.attr.attr.ino = ino;	// Must match nodeid
161
		out.body.attr.attr.blksize = 0;
161
		out.body.attr.attr.blksize = 0;
162
		out.body.attr.attr.size = 1;
162
	})));
163
	})));
163
164
164
	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
165
	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
(-)tests/sys/fs/fusefs/io.cc (-22 lines)
Lines 50-77 Link Here
50
50
51
using namespace testing;
51
using namespace testing;
52
52
53
enum cache_mode {
54
	Uncached,
55
	Writethrough,
56
	Writeback,
57
	WritebackAsync
58
};
59
60
const char *cache_mode_to_s(enum cache_mode cm) {
61
	switch (cm) {
62
	case Uncached:
63
		return "Uncached";
64
	case Writethrough:
65
		return "Writethrough";
66
	case Writeback:
67
		return "Writeback";
68
	case WritebackAsync:
69
		return "WritebackAsync";
70
	default:
71
		return "Unknown";
72
	}
73
}
74
75
const char FULLPATH[] = "mountpoint/some_file.txt";
53
const char FULLPATH[] = "mountpoint/some_file.txt";
76
const char RELPATH[] = "some_file.txt";
54
const char RELPATH[] = "some_file.txt";
77
const uint64_t ino = 42;
55
const uint64_t ino = 42;
(-)tests/sys/fs/fusefs/utils.cc (+15 lines)
Lines 90-95 Link Here
90
		GTEST_SKIP() << "current user is not allowed to mount";
90
		GTEST_SKIP() << "current user is not allowed to mount";
91
}
91
}
92
92
93
const char *cache_mode_to_s(enum cache_mode cm) {
94
	switch (cm) {
95
	case Uncached:
96
		return "Uncached";
97
	case Writethrough:
98
		return "Writethrough";
99
	case Writeback:
100
		return "Writeback";
101
	case WritebackAsync:
102
		return "WritebackAsync";
103
	default:
104
		return "Unknown";
105
	}
106
}
107
93
bool is_unsafe_aio_enabled(void) {
108
bool is_unsafe_aio_enabled(void) {
94
	const char *node = "vfs.aio.enable_unsafe";
109
	const char *node = "vfs.aio.enable_unsafe";
95
	int val = 0;
110
	int val = 0;
(-)tests/sys/fs/fusefs/utils.hh (+8 lines)
Lines 44-49 Link Here
44
	usleep(NAP_NS / 1000);
44
	usleep(NAP_NS / 1000);
45
}
45
}
46
46
47
enum cache_mode {
48
	Uncached,
49
	Writethrough,
50
	Writeback,
51
	WritebackAsync
52
};
53
54
const char *cache_mode_to_s(enum cache_mode cm);
47
bool is_unsafe_aio_enabled(void);
55
bool is_unsafe_aio_enabled(void);
48
56
49
extern const uint32_t libfuse_max_write;
57
extern const uint32_t libfuse_max_write;

Return to bug 244178