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