]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fuse/create.cc
fuse(4): combine common code in the tests
[FreeBSD/FreeBSD.git] / tests / sys / fs / fuse / 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
31 extern "C" {
32 #include <fcntl.h>
33 }
34
35 #include "mockfs.hh"
36 #include "utils.hh"
37
38 using namespace testing;
39
40 class Create: public FuseTest {};
41
42 /*
43  * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the
44  * attribute cache
45  */
46 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
47 TEST_F(Create, DISABLED_attr_cache)
48 {
49         const char FULLPATH[] = "mountpoint/some_file.txt";
50         const char RELPATH[] = "some_file.txt";
51         mode_t mode = 0755;
52         uint64_t ino = 42;
53         int fd;
54
55         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
56
57         EXPECT_CALL(*m_mock, process(
58                 ResultOf([=](auto in) {
59                         const char *name = (const char*)in->body.bytes +
60                                 sizeof(fuse_open_in);
61                         return (in->header.opcode == FUSE_CREATE &&
62                                 (0 == strcmp(RELPATH, name)));
63                 }, Eq(true)),
64                 _)
65         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
66                 SET_OUT_HEADER_LEN(out, create);
67                 out->body.create.entry.attr.mode = S_IFREG | mode;
68                 out->body.create.entry.nodeid = ino;
69                 out->body.create.entry.entry_valid = UINT64_MAX;
70                 out->body.create.entry.attr_valid = UINT64_MAX;
71         })));
72
73         EXPECT_CALL(*m_mock, process(
74                 ResultOf([=](auto in) {
75                         return (in->header.opcode == FUSE_GETATTR &&
76                                 in->header.nodeid == ino);
77                 }, Eq(true)),
78                 _)
79         ).Times(0);
80
81         fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
82         EXPECT_LE(0, fd) << strerror(errno);
83         /* Deliberately leak fd.  close(2) will be tested in release.cc */
84 }
85
86 /* 
87  * The fuse daemon fails the request with EEXIST.  This usually indicates a
88  * race condition: some other FUSE client created the file in between when the
89  * kernel checked for it with lookup and tried to create it with create
90  */
91 TEST_F(Create, eexist)
92 {
93         const char FULLPATH[] = "mountpoint/some_file.txt";
94         const char RELPATH[] = "some_file.txt";
95         mode_t mode = 0755;
96
97         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
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                                 (0 == strcmp(RELPATH, name)));
105                 }, Eq(true)),
106                 _)
107         ).WillOnce(Invoke(ReturnErrno(EEXIST)));
108         EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
109         EXPECT_EQ(EEXIST, errno);
110 }
111
112 /*
113  * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback
114  * to FUSE_MKNOD/FUSE_OPEN
115  */
116 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */
117 TEST_F(Create, DISABLED_Enosys)
118 {
119         const char FULLPATH[] = "mountpoint/some_file.txt";
120         const char RELPATH[] = "some_file.txt";
121         mode_t mode = 0755;
122         uint64_t ino = 42;
123         int fd;
124
125         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
126
127         EXPECT_CALL(*m_mock, process(
128                 ResultOf([=](auto in) {
129                         const char *name = (const char*)in->body.bytes +
130                                 sizeof(fuse_open_in);
131                         return (in->header.opcode == FUSE_CREATE &&
132                                 (0 == strcmp(RELPATH, name)));
133                 }, Eq(true)),
134                 _)
135         ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
136
137         EXPECT_CALL(*m_mock, process(
138                 ResultOf([=](auto in) {
139                         const char *name = (const char*)in->body.bytes +
140                                 sizeof(fuse_mknod_in);
141                         return (in->header.opcode == FUSE_MKNOD &&
142                                 in->body.mknod.mode == (S_IFREG | mode) &&
143                                 in->body.mknod.rdev == 0 &&
144                                 (0 == strcmp(RELPATH, name)));
145                 }, Eq(true)),
146                 _)
147         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
148                 SET_OUT_HEADER_LEN(out, create);
149                 out->body.create.entry.attr.mode = S_IFREG | mode;
150                 out->body.create.entry.nodeid = ino;
151                 out->body.create.entry.entry_valid = UINT64_MAX;
152                 out->body.create.entry.attr_valid = UINT64_MAX;
153         })));
154
155         EXPECT_CALL(*m_mock, process(
156                 ResultOf([=](auto in) {
157                         return (in->header.opcode == FUSE_OPEN &&
158                                 in->header.nodeid == ino);
159                 }, Eq(true)),
160                 _)
161         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
162                 out->header.len = sizeof(out->header);
163                 SET_OUT_HEADER_LEN(out, open);
164         })));
165
166         /* Until the attr cache is working, we may send an additional GETATTR */
167         EXPECT_CALL(*m_mock, process(
168                 ResultOf([=](auto in) {
169                         return (in->header.opcode == FUSE_GETATTR &&
170                                 in->header.nodeid == ino);
171                 }, Eq(true)),
172                 _)
173         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
174                 SET_OUT_HEADER_LEN(out, attr);
175                 out->body.attr.attr.ino = ino;  // Must match nodeid
176                 out->body.attr.attr.mode = S_IFREG | 0644;
177         })));
178
179         fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
180         EXPECT_LE(0, fd) << strerror(errno);
181         /* Deliberately leak fd.  close(2) will be tested in release.cc */
182 }
183
184 /*
185  * Creating a new file after FUSE_LOOKUP returned a negative cache entry
186  */
187 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */
188 TEST_F(Create, DISABLED_entry_cache_negative)
189 {
190         const char FULLPATH[] = "mountpoint/some_file.txt";
191         const char RELPATH[] = "some_file.txt";
192         mode_t mode = 0755;
193         uint64_t ino = 42;
194         int fd;
195         /* 
196          * Set entry_valid = 0 because this test isn't concerned with whether
197          * or not we actually cache negative entries, only with whether we
198          * interpret negative cache responses correctly.
199          */
200         struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
201
202         /* create will first do a LOOKUP, adding a negative cache entry */
203         EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid));
204
205         EXPECT_CALL(*m_mock, process(
206                 ResultOf([=](auto in) {
207                         const char *name = (const char*)in->body.bytes +
208                                 sizeof(fuse_open_in);
209                         return (in->header.opcode == FUSE_CREATE &&
210                                 (0 == strcmp(RELPATH, name)));
211                 }, Eq(true)),
212                 _)
213         ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
214                 SET_OUT_HEADER_LEN(out, create);
215                 out->body.create.entry.attr.mode = S_IFREG | mode;
216                 out->body.create.entry.nodeid = ino;
217                 out->body.create.entry.entry_valid = UINT64_MAX;
218                 out->body.create.entry.attr_valid = UINT64_MAX;
219         })));
220
221         /* Until the attr cache is working, we may send an additional GETATTR */
222         EXPECT_CALL(*m_mock, process(
223                 ResultOf([=](auto in) {
224                         return (in->header.opcode == FUSE_GETATTR &&
225                                 in->header.nodeid == ino);
226                 }, Eq(true)),
227                 _)
228         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
229                 SET_OUT_HEADER_LEN(out, attr);
230                 out->body.attr.attr.ino = ino;  // Must match nodeid
231                 out->body.attr.attr.mode = S_IFREG | 0644;
232         })));
233
234         fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
235         ASSERT_LE(0, fd) << strerror(errno);
236         /* Deliberately leak fd.  close(2) will be tested in release.cc */
237 }
238
239 /*
240  * Creating a new file should purge any negative namecache entries
241  */
242 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */
243 TEST_F(Create, DISABLED_entry_cache_negative_purge)
244 {
245         const char FULLPATH[] = "mountpoint/some_file.txt";
246         const char RELPATH[] = "some_file.txt";
247         mode_t mode = 0755;
248         uint64_t ino = 42;
249         int fd;
250         struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
251
252         /* create will first do a LOOKUP, adding a negative cache entry */
253         EXPECT_LOOKUP(1, RELPATH).Times(1)
254         .WillOnce(Invoke(ReturnNegativeCache(&entry_valid)))
255         .RetiresOnSaturation();
256
257         /* Then the CREATE should purge the negative cache entry */
258         EXPECT_CALL(*m_mock, process(
259                 ResultOf([=](auto in) {
260                         const char *name = (const char*)in->body.bytes +
261                                 sizeof(fuse_open_in);
262                         return (in->header.opcode == FUSE_CREATE &&
263                                 (0 == strcmp(RELPATH, name)));
264                 }, Eq(true)),
265                 _)
266         ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
267                 SET_OUT_HEADER_LEN(out, create);
268                 out->body.create.entry.attr.mode = S_IFREG | mode;
269                 out->body.create.entry.nodeid = ino;
270                 out->body.create.entry.attr_valid = UINT64_MAX;
271         })));
272
273         /* Until the attr cache is working, we may send an additional GETATTR */
274         EXPECT_CALL(*m_mock, process(
275                 ResultOf([=](auto in) {
276                         return (in->header.opcode == FUSE_GETATTR &&
277                                 in->header.nodeid == ino);
278                 }, Eq(true)),
279                 _)
280         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
281                 SET_OUT_HEADER_LEN(out, attr);
282                 out->body.attr.attr.ino = ino;  // Must match nodeid
283                 out->body.attr.attr.mode = S_IFREG | 0644;
284         })));
285
286         fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
287         ASSERT_LE(0, fd) << strerror(errno);
288
289         /* Finally, a subsequent lookup should query the daemon */
290         expect_lookup(RELPATH, ino, S_IFREG | mode, 1);
291
292         ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
293         /* Deliberately leak fd.  close(2) will be tested in release.cc */
294 }
295
296 /* 
297  * The daemon is responsible for checking file permissions (unless the
298  * default_permissions mount option was used)
299  */
300 TEST_F(Create, eperm)
301 {
302         const char FULLPATH[] = "mountpoint/some_file.txt";
303         const char RELPATH[] = "some_file.txt";
304         mode_t mode = 0755;
305
306         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
307
308         EXPECT_CALL(*m_mock, process(
309                 ResultOf([=](auto in) {
310                         const char *name = (const char*)in->body.bytes +
311                                 sizeof(fuse_open_in);
312                         return (in->header.opcode == FUSE_CREATE &&
313                                 (0 == strcmp(RELPATH, name)));
314                 }, Eq(true)),
315                 _)
316         ).WillOnce(Invoke(ReturnErrno(EPERM)));
317         EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
318         EXPECT_EQ(EPERM, errno);
319 }
320
321 TEST_F(Create, ok)
322 {
323         const char FULLPATH[] = "mountpoint/some_file.txt";
324         const char RELPATH[] = "some_file.txt";
325         mode_t mode = 0755;
326         uint64_t ino = 42;
327         int fd;
328
329         EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
330
331         EXPECT_CALL(*m_mock, process(
332                 ResultOf([=](auto in) {
333                         const char *name = (const char*)in->body.bytes +
334                                 sizeof(fuse_open_in);
335                         return (in->header.opcode == FUSE_CREATE &&
336                                 (0 == strcmp(RELPATH, name)));
337                 }, Eq(true)),
338                 _)
339         ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
340                 SET_OUT_HEADER_LEN(out, create);
341                 out->body.create.entry.attr.mode = S_IFREG | mode;
342                 out->body.create.entry.nodeid = ino;
343                 out->body.create.entry.entry_valid = UINT64_MAX;
344                 out->body.create.entry.attr_valid = UINT64_MAX;
345         })));
346
347         /* Until the attr cache is working, we may send an additional GETATTR */
348         EXPECT_CALL(*m_mock, process(
349                 ResultOf([=](auto in) {
350                         return (in->header.opcode == FUSE_GETATTR &&
351                                 in->header.nodeid == ino);
352                 }, Eq(true)),
353                 _)
354         ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
355                 SET_OUT_HEADER_LEN(out, attr);
356                 out->body.attr.attr.ino = ino;  // Must match nodeid
357                 out->body.attr.attr.mode = S_IFREG | 0644;
358         })));
359
360         fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
361         EXPECT_LE(0, fd) << strerror(errno);
362         /* Deliberately leak fd.  close(2) will be tested in release.cc */
363 }