xref: /freebsd/tests/sys/fs/fusefs/create.cc (revision a4856c96d0e0c6a41d48c3fd43c139c8ab0857c1)
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 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(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
125 	expect_create(RELPATH, mode,
126 		ReturnImmediate([=](auto in __unused, auto& out) {
127 		SET_OUT_HEADER_LEN(out, create);
128 		out.body.create.entry.attr.mode = mode;
129 		out.body.create.entry.nodeid = ino;
130 		out.body.create.entry.entry_valid = UINT64_MAX;
131 		out.body.create.entry.attr_valid = UINT64_MAX;
132 	}));
133 
134 	EXPECT_CALL(*m_mock, process(
135 		ResultOf([=](auto in) {
136 			return (in.header.opcode == FUSE_GETATTR &&
137 				in.header.nodeid == ino);
138 		}, Eq(true)),
139 		_)
140 	).Times(0);
141 
142 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
143 	EXPECT_LE(0, fd) << strerror(errno);
144 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
145 }
146 
147 /* A successful CREATE operation should purge the parent dir's attr cache */
148 TEST_F(Create, clear_attr_cache)
149 {
150 	const char FULLPATH[] = "mountpoint/src";
151 	const char RELPATH[] = "src";
152 	mode_t mode = S_IFREG | 0755;
153 	uint64_t ino = 42;
154 	int fd;
155 	struct stat sb;
156 
157 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
158 	EXPECT_CALL(*m_mock, process(
159 		ResultOf([=](auto in) {
160 			return (in.header.opcode == FUSE_GETATTR &&
161 				in.header.nodeid == 1);
162 		}, Eq(true)),
163 		_)
164 	).Times(2)
165 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
166 		SET_OUT_HEADER_LEN(out, attr);
167 		out.body.attr.attr.ino = 1;
168 		out.body.attr.attr.mode = S_IFDIR | 0755;
169 		out.body.attr.attr_valid = UINT64_MAX;
170 	})));
171 
172 	expect_create(RELPATH, mode,
173 		ReturnImmediate([=](auto in __unused, auto& out) {
174 		SET_OUT_HEADER_LEN(out, create);
175 		out.body.create.entry.attr.mode = mode;
176 		out.body.create.entry.nodeid = ino;
177 		out.body.create.entry.entry_valid = UINT64_MAX;
178 		out.body.create.entry.attr_valid = UINT64_MAX;
179 	}));
180 
181 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
182 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
183 	EXPECT_LE(0, fd) << strerror(errno);
184 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
185 
186 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
187 }
188 
189 /*
190  * The fuse daemon fails the request with EEXIST.  This usually indicates a
191  * race condition: some other FUSE client created the file in between when the
192  * kernel checked for it with lookup and tried to create it with create
193  */
194 TEST_F(Create, eexist)
195 {
196 	const char FULLPATH[] = "mountpoint/some_file.txt";
197 	const char RELPATH[] = "some_file.txt";
198 	mode_t mode = S_IFREG | 0755;
199 
200 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
201 	expect_create(RELPATH, mode, ReturnErrno(EEXIST));
202 	EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
203 	EXPECT_EQ(EEXIST, errno);
204 }
205 
206 /*
207  * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback
208  * to FUSE_MKNOD/FUSE_OPEN
209  */
210 TEST_F(Create, Enosys)
211 {
212 	const char FULLPATH[] = "mountpoint/some_file.txt";
213 	const char RELPATH[] = "some_file.txt";
214 	mode_t mode = S_IFREG | 0755;
215 	uint64_t ino = 42;
216 	int fd;
217 
218 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
219 	expect_create(RELPATH, mode, ReturnErrno(ENOSYS));
220 
221 	EXPECT_CALL(*m_mock, process(
222 		ResultOf([=](auto in) {
223 			const char *name = (const char*)in.body.bytes +
224 				sizeof(fuse_mknod_in);
225 			return (in.header.opcode == FUSE_MKNOD &&
226 				in.body.mknod.mode == (S_IFREG | mode) &&
227 				in.body.mknod.rdev == 0 &&
228 				(0 == strcmp(RELPATH, name)));
229 		}, Eq(true)),
230 		_)
231 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
232 		SET_OUT_HEADER_LEN(out, entry);
233 		out.body.entry.attr.mode = mode;
234 		out.body.entry.nodeid = ino;
235 		out.body.entry.entry_valid = UINT64_MAX;
236 		out.body.entry.attr_valid = UINT64_MAX;
237 	})));
238 
239 	EXPECT_CALL(*m_mock, process(
240 		ResultOf([=](auto in) {
241 			return (in.header.opcode == FUSE_OPEN &&
242 				in.header.nodeid == ino);
243 		}, Eq(true)),
244 		_)
245 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
246 		out.header.len = sizeof(out.header);
247 		SET_OUT_HEADER_LEN(out, open);
248 	})));
249 
250 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
251 	EXPECT_LE(0, fd) << strerror(errno);
252 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
253 }
254 
255 /*
256  * Creating a new file after FUSE_LOOKUP returned a negative cache entry
257  */
258 TEST_F(Create, entry_cache_negative)
259 {
260 	const char FULLPATH[] = "mountpoint/some_file.txt";
261 	const char RELPATH[] = "some_file.txt";
262 	mode_t mode = S_IFREG | 0755;
263 	uint64_t ino = 42;
264 	int fd;
265 	/*
266 	 * Set entry_valid = 0 because this test isn't concerned with whether
267 	 * or not we actually cache negative entries, only with whether we
268 	 * interpret negative cache responses correctly.
269 	 */
270 	struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
271 
272 	/* create will first do a LOOKUP, adding a negative cache entry */
273 	EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid));
274 	expect_create(RELPATH, mode,
275 		ReturnImmediate([=](auto in __unused, auto& out) {
276 		SET_OUT_HEADER_LEN(out, create);
277 		out.body.create.entry.attr.mode = mode;
278 		out.body.create.entry.nodeid = ino;
279 		out.body.create.entry.entry_valid = UINT64_MAX;
280 		out.body.create.entry.attr_valid = UINT64_MAX;
281 	}));
282 
283 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
284 	ASSERT_LE(0, fd) << strerror(errno);
285 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
286 }
287 
288 /*
289  * Creating a new file should purge any negative namecache entries
290  */
291 TEST_F(Create, entry_cache_negative_purge)
292 {
293 	const char FULLPATH[] = "mountpoint/some_file.txt";
294 	const char RELPATH[] = "some_file.txt";
295 	mode_t mode = S_IFREG | 0755;
296 	uint64_t ino = 42;
297 	int fd;
298 	struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
299 
300 	/* create will first do a LOOKUP, adding a negative cache entry */
301 	EXPECT_LOOKUP(1, RELPATH).Times(1)
302 	.WillOnce(Invoke(ReturnNegativeCache(&entry_valid)))
303 	.RetiresOnSaturation();
304 
305 	/* Then the CREATE should purge the negative cache entry */
306 	expect_create(RELPATH, mode,
307 		ReturnImmediate([=](auto in __unused, auto& out) {
308 		SET_OUT_HEADER_LEN(out, create);
309 		out.body.create.entry.attr.mode = mode;
310 		out.body.create.entry.nodeid = ino;
311 		out.body.create.entry.attr_valid = UINT64_MAX;
312 	}));
313 
314 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
315 	ASSERT_LE(0, fd) << strerror(errno);
316 
317 	/* Finally, a subsequent lookup should query the daemon */
318 	expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1);
319 
320 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
321 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
322 }
323 
324 /*
325  * The daemon is responsible for checking file permissions (unless the
326  * default_permissions mount option was used)
327  */
328 TEST_F(Create, eperm)
329 {
330 	const char FULLPATH[] = "mountpoint/some_file.txt";
331 	const char RELPATH[] = "some_file.txt";
332 	mode_t mode = S_IFREG | 0755;
333 
334 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
335 	expect_create(RELPATH, mode, ReturnErrno(EPERM));
336 
337 	EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
338 	EXPECT_EQ(EPERM, errno);
339 }
340 
341 TEST_F(Create, ok)
342 {
343 	const char FULLPATH[] = "mountpoint/some_file.txt";
344 	const char RELPATH[] = "some_file.txt";
345 	mode_t mode = S_IFREG | 0755;
346 	uint64_t ino = 42;
347 	int fd;
348 
349 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
350 	expect_create(RELPATH, mode,
351 		ReturnImmediate([=](auto in __unused, auto& out) {
352 		SET_OUT_HEADER_LEN(out, create);
353 		out.body.create.entry.attr.mode = mode;
354 		out.body.create.entry.nodeid = ino;
355 		out.body.create.entry.entry_valid = UINT64_MAX;
356 		out.body.create.entry.attr_valid = UINT64_MAX;
357 	}));
358 
359 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
360 	EXPECT_LE(0, fd) << strerror(errno);
361 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
362 }
363 
364 /*
365  * A regression test for a bug that affected old FUSE implementations:
366  * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming
367  * contradiction between O_WRONLY and 0444
368  *
369  * For example:
370  * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886
371  */
372 TEST_F(Create, wronly_0444)
373 {
374 	const char FULLPATH[] = "mountpoint/some_file.txt";
375 	const char RELPATH[] = "some_file.txt";
376 	mode_t mode = S_IFREG | 0444;
377 	uint64_t ino = 42;
378 	int fd;
379 
380 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
381 	expect_create(RELPATH, mode,
382 		ReturnImmediate([=](auto in __unused, auto& out) {
383 		SET_OUT_HEADER_LEN(out, create);
384 		out.body.create.entry.attr.mode = mode;
385 		out.body.create.entry.nodeid = ino;
386 		out.body.create.entry.entry_valid = UINT64_MAX;
387 		out.body.create.entry.attr_valid = UINT64_MAX;
388 	}));
389 
390 	fd = open(FULLPATH, O_CREAT | O_WRONLY, mode);
391 	EXPECT_LE(0, fd) << strerror(errno);
392 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
393 }
394 
395 TEST_F(Create_7_8, ok)
396 {
397 	const char FULLPATH[] = "mountpoint/some_file.txt";
398 	const char RELPATH[] = "some_file.txt";
399 	mode_t mode = S_IFREG | 0755;
400 	uint64_t ino = 42;
401 	int fd;
402 
403 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
404 	expect_create(RELPATH, mode,
405 		ReturnImmediate([=](auto in __unused, auto& out) {
406 		SET_OUT_HEADER_LEN(out, create_7_8);
407 		out.body.create.entry.attr.mode = mode;
408 		out.body.create.entry.nodeid = ino;
409 		out.body.create.entry.entry_valid = UINT64_MAX;
410 		out.body.create.entry.attr_valid = UINT64_MAX;
411 	}));
412 
413 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
414 	EXPECT_LE(0, fd) << strerror(errno);
415 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
416 }
417 
418 TEST_F(Create_7_11, ok)
419 {
420 	const char FULLPATH[] = "mountpoint/some_file.txt";
421 	const char RELPATH[] = "some_file.txt";
422 	mode_t mode = S_IFREG | 0755;
423 	uint64_t ino = 42;
424 	int fd;
425 
426 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
427 	expect_create(RELPATH, mode,
428 		ReturnImmediate([=](auto in __unused, auto& out) {
429 		SET_OUT_HEADER_LEN(out, create);
430 		out.body.create.entry.attr.mode = mode;
431 		out.body.create.entry.nodeid = ino;
432 		out.body.create.entry.entry_valid = UINT64_MAX;
433 		out.body.create.entry.attr_valid = UINT64_MAX;
434 	}));
435 
436 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
437 	EXPECT_LE(0, fd) << strerror(errno);
438 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
439 }
440