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
33extern "C" {
34#include <fcntl.h>
35}
36
37#include "mockfs.hh"
38#include "utils.hh"
39
40using namespace testing;
41
42class Create: public FuseTest {
43public:
44
45void 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 */
66class Create_7_8: public Create {
67public:
68virtual void SetUp() {
69	m_kernel_minor_version = 8;
70	Create::SetUp();
71}
72
73void 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 */
90class Create_7_11: public FuseTest {
91public:
92virtual void SetUp() {
93	m_kernel_minor_version = 11;
94	FuseTest::SetUp();
95}
96
97void 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 */
118TEST_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 */
151TEST_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 */
198TEST_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 */
215TEST_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 */
264TEST_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 */
298TEST_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 */
335TEST_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
349TEST_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 */
381TEST_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
405TEST_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
429TEST_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