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
34 #include <sys/types.h>
35 #include <sys/resource.h>
40 #include <semaphore.h>
47 using namespace testing;
49 class Setattr : public FuseTest {
51 static sig_atomic_t s_sigxfsz;
54 class RofsSetattr: public Setattr {
56 virtual void SetUp() {
63 class Setattr_7_8: public Setattr {
65 virtual void SetUp() {
66 m_kernel_minor_version = 8;
72 sig_atomic_t Setattr::s_sigxfsz = 0;
74 void sigxfsz_handler(int __unused sig) {
75 Setattr::s_sigxfsz = 1;
79 * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
80 * should use the cached attributes, rather than query the daemon
82 TEST_F(Setattr, attr_cache)
84 const char FULLPATH[] = "mountpoint/some_file.txt";
85 const char RELPATH[] = "some_file.txt";
86 const uint64_t ino = 42;
88 const mode_t newmode = 0644;
90 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
91 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
92 SET_OUT_HEADER_LEN(out, entry);
93 out.body.entry.attr.mode = S_IFREG | 0644;
94 out.body.entry.nodeid = ino;
95 out.body.entry.entry_valid = UINT64_MAX;
98 EXPECT_CALL(*m_mock, process(
99 ResultOf([](auto in) {
100 return (in.header.opcode == FUSE_SETATTR &&
101 in.header.nodeid == ino);
104 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
105 SET_OUT_HEADER_LEN(out, attr);
106 out.body.attr.attr.ino = ino; // Must match nodeid
107 out.body.attr.attr.mode = S_IFREG | newmode;
108 out.body.attr.attr_valid = UINT64_MAX;
110 EXPECT_CALL(*m_mock, process(
111 ResultOf([](auto in) {
112 return (in.header.opcode == FUSE_GETATTR);
117 /* Set an attribute with SETATTR */
118 ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
120 /* The stat(2) should use cached attributes */
121 ASSERT_EQ(0, stat(FULLPATH, &sb));
122 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
125 /* Change the mode of a file */
126 TEST_F(Setattr, chmod)
128 const char FULLPATH[] = "mountpoint/some_file.txt";
129 const char RELPATH[] = "some_file.txt";
130 const uint64_t ino = 42;
131 const mode_t oldmode = 0755;
132 const mode_t newmode = 0644;
134 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
135 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
136 SET_OUT_HEADER_LEN(out, entry);
137 out.body.entry.attr.mode = S_IFREG | oldmode;
138 out.body.entry.nodeid = ino;
141 EXPECT_CALL(*m_mock, process(
142 ResultOf([](auto in) {
143 uint32_t valid = FATTR_MODE;
144 return (in.header.opcode == FUSE_SETATTR &&
145 in.header.nodeid == ino &&
146 in.body.setattr.valid == valid &&
147 in.body.setattr.mode == newmode);
150 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
151 SET_OUT_HEADER_LEN(out, attr);
152 out.body.attr.attr.ino = ino; // Must match nodeid
153 out.body.attr.attr.mode = S_IFREG | newmode;
155 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
159 * Chmod a multiply-linked file with cached attributes. Check that both files'
160 * attributes have changed.
162 TEST_F(Setattr, chmod_multiply_linked)
164 const char FULLPATH0[] = "mountpoint/some_file.txt";
165 const char RELPATH0[] = "some_file.txt";
166 const char FULLPATH1[] = "mountpoint/other_file.txt";
167 const char RELPATH1[] = "other_file.txt";
169 const uint64_t ino = 42;
170 const mode_t oldmode = 0777;
171 const mode_t newmode = 0666;
173 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0)
174 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
175 SET_OUT_HEADER_LEN(out, entry);
176 out.body.entry.attr.mode = S_IFREG | oldmode;
177 out.body.entry.nodeid = ino;
178 out.body.entry.attr.nlink = 2;
179 out.body.entry.attr_valid = UINT64_MAX;
180 out.body.entry.entry_valid = UINT64_MAX;
183 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1)
184 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
185 SET_OUT_HEADER_LEN(out, entry);
186 out.body.entry.attr.mode = S_IFREG | oldmode;
187 out.body.entry.nodeid = ino;
188 out.body.entry.attr.nlink = 2;
189 out.body.entry.attr_valid = UINT64_MAX;
190 out.body.entry.entry_valid = UINT64_MAX;
193 EXPECT_CALL(*m_mock, process(
194 ResultOf([](auto in) {
195 uint32_t valid = FATTR_MODE;
196 return (in.header.opcode == FUSE_SETATTR &&
197 in.header.nodeid == ino &&
198 in.body.setattr.valid == valid &&
199 in.body.setattr.mode == newmode);
202 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
203 SET_OUT_HEADER_LEN(out, attr);
204 out.body.attr.attr.ino = ino;
205 out.body.attr.attr.mode = S_IFREG | newmode;
206 out.body.attr.attr.nlink = 2;
207 out.body.attr.attr_valid = UINT64_MAX;
210 /* For a lookup of the 2nd file to get it into the cache*/
211 ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
212 EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
214 ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno);
215 ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno);
216 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
217 ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
218 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
222 /* Change the owner and group of a file */
223 TEST_F(Setattr, chown)
225 const char FULLPATH[] = "mountpoint/some_file.txt";
226 const char RELPATH[] = "some_file.txt";
227 const uint64_t ino = 42;
228 const gid_t oldgroup = 66;
229 const gid_t newgroup = 99;
230 const uid_t olduser = 33;
231 const uid_t newuser = 44;
233 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
234 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
235 SET_OUT_HEADER_LEN(out, entry);
236 out.body.entry.attr.mode = S_IFREG | 0644;
237 out.body.entry.nodeid = ino;
238 out.body.entry.attr.gid = oldgroup;
239 out.body.entry.attr.uid = olduser;
242 EXPECT_CALL(*m_mock, process(
243 ResultOf([](auto in) {
244 uint32_t valid = FATTR_GID | FATTR_UID;
245 return (in.header.opcode == FUSE_SETATTR &&
246 in.header.nodeid == ino &&
247 in.body.setattr.valid == valid &&
248 in.body.setattr.uid == newuser &&
249 in.body.setattr.gid == newgroup);
252 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
253 SET_OUT_HEADER_LEN(out, attr);
254 out.body.attr.attr.ino = ino; // Must match nodeid
255 out.body.attr.attr.mode = S_IFREG | 0644;
256 out.body.attr.attr.uid = newuser;
257 out.body.attr.attr.gid = newgroup;
259 EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
265 * FUSE daemons are allowed to check permissions however they like. If the
266 * daemon returns EPERM, even if the file permissions "should" grant access,
267 * then fuse(4) should return EPERM too.
269 TEST_F(Setattr, eperm)
271 const char FULLPATH[] = "mountpoint/some_file.txt";
272 const char RELPATH[] = "some_file.txt";
273 const uint64_t ino = 42;
275 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
276 .WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
277 SET_OUT_HEADER_LEN(out, entry);
278 out.body.entry.attr.mode = S_IFREG | 0777;
279 out.body.entry.nodeid = ino;
280 out.body.entry.attr.uid = in.header.uid;
281 out.body.entry.attr.gid = in.header.gid;
284 EXPECT_CALL(*m_mock, process(
285 ResultOf([](auto in) {
286 return (in.header.opcode == FUSE_SETATTR &&
287 in.header.nodeid == ino);
290 ).WillOnce(Invoke(ReturnErrno(EPERM)));
291 EXPECT_NE(0, truncate(FULLPATH, 10));
292 EXPECT_EQ(EPERM, errno);
295 /* Change the mode of an open file, by its file descriptor */
296 TEST_F(Setattr, fchmod)
298 const char FULLPATH[] = "mountpoint/some_file.txt";
299 const char RELPATH[] = "some_file.txt";
302 const mode_t oldmode = 0755;
303 const mode_t newmode = 0644;
305 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
306 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
307 SET_OUT_HEADER_LEN(out, entry);
308 out.body.entry.attr.mode = S_IFREG | oldmode;
309 out.body.entry.nodeid = ino;
310 out.body.entry.attr_valid = UINT64_MAX;
313 EXPECT_CALL(*m_mock, process(
314 ResultOf([=](auto in) {
315 return (in.header.opcode == FUSE_OPEN &&
316 in.header.nodeid == ino);
319 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
320 out.header.len = sizeof(out.header);
321 SET_OUT_HEADER_LEN(out, open);
324 EXPECT_CALL(*m_mock, process(
325 ResultOf([=](auto in) {
326 uint32_t valid = FATTR_MODE;
327 return (in.header.opcode == FUSE_SETATTR &&
328 in.header.nodeid == ino &&
329 in.body.setattr.valid == valid &&
330 in.body.setattr.mode == newmode);
333 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
334 SET_OUT_HEADER_LEN(out, attr);
335 out.body.attr.attr.ino = ino; // Must match nodeid
336 out.body.attr.attr.mode = S_IFREG | newmode;
339 fd = open(FULLPATH, O_RDONLY);
340 ASSERT_LE(0, fd) << strerror(errno);
341 ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
345 /* Change the size of an open file, by its file descriptor */
346 TEST_F(Setattr, ftruncate)
348 const char FULLPATH[] = "mountpoint/some_file.txt";
349 const char RELPATH[] = "some_file.txt";
352 uint64_t fh = 0xdeadbeef1a7ebabe;
353 const off_t oldsize = 99;
354 const off_t newsize = 12345;
356 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
357 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
358 SET_OUT_HEADER_LEN(out, entry);
359 out.body.entry.attr.mode = S_IFREG | 0755;
360 out.body.entry.nodeid = ino;
361 out.body.entry.attr_valid = UINT64_MAX;
362 out.body.entry.attr.size = oldsize;
365 EXPECT_CALL(*m_mock, process(
366 ResultOf([=](auto in) {
367 return (in.header.opcode == FUSE_OPEN &&
368 in.header.nodeid == ino);
371 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
372 out.header.len = sizeof(out.header);
373 SET_OUT_HEADER_LEN(out, open);
374 out.body.open.fh = fh;
377 EXPECT_CALL(*m_mock, process(
378 ResultOf([=](auto in) {
379 uint32_t valid = FATTR_SIZE | FATTR_FH;
380 return (in.header.opcode == FUSE_SETATTR &&
381 in.header.nodeid == ino &&
382 in.body.setattr.valid == valid &&
383 in.body.setattr.fh == fh);
386 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
387 SET_OUT_HEADER_LEN(out, attr);
388 out.body.attr.attr.ino = ino; // Must match nodeid
389 out.body.attr.attr.mode = S_IFREG | 0755;
390 out.body.attr.attr.size = newsize;
393 fd = open(FULLPATH, O_RDWR);
394 ASSERT_LE(0, fd) << strerror(errno);
395 ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
399 /* Change the size of the file */
400 TEST_F(Setattr, truncate) {
401 const char FULLPATH[] = "mountpoint/some_file.txt";
402 const char RELPATH[] = "some_file.txt";
403 const uint64_t ino = 42;
404 const uint64_t oldsize = 100'000'000;
405 const uint64_t newsize = 20'000'000;
407 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
408 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
409 SET_OUT_HEADER_LEN(out, entry);
410 out.body.entry.attr.mode = S_IFREG | 0644;
411 out.body.entry.nodeid = ino;
412 out.body.entry.attr.size = oldsize;
415 EXPECT_CALL(*m_mock, process(
416 ResultOf([](auto in) {
417 uint32_t valid = FATTR_SIZE;
418 return (in.header.opcode == FUSE_SETATTR &&
419 in.header.nodeid == ino &&
420 in.body.setattr.valid == valid &&
421 in.body.setattr.size == newsize);
424 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
425 SET_OUT_HEADER_LEN(out, attr);
426 out.body.attr.attr.ino = ino; // Must match nodeid
427 out.body.attr.attr.mode = S_IFREG | 0644;
428 out.body.attr.attr.size = newsize;
430 EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
434 * Truncating a file should discard cached data past the truncation point.
435 * This is a regression test for bug 233783.
437 * There are two distinct failure modes. The first one is a failure to zero
438 * the portion of the file's final buffer past EOF. It can be reproduced by
439 * fsx -WR -P /tmp -S10 fsx.bin
441 * The second is a failure to drop buffers beyond that. It can be reproduced by
442 * fsx -WR -P /tmp -S18 -n fsx.bin
443 * Also reproducible in sh with:
444 * $> /path/to/libfuse/build/example/passthrough -d /tmp/mnt
446 * $> dd if=/dev/random of=randfile bs=1k count=192
447 * $> truncate -s 1k randfile && truncate -s 192k randfile
448 * $> xxd randfile | less # xxd will wrongly show random data at offset 0x8000
450 TEST_F(Setattr, truncate_discards_cached_data) {
451 const char FULLPATH[] = "mountpoint/some_file.txt";
452 const char RELPATH[] = "some_file.txt";
453 void *w0buf, *r0buf, *r1buf, *expected;
455 size_t w0_size = 0x30000;
457 off_t r0_size = w0_size;
458 size_t trunc0_size = 0x400;
459 size_t trunc1_size = w0_size;
460 off_t r1_offset = trunc0_size;
461 off_t r1_size = w0_size - trunc0_size;
463 const uint64_t ino = 42;
464 mode_t mode = S_IFREG | 0644;
466 bool should_have_data = false;
468 w0buf = malloc(w0_size);
469 ASSERT_NE(nullptr, w0buf) << strerror(errno);
470 memset(w0buf, 'X', w0_size);
472 r0buf = malloc(r0_size);
473 ASSERT_NE(nullptr, r0buf) << strerror(errno);
474 r1buf = malloc(r1_size);
475 ASSERT_NE(nullptr, r1buf) << strerror(errno);
477 expected = malloc(r1_size);
478 ASSERT_NE(nullptr, expected) << strerror(errno);
479 memset(expected, 0, r1_size);
481 expect_lookup(RELPATH, ino, mode, 0, 1);
482 expect_open(ino, O_RDWR, 1);
483 EXPECT_CALL(*m_mock, process(
484 ResultOf([=](auto in) {
485 return (in.header.opcode == FUSE_GETATTR &&
486 in.header.nodeid == ino);
489 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto& out) {
490 SET_OUT_HEADER_LEN(out, attr);
491 out.body.attr.attr.ino = ino;
492 out.body.attr.attr.mode = mode;
493 out.body.attr.attr.size = cur_size;
495 EXPECT_CALL(*m_mock, process(
496 ResultOf([=](auto in) {
497 return (in.header.opcode == FUSE_WRITE);
500 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
501 SET_OUT_HEADER_LEN(out, write);
502 out.body.attr.attr.ino = ino;
503 out.body.write.size = in.body.write.size;
504 cur_size = std::max(static_cast<uint64_t>(cur_size),
505 in.body.write.size + in.body.write.offset);
508 EXPECT_CALL(*m_mock, process(
509 ResultOf([=](auto in) {
510 return (in.header.opcode == FUSE_SETATTR &&
511 in.header.nodeid == ino &&
512 (in.body.setattr.valid & FATTR_SIZE));
515 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
516 auto trunc_size = in.body.setattr.size;
517 SET_OUT_HEADER_LEN(out, attr);
518 out.body.attr.attr.ino = ino;
519 out.body.attr.attr.mode = mode;
520 out.body.attr.attr.size = trunc_size;
521 cur_size = trunc_size;
524 EXPECT_CALL(*m_mock, process(
525 ResultOf([=](auto in) {
526 return (in.header.opcode == FUSE_READ);
529 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
530 auto osize = std::min(
531 static_cast<uint64_t>(cur_size) - in.body.read.offset,
532 static_cast<uint64_t>(in.body.read.size));
533 assert(osize <= sizeof(out.body.bytes));
534 out.header.len = sizeof(struct fuse_out_header) + osize;
535 if (should_have_data)
536 memset(out.body.bytes, 'X', osize);
538 bzero(out.body.bytes, osize);
541 fd = open(FULLPATH, O_RDWR, 0644);
542 ASSERT_LE(0, fd) << strerror(errno);
544 /* Fill the file with Xs */
545 ASSERT_EQ(static_cast<ssize_t>(w0_size),
546 pwrite(fd, w0buf, w0_size, w0_offset));
547 should_have_data = true;
549 ASSERT_EQ(static_cast<ssize_t>(r0_size),
550 pread(fd, r0buf, r0_size, r0_offset));
551 /* 1st truncate should discard cached data */
552 EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
553 should_have_data = false;
554 /* 2nd truncate extends file into previously cached data */
555 EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
556 /* Read should return all zeros */
557 ASSERT_EQ(static_cast<ssize_t>(r1_size),
558 pread(fd, r1buf, r1_size, r1_offset));
560 r = memcmp(expected, r1buf, r1_size);
571 /* truncate should fail if it would cause the file to exceed RLIMIT_FSIZE */
572 TEST_F(Setattr, truncate_rlimit_rsize)
574 const char FULLPATH[] = "mountpoint/some_file.txt";
575 const char RELPATH[] = "some_file.txt";
577 const uint64_t ino = 42;
578 const uint64_t oldsize = 0;
579 const uint64_t newsize = 100'000'000;
581 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
582 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
583 SET_OUT_HEADER_LEN(out, entry);
584 out.body.entry.attr.mode = S_IFREG | 0644;
585 out.body.entry.nodeid = ino;
586 out.body.entry.attr.size = oldsize;
589 rl.rlim_cur = newsize / 2;
590 rl.rlim_max = 10 * newsize;
591 ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
592 ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
594 EXPECT_EQ(-1, truncate(FULLPATH, newsize));
595 EXPECT_EQ(EFBIG, errno);
596 EXPECT_EQ(1, s_sigxfsz);
599 /* Change a file's timestamps */
600 TEST_F(Setattr, utimensat) {
601 const char FULLPATH[] = "mountpoint/some_file.txt";
602 const char RELPATH[] = "some_file.txt";
603 const uint64_t ino = 42;
604 const timespec oldtimes[2] = {
605 {.tv_sec = 1, .tv_nsec = 2},
606 {.tv_sec = 3, .tv_nsec = 4},
608 const timespec newtimes[2] = {
609 {.tv_sec = 5, .tv_nsec = 6},
610 {.tv_sec = 7, .tv_nsec = 8},
613 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
614 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
615 SET_OUT_HEADER_LEN(out, entry);
616 out.body.entry.attr.mode = S_IFREG | 0644;
617 out.body.entry.nodeid = ino;
618 out.body.entry.attr_valid = UINT64_MAX;
619 out.body.entry.attr.atime = oldtimes[0].tv_sec;
620 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
621 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
622 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
625 EXPECT_CALL(*m_mock, process(
626 ResultOf([=](auto in) {
627 uint32_t valid = FATTR_ATIME | FATTR_MTIME;
628 return (in.header.opcode == FUSE_SETATTR &&
629 in.header.nodeid == ino &&
630 in.body.setattr.valid == valid &&
631 (time_t)in.body.setattr.atime ==
632 newtimes[0].tv_sec &&
633 (long)in.body.setattr.atimensec ==
634 newtimes[0].tv_nsec &&
635 (time_t)in.body.setattr.mtime ==
636 newtimes[1].tv_sec &&
637 (long)in.body.setattr.mtimensec ==
638 newtimes[1].tv_nsec);
641 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
642 SET_OUT_HEADER_LEN(out, attr);
643 out.body.attr.attr.ino = ino; // Must match nodeid
644 out.body.attr.attr.mode = S_IFREG | 0644;
645 out.body.attr.attr.atime = newtimes[0].tv_sec;
646 out.body.attr.attr.atimensec = newtimes[0].tv_nsec;
647 out.body.attr.attr.mtime = newtimes[1].tv_sec;
648 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
650 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
654 /* Change a file mtime but not its atime */
655 TEST_F(Setattr, utimensat_mtime_only) {
656 const char FULLPATH[] = "mountpoint/some_file.txt";
657 const char RELPATH[] = "some_file.txt";
658 const uint64_t ino = 42;
659 const timespec oldtimes[2] = {
660 {.tv_sec = 1, .tv_nsec = 2},
661 {.tv_sec = 3, .tv_nsec = 4},
663 const timespec newtimes[2] = {
664 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
665 {.tv_sec = 7, .tv_nsec = 8},
668 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
669 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
670 SET_OUT_HEADER_LEN(out, entry);
671 out.body.entry.attr.mode = S_IFREG | 0644;
672 out.body.entry.nodeid = ino;
673 out.body.entry.attr_valid = UINT64_MAX;
674 out.body.entry.attr.atime = oldtimes[0].tv_sec;
675 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
676 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
677 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
680 EXPECT_CALL(*m_mock, process(
681 ResultOf([=](auto in) {
682 uint32_t valid = FATTR_MTIME;
683 return (in.header.opcode == FUSE_SETATTR &&
684 in.header.nodeid == ino &&
685 in.body.setattr.valid == valid &&
686 (time_t)in.body.setattr.mtime ==
687 newtimes[1].tv_sec &&
688 (long)in.body.setattr.mtimensec ==
689 newtimes[1].tv_nsec);
692 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
693 SET_OUT_HEADER_LEN(out, attr);
694 out.body.attr.attr.ino = ino; // Must match nodeid
695 out.body.attr.attr.mode = S_IFREG | 0644;
696 out.body.attr.attr.atime = oldtimes[0].tv_sec;
697 out.body.attr.attr.atimensec = oldtimes[0].tv_nsec;
698 out.body.attr.attr.mtime = newtimes[1].tv_sec;
699 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
701 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
706 * Set a file's mtime and atime to now
708 * The design of FreeBSD's VFS does not allow fusefs to set just one of atime
709 * or mtime to UTIME_NOW; it's both or neither.
711 TEST_F(Setattr, utimensat_utime_now) {
712 const char FULLPATH[] = "mountpoint/some_file.txt";
713 const char RELPATH[] = "some_file.txt";
714 const uint64_t ino = 42;
715 const timespec oldtimes[2] = {
716 {.tv_sec = 1, .tv_nsec = 2},
717 {.tv_sec = 3, .tv_nsec = 4},
719 const timespec newtimes[2] = {
720 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
721 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
723 /* "now" is whatever the server says it is */
724 const timespec now[2] = {
725 {.tv_sec = 5, .tv_nsec = 7},
726 {.tv_sec = 6, .tv_nsec = 8},
730 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
731 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
732 SET_OUT_HEADER_LEN(out, entry);
733 out.body.entry.attr.mode = S_IFREG | 0644;
734 out.body.entry.nodeid = ino;
735 out.body.entry.attr_valid = UINT64_MAX;
736 out.body.entry.entry_valid = UINT64_MAX;
737 out.body.entry.attr.atime = oldtimes[0].tv_sec;
738 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
739 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
740 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
743 EXPECT_CALL(*m_mock, process(
744 ResultOf([=](auto in) {
745 uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW |
746 FATTR_MTIME | FATTR_MTIME_NOW;
747 return (in.header.opcode == FUSE_SETATTR &&
748 in.header.nodeid == ino &&
749 in.body.setattr.valid == valid);
752 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
753 SET_OUT_HEADER_LEN(out, attr);
754 out.body.attr.attr.ino = ino; // Must match nodeid
755 out.body.attr.attr.mode = S_IFREG | 0644;
756 out.body.attr.attr.atime = now[0].tv_sec;
757 out.body.attr.attr.atimensec = now[0].tv_nsec;
758 out.body.attr.attr.mtime = now[1].tv_sec;
759 out.body.attr.attr.mtimensec = now[1].tv_nsec;
760 out.body.attr.attr_valid = UINT64_MAX;
762 ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
764 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
765 EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec);
766 EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec);
767 EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec);
768 EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec);
772 * FUSE_SETATTR returns a different file type, even though the entry cache
773 * hasn't expired. This is a server bug! It probably means that the server
774 * removed the file and recreated it with the same inode but a different vtyp.
775 * The best thing fusefs can do is return ENOENT to the caller. After all, the
776 * entry must not have existed recently.
778 TEST_F(Setattr, vtyp_conflict)
780 const char FULLPATH[] = "mountpoint/some_file.txt";
781 const char RELPATH[] = "some_file.txt";
782 const uint64_t ino = 42;
783 uid_t newuser = 12345;
786 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
788 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
789 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
790 SET_OUT_HEADER_LEN(out, entry);
791 out.body.entry.attr.mode = S_IFREG | 0777;
792 out.body.entry.nodeid = ino;
793 out.body.entry.entry_valid = UINT64_MAX;
796 EXPECT_CALL(*m_mock, process(
797 ResultOf([](auto in) {
798 return (in.header.opcode == FUSE_SETATTR &&
799 in.header.nodeid == ino);
802 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
803 SET_OUT_HEADER_LEN(out, attr);
804 out.body.attr.attr.ino = ino;
805 out.body.attr.attr.mode = S_IFDIR | 0777; // Changed!
806 out.body.attr.attr.uid = newuser;
808 // We should reclaim stale vnodes
809 expect_forget(ino, 1, &sem);
811 EXPECT_NE(0, chown(FULLPATH, newuser, -1));
812 EXPECT_EQ(ENOENT, errno);
818 /* On a read-only mount, no attributes may be changed */
819 TEST_F(RofsSetattr, erofs)
821 const char FULLPATH[] = "mountpoint/some_file.txt";
822 const char RELPATH[] = "some_file.txt";
823 const uint64_t ino = 42;
824 const mode_t oldmode = 0755;
825 const mode_t newmode = 0644;
827 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
828 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
829 SET_OUT_HEADER_LEN(out, entry);
830 out.body.entry.attr.mode = S_IFREG | oldmode;
831 out.body.entry.nodeid = ino;
834 ASSERT_EQ(-1, chmod(FULLPATH, newmode));
835 ASSERT_EQ(EROFS, errno);
838 /* Change the mode of a file */
839 TEST_F(Setattr_7_8, chmod)
841 const char FULLPATH[] = "mountpoint/some_file.txt";
842 const char RELPATH[] = "some_file.txt";
843 const uint64_t ino = 42;
844 const mode_t oldmode = 0755;
845 const mode_t newmode = 0644;
847 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
848 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
849 SET_OUT_HEADER_LEN(out, entry_7_8);
850 out.body.entry.attr.mode = S_IFREG | oldmode;
851 out.body.entry.nodeid = ino;
854 EXPECT_CALL(*m_mock, process(
855 ResultOf([](auto in) {
856 uint32_t valid = FATTR_MODE;
857 return (in.header.opcode == FUSE_SETATTR &&
858 in.header.nodeid == ino &&
859 in.body.setattr.valid == valid &&
860 in.body.setattr.mode == newmode);
863 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
864 SET_OUT_HEADER_LEN(out, attr_7_8);
865 out.body.attr.attr.ino = ino; // Must match nodeid
866 out.body.attr.attr.mode = S_IFREG | newmode;
868 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);