xref: /freebsd/tests/sys/fs/fusefs/create.cc (revision 16bd2d47c764179be65290039fd3378425bfa876)
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 	EXPECT_CALL(*m_mock, process(
46 		ResultOf([=](auto in) {
47 			const char *name = (const char*)in->body.bytes +
48 				sizeof(fuse_open_in);
49 			return (in->header.opcode == FUSE_CREATE &&
50 				in->body.open.mode == mode &&
51 				(0 == strcmp(relpath, name)));
52 		}, Eq(true)),
53 		_)
54 	).WillOnce(Invoke(r));
55 }
56 
57 };
58 
59 /* FUSE_CREATE operations for a protocol 7.8 server */
60 class Create_7_8: public Create {
61 public:
62 virtual void SetUp() {
63 	m_kernel_minor_version = 8;
64 	Create::SetUp();
65 }
66 };
67 
68 
69 /*
70  * If FUSE_CREATE sets attr_valid, then subsequent GETATTRs should use the
71  * attribute cache
72  */
73 TEST_F(Create, attr_cache)
74 {
75 	const char FULLPATH[] = "mountpoint/some_file.txt";
76 	const char RELPATH[] = "some_file.txt";
77 	mode_t mode = S_IFREG | 0755;
78 	uint64_t ino = 42;
79 	int fd;
80 
81 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
82 	expect_create(RELPATH, mode,
83 		ReturnImmediate([=](auto in __unused, auto out) {
84 		SET_OUT_HEADER_LEN(out, create);
85 		out->body.create.entry.attr.mode = mode;
86 		out->body.create.entry.nodeid = ino;
87 		out->body.create.entry.entry_valid = UINT64_MAX;
88 		out->body.create.entry.attr_valid = UINT64_MAX;
89 	}));
90 
91 	EXPECT_CALL(*m_mock, process(
92 		ResultOf([=](auto in) {
93 			return (in->header.opcode == FUSE_GETATTR &&
94 				in->header.nodeid == ino);
95 		}, Eq(true)),
96 		_)
97 	).Times(0);
98 
99 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
100 	EXPECT_LE(0, fd) << strerror(errno);
101 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
102 }
103 
104 /* A successful CREATE operation should purge the parent dir's attr cache */
105 TEST_F(Create, clear_attr_cache)
106 {
107 	const char FULLPATH[] = "mountpoint/src";
108 	const char RELPATH[] = "src";
109 	mode_t mode = S_IFREG | 0755;
110 	uint64_t ino = 42;
111 	int fd;
112 	struct stat sb;
113 
114 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
115 	EXPECT_CALL(*m_mock, process(
116 		ResultOf([=](auto in) {
117 			return (in->header.opcode == FUSE_GETATTR &&
118 				in->header.nodeid == 1);
119 		}, Eq(true)),
120 		_)
121 	).Times(2)
122 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
123 		SET_OUT_HEADER_LEN(out, attr);
124 		out->body.attr.attr.ino = 1;
125 		out->body.attr.attr.mode = S_IFDIR | 0755;
126 		out->body.attr.attr_valid = UINT64_MAX;
127 	})));
128 
129 	expect_create(RELPATH, mode,
130 		ReturnImmediate([=](auto in __unused, auto out) {
131 		SET_OUT_HEADER_LEN(out, create);
132 		out->body.create.entry.attr.mode = mode;
133 		out->body.create.entry.nodeid = ino;
134 		out->body.create.entry.entry_valid = UINT64_MAX;
135 		out->body.create.entry.attr_valid = UINT64_MAX;
136 	}));
137 
138 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
139 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
140 	EXPECT_LE(0, fd) << strerror(errno);
141 	EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno);
142 
143 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
144 }
145 
146 /*
147  * The fuse daemon fails the request with EEXIST.  This usually indicates a
148  * race condition: some other FUSE client created the file in between when the
149  * kernel checked for it with lookup and tried to create it with create
150  */
151 TEST_F(Create, eexist)
152 {
153 	const char FULLPATH[] = "mountpoint/some_file.txt";
154 	const char RELPATH[] = "some_file.txt";
155 	mode_t mode = S_IFREG | 0755;
156 
157 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
158 	expect_create(RELPATH, mode, ReturnErrno(EEXIST));
159 	EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
160 	EXPECT_EQ(EEXIST, errno);
161 }
162 
163 /*
164  * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback
165  * to FUSE_MKNOD/FUSE_OPEN
166  */
167 TEST_F(Create, Enosys)
168 {
169 	const char FULLPATH[] = "mountpoint/some_file.txt";
170 	const char RELPATH[] = "some_file.txt";
171 	mode_t mode = S_IFREG | 0755;
172 	uint64_t ino = 42;
173 	int fd;
174 
175 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
176 	expect_create(RELPATH, mode, ReturnErrno(ENOSYS));
177 
178 	EXPECT_CALL(*m_mock, process(
179 		ResultOf([=](auto in) {
180 			const char *name = (const char*)in->body.bytes +
181 				sizeof(fuse_mknod_in);
182 			return (in->header.opcode == FUSE_MKNOD &&
183 				in->body.mknod.mode == (S_IFREG | mode) &&
184 				in->body.mknod.rdev == 0 &&
185 				(0 == strcmp(RELPATH, name)));
186 		}, Eq(true)),
187 		_)
188 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
189 		SET_OUT_HEADER_LEN(out, entry);
190 		out->body.entry.attr.mode = mode;
191 		out->body.entry.nodeid = ino;
192 		out->body.entry.entry_valid = UINT64_MAX;
193 		out->body.entry.attr_valid = UINT64_MAX;
194 	})));
195 
196 	EXPECT_CALL(*m_mock, process(
197 		ResultOf([=](auto in) {
198 			return (in->header.opcode == FUSE_OPEN &&
199 				in->header.nodeid == ino);
200 		}, Eq(true)),
201 		_)
202 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
203 		out->header.len = sizeof(out->header);
204 		SET_OUT_HEADER_LEN(out, open);
205 	})));
206 
207 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
208 	EXPECT_LE(0, fd) << strerror(errno);
209 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
210 }
211 
212 /*
213  * Creating a new file after FUSE_LOOKUP returned a negative cache entry
214  */
215 TEST_F(Create, entry_cache_negative)
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 	 * Set entry_valid = 0 because this test isn't concerned with whether
224 	 * or not we actually cache negative entries, only with whether we
225 	 * interpret negative cache responses correctly.
226 	 */
227 	struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
228 
229 	/* create will first do a LOOKUP, adding a negative cache entry */
230 	EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid));
231 	expect_create(RELPATH, mode,
232 		ReturnImmediate([=](auto in __unused, auto out) {
233 		SET_OUT_HEADER_LEN(out, create);
234 		out->body.create.entry.attr.mode = mode;
235 		out->body.create.entry.nodeid = ino;
236 		out->body.create.entry.entry_valid = UINT64_MAX;
237 		out->body.create.entry.attr_valid = UINT64_MAX;
238 	}));
239 
240 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
241 	ASSERT_LE(0, fd) << strerror(errno);
242 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
243 }
244 
245 /*
246  * Creating a new file should purge any negative namecache entries
247  */
248 TEST_F(Create, entry_cache_negative_purge)
249 {
250 	const char FULLPATH[] = "mountpoint/some_file.txt";
251 	const char RELPATH[] = "some_file.txt";
252 	mode_t mode = S_IFREG | 0755;
253 	uint64_t ino = 42;
254 	int fd;
255 	struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
256 
257 	/* create will first do a LOOKUP, adding a negative cache entry */
258 	EXPECT_LOOKUP(1, RELPATH).Times(1)
259 	.WillOnce(Invoke(ReturnNegativeCache(&entry_valid)))
260 	.RetiresOnSaturation();
261 
262 	/* Then the CREATE should purge the negative cache entry */
263 	expect_create(RELPATH, mode,
264 		ReturnImmediate([=](auto in __unused, auto out) {
265 		SET_OUT_HEADER_LEN(out, create);
266 		out->body.create.entry.attr.mode = mode;
267 		out->body.create.entry.nodeid = ino;
268 		out->body.create.entry.attr_valid = UINT64_MAX;
269 	}));
270 
271 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
272 	ASSERT_LE(0, fd) << strerror(errno);
273 
274 	/* Finally, a subsequent lookup should query the daemon */
275 	expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1);
276 
277 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
278 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
279 }
280 
281 /*
282  * The daemon is responsible for checking file permissions (unless the
283  * default_permissions mount option was used)
284  */
285 TEST_F(Create, eperm)
286 {
287 	const char FULLPATH[] = "mountpoint/some_file.txt";
288 	const char RELPATH[] = "some_file.txt";
289 	mode_t mode = S_IFREG | 0755;
290 
291 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
292 	expect_create(RELPATH, mode, ReturnErrno(EPERM));
293 
294 	EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
295 	EXPECT_EQ(EPERM, errno);
296 }
297 
298 TEST_F(Create, ok)
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 
306 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
307 	expect_create(RELPATH, mode,
308 		ReturnImmediate([=](auto in __unused, auto out) {
309 		SET_OUT_HEADER_LEN(out, create);
310 		out->body.create.entry.attr.mode = mode;
311 		out->body.create.entry.nodeid = ino;
312 		out->body.create.entry.entry_valid = UINT64_MAX;
313 		out->body.create.entry.attr_valid = UINT64_MAX;
314 	}));
315 
316 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
317 	EXPECT_LE(0, fd) << strerror(errno);
318 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
319 }
320 
321 /*
322  * A regression test for a bug that affected old FUSE implementations:
323  * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming
324  * contradiction between O_WRONLY and 0444
325  *
326  * For example:
327  * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886
328  */
329 TEST_F(Create, wronly_0444)
330 {
331 	const char FULLPATH[] = "mountpoint/some_file.txt";
332 	const char RELPATH[] = "some_file.txt";
333 	mode_t mode = S_IFREG | 0444;
334 	uint64_t ino = 42;
335 	int fd;
336 
337 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
338 	expect_create(RELPATH, mode,
339 		ReturnImmediate([=](auto in __unused, auto out) {
340 		SET_OUT_HEADER_LEN(out, create);
341 		out->body.create.entry.attr.mode = mode;
342 		out->body.create.entry.nodeid = ino;
343 		out->body.create.entry.entry_valid = UINT64_MAX;
344 		out->body.create.entry.attr_valid = UINT64_MAX;
345 	}));
346 
347 	fd = open(FULLPATH, O_CREAT | O_WRONLY, mode);
348 	EXPECT_LE(0, fd) << strerror(errno);
349 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
350 }
351 
352 TEST_F(Create_7_8, ok)
353 {
354 	const char FULLPATH[] = "mountpoint/some_file.txt";
355 	const char RELPATH[] = "some_file.txt";
356 	mode_t mode = S_IFREG | 0755;
357 	uint64_t ino = 42;
358 	int fd;
359 
360 	EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
361 	expect_create(RELPATH, mode,
362 		ReturnImmediate([=](auto in __unused, auto out) {
363 		SET_OUT_HEADER_LEN(out, create_7_8);
364 		out->body.create.entry.attr.mode = mode;
365 		out->body.create.entry.nodeid = ino;
366 		out->body.create.entry.entry_valid = UINT64_MAX;
367 		out->body.create.entry.attr_valid = UINT64_MAX;
368 	}));
369 
370 	fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
371 	EXPECT_LE(0, fd) << strerror(errno);
372 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
373 }
374 
375 
376