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