xref: /freebsd/tests/sys/fs/fusefs/create.cc (revision a90b9d0159070121c221b966469c3e36d912bf82)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
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 public:
42 
43 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
44 {
45 	mode_t mask = umask(0);
46 	(void)umask(mask);
47 
48 	EXPECT_CALL(*m_mock, process(
49 		ResultOf([=](auto in) {
50 			const char *name = (const char*)in.body.bytes +
51 				sizeof(fuse_create_in);
52 			return (in.header.opcode == FUSE_CREATE &&
53 				in.body.create.mode == mode &&
54 				in.body.create.umask == mask &&
55 				(0 == strcmp(relpath, name)));
56 		}, Eq(true)),
57 		_)
58 	).WillOnce(Invoke(r));
59 }
60 
61 };
62 
63 /* FUSE_CREATE operations for a protocol 7.8 server */
64 class Create_7_8: public Create {
65 public:
66 virtual void SetUp() {
67 	m_kernel_minor_version = 8;
68 	Create::SetUp();
69 }
70 
71 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
72 {
73 	EXPECT_CALL(*m_mock, process(
74 		ResultOf([=](auto in) {
75 			const char *name = (const char*)in.body.bytes +
76 				sizeof(fuse_open_in);
77 			return (in.header.opcode == FUSE_CREATE &&
78 				in.body.create.mode == mode &&
79 				(0 == strcmp(relpath, name)));
80 		}, Eq(true)),
81 		_)
82 	).WillOnce(Invoke(r));
83 }
84 
85 };
86 
87 /* FUSE_CREATE operations for a server built at protocol <= 7.11 */
88 class Create_7_11: public FuseTest {
89 public:
90 virtual void SetUp() {
91 	m_kernel_minor_version = 11;
92 	FuseTest::SetUp();
93 }
94 
95 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
96 {
97 	EXPECT_CALL(*m_mock, process(
98 		ResultOf([=](auto in) {
99 			const char *name = (const char*)in.body.bytes +
100 				sizeof(fuse_open_in);
101 			return (in.header.opcode == FUSE_CREATE &&
102 				in.body.create.mode == mode &&
103 				(0 == strcmp(relpath, name)));
104 		}, Eq(true)),
105 		_)
106 	).WillOnce(Invoke(r));
107 }
108 
109 };
110 
111 
112 /*
113  * If FUSE_CREATE sets attr_valid, then subsequent GETATTRs should use the
114  * attribute cache
115  */
116 TEST_F(Create, attr_cache)
117 {
118 	const char FULLPATH[] = "mountpoint/some_file.txt";
119 	const char RELPATH[] = "some_file.txt";
120 	mode_t mode = S_IFREG | 0755;
121 	uint64_t ino = 42;
122 	int fd;
123 
124 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
125 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
126 	expect_create(RELPATH, mode,
127 		ReturnImmediate([=](auto in __unused, auto& out) {
128 		SET_OUT_HEADER_LEN(out, create);
129 		out.body.create.entry.attr.mode = mode;
130 		out.body.create.entry.nodeid = ino;
131 		out.body.create.entry.entry_valid = UINT64_MAX;
132 		out.body.create.entry.attr_valid = UINT64_MAX;
133 	}));
134 
135 	EXPECT_CALL(*m_mock, process(
136 		ResultOf([=](auto in) {
137 			return (in.header.opcode == FUSE_GETATTR &&
138 				in.header.nodeid == ino);
139 		}, Eq(true)),
140 		_)
141 	).Times(0);
142 
143 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
144 	ASSERT_LE(0, fd) << strerror(errno);
145 	leak(fd);
146 }
147 
148 /* A successful CREATE operation should purge the parent dir's attr cache */
149 TEST_F(Create, clear_attr_cache)
150 {
151 	const char FULLPATH[] = "mountpoint/src";
152 	const char RELPATH[] = "src";
153 	mode_t mode = S_IFREG | 0755;
154 	uint64_t ino = 42;
155 	int fd;
156 	struct stat sb;
157 
158 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
159 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
160 	EXPECT_CALL(*m_mock, process(
161 		ResultOf([=](auto in) {
162 			return (in.header.opcode == FUSE_GETATTR &&
163 				in.header.nodeid == FUSE_ROOT_ID);
164 		}, Eq(true)),
165 		_)
166 	).Times(2)
167 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
168 		SET_OUT_HEADER_LEN(out, attr);
169 		out.body.attr.attr.ino = FUSE_ROOT_ID;
170 		out.body.attr.attr.mode = S_IFDIR | 0755;
171 		out.body.attr.attr_valid = UINT64_MAX;
172 	})));
173 
174 	expect_create(RELPATH, mode,
175 		ReturnImmediate([=](auto in __unused, auto& out) {
176 		SET_OUT_HEADER_LEN(out, create);
177 		out.body.create.entry.attr.mode = mode;
178 		out.body.create.entry.nodeid = ino;
179 		out.body.create.entry.entry_valid = UINT64_MAX;
180 		out.body.create.entry.attr_valid = UINT64_MAX;
181 	}));
182 
183 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
184 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
185 	ASSERT_LE(0, fd) << strerror(errno);
186 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
187 
188 	leak(fd);
189 }
190 
191 /*
192  * The fuse daemon fails the request with EEXIST.  This usually indicates a
193  * race condition: some other FUSE client created the file in between when the
194  * kernel checked for it with lookup and tried to create it with create
195  */
196 TEST_F(Create, eexist)
197 {
198 	const char FULLPATH[] = "mountpoint/some_file.txt";
199 	const char RELPATH[] = "some_file.txt";
200 	mode_t mode = S_IFREG | 0755;
201 
202 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
203 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
204 	expect_create(RELPATH, mode, ReturnErrno(EEXIST));
205 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode));
206 	EXPECT_EQ(EEXIST, errno);
207 }
208 
209 /*
210  * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback
211  * to FUSE_MKNOD/FUSE_OPEN
212  */
213 TEST_F(Create, Enosys)
214 {
215 	const char FULLPATH[] = "mountpoint/some_file.txt";
216 	const char RELPATH[] = "some_file.txt";
217 	mode_t mode = S_IFREG | 0755;
218 	uint64_t ino = 42;
219 	int fd;
220 
221 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
222 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
223 	expect_create(RELPATH, mode, ReturnErrno(ENOSYS));
224 
225 	EXPECT_CALL(*m_mock, process(
226 		ResultOf([=](auto in) {
227 			const char *name = (const char*)in.body.bytes +
228 				sizeof(fuse_mknod_in);
229 			return (in.header.opcode == FUSE_MKNOD &&
230 				in.body.mknod.mode == (S_IFREG | mode) &&
231 				in.body.mknod.rdev == 0 &&
232 				(0 == strcmp(RELPATH, name)));
233 		}, Eq(true)),
234 		_)
235 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
236 		SET_OUT_HEADER_LEN(out, entry);
237 		out.body.entry.attr.mode = mode;
238 		out.body.entry.nodeid = ino;
239 		out.body.entry.entry_valid = UINT64_MAX;
240 		out.body.entry.attr_valid = UINT64_MAX;
241 	})));
242 
243 	EXPECT_CALL(*m_mock, process(
244 		ResultOf([=](auto in) {
245 			return (in.header.opcode == FUSE_OPEN &&
246 				in.header.nodeid == ino);
247 		}, Eq(true)),
248 		_)
249 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
250 		out.header.len = sizeof(out.header);
251 		SET_OUT_HEADER_LEN(out, open);
252 	})));
253 
254 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
255 	ASSERT_LE(0, fd) << strerror(errno);
256 	leak(fd);
257 }
258 
259 /*
260  * Creating a new file after FUSE_LOOKUP returned a negative cache entry
261  */
262 TEST_F(Create, entry_cache_negative)
263 {
264 	const char FULLPATH[] = "mountpoint/some_file.txt";
265 	const char RELPATH[] = "some_file.txt";
266 	mode_t mode = S_IFREG | 0755;
267 	uint64_t ino = 42;
268 	int fd;
269 	/*
270 	 * Set entry_valid = 0 because this test isn't concerned with whether
271 	 * or not we actually cache negative entries, only with whether we
272 	 * interpret negative cache responses correctly.
273 	 */
274 	struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
275 
276 	/* create will first do a LOOKUP, adding a negative cache entry */
277 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
278 		.WillOnce(ReturnNegativeCache(&entry_valid));
279 	expect_create(RELPATH, mode,
280 		ReturnImmediate([=](auto in __unused, auto& out) {
281 		SET_OUT_HEADER_LEN(out, create);
282 		out.body.create.entry.attr.mode = mode;
283 		out.body.create.entry.nodeid = ino;
284 		out.body.create.entry.entry_valid = UINT64_MAX;
285 		out.body.create.entry.attr_valid = UINT64_MAX;
286 	}));
287 
288 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
289 	ASSERT_LE(0, fd) << strerror(errno);
290 	leak(fd);
291 }
292 
293 /*
294  * Creating a new file should purge any negative namecache entries
295  */
296 TEST_F(Create, entry_cache_negative_purge)
297 {
298 	const char FULLPATH[] = "mountpoint/some_file.txt";
299 	const char RELPATH[] = "some_file.txt";
300 	mode_t mode = S_IFREG | 0755;
301 	uint64_t ino = 42;
302 	int fd;
303 	struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
304 
305 	/* create will first do a LOOKUP, adding a negative cache entry */
306 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH).Times(1)
307 		.WillOnce(Invoke(ReturnNegativeCache(&entry_valid)))
308 	.RetiresOnSaturation();
309 
310 	/* Then the CREATE should purge the negative cache entry */
311 	expect_create(RELPATH, mode,
312 		ReturnImmediate([=](auto in __unused, auto& out) {
313 		SET_OUT_HEADER_LEN(out, create);
314 		out.body.create.entry.attr.mode = mode;
315 		out.body.create.entry.nodeid = ino;
316 		out.body.create.entry.attr_valid = UINT64_MAX;
317 	}));
318 
319 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
320 	ASSERT_LE(0, fd) << strerror(errno);
321 
322 	/* Finally, a subsequent lookup should query the daemon */
323 	expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1);
324 
325 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
326 	leak(fd);
327 }
328 
329 /*
330  * The daemon is responsible for checking file permissions (unless the
331  * default_permissions mount option was used)
332  */
333 TEST_F(Create, eperm)
334 {
335 	const char FULLPATH[] = "mountpoint/some_file.txt";
336 	const char RELPATH[] = "some_file.txt";
337 	mode_t mode = S_IFREG | 0755;
338 
339 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
340 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
341 	expect_create(RELPATH, mode, ReturnErrno(EPERM));
342 
343 	EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode));
344 	EXPECT_EQ(EPERM, errno);
345 }
346 
347 TEST_F(Create, ok)
348 {
349 	const char FULLPATH[] = "mountpoint/some_file.txt";
350 	const char RELPATH[] = "some_file.txt";
351 	mode_t mode = S_IFREG | 0755;
352 	uint64_t ino = 42;
353 	int fd;
354 
355 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
356 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
357 	expect_create(RELPATH, mode,
358 		ReturnImmediate([=](auto in __unused, auto& out) {
359 		SET_OUT_HEADER_LEN(out, create);
360 		out.body.create.entry.attr.mode = mode;
361 		out.body.create.entry.nodeid = ino;
362 		out.body.create.entry.entry_valid = UINT64_MAX;
363 		out.body.create.entry.attr_valid = UINT64_MAX;
364 	}));
365 
366 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
367 	ASSERT_LE(0, fd) << strerror(errno);
368 	leak(fd);
369 }
370 
371 /*
372  * Nothing bad should happen if the server returns the parent's inode number
373  * for the newly created file.  Regression test for bug 263662
374  * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263662
375  */
376 TEST_F(Create, parent_inode)
377 {
378 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
379 	const char RELDIRPATH[] = "some_dir";
380 	const char RELPATH[] = "some_file.txt";
381 	mode_t mode = 0755;
382 	uint64_t ino = 42;
383 	int fd;
384 
385 	expect_lookup(RELDIRPATH, ino, S_IFDIR | mode, 0, 1);
386 	EXPECT_LOOKUP(ino, RELPATH)
387 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
388 	expect_create(RELPATH, S_IFREG | mode,
389 		ReturnImmediate([=](auto in __unused, auto& out) {
390 		SET_OUT_HEADER_LEN(out, create);
391 		out.body.create.entry.attr.mode = S_IFREG | mode;
392 		/* Return the same inode as the parent dir */
393 		out.body.create.entry.nodeid = ino;
394 		out.body.create.entry.entry_valid = UINT64_MAX;
395 		out.body.create.entry.attr_valid = UINT64_MAX;
396 	}));
397 	// FUSE_RELEASE happens asynchronously, so it may or may not arrive
398 	// before the test completes.
399 	EXPECT_CALL(*m_mock, process(
400 		ResultOf([=](auto in) {
401 			return (in.header.opcode == FUSE_RELEASE);
402 		}, Eq(true)),
403 		_)
404 	).Times(AtMost(1))
405 	.WillOnce(Invoke([=](auto in __unused, auto &out __unused) { }));
406 
407 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
408 	ASSERT_EQ(-1, fd);
409 	EXPECT_EQ(EIO, errno);
410 }
411 
412 /*
413  * A regression test for a bug that affected old FUSE implementations:
414  * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming
415  * contradiction between O_WRONLY and 0444
416  *
417  * For example:
418  * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886
419  */
420 TEST_F(Create, wronly_0444)
421 {
422 	const char FULLPATH[] = "mountpoint/some_file.txt";
423 	const char RELPATH[] = "some_file.txt";
424 	mode_t mode = S_IFREG | 0444;
425 	uint64_t ino = 42;
426 	int fd;
427 
428 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
429 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
430 	expect_create(RELPATH, mode,
431 		ReturnImmediate([=](auto in __unused, auto& out) {
432 		SET_OUT_HEADER_LEN(out, create);
433 		out.body.create.entry.attr.mode = mode;
434 		out.body.create.entry.nodeid = ino;
435 		out.body.create.entry.entry_valid = UINT64_MAX;
436 		out.body.create.entry.attr_valid = UINT64_MAX;
437 	}));
438 
439 	fd = open(FULLPATH, O_CREAT | O_WRONLY, mode);
440 	ASSERT_LE(0, fd) << strerror(errno);
441 	leak(fd);
442 }
443 
444 TEST_F(Create_7_8, ok)
445 {
446 	const char FULLPATH[] = "mountpoint/some_file.txt";
447 	const char RELPATH[] = "some_file.txt";
448 	mode_t mode = S_IFREG | 0755;
449 	uint64_t ino = 42;
450 	int fd;
451 
452 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
453 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
454 	expect_create(RELPATH, mode,
455 		ReturnImmediate([=](auto in __unused, auto& out) {
456 		SET_OUT_HEADER_LEN(out, create_7_8);
457 		out.body.create_7_8.entry.attr.mode = mode;
458 		out.body.create_7_8.entry.nodeid = ino;
459 		out.body.create_7_8.entry.entry_valid = UINT64_MAX;
460 		out.body.create_7_8.entry.attr_valid = UINT64_MAX;
461 		out.body.create_7_8.open.fh = FH;
462 	}));
463 	expect_flush(ino, 1, ReturnErrno(0));
464 	expect_release(ino, FH);
465 
466 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
467 	ASSERT_LE(0, fd) << strerror(errno);
468 	close(fd);
469 }
470 
471 TEST_F(Create_7_11, ok)
472 {
473 	const char FULLPATH[] = "mountpoint/some_file.txt";
474 	const char RELPATH[] = "some_file.txt";
475 	mode_t mode = S_IFREG | 0755;
476 	uint64_t ino = 42;
477 	int fd;
478 
479 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
480 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
481 	expect_create(RELPATH, mode,
482 		ReturnImmediate([=](auto in __unused, auto& out) {
483 		SET_OUT_HEADER_LEN(out, create);
484 		out.body.create.entry.attr.mode = mode;
485 		out.body.create.entry.nodeid = ino;
486 		out.body.create.entry.entry_valid = UINT64_MAX;
487 		out.body.create.entry.attr_valid = UINT64_MAX;
488 	}));
489 
490 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
491 	ASSERT_LE(0, fd) << strerror(errno);
492 	leak(fd);
493 }
494