2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 * 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
379 * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886
381 TEST_F(Create, wronly_0444)
383 const char FULLPATH[] = "mountpoint/some_file.txt";
384 const char RELPATH[] = "some_file.txt";
385 mode_t mode = S_IFREG | 0444;
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;
400 fd = open(FULLPATH, O_CREAT | O_WRONLY, mode);
401 ASSERT_LE(0, fd) << strerror(errno);
405 TEST_F(Create_7_8, ok)
407 const char FULLPATH[] = "mountpoint/some_file.txt";
408 const char RELPATH[] = "some_file.txt";
409 mode_t mode = S_IFREG | 0755;
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;
424 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
425 ASSERT_LE(0, fd) << strerror(errno);
429 TEST_F(Create_7_11, ok)
431 const char FULLPATH[] = "mountpoint/some_file.txt";
432 const char RELPATH[] = "some_file.txt";
433 mode_t mode = S_IFREG | 0755;
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;
448 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
449 ASSERT_LE(0, fd) << strerror(errno);