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