]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/create.cc
Import device-tree files from Linux 6.2
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / create.cc
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  * $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
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:
68 virtual void SetUp() {
69         m_kernel_minor_version = 8;
70         Create::SetUp();
71 }
72
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:
92 virtual void SetUp() {
93         m_kernel_minor_version = 11;
94         FuseTest::SetUp();
95 }
96
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  */
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 */
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  */
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  */
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  */
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  */
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  */
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
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  * 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
377  */
378 TEST_F(Create, parent_inode)
379 {
380         const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
381         const char RELDIRPATH[] = "some_dir";
382         const char RELPATH[] = "some_file.txt";
383         mode_t mode = 0755;
384         uint64_t ino = 42;
385         int fd;
386
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;
398         }));
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);
404                 }, Eq(true)),
405                 _)
406         ).Times(AtMost(1))
407         .WillOnce(Invoke([=](auto in __unused, auto &out __unused) { }));
408
409         fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
410         ASSERT_EQ(-1, fd);
411         EXPECT_EQ(EIO, errno);
412 }
413
414 /*
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
418  *
419  * For example:
420  * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886
421  */
422 TEST_F(Create, wronly_0444)
423 {
424         const char FULLPATH[] = "mountpoint/some_file.txt";
425         const char RELPATH[] = "some_file.txt";
426         mode_t mode = S_IFREG | 0444;
427         uint64_t ino = 42;
428         int fd;
429
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;
439         }));
440
441         fd = open(FULLPATH, O_CREAT | O_WRONLY, mode);
442         ASSERT_LE(0, fd) << strerror(errno);
443         leak(fd);
444 }
445
446 TEST_F(Create_7_8, ok)
447 {
448         const char FULLPATH[] = "mountpoint/some_file.txt";
449         const char RELPATH[] = "some_file.txt";
450         mode_t mode = S_IFREG | 0755;
451         uint64_t ino = 42;
452         int fd;
453
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;
464         }));
465         expect_flush(ino, 1, ReturnErrno(0));
466         expect_release(ino, FH);
467
468         fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
469         ASSERT_LE(0, fd) << strerror(errno);
470         close(fd);
471 }
472
473 TEST_F(Create_7_11, ok)
474 {
475         const char FULLPATH[] = "mountpoint/some_file.txt";
476         const char RELPATH[] = "some_file.txt";
477         mode_t mode = S_IFREG | 0755;
478         uint64_t ino = 42;
479         int fd;
480
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;
490         }));
491
492         fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
493         ASSERT_LE(0, fd) << strerror(errno);
494         leak(fd);
495 }