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
40 using namespace testing;
42 class Setattr : public FuseTest {};
46 * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
47 * should use the cached attributes, rather than query the daemon
49 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */
50 TEST_F(Setattr, DISABLED_attr_cache)
52 const char FULLPATH[] = "mountpoint/some_file.txt";
53 const char RELPATH[] = "some_file.txt";
54 const uint64_t ino = 42;
56 const mode_t newmode = 0644;
58 EXPECT_LOOKUP(1, RELPATH)
59 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
60 SET_OUT_HEADER_LEN(out, entry);
61 out->body.entry.attr.mode = S_IFREG | 0644;
62 out->body.entry.nodeid = ino;
65 EXPECT_CALL(*m_mock, process(
66 ResultOf([](auto in) {
67 /* In protocol 7.23, ctime will be changed too */
68 return (in->header.opcode == FUSE_SETATTR &&
69 in->header.nodeid == ino);
72 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
73 SET_OUT_HEADER_LEN(out, attr);
74 out->body.attr.attr.ino = ino; // Must match nodeid
75 out->body.attr.attr.mode = S_IFREG | newmode;
77 EXPECT_CALL(*m_mock, process(
78 ResultOf([](auto in) {
79 return (in->header.opcode == FUSE_GETATTR);
84 /* Set an attribute with SETATTR */
85 ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
87 /* The stat(2) should use cached attributes */
88 ASSERT_EQ(0, stat(FULLPATH, &sb));
89 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
92 /* Change the mode of a file */
93 TEST_F(Setattr, chmod)
95 const char FULLPATH[] = "mountpoint/some_file.txt";
96 const char RELPATH[] = "some_file.txt";
97 const uint64_t ino = 42;
98 const mode_t oldmode = 0755;
99 const mode_t newmode = 0644;
101 EXPECT_LOOKUP(1, RELPATH)
102 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
103 SET_OUT_HEADER_LEN(out, entry);
104 out->body.entry.attr.mode = S_IFREG | oldmode;
105 out->body.entry.nodeid = ino;
106 out->body.entry.attr.mode = S_IFREG | oldmode;
109 EXPECT_CALL(*m_mock, process(
110 ResultOf([](auto in) {
111 /* In protocol 7.23, ctime will be changed too */
112 uint32_t valid = FATTR_MODE;
113 return (in->header.opcode == FUSE_SETATTR &&
114 in->header.nodeid == ino &&
115 in->body.setattr.valid == valid &&
116 in->body.setattr.mode == newmode);
119 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
120 SET_OUT_HEADER_LEN(out, attr);
121 out->body.attr.attr.ino = ino; // Must match nodeid
122 out->body.attr.attr.mode = S_IFREG | newmode;
124 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
127 /* Change the owner and group of a file */
128 TEST_F(Setattr, chown)
130 const char FULLPATH[] = "mountpoint/some_file.txt";
131 const char RELPATH[] = "some_file.txt";
132 const uint64_t ino = 42;
133 const gid_t oldgroup = 66;
134 const gid_t newgroup = 99;
135 const uid_t olduser = 33;
136 const uid_t newuser = 44;
138 EXPECT_LOOKUP(1, RELPATH)
139 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
140 SET_OUT_HEADER_LEN(out, entry);
141 out->body.entry.attr.mode = S_IFREG | 0644;
142 out->body.entry.nodeid = ino;
143 out->body.entry.attr.gid = oldgroup;
144 out->body.entry.attr.uid = olduser;
147 EXPECT_CALL(*m_mock, process(
148 ResultOf([](auto in) {
149 /* In protocol 7.23, ctime will be changed too */
150 uint32_t valid = FATTR_GID | FATTR_UID;
151 return (in->header.opcode == FUSE_SETATTR &&
152 in->header.nodeid == ino &&
153 in->body.setattr.valid == valid &&
154 in->body.setattr.uid == newuser &&
155 in->body.setattr.gid == newgroup);
158 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
159 SET_OUT_HEADER_LEN(out, attr);
160 out->body.attr.attr.ino = ino; // Must match nodeid
161 out->body.attr.attr.mode = S_IFREG | 0644;
162 out->body.attr.attr.uid = newuser;
163 out->body.attr.attr.gid = newgroup;
165 EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
171 * FUSE daemons are allowed to check permissions however they like. If the
172 * daemon returns EPERM, even if the file permissions "should" grant access,
173 * then fuse(4) should return EPERM too.
175 TEST_F(Setattr, eperm)
177 const char FULLPATH[] = "mountpoint/some_file.txt";
178 const char RELPATH[] = "some_file.txt";
179 const uint64_t ino = 42;
181 EXPECT_LOOKUP(1, RELPATH)
182 .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
183 SET_OUT_HEADER_LEN(out, entry);
184 out->body.entry.attr.mode = S_IFREG | 0777;
185 out->body.entry.nodeid = ino;
186 out->body.entry.attr.uid = in->header.uid;
187 out->body.entry.attr.gid = in->header.gid;
190 EXPECT_CALL(*m_mock, process(
191 ResultOf([](auto in) {
192 return (in->header.opcode == FUSE_SETATTR &&
193 in->header.nodeid == ino);
196 ).WillOnce(Invoke(ReturnErrno(EPERM)));
197 EXPECT_NE(0, truncate(FULLPATH, 10));
198 EXPECT_EQ(EPERM, errno);
201 /* Change the mode of an open file, by its file descriptor */
202 TEST_F(Setattr, fchmod)
204 const char FULLPATH[] = "mountpoint/some_file.txt";
205 const char RELPATH[] = "some_file.txt";
208 const mode_t oldmode = 0755;
209 const mode_t newmode = 0644;
211 EXPECT_LOOKUP(1, RELPATH)
212 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
213 SET_OUT_HEADER_LEN(out, entry);
214 out->body.entry.attr.mode = S_IFREG | oldmode;
215 out->body.entry.nodeid = ino;
216 out->body.entry.attr_valid = UINT64_MAX;
219 EXPECT_CALL(*m_mock, process(
220 ResultOf([=](auto in) {
221 return (in->header.opcode == FUSE_OPEN &&
222 in->header.nodeid == ino);
225 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
226 out->header.len = sizeof(out->header);
227 SET_OUT_HEADER_LEN(out, open);
230 /* Until the attr cache is working, we may send an additional GETATTR */
231 EXPECT_CALL(*m_mock, process(
232 ResultOf([=](auto in) {
233 return (in->header.opcode == FUSE_GETATTR &&
234 in->header.nodeid == ino);
237 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
238 SET_OUT_HEADER_LEN(out, attr);
239 out->body.attr.attr.ino = ino; // Must match nodeid
240 out->body.attr.attr.mode = S_IFREG | oldmode;
243 EXPECT_CALL(*m_mock, process(
244 ResultOf([=](auto in) {
245 /* In protocol 7.23, ctime will be changed too */
246 uint32_t valid = FATTR_MODE;
247 return (in->header.opcode == FUSE_SETATTR &&
248 in->header.nodeid == ino &&
249 in->body.setattr.valid == valid &&
250 in->body.setattr.mode == newmode);
253 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
254 SET_OUT_HEADER_LEN(out, attr);
255 out->body.attr.attr.ino = ino; // Must match nodeid
256 out->body.attr.attr.mode = S_IFREG | newmode;
259 fd = open(FULLPATH, O_RDONLY);
260 ASSERT_LE(0, fd) << strerror(errno);
261 ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
262 /* Deliberately leak fd. close(2) will be tested in release.cc */
265 /* Change the size of an open file, by its file descriptor */
266 TEST_F(Setattr, ftruncate)
268 const char FULLPATH[] = "mountpoint/some_file.txt";
269 const char RELPATH[] = "some_file.txt";
272 uint64_t fh = 0xdeadbeef1a7ebabe;
273 const off_t oldsize = 99;
274 const off_t newsize = 12345;
276 EXPECT_LOOKUP(1, RELPATH)
277 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
278 SET_OUT_HEADER_LEN(out, entry);
279 out->body.entry.attr.mode = S_IFREG | 0755;
280 out->body.entry.nodeid = ino;
281 out->body.entry.attr_valid = UINT64_MAX;
282 out->body.entry.attr.size = oldsize;
285 EXPECT_CALL(*m_mock, process(
286 ResultOf([=](auto in) {
287 return (in->header.opcode == FUSE_OPEN &&
288 in->header.nodeid == ino);
291 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
292 out->header.len = sizeof(out->header);
293 SET_OUT_HEADER_LEN(out, open);
294 out->body.open.fh = fh;
297 /* Until the attr cache is working, we may send an additional GETATTR */
298 EXPECT_CALL(*m_mock, process(
299 ResultOf([=](auto in) {
300 return (in->header.opcode == FUSE_GETATTR &&
301 in->header.nodeid == ino);
304 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
305 SET_OUT_HEADER_LEN(out, attr);
306 out->body.attr.attr.ino = ino; // Must match nodeid
307 out->body.attr.attr.mode = S_IFREG | 0755;
308 out->body.attr.attr.size = oldsize;
311 EXPECT_CALL(*m_mock, process(
312 ResultOf([=](auto in) {
313 /* In protocol 7.23, ctime will be changed too */
314 uint32_t valid = FATTR_SIZE | FATTR_FH;
315 return (in->header.opcode == FUSE_SETATTR &&
316 in->header.nodeid == ino &&
317 in->body.setattr.valid == valid &&
318 in->body.setattr.fh == fh);
321 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
322 SET_OUT_HEADER_LEN(out, attr);
323 out->body.attr.attr.ino = ino; // Must match nodeid
324 out->body.attr.attr.mode = S_IFREG | 0755;
325 out->body.attr.attr.size = newsize;
328 fd = open(FULLPATH, O_RDWR);
329 ASSERT_LE(0, fd) << strerror(errno);
330 ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
331 /* Deliberately leak fd. close(2) will be tested in release.cc */
334 /* Change the size of the file */
335 TEST_F(Setattr, truncate) {
336 const char FULLPATH[] = "mountpoint/some_file.txt";
337 const char RELPATH[] = "some_file.txt";
338 const uint64_t ino = 42;
339 const uint64_t oldsize = 100'000'000;
340 const uint64_t newsize = 20'000'000;
342 EXPECT_LOOKUP(1, RELPATH)
343 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
344 SET_OUT_HEADER_LEN(out, entry);
345 out->body.entry.attr.mode = S_IFREG | 0644;
346 out->body.entry.nodeid = ino;
347 out->body.entry.attr.size = oldsize;
350 EXPECT_CALL(*m_mock, process(
351 ResultOf([](auto in) {
352 /* In protocol 7.23, ctime will be changed too */
353 uint32_t valid = FATTR_SIZE;
354 return (in->header.opcode == FUSE_SETATTR &&
355 in->header.nodeid == ino &&
356 in->body.setattr.valid == valid &&
357 in->body.setattr.size == newsize);
360 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
361 SET_OUT_HEADER_LEN(out, attr);
362 out->body.attr.attr.ino = ino; // Must match nodeid
363 out->body.attr.attr.mode = S_IFREG | 0644;
364 out->body.attr.attr.size = newsize;
366 EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
369 /* Change a file's timestamps */
370 TEST_F(Setattr, utimensat) {
371 const char FULLPATH[] = "mountpoint/some_file.txt";
372 const char RELPATH[] = "some_file.txt";
373 const uint64_t ino = 42;
374 const timespec oldtimes[2] = {
375 {.tv_sec = 1, .tv_nsec = 2},
376 {.tv_sec = 3, .tv_nsec = 4},
378 const timespec newtimes[2] = {
379 {.tv_sec = 5, .tv_nsec = 6},
380 {.tv_sec = 7, .tv_nsec = 8},
383 EXPECT_LOOKUP(1, RELPATH)
384 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
385 SET_OUT_HEADER_LEN(out, entry);
386 out->body.entry.attr.mode = S_IFREG | 0644;
387 out->body.entry.nodeid = ino;
388 out->body.entry.attr_valid = UINT64_MAX;
389 out->body.entry.attr.atime = oldtimes[0].tv_sec;
390 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
391 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
392 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
396 * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR
399 EXPECT_CALL(*m_mock, process(
400 ResultOf([](auto in) {
401 return (in->header.opcode == FUSE_GETATTR &&
402 in->header.nodeid == ino);
405 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
406 SET_OUT_HEADER_LEN(out, attr);
407 out->body.attr.attr.ino = ino; // Must match nodeid
408 out->body.attr.attr.mode = S_IFREG | 0644;
409 out->body.attr.attr.atime = oldtimes[0].tv_sec;
410 out->body.attr.attr.atimensec = oldtimes[0].tv_nsec;
411 out->body.attr.attr.mtime = oldtimes[1].tv_sec;
412 out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec;
415 EXPECT_CALL(*m_mock, process(
416 ResultOf([=](auto in) {
417 /* In protocol 7.23, ctime will be changed too */
418 uint32_t valid = FATTR_ATIME | FATTR_MTIME;
419 return (in->header.opcode == FUSE_SETATTR &&
420 in->header.nodeid == ino &&
421 in->body.setattr.valid == valid &&
422 in->body.setattr.atime == newtimes[0].tv_sec &&
423 in->body.setattr.atimensec ==
424 newtimes[0].tv_nsec &&
425 in->body.setattr.mtime == newtimes[1].tv_sec &&
426 in->body.setattr.mtimensec ==
427 newtimes[1].tv_nsec);
430 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
431 SET_OUT_HEADER_LEN(out, attr);
432 out->body.attr.attr.ino = ino; // Must match nodeid
433 out->body.attr.attr.mode = S_IFREG | 0644;
434 out->body.attr.attr.atime = newtimes[0].tv_sec;
435 out->body.attr.attr.atimensec = newtimes[0].tv_nsec;
436 out->body.attr.attr.mtime = newtimes[1].tv_sec;
437 out->body.attr.attr.mtimensec = newtimes[1].tv_nsec;
439 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
443 /* Change a file mtime but not its atime */
444 TEST_F(Setattr, utimensat_mtime_only) {
445 const char FULLPATH[] = "mountpoint/some_file.txt";
446 const char RELPATH[] = "some_file.txt";
447 const uint64_t ino = 42;
448 const timespec oldtimes[2] = {
449 {.tv_sec = 1, .tv_nsec = 2},
450 {.tv_sec = 3, .tv_nsec = 4},
452 const timespec newtimes[2] = {
453 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
454 {.tv_sec = 7, .tv_nsec = 8},
457 EXPECT_LOOKUP(1, RELPATH)
458 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
459 SET_OUT_HEADER_LEN(out, entry);
460 out->body.entry.attr.mode = S_IFREG | 0644;
461 out->body.entry.nodeid = ino;
462 out->body.entry.attr_valid = UINT64_MAX;
463 out->body.entry.attr.atime = oldtimes[0].tv_sec;
464 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
465 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
466 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
470 * Until bug 235775 is fixed, utimensat will make an extra FUSE_GETATTR
473 EXPECT_CALL(*m_mock, process(
474 ResultOf([](auto in) {
475 return (in->header.opcode == FUSE_GETATTR &&
476 in->header.nodeid == ino);
479 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
480 SET_OUT_HEADER_LEN(out, attr);
481 out->body.attr.attr.ino = ino; // Must match nodeid
482 out->body.attr.attr.mode = S_IFREG | 0644;
483 out->body.attr.attr.atime = oldtimes[0].tv_sec;
484 out->body.attr.attr.atimensec = oldtimes[0].tv_nsec;
485 out->body.attr.attr.mtime = oldtimes[1].tv_sec;
486 out->body.attr.attr.mtimensec = oldtimes[1].tv_nsec;
489 EXPECT_CALL(*m_mock, process(
490 ResultOf([=](auto in) {
491 /* In protocol 7.23, ctime will be changed too */
492 uint32_t valid = FATTR_MTIME;
493 return (in->header.opcode == FUSE_SETATTR &&
494 in->header.nodeid == ino &&
495 in->body.setattr.valid == valid &&
496 in->body.setattr.mtime == newtimes[1].tv_sec &&
497 in->body.setattr.mtimensec ==
498 newtimes[1].tv_nsec);
501 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
502 SET_OUT_HEADER_LEN(out, attr);
503 out->body.attr.attr.ino = ino; // Must match nodeid
504 out->body.attr.attr.mode = S_IFREG | 0644;
505 out->body.attr.attr.atime = oldtimes[0].tv_sec;
506 out->body.attr.attr.atimensec = oldtimes[0].tv_nsec;
507 out->body.attr.attr.mtime = newtimes[1].tv_sec;
508 out->body.attr.attr.mtimensec = newtimes[1].tv_nsec;
510 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))