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
38 using namespace testing;
40 class Create: public FuseTest {};
43 * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the
46 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
47 TEST_F(Create, DISABLED_attr_cache)
49 const char FULLPATH[] = "mountpoint/some_file.txt";
50 const char RELPATH[] = "some_file.txt";
55 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
57 EXPECT_CALL(*m_mock, process(
58 ResultOf([=](auto in) {
59 const char *name = (const char*)in->body.bytes +
61 return (in->header.opcode == FUSE_CREATE &&
62 (0 == strcmp(RELPATH, name)));
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;
73 EXPECT_CALL(*m_mock, process(
74 ResultOf([=](auto in) {
75 return (in->header.opcode == FUSE_GETATTR &&
76 in->header.nodeid == ino);
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 */
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
91 TEST_F(Create, eexist)
93 const char FULLPATH[] = "mountpoint/some_file.txt";
94 const char RELPATH[] = "some_file.txt";
97 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
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)));
107 ).WillOnce(Invoke(ReturnErrno(EEXIST)));
108 EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
109 EXPECT_EQ(EEXIST, errno);
113 * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback
114 * to FUSE_MKNOD/FUSE_OPEN
116 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */
117 TEST_F(Create, DISABLED_Enosys)
119 const char FULLPATH[] = "mountpoint/some_file.txt";
120 const char RELPATH[] = "some_file.txt";
125 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
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)));
135 ).WillOnce(Invoke(ReturnErrno(ENOSYS)));
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)));
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;
155 EXPECT_CALL(*m_mock, process(
156 ResultOf([=](auto in) {
157 return (in->header.opcode == FUSE_OPEN &&
158 in->header.nodeid == ino);
161 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
162 out->header.len = sizeof(out->header);
163 SET_OUT_HEADER_LEN(out, open);
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);
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;
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 */
185 * Creating a new file after FUSE_LOOKUP returned a negative cache entry
187 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */
188 TEST_F(Create, DISABLED_entry_cache_negative)
190 const char FULLPATH[] = "mountpoint/some_file.txt";
191 const char RELPATH[] = "some_file.txt";
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.
200 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0};
202 /* create will first do a LOOKUP, adding a negative cache entry */
203 EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid));
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)));
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;
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);
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;
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 */
240 * Creating a new file should purge any negative namecache entries
242 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236231 */
243 TEST_F(Create, DISABLED_entry_cache_negative_purge)
245 const char FULLPATH[] = "mountpoint/some_file.txt";
246 const char RELPATH[] = "some_file.txt";
250 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0};
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();
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)));
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;
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);
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;
286 fd = open(FULLPATH, O_CREAT | O_EXCL, mode);
287 ASSERT_LE(0, fd) << strerror(errno);
289 /* Finally, a subsequent lookup should query the daemon */
290 expect_lookup(RELPATH, ino, S_IFREG | mode, 1);
292 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
293 /* Deliberately leak fd. close(2) will be tested in release.cc */
297 * The daemon is responsible for checking file permissions (unless the
298 * default_permissions mount option was used)
300 TEST_F(Create, eperm)
302 const char FULLPATH[] = "mountpoint/some_file.txt";
303 const char RELPATH[] = "some_file.txt";
306 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
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)));
316 ).WillOnce(Invoke(ReturnErrno(EPERM)));
317 EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode));
318 EXPECT_EQ(EPERM, errno);
323 const char FULLPATH[] = "mountpoint/some_file.txt";
324 const char RELPATH[] = "some_file.txt";
329 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT)));
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)));
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;
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);
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;
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 */