xref: /freebsd/tests/sys/fs/fusefs/create.cc (revision 19ef317d62611f14022e0a37e969c3cf5adc6478)
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 }
34 
35 #include "mockfs.hh"
36 #include "utils.hh"
37 
38 using namespace testing;
39 
40 class Create: public FuseTest {};
41 
42 /*
43  * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the
44  * attribute cache
45  */
46 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
47 TEST_F(Create, DISABLED_attr_cache)
48 {
49 	const char FULLPATH[] = "mountpoint/some_file.txt";
50 	const char RELPATH[] = "some_file.txt";
51 	mode_t mode = 0755;
52 	uint64_t ino = 42;
53 	int fd;
54 
55 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
56 
57 	EXPECT_CALL(*m_mock, process(
58 		ResultOf([=](auto in) {
59 			const char *name = (const char*)in->body.bytes +
60 				sizeof(fuse_open_in);
61 			return (in->header.opcode == FUSE_CREATE &&
62 				(0 == strcmp(RELPATH, name)));
63 		}, Eq(true)),
64 		_)
65 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
66 		SET_OUT_HEADER_LEN(out, create);
67 		out->body.create.entry.attr.mode = S_IFREG | mode;
68 		out->body.create.entry.nodeid = ino;
69 		out->body.create.entry.entry_valid = UINT64_MAX;
70 		out->body.create.entry.attr_valid = UINT64_MAX;
71 	})));
72 
73 	EXPECT_CALL(*m_mock, process(
74 		ResultOf([=](auto in) {
75 			return (in->header.opcode == FUSE_GETATTR &&
76 				in->header.nodeid == ino);
77 		}, Eq(true)),
78 		_)
79 	).Times(0);
80 
81 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
82 	EXPECT_LE(0, fd) << strerror(errno);
83 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
84 }
85 
86 /*
87  * The fuse daemon fails the request with EEXIST.  This usually indicates a
88  * race condition: some other FUSE client created the file in between when the
89  * kernel checked for it with lookup and tried to create it with create
90  */
91 TEST_F(Create, eexist)
92 {
93 	const char FULLPATH[] = "mountpoint/some_file.txt";
94 	const char RELPATH[] = "some_file.txt";
95 	mode_t mode = 0755;
96 
97 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
98 
99 	EXPECT_CALL(*m_mock, process(
100 		ResultOf([=](auto in) {
101 			const char *name = (const char*)in->body.bytes +
102 				sizeof(fuse_open_in);
103 			return (in->header.opcode == FUSE_CREATE &&
104 				(0 == strcmp(RELPATH, name)));
105 		}, Eq(true)),
106 		_)
107 	).WillOnce(Invoke(ReturnErrno(EEXIST)));
108 	EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
109 	EXPECT_EQ(EEXIST, errno);
110 }
111 
112 /*
113  * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback
114  * to FUSE_MKNOD/FUSE_OPEN
115  */
116 TEST_F(Create, Enosys)
117 {
118 	const char FULLPATH[] = "mountpoint/some_file.txt";
119 	const char RELPATH[] = "some_file.txt";
120 	mode_t mode = 0755;
121 	uint64_t ino = 42;
122 	int fd;
123 
124 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
125 
126 	EXPECT_CALL(*m_mock, process(
127 		ResultOf([=](auto in) {
128 			const char *name = (const char*)in->body.bytes +
129 				sizeof(fuse_open_in);
130 			return (in->header.opcode == FUSE_CREATE &&
131 				(0 == strcmp(RELPATH, name)));
132 		}, Eq(true)),
133 		_)
134 	).WillOnce(Invoke(ReturnErrno(ENOSYS)));
135 
136 	EXPECT_CALL(*m_mock, process(
137 		ResultOf([=](auto in) {
138 			const char *name = (const char*)in->body.bytes +
139 				sizeof(fuse_mknod_in);
140 			return (in->header.opcode == FUSE_MKNOD &&
141 				in->body.mknod.mode == (S_IFREG | mode) &&
142 				in->body.mknod.rdev == 0 &&
143 				(0 == strcmp(RELPATH, name)));
144 		}, Eq(true)),
145 		_)
146 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
147 		SET_OUT_HEADER_LEN(out, entry);
148 		out->body.entry.attr.mode = S_IFREG | mode;
149 		out->body.entry.nodeid = ino;
150 		out->body.entry.entry_valid = UINT64_MAX;
151 		out->body.entry.attr_valid = UINT64_MAX;
152 	})));
153 
154 	EXPECT_CALL(*m_mock, process(
155 		ResultOf([=](auto in) {
156 			return (in->header.opcode == FUSE_OPEN &&
157 				in->header.nodeid == ino);
158 		}, Eq(true)),
159 		_)
160 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
161 		out->header.len = sizeof(out->header);
162 		SET_OUT_HEADER_LEN(out, open);
163 	})));
164 
165 	/* Until the attr cache is working, we may send an additional GETATTR */
166 	EXPECT_CALL(*m_mock, process(
167 		ResultOf([=](auto in) {
168 			return (in->header.opcode == FUSE_GETATTR &&
169 				in->header.nodeid == ino);
170 		}, Eq(true)),
171 		_)
172 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
173 		SET_OUT_HEADER_LEN(out, attr);
174 		out->body.attr.attr.ino = ino;	// Must match nodeid
175 		out->body.attr.attr.mode = S_IFREG | 0644;
176 	})));
177 
178 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
179 	EXPECT_LE(0, fd) << strerror(errno);
180 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
181 }
182 
183 /*
184  * Creating a new file after FUSE_LOOKUP returned a negative cache entry
185  */
186 TEST_F(Create, entry_cache_negative)
187 {
188 	const char FULLPATH[] = "mountpoint/some_file.txt";
189 	const char RELPATH[] = "some_file.txt";
190 	mode_t mode = 0755;
191 	uint64_t ino = 42;
192 	int fd;
193 	/*
194 	 * Set entry_valid = 0 because this test isn't concerned with whether
195 	 * or not we actually cache negative entries, only with whether we
196 	 * interpret negative cache responses correctly.
197 	 */
198 	struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
199 
200 	/* create will first do a LOOKUP, adding a negative cache entry */
201 	EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid));
202 
203 	EXPECT_CALL(*m_mock, process(
204 		ResultOf([=](auto in) {
205 			const char *name = (const char*)in->body.bytes +
206 				sizeof(fuse_open_in);
207 			return (in->header.opcode == FUSE_CREATE &&
208 				(0 == strcmp(RELPATH, name)));
209 		}, Eq(true)),
210 		_)
211 	).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
212 		SET_OUT_HEADER_LEN(out, create);
213 		out->body.create.entry.attr.mode = S_IFREG | mode;
214 		out->body.create.entry.nodeid = ino;
215 		out->body.create.entry.entry_valid = UINT64_MAX;
216 		out->body.create.entry.attr_valid = UINT64_MAX;
217 	})));
218 
219 	/* Until the attr cache is working, we may send an additional GETATTR */
220 	EXPECT_CALL(*m_mock, process(
221 		ResultOf([=](auto in) {
222 			return (in->header.opcode == FUSE_GETATTR &&
223 				in->header.nodeid == ino);
224 		}, Eq(true)),
225 		_)
226 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
227 		SET_OUT_HEADER_LEN(out, attr);
228 		out->body.attr.attr.ino = ino;	// Must match nodeid
229 		out->body.attr.attr.mode = S_IFREG | 0644;
230 	})));
231 
232 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
233 	ASSERT_LE(0, fd) << strerror(errno);
234 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
235 }
236 
237 /*
238  * Creating a new file should purge any negative namecache entries
239  */
240 TEST_F(Create, entry_cache_negative_purge)
241 {
242 	const char FULLPATH[] = "mountpoint/some_file.txt";
243 	const char RELPATH[] = "some_file.txt";
244 	mode_t mode = 0755;
245 	uint64_t ino = 42;
246 	int fd;
247 	struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
248 
249 	/* create will first do a LOOKUP, adding a negative cache entry */
250 	EXPECT_LOOKUP(1, RELPATH).Times(1)
251 	.WillOnce(Invoke(ReturnNegativeCache(&entry_valid)))
252 	.RetiresOnSaturation();
253 
254 	/* Then the CREATE should purge the negative cache entry */
255 	EXPECT_CALL(*m_mock, process(
256 		ResultOf([=](auto in) {
257 			const char *name = (const char*)in->body.bytes +
258 				sizeof(fuse_open_in);
259 			return (in->header.opcode == FUSE_CREATE &&
260 				(0 == strcmp(RELPATH, name)));
261 		}, Eq(true)),
262 		_)
263 	).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
264 		SET_OUT_HEADER_LEN(out, create);
265 		out->body.create.entry.attr.mode = S_IFREG | mode;
266 		out->body.create.entry.nodeid = ino;
267 		out->body.create.entry.attr_valid = UINT64_MAX;
268 	})));
269 
270 	/* Until the attr cache is working, we may send an additional GETATTR */
271 	EXPECT_CALL(*m_mock, process(
272 		ResultOf([=](auto in) {
273 			return (in->header.opcode == FUSE_GETATTR &&
274 				in->header.nodeid == ino);
275 		}, Eq(true)),
276 		_)
277 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
278 		SET_OUT_HEADER_LEN(out, attr);
279 		out->body.attr.attr.ino = ino;	// Must match nodeid
280 		out->body.attr.attr.mode = S_IFREG | 0644;
281 	})));
282 
283 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
284 	ASSERT_LE(0, fd) << strerror(errno);
285 
286 	/* Finally, a subsequent lookup should query the daemon */
287 	expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1);
288 
289 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
290 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
291 }
292 
293 /*
294  * The daemon is responsible for checking file permissions (unless the
295  * default_permissions mount option was used)
296  */
297 TEST_F(Create, eperm)
298 {
299 	const char FULLPATH[] = "mountpoint/some_file.txt";
300 	const char RELPATH[] = "some_file.txt";
301 	mode_t mode = 0755;
302 
303 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
304 
305 	EXPECT_CALL(*m_mock, process(
306 		ResultOf([=](auto in) {
307 			const char *name = (const char*)in->body.bytes +
308 				sizeof(fuse_open_in);
309 			return (in->header.opcode == FUSE_CREATE &&
310 				(0 == strcmp(RELPATH, name)));
311 		}, Eq(true)),
312 		_)
313 	).WillOnce(Invoke(ReturnErrno(EPERM)));
314 	EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
315 	EXPECT_EQ(EPERM, errno);
316 }
317 
318 TEST_F(Create, ok)
319 {
320 	const char FULLPATH[] = "mountpoint/some_file.txt";
321 	const char RELPATH[] = "some_file.txt";
322 	mode_t mode = 0755;
323 	uint64_t ino = 42;
324 	int fd;
325 
326 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
327 
328 	EXPECT_CALL(*m_mock, process(
329 		ResultOf([=](auto in) {
330 			const char *name = (const char*)in->body.bytes +
331 				sizeof(fuse_open_in);
332 			return (in->header.opcode == FUSE_CREATE &&
333 				(0 == strcmp(RELPATH, name)));
334 		}, Eq(true)),
335 		_)
336 	).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
337 		SET_OUT_HEADER_LEN(out, create);
338 		out->body.create.entry.attr.mode = S_IFREG | mode;
339 		out->body.create.entry.nodeid = ino;
340 		out->body.create.entry.entry_valid = UINT64_MAX;
341 		out->body.create.entry.attr_valid = UINT64_MAX;
342 	})));
343 
344 	/* Until the attr cache is working, we may send an additional GETATTR */
345 	EXPECT_CALL(*m_mock, process(
346 		ResultOf([=](auto in) {
347 			return (in->header.opcode == FUSE_GETATTR &&
348 				in->header.nodeid == ino);
349 		}, Eq(true)),
350 		_)
351 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
352 		SET_OUT_HEADER_LEN(out, attr);
353 		out->body.attr.attr.ino = ino;	// Must match nodeid
354 		out->body.attr.attr.mode = S_IFREG | 0644;
355 	})));
356 
357 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
358 	EXPECT_LE(0, fd) << strerror(errno);
359 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
360 }
361