]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/create.cc
THIS BRANCH IS OBSOLETE, PLEASE READ:
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / create.cc
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
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  * 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  */
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
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
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 }