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 * $FreeBSD$
31 */
32
33 extern "C" {
34 #include <fcntl.h>
35 }
36
37 #include "mockfs.hh"
38 #include "utils.hh"
39
40 using namespace testing;
41
42 class Create: public FuseTest {
43 public:
44
expect_create(const char * relpath,mode_t mode,ProcessMockerT r)45 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
46 {
47 mode_t mask = umask(0);
48 (void)umask(mask);
49
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)));
58 }, Eq(true)),
59 _)
60 ).WillOnce(Invoke(r));
61 }
62
63 };
64
65 /* FUSE_CREATE operations for a protocol 7.8 server */
66 class Create_7_8: public Create {
67 public:
SetUp()68 virtual void SetUp() {
69 m_kernel_minor_version = 8;
70 Create::SetUp();
71 }
72
expect_create(const char * relpath,mode_t mode,ProcessMockerT r)73 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
74 {
75 EXPECT_CALL(*m_mock, process(
76 ResultOf([=](auto in) {
77 const char *name = (const char*)in.body.bytes +
78 sizeof(fuse_open_in);
79 return (in.header.opcode == FUSE_CREATE &&
80 in.body.create.mode == mode &&
81 (0 == strcmp(relpath, name)));
82 }, Eq(true)),
83 _)
84 ).WillOnce(Invoke(r));
85 }
86
87 };
88
89 /* FUSE_CREATE operations for a server built at protocol <= 7.11 */
90 class Create_7_11: public FuseTest {
91 public:
SetUp()92 virtual void SetUp() {
93 m_kernel_minor_version = 11;
94 FuseTest::SetUp();
95 }
96
expect_create(const char * relpath,mode_t mode,ProcessMockerT r)97 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r)
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 in.body.create.mode == mode &&
105 (0 == strcmp(relpath, name)));
106 }, Eq(true)),
107 _)
108 ).WillOnce(Invoke(r));
109 }
110
111 };
112
113
114 /*
115 * If FUSE_CREATE sets attr_valid, then subsequent GETATTRs should use the
116 * attribute cache
117 */
TEST_F(Create,attr_cache)118 TEST_F(Create, attr_cache)
119 {
120 const char FULLPATH[] = "mountpoint/some_file.txt";
121 const char RELPATH[] = "some_file.txt";
122 mode_t mode = S_IFREG | 0755;
123 uint64_t ino = 42;
124 int fd;
125
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;
135 }));
136
137 EXPECT_CALL(*m_mock, process(
138 ResultOf([=](auto in) {
139 return (in.header.opcode == FUSE_GETATTR &&
140 in.header.nodeid == ino);
141 }, Eq(true)),
142 _)
143 ).Times(0);
144
145 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
146 ASSERT_LE(0, fd) << strerror(errno);
147 leak(fd);
148 }
149
150 /* A successful CREATE operation should purge the parent dir's attr cache */
TEST_F(Create,clear_attr_cache)151 TEST_F(Create, clear_attr_cache)
152 {
153 const char FULLPATH[] = "mountpoint/src";
154 const char RELPATH[] = "src";
155 mode_t mode = S_IFREG | 0755;
156 uint64_t ino = 42;
157 int fd;
158 struct stat sb;
159
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);
166 }, Eq(true)),
167 _)
168 ).Times(2)
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;
174 })));
175
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;
183 }));
184
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);
189
190 leak(fd);
191 }
192
193 /*
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
197 */
TEST_F(Create,eexist)198 TEST_F(Create, eexist)
199 {
200 const char FULLPATH[] = "mountpoint/some_file.txt";
201 const char RELPATH[] = "some_file.txt";
202 mode_t mode = S_IFREG | 0755;
203
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);
209 }
210
211 /*
212 * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback
213 * to FUSE_MKNOD/FUSE_OPEN
214 */
TEST_F(Create,Enosys)215 TEST_F(Create, Enosys)
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 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
224 .WillOnce(Invoke(ReturnErrno(ENOENT)));
225 expect_create(RELPATH, mode, ReturnErrno(ENOSYS));
226
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)));
235 }, Eq(true)),
236 _)
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;
243 })));
244
245 EXPECT_CALL(*m_mock, process(
246 ResultOf([=](auto in) {
247 return (in.header.opcode == FUSE_OPEN &&
248 in.header.nodeid == ino);
249 }, Eq(true)),
250 _)
251 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
252 out.header.len = sizeof(out.header);
253 SET_OUT_HEADER_LEN(out, open);
254 })));
255
256 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
257 ASSERT_LE(0, fd) << strerror(errno);
258 leak(fd);
259 }
260
261 /*
262 * Creating a new file after FUSE_LOOKUP returned a negative cache entry
263 */
TEST_F(Create,entry_cache_negative)264 TEST_F(Create, entry_cache_negative)
265 {
266 const char FULLPATH[] = "mountpoint/some_file.txt";
267 const char RELPATH[] = "some_file.txt";
268 mode_t mode = S_IFREG | 0755;
269 uint64_t ino = 42;
270 int fd;
271 /*
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.
275 */
276 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
277
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;
288 }));
289
290 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
291 ASSERT_LE(0, fd) << strerror(errno);
292 leak(fd);
293 }
294
295 /*
296 * Creating a new file should purge any negative namecache entries
297 */
TEST_F(Create,entry_cache_negative_purge)298 TEST_F(Create, entry_cache_negative_purge)
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 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
306
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();
311
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;
319 }));
320
321 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
322 ASSERT_LE(0, fd) << strerror(errno);
323
324 /* Finally, a subsequent lookup should query the daemon */
325 expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1);
326
327 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
328 leak(fd);
329 }
330
331 /*
332 * The daemon is responsible for checking file permissions (unless the
333 * default_permissions mount option was used)
334 */
TEST_F(Create,eperm)335 TEST_F(Create, eperm)
336 {
337 const char FULLPATH[] = "mountpoint/some_file.txt";
338 const char RELPATH[] = "some_file.txt";
339 mode_t mode = S_IFREG | 0755;
340
341 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
342 .WillOnce(Invoke(ReturnErrno(ENOENT)));
343 expect_create(RELPATH, mode, ReturnErrno(EPERM));
344
345 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode));
346 EXPECT_EQ(EPERM, errno);
347 }
348
TEST_F(Create,ok)349 TEST_F(Create, ok)
350 {
351 const char FULLPATH[] = "mountpoint/some_file.txt";
352 const char RELPATH[] = "some_file.txt";
353 mode_t mode = S_IFREG | 0755;
354 uint64_t ino = 42;
355 int fd;
356
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;
366 }));
367
368 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
369 ASSERT_LE(0, fd) << strerror(errno);
370 leak(fd);
371 }
372
373 /*
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
377 *
378 * For example:
379 * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886
380 */
TEST_F(Create,wronly_0444)381 TEST_F(Create, wronly_0444)
382 {
383 const char FULLPATH[] = "mountpoint/some_file.txt";
384 const char RELPATH[] = "some_file.txt";
385 mode_t mode = S_IFREG | 0444;
386 uint64_t ino = 42;
387 int fd;
388
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;
398 }));
399
400 fd = open(FULLPATH, O_CREAT | O_WRONLY, mode);
401 ASSERT_LE(0, fd) << strerror(errno);
402 leak(fd);
403 }
404
TEST_F(Create_7_8,ok)405 TEST_F(Create_7_8, ok)
406 {
407 const char FULLPATH[] = "mountpoint/some_file.txt";
408 const char RELPATH[] = "some_file.txt";
409 mode_t mode = S_IFREG | 0755;
410 uint64_t ino = 42;
411 int fd;
412
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;
422 }));
423
424 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
425 ASSERT_LE(0, fd) << strerror(errno);
426 leak(fd);
427 }
428
TEST_F(Create_7_11,ok)429 TEST_F(Create_7_11, ok)
430 {
431 const char FULLPATH[] = "mountpoint/some_file.txt";
432 const char RELPATH[] = "some_file.txt";
433 mode_t mode = S_IFREG | 0755;
434 uint64_t ino = 42;
435 int fd;
436
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;
446 }));
447
448 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
449 ASSERT_LE(0, fd) << strerror(errno);
450 leak(fd);
451 }
452