2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
40 using namespace testing;
42 class Create: public FuseTest {
45 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
47 mode_t mask = umask(0);
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)));
60 ).WillOnce(Invoke(r));
65 /* FUSE_CREATE operations for a protocol 7.8 server */
66 class Create_7_8: public Create {
68 virtual void SetUp() {
69 m_kernel_minor_version = 8;
73 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
75 EXPECT_CALL(*m_mock, process(
76 ResultOf([=](auto in) {
77 const char *name = (const char*)in.body.bytes +
79 return (in.header.opcode == FUSE_CREATE &&
80 in.body.create.mode == mode &&
81 (0 == strcmp(relpath, name)));
84 ).WillOnce(Invoke(r));
89 /* FUSE_CREATE operations for a server built at protocol <= 7.11 */
90 class Create_7_11: public FuseTest {
92 virtual void SetUp() {
93 m_kernel_minor_version = 11;
97 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
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)));
108 ).WillOnce(Invoke(r));
115 * If FUSE_CREATE sets attr_valid, then subsequent GETATTRs should use the
118 TEST_F(Create, attr_cache)
120 const char FULLPATH[] = "mountpoint/some_file.txt";
121 const char RELPATH[] = "some_file.txt";
122 mode_t mode = S_IFREG | 0755;
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;
137 EXPECT_CALL(*m_mock, process(
138 ResultOf([=](auto in) {
139 return (in.header.opcode == FUSE_GETATTR &&
140 in.header.nodeid == ino);
145 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
146 ASSERT_LE(0, fd) << strerror(errno);
150 /* A successful CREATE operation should purge the parent dir's attr cache */
151 TEST_F(Create, clear_attr_cache)
153 const char FULLPATH[] = "mountpoint/src";
154 const char RELPATH[] = "src";
155 mode_t mode = S_IFREG | 0755;
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);
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;
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;
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);
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
198 TEST_F(Create, eexist)
200 const char FULLPATH[] = "mountpoint/some_file.txt";
201 const char RELPATH[] = "some_file.txt";
202 mode_t mode = S_IFREG | 0755;
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);
212 * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback
213 * to FUSE_MKNOD/FUSE_OPEN
215 TEST_F(Create, Enosys)
217 const char FULLPATH[] = "mountpoint/some_file.txt";
218 const char RELPATH[] = "some_file.txt";
219 mode_t mode = S_IFREG | 0755;
223 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
224 .WillOnce(Invoke(ReturnErrno(ENOENT)));
225 expect_create(RELPATH, mode, ReturnErrno(ENOSYS));
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)));
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;
245 EXPECT_CALL(*m_mock, process(
246 ResultOf([=](auto in) {
247 return (in.header.opcode == FUSE_OPEN &&
248 in.header.nodeid == ino);
251 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
252 out.header.len = sizeof(out.header);
253 SET_OUT_HEADER_LEN(out, open);
256 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
257 ASSERT_LE(0, fd) << strerror(errno);
262 * Creating a new file after FUSE_LOOKUP returned a negative cache entry
264 TEST_F(Create, entry_cache_negative)
266 const char FULLPATH[] = "mountpoint/some_file.txt";
267 const char RELPATH[] = "some_file.txt";
268 mode_t mode = S_IFREG | 0755;
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.
276 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
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;
290 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
291 ASSERT_LE(0, fd) << strerror(errno);
296 * Creating a new file should purge any negative namecache entries
298 TEST_F(Create, entry_cache_negative_purge)
300 const char FULLPATH[] = "mountpoint/some_file.txt";
301 const char RELPATH[] = "some_file.txt";
302 mode_t mode = S_IFREG | 0755;
305 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
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();
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;
321 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
322 ASSERT_LE(0, fd) << strerror(errno);
324 /* Finally, a subsequent lookup should query the daemon */
325 expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1);
327 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
332 * The daemon is responsible for checking file permissions (unless the
333 * default_permissions mount option was used)
335 TEST_F(Create, eperm)
337 const char FULLPATH[] = "mountpoint/some_file.txt";
338 const char RELPATH[] = "some_file.txt";
339 mode_t mode = S_IFREG | 0755;
341 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
342 .WillOnce(Invoke(ReturnErrno(ENOENT)));
343 expect_create(RELPATH, mode, ReturnErrno(EPERM));
345 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode));
346 EXPECT_EQ(EPERM, errno);
351 const char FULLPATH[] = "mountpoint/some_file.txt";
352 const char RELPATH[] = "some_file.txt";
353 mode_t mode = S_IFREG | 0755;
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;
368 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
369 ASSERT_LE(0, fd) << strerror(errno);
374 * Nothing bad should happen if the server returns the parent's inode number
375 * for the newly created file. Regression test for bug 263662
376 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263662
378 TEST_F(Create, parent_inode)
380 const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
381 const char RELDIRPATH[] = "some_dir";
382 const char RELPATH[] = "some_file.txt";
387 expect_lookup(RELDIRPATH, ino, S_IFDIR | mode, 0, 1);
388 EXPECT_LOOKUP(ino, RELPATH)
389 .WillOnce(Invoke(ReturnErrno(ENOENT)));
390 expect_create(RELPATH, S_IFREG | mode,
391 ReturnImmediate([=](auto in __unused, auto& out) {
392 SET_OUT_HEADER_LEN(out, create);
393 out.body.create.entry.attr.mode = S_IFREG | mode;
394 /* Return the same inode as the parent dir */
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;
399 // FUSE_RELEASE happens asynchronously, so it may or may not arrive
400 // before the test completes.
401 EXPECT_CALL(*m_mock, process(
402 ResultOf([=](auto in) {
403 return (in.header.opcode == FUSE_RELEASE);
407 .WillOnce(Invoke([=](auto in __unused, auto &out __unused) { }));
409 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
411 EXPECT_EQ(EIO, errno);
415 * A regression test for a bug that affected old FUSE implementations:
416 * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming
417 * contradiction between O_WRONLY and 0444
420 * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886
422 TEST_F(Create, wronly_0444)
424 const char FULLPATH[] = "mountpoint/some_file.txt";
425 const char RELPATH[] = "some_file.txt";
426 mode_t mode = S_IFREG | 0444;
430 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
431 .WillOnce(Invoke(ReturnErrno(ENOENT)));
432 expect_create(RELPATH, mode,
433 ReturnImmediate([=](auto in __unused, auto& out) {
434 SET_OUT_HEADER_LEN(out, create);
435 out.body.create.entry.attr.mode = mode;
436 out.body.create.entry.nodeid = ino;
437 out.body.create.entry.entry_valid = UINT64_MAX;
438 out.body.create.entry.attr_valid = UINT64_MAX;
441 fd = open(FULLPATH, O_CREAT | O_WRONLY, mode);
442 ASSERT_LE(0, fd) << strerror(errno);
446 TEST_F(Create_7_8, ok)
448 const char FULLPATH[] = "mountpoint/some_file.txt";
449 const char RELPATH[] = "some_file.txt";
450 mode_t mode = S_IFREG | 0755;
454 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
455 .WillOnce(Invoke(ReturnErrno(ENOENT)));
456 expect_create(RELPATH, mode,
457 ReturnImmediate([=](auto in __unused, auto& out) {
458 SET_OUT_HEADER_LEN(out, create_7_8);
459 out.body.create_7_8.entry.attr.mode = mode;
460 out.body.create_7_8.entry.nodeid = ino;
461 out.body.create_7_8.entry.entry_valid = UINT64_MAX;
462 out.body.create_7_8.entry.attr_valid = UINT64_MAX;
463 out.body.create_7_8.open.fh = FH;
465 expect_flush(ino, 1, ReturnErrno(0));
466 expect_release(ino, FH);
468 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
469 ASSERT_LE(0, fd) << strerror(errno);
473 TEST_F(Create_7_11, ok)
475 const char FULLPATH[] = "mountpoint/some_file.txt";
476 const char RELPATH[] = "some_file.txt";
477 mode_t mode = S_IFREG | 0755;
481 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
482 .WillOnce(Invoke(ReturnErrno(ENOENT)));
483 expect_create(RELPATH, mode,
484 ReturnImmediate([=](auto in __unused, auto& out) {
485 SET_OUT_HEADER_LEN(out, create);
486 out.body.create.entry.attr.mode = mode;
487 out.body.create.entry.nodeid = ino;
488 out.body.create.entry.entry_valid = UINT64_MAX;
489 out.body.create.entry.attr_valid = UINT64_MAX;
492 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
493 ASSERT_LE(0, fd) << strerror(errno);