xref: /freebsd/tests/sys/fs/fusefs/create.cc (revision fe6060f10f634930ff71b7c50291ddc610da2475)
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  * $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  * A regression test for a bug that affected old FUSE implementations:
375  * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming
376  * contradiction between O_WRONLY and 0444
377  *
378  * For example:
379  * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886
380  */
381 TEST_F(Create, wronly_0444)
382 {
383 	const char FULLPATH[] = "mountpoint/some_file.txt";
384 	const char RELPATH[] = "some_file.txt";
385 	mode_t mode = S_IFREG | 0444;
386 	uint64_t ino = 42;
387 	int fd;
388 
389 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
390 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
391 	expect_create(RELPATH, mode,
392 		ReturnImmediate([=](auto in __unused, auto& out) {
393 		SET_OUT_HEADER_LEN(out, create);
394 		out.body.create.entry.attr.mode = mode;
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 
400 	fd = open(FULLPATH, O_CREAT | O_WRONLY, mode);
401 	ASSERT_LE(0, fd) << strerror(errno);
402 	leak(fd);
403 }
404 
405 TEST_F(Create_7_8, ok)
406 {
407 	const char FULLPATH[] = "mountpoint/some_file.txt";
408 	const char RELPATH[] = "some_file.txt";
409 	mode_t mode = S_IFREG | 0755;
410 	uint64_t ino = 42;
411 	int fd;
412 
413 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
414 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
415 	expect_create(RELPATH, mode,
416 		ReturnImmediate([=](auto in __unused, auto& out) {
417 		SET_OUT_HEADER_LEN(out, create_7_8);
418 		out.body.create.entry.attr.mode = mode;
419 		out.body.create.entry.nodeid = ino;
420 		out.body.create.entry.entry_valid = UINT64_MAX;
421 		out.body.create.entry.attr_valid = UINT64_MAX;
422 	}));
423 
424 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
425 	ASSERT_LE(0, fd) << strerror(errno);
426 	leak(fd);
427 }
428 
429 TEST_F(Create_7_11, ok)
430 {
431 	const char FULLPATH[] = "mountpoint/some_file.txt";
432 	const char RELPATH[] = "some_file.txt";
433 	mode_t mode = S_IFREG | 0755;
434 	uint64_t ino = 42;
435 	int fd;
436 
437 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
438 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
439 	expect_create(RELPATH, mode,
440 		ReturnImmediate([=](auto in __unused, auto& out) {
441 		SET_OUT_HEADER_LEN(out, create);
442 		out.body.create.entry.attr.mode = mode;
443 		out.body.create.entry.nodeid = ino;
444 		out.body.create.entry.entry_valid = UINT64_MAX;
445 		out.body.create.entry.attr_valid = UINT64_MAX;
446 	}));
447 
448 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
449 	ASSERT_LE(0, fd) << strerror(errno);
450 	leak(fd);
451 }
452