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
42 using namespace testing;
44 class Setattr : public FuseTest {};
46 class RofsSetattr: public Setattr {
48 virtual void SetUp() {
54 class Setattr_7_8: public Setattr {
56 virtual void SetUp() {
57 m_kernel_minor_version = 8;
64 * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
65 * should use the cached attributes, rather than query the daemon
67 TEST_F(Setattr, attr_cache)
69 const char FULLPATH[] = "mountpoint/some_file.txt";
70 const char RELPATH[] = "some_file.txt";
71 const uint64_t ino = 42;
73 const mode_t newmode = 0644;
75 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
76 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
77 SET_OUT_HEADER_LEN(out, entry);
78 out.body.entry.attr.mode = S_IFREG | 0644;
79 out.body.entry.nodeid = ino;
80 out.body.entry.entry_valid = UINT64_MAX;
83 EXPECT_CALL(*m_mock, process(
84 ResultOf([](auto in) {
85 return (in.header.opcode == FUSE_SETATTR &&
86 in.header.nodeid == ino);
89 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
90 SET_OUT_HEADER_LEN(out, attr);
91 out.body.attr.attr.ino = ino; // Must match nodeid
92 out.body.attr.attr.mode = S_IFREG | newmode;
93 out.body.attr.attr_valid = UINT64_MAX;
95 EXPECT_CALL(*m_mock, process(
96 ResultOf([](auto in) {
97 return (in.header.opcode == FUSE_GETATTR);
102 /* Set an attribute with SETATTR */
103 ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
105 /* The stat(2) should use cached attributes */
106 ASSERT_EQ(0, stat(FULLPATH, &sb));
107 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
110 /* Change the mode of a file */
111 TEST_F(Setattr, chmod)
113 const char FULLPATH[] = "mountpoint/some_file.txt";
114 const char RELPATH[] = "some_file.txt";
115 const uint64_t ino = 42;
116 const mode_t oldmode = 0755;
117 const mode_t newmode = 0644;
119 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
120 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
121 SET_OUT_HEADER_LEN(out, entry);
122 out.body.entry.attr.mode = S_IFREG | oldmode;
123 out.body.entry.nodeid = ino;
126 EXPECT_CALL(*m_mock, process(
127 ResultOf([](auto in) {
128 uint32_t valid = FATTR_MODE;
129 return (in.header.opcode == FUSE_SETATTR &&
130 in.header.nodeid == ino &&
131 in.body.setattr.valid == valid &&
132 in.body.setattr.mode == newmode);
135 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
136 SET_OUT_HEADER_LEN(out, attr);
137 out.body.attr.attr.ino = ino; // Must match nodeid
138 out.body.attr.attr.mode = S_IFREG | newmode;
140 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
144 * Chmod a multiply-linked file with cached attributes. Check that both files'
145 * attributes have changed.
147 TEST_F(Setattr, chmod_multiply_linked)
149 const char FULLPATH0[] = "mountpoint/some_file.txt";
150 const char RELPATH0[] = "some_file.txt";
151 const char FULLPATH1[] = "mountpoint/other_file.txt";
152 const char RELPATH1[] = "other_file.txt";
154 const uint64_t ino = 42;
155 const mode_t oldmode = 0777;
156 const mode_t newmode = 0666;
158 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0)
159 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
160 SET_OUT_HEADER_LEN(out, entry);
161 out.body.entry.attr.mode = S_IFREG | oldmode;
162 out.body.entry.nodeid = ino;
163 out.body.entry.attr.nlink = 2;
164 out.body.entry.attr_valid = UINT64_MAX;
165 out.body.entry.entry_valid = UINT64_MAX;
168 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1)
169 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
170 SET_OUT_HEADER_LEN(out, entry);
171 out.body.entry.attr.mode = S_IFREG | oldmode;
172 out.body.entry.nodeid = ino;
173 out.body.entry.attr.nlink = 2;
174 out.body.entry.attr_valid = UINT64_MAX;
175 out.body.entry.entry_valid = UINT64_MAX;
178 EXPECT_CALL(*m_mock, process(
179 ResultOf([](auto in) {
180 uint32_t valid = FATTR_MODE;
181 return (in.header.opcode == FUSE_SETATTR &&
182 in.header.nodeid == ino &&
183 in.body.setattr.valid == valid &&
184 in.body.setattr.mode == newmode);
187 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
188 SET_OUT_HEADER_LEN(out, attr);
189 out.body.attr.attr.ino = ino;
190 out.body.attr.attr.mode = S_IFREG | newmode;
191 out.body.attr.attr.nlink = 2;
192 out.body.attr.attr_valid = UINT64_MAX;
195 /* For a lookup of the 2nd file to get it into the cache*/
196 ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
197 EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
199 ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno);
200 ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno);
201 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
202 ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
203 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
207 /* Change the owner and group of a file */
208 TEST_F(Setattr, chown)
210 const char FULLPATH[] = "mountpoint/some_file.txt";
211 const char RELPATH[] = "some_file.txt";
212 const uint64_t ino = 42;
213 const gid_t oldgroup = 66;
214 const gid_t newgroup = 99;
215 const uid_t olduser = 33;
216 const uid_t newuser = 44;
218 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
219 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
220 SET_OUT_HEADER_LEN(out, entry);
221 out.body.entry.attr.mode = S_IFREG | 0644;
222 out.body.entry.nodeid = ino;
223 out.body.entry.attr.gid = oldgroup;
224 out.body.entry.attr.uid = olduser;
227 EXPECT_CALL(*m_mock, process(
228 ResultOf([](auto in) {
229 uint32_t valid = FATTR_GID | FATTR_UID;
230 return (in.header.opcode == FUSE_SETATTR &&
231 in.header.nodeid == ino &&
232 in.body.setattr.valid == valid &&
233 in.body.setattr.uid == newuser &&
234 in.body.setattr.gid == newgroup);
237 ).WillOnce(Invoke(ReturnImmediate([](auto in __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 | 0644;
241 out.body.attr.attr.uid = newuser;
242 out.body.attr.attr.gid = newgroup;
244 EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
250 * FUSE daemons are allowed to check permissions however they like. If the
251 * daemon returns EPERM, even if the file permissions "should" grant access,
252 * then fuse(4) should return EPERM too.
254 TEST_F(Setattr, eperm)
256 const char FULLPATH[] = "mountpoint/some_file.txt";
257 const char RELPATH[] = "some_file.txt";
258 const uint64_t ino = 42;
260 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
261 .WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
262 SET_OUT_HEADER_LEN(out, entry);
263 out.body.entry.attr.mode = S_IFREG | 0777;
264 out.body.entry.nodeid = ino;
265 out.body.entry.attr.uid = in.header.uid;
266 out.body.entry.attr.gid = in.header.gid;
269 EXPECT_CALL(*m_mock, process(
270 ResultOf([](auto in) {
271 return (in.header.opcode == FUSE_SETATTR &&
272 in.header.nodeid == ino);
275 ).WillOnce(Invoke(ReturnErrno(EPERM)));
276 EXPECT_NE(0, truncate(FULLPATH, 10));
277 EXPECT_EQ(EPERM, errno);
280 /* Change the mode of an open file, by its file descriptor */
281 TEST_F(Setattr, fchmod)
283 const char FULLPATH[] = "mountpoint/some_file.txt";
284 const char RELPATH[] = "some_file.txt";
287 const mode_t oldmode = 0755;
288 const mode_t newmode = 0644;
290 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
291 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
292 SET_OUT_HEADER_LEN(out, entry);
293 out.body.entry.attr.mode = S_IFREG | oldmode;
294 out.body.entry.nodeid = ino;
295 out.body.entry.attr_valid = UINT64_MAX;
298 EXPECT_CALL(*m_mock, process(
299 ResultOf([=](auto in) {
300 return (in.header.opcode == FUSE_OPEN &&
301 in.header.nodeid == ino);
304 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
305 out.header.len = sizeof(out.header);
306 SET_OUT_HEADER_LEN(out, open);
309 EXPECT_CALL(*m_mock, process(
310 ResultOf([=](auto in) {
311 uint32_t valid = FATTR_MODE;
312 return (in.header.opcode == FUSE_SETATTR &&
313 in.header.nodeid == ino &&
314 in.body.setattr.valid == valid &&
315 in.body.setattr.mode == newmode);
318 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
319 SET_OUT_HEADER_LEN(out, attr);
320 out.body.attr.attr.ino = ino; // Must match nodeid
321 out.body.attr.attr.mode = S_IFREG | newmode;
324 fd = open(FULLPATH, O_RDONLY);
325 ASSERT_LE(0, fd) << strerror(errno);
326 ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
330 /* Change the size of an open file, by its file descriptor */
331 TEST_F(Setattr, ftruncate)
333 const char FULLPATH[] = "mountpoint/some_file.txt";
334 const char RELPATH[] = "some_file.txt";
337 uint64_t fh = 0xdeadbeef1a7ebabe;
338 const off_t oldsize = 99;
339 const off_t newsize = 12345;
341 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
342 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
343 SET_OUT_HEADER_LEN(out, entry);
344 out.body.entry.attr.mode = S_IFREG | 0755;
345 out.body.entry.nodeid = ino;
346 out.body.entry.attr_valid = UINT64_MAX;
347 out.body.entry.attr.size = oldsize;
350 EXPECT_CALL(*m_mock, process(
351 ResultOf([=](auto in) {
352 return (in.header.opcode == FUSE_OPEN &&
353 in.header.nodeid == ino);
356 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
357 out.header.len = sizeof(out.header);
358 SET_OUT_HEADER_LEN(out, open);
359 out.body.open.fh = fh;
362 EXPECT_CALL(*m_mock, process(
363 ResultOf([=](auto in) {
364 uint32_t valid = FATTR_SIZE | FATTR_FH;
365 return (in.header.opcode == FUSE_SETATTR &&
366 in.header.nodeid == ino &&
367 in.body.setattr.valid == valid &&
368 in.body.setattr.fh == fh);
371 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
372 SET_OUT_HEADER_LEN(out, attr);
373 out.body.attr.attr.ino = ino; // Must match nodeid
374 out.body.attr.attr.mode = S_IFREG | 0755;
375 out.body.attr.attr.size = newsize;
378 fd = open(FULLPATH, O_RDWR);
379 ASSERT_LE(0, fd) << strerror(errno);
380 ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
384 /* Change the size of the file */
385 TEST_F(Setattr, truncate) {
386 const char FULLPATH[] = "mountpoint/some_file.txt";
387 const char RELPATH[] = "some_file.txt";
388 const uint64_t ino = 42;
389 const uint64_t oldsize = 100'000'000;
390 const uint64_t newsize = 20'000'000;
392 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
393 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
394 SET_OUT_HEADER_LEN(out, entry);
395 out.body.entry.attr.mode = S_IFREG | 0644;
396 out.body.entry.nodeid = ino;
397 out.body.entry.attr.size = oldsize;
400 EXPECT_CALL(*m_mock, process(
401 ResultOf([](auto in) {
402 uint32_t valid = FATTR_SIZE;
403 return (in.header.opcode == FUSE_SETATTR &&
404 in.header.nodeid == ino &&
405 in.body.setattr.valid == valid &&
406 in.body.setattr.size == newsize);
409 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
410 SET_OUT_HEADER_LEN(out, attr);
411 out.body.attr.attr.ino = ino; // Must match nodeid
412 out.body.attr.attr.mode = S_IFREG | 0644;
413 out.body.attr.attr.size = newsize;
415 EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
419 * Truncating a file should discard cached data past the truncation point.
420 * This is a regression test for bug 233783.
422 * There are two distinct failure modes. The first one is a failure to zero
423 * the portion of the file's final buffer past EOF. It can be reproduced by
424 * fsx -WR -P /tmp -S10 fsx.bin
426 * The second is a failure to drop buffers beyond that. It can be reproduced by
427 * fsx -WR -P /tmp -S18 -n fsx.bin
428 * Also reproducible in sh with:
429 * $> /path/to/libfuse/build/example/passthrough -d /tmp/mnt
431 * $> dd if=/dev/random of=randfile bs=1k count=192
432 * $> truncate -s 1k randfile && truncate -s 192k randfile
433 * $> xxd randfile | less # xxd will wrongly show random data at offset 0x8000
435 TEST_F(Setattr, truncate_discards_cached_data) {
436 const char FULLPATH[] = "mountpoint/some_file.txt";
437 const char RELPATH[] = "some_file.txt";
438 void *w0buf, *r0buf, *r1buf, *expected;
440 size_t w0_size = 0x30000;
442 off_t r0_size = w0_size;
443 size_t trunc0_size = 0x400;
444 size_t trunc1_size = w0_size;
445 off_t r1_offset = trunc0_size;
446 off_t r1_size = w0_size - trunc0_size;
448 const uint64_t ino = 42;
449 mode_t mode = S_IFREG | 0644;
451 bool should_have_data = false;
453 w0buf = malloc(w0_size);
454 ASSERT_NE(nullptr, w0buf) << strerror(errno);
455 memset(w0buf, 'X', w0_size);
457 r0buf = malloc(r0_size);
458 ASSERT_NE(nullptr, r0buf) << strerror(errno);
459 r1buf = malloc(r1_size);
460 ASSERT_NE(nullptr, r1buf) << strerror(errno);
462 expected = malloc(r1_size);
463 ASSERT_NE(nullptr, expected) << strerror(errno);
464 memset(expected, 0, r1_size);
466 expect_lookup(RELPATH, ino, mode, 0, 1);
467 expect_open(ino, O_RDWR, 1);
468 EXPECT_CALL(*m_mock, process(
469 ResultOf([=](auto in) {
470 return (in.header.opcode == FUSE_GETATTR &&
471 in.header.nodeid == ino);
474 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto& out) {
475 SET_OUT_HEADER_LEN(out, attr);
476 out.body.attr.attr.ino = ino;
477 out.body.attr.attr.mode = mode;
478 out.body.attr.attr.size = cur_size;
480 EXPECT_CALL(*m_mock, process(
481 ResultOf([=](auto in) {
482 return (in.header.opcode == FUSE_WRITE);
485 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
486 SET_OUT_HEADER_LEN(out, write);
487 out.body.attr.attr.ino = ino;
488 out.body.write.size = in.body.write.size;
489 cur_size = std::max(static_cast<uint64_t>(cur_size),
490 in.body.write.size + in.body.write.offset);
493 EXPECT_CALL(*m_mock, process(
494 ResultOf([=](auto in) {
495 return (in.header.opcode == FUSE_SETATTR &&
496 in.header.nodeid == ino &&
497 (in.body.setattr.valid & FATTR_SIZE));
500 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
501 auto trunc_size = in.body.setattr.size;
502 SET_OUT_HEADER_LEN(out, attr);
503 out.body.attr.attr.ino = ino;
504 out.body.attr.attr.mode = mode;
505 out.body.attr.attr.size = trunc_size;
506 cur_size = trunc_size;
509 EXPECT_CALL(*m_mock, process(
510 ResultOf([=](auto in) {
511 return (in.header.opcode == FUSE_READ);
514 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
515 auto osize = std::min(
516 static_cast<uint64_t>(cur_size) - in.body.read.offset,
517 static_cast<uint64_t>(in.body.read.size));
518 out.header.len = sizeof(struct fuse_out_header) + osize;
519 if (should_have_data)
520 memset(out.body.bytes, 'X', osize);
522 bzero(out.body.bytes, osize);
525 fd = open(FULLPATH, O_RDWR, 0644);
526 ASSERT_LE(0, fd) << strerror(errno);
528 /* Fill the file with Xs */
529 ASSERT_EQ(static_cast<ssize_t>(w0_size),
530 pwrite(fd, w0buf, w0_size, w0_offset));
531 should_have_data = true;
533 ASSERT_EQ(static_cast<ssize_t>(r0_size),
534 pread(fd, r0buf, r0_size, r0_offset));
535 /* 1st truncate should discard cached data */
536 EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
537 should_have_data = false;
538 /* 2nd truncate extends file into previously cached data */
539 EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
540 /* Read should return all zeros */
541 ASSERT_EQ(static_cast<ssize_t>(r1_size),
542 pread(fd, r1buf, r1_size, r1_offset));
544 r = memcmp(expected, r1buf, r1_size);
555 /* Change a file's timestamps */
556 TEST_F(Setattr, utimensat) {
557 const char FULLPATH[] = "mountpoint/some_file.txt";
558 const char RELPATH[] = "some_file.txt";
559 const uint64_t ino = 42;
560 const timespec oldtimes[2] = {
561 {.tv_sec = 1, .tv_nsec = 2},
562 {.tv_sec = 3, .tv_nsec = 4},
564 const timespec newtimes[2] = {
565 {.tv_sec = 5, .tv_nsec = 6},
566 {.tv_sec = 7, .tv_nsec = 8},
569 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
570 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
571 SET_OUT_HEADER_LEN(out, entry);
572 out.body.entry.attr.mode = S_IFREG | 0644;
573 out.body.entry.nodeid = ino;
574 out.body.entry.attr_valid = UINT64_MAX;
575 out.body.entry.attr.atime = oldtimes[0].tv_sec;
576 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
577 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
578 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
581 EXPECT_CALL(*m_mock, process(
582 ResultOf([=](auto in) {
583 uint32_t valid = FATTR_ATIME | FATTR_MTIME;
584 return (in.header.opcode == FUSE_SETATTR &&
585 in.header.nodeid == ino &&
586 in.body.setattr.valid == valid &&
587 (time_t)in.body.setattr.atime ==
588 newtimes[0].tv_sec &&
589 (long)in.body.setattr.atimensec ==
590 newtimes[0].tv_nsec &&
591 (time_t)in.body.setattr.mtime ==
592 newtimes[1].tv_sec &&
593 (long)in.body.setattr.mtimensec ==
594 newtimes[1].tv_nsec);
597 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
598 SET_OUT_HEADER_LEN(out, attr);
599 out.body.attr.attr.ino = ino; // Must match nodeid
600 out.body.attr.attr.mode = S_IFREG | 0644;
601 out.body.attr.attr.atime = newtimes[0].tv_sec;
602 out.body.attr.attr.atimensec = newtimes[0].tv_nsec;
603 out.body.attr.attr.mtime = newtimes[1].tv_sec;
604 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
606 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
610 /* Change a file mtime but not its atime */
611 TEST_F(Setattr, utimensat_mtime_only) {
612 const char FULLPATH[] = "mountpoint/some_file.txt";
613 const char RELPATH[] = "some_file.txt";
614 const uint64_t ino = 42;
615 const timespec oldtimes[2] = {
616 {.tv_sec = 1, .tv_nsec = 2},
617 {.tv_sec = 3, .tv_nsec = 4},
619 const timespec newtimes[2] = {
620 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
621 {.tv_sec = 7, .tv_nsec = 8},
624 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
625 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
626 SET_OUT_HEADER_LEN(out, entry);
627 out.body.entry.attr.mode = S_IFREG | 0644;
628 out.body.entry.nodeid = ino;
629 out.body.entry.attr_valid = UINT64_MAX;
630 out.body.entry.attr.atime = oldtimes[0].tv_sec;
631 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
632 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
633 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
636 EXPECT_CALL(*m_mock, process(
637 ResultOf([=](auto in) {
638 uint32_t valid = FATTR_MTIME;
639 return (in.header.opcode == FUSE_SETATTR &&
640 in.header.nodeid == ino &&
641 in.body.setattr.valid == valid &&
642 (time_t)in.body.setattr.mtime ==
643 newtimes[1].tv_sec &&
644 (long)in.body.setattr.mtimensec ==
645 newtimes[1].tv_nsec);
648 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
649 SET_OUT_HEADER_LEN(out, attr);
650 out.body.attr.attr.ino = ino; // Must match nodeid
651 out.body.attr.attr.mode = S_IFREG | 0644;
652 out.body.attr.attr.atime = oldtimes[0].tv_sec;
653 out.body.attr.attr.atimensec = oldtimes[0].tv_nsec;
654 out.body.attr.attr.mtime = newtimes[1].tv_sec;
655 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
657 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
662 * Set a file's mtime and atime to now
664 * The design of FreeBSD's VFS does not allow fusefs to set just one of atime
665 * or mtime to UTIME_NOW; it's both or neither.
667 TEST_F(Setattr, utimensat_utime_now) {
668 const char FULLPATH[] = "mountpoint/some_file.txt";
669 const char RELPATH[] = "some_file.txt";
670 const uint64_t ino = 42;
671 const timespec oldtimes[2] = {
672 {.tv_sec = 1, .tv_nsec = 2},
673 {.tv_sec = 3, .tv_nsec = 4},
675 const timespec newtimes[2] = {
676 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
677 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
679 /* "now" is whatever the server says it is */
680 const timespec now[2] = {
681 {.tv_sec = 5, .tv_nsec = 7},
682 {.tv_sec = 6, .tv_nsec = 8},
686 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
687 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
688 SET_OUT_HEADER_LEN(out, entry);
689 out.body.entry.attr.mode = S_IFREG | 0644;
690 out.body.entry.nodeid = ino;
691 out.body.entry.attr_valid = UINT64_MAX;
692 out.body.entry.entry_valid = UINT64_MAX;
693 out.body.entry.attr.atime = oldtimes[0].tv_sec;
694 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
695 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
696 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
699 EXPECT_CALL(*m_mock, process(
700 ResultOf([=](auto in) {
701 uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW |
702 FATTR_MTIME | FATTR_MTIME_NOW;
703 return (in.header.opcode == FUSE_SETATTR &&
704 in.header.nodeid == ino &&
705 in.body.setattr.valid == valid);
708 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
709 SET_OUT_HEADER_LEN(out, attr);
710 out.body.attr.attr.ino = ino; // Must match nodeid
711 out.body.attr.attr.mode = S_IFREG | 0644;
712 out.body.attr.attr.atime = now[0].tv_sec;
713 out.body.attr.attr.atimensec = now[0].tv_nsec;
714 out.body.attr.attr.mtime = now[1].tv_sec;
715 out.body.attr.attr.mtimensec = now[1].tv_nsec;
716 out.body.attr.attr_valid = UINT64_MAX;
718 ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
720 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
721 EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec);
722 EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec);
723 EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec);
724 EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec);
727 /* On a read-only mount, no attributes may be changed */
728 TEST_F(RofsSetattr, erofs)
730 const char FULLPATH[] = "mountpoint/some_file.txt";
731 const char RELPATH[] = "some_file.txt";
732 const uint64_t ino = 42;
733 const mode_t oldmode = 0755;
734 const mode_t newmode = 0644;
736 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
737 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
738 SET_OUT_HEADER_LEN(out, entry);
739 out.body.entry.attr.mode = S_IFREG | oldmode;
740 out.body.entry.nodeid = ino;
743 ASSERT_EQ(-1, chmod(FULLPATH, newmode));
744 ASSERT_EQ(EROFS, errno);
747 /* Change the mode of a file */
748 TEST_F(Setattr_7_8, chmod)
750 const char FULLPATH[] = "mountpoint/some_file.txt";
751 const char RELPATH[] = "some_file.txt";
752 const uint64_t ino = 42;
753 const mode_t oldmode = 0755;
754 const mode_t newmode = 0644;
756 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
757 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
758 SET_OUT_HEADER_LEN(out, entry_7_8);
759 out.body.entry.attr.mode = S_IFREG | oldmode;
760 out.body.entry.nodeid = ino;
763 EXPECT_CALL(*m_mock, process(
764 ResultOf([](auto in) {
765 uint32_t valid = FATTR_MODE;
766 return (in.header.opcode == FUSE_SETATTR &&
767 in.header.nodeid == ino &&
768 in.body.setattr.valid == valid &&
769 in.body.setattr.mode == newmode);
772 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
773 SET_OUT_HEADER_LEN(out, attr_7_8);
774 out.body.attr.attr.ino = ino; // Must match nodeid
775 out.body.attr.attr.mode = S_IFREG | newmode;
777 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);