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 {};
44 class RofsSetattr: public Setattr {
46 virtual void SetUp() {
54 * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
55 * should use the cached attributes, rather than query the daemon
57 TEST_F(Setattr, attr_cache)
59 const char FULLPATH[] = "mountpoint/some_file.txt";
60 const char RELPATH[] = "some_file.txt";
61 const uint64_t ino = 42;
63 const mode_t newmode = 0644;
65 EXPECT_LOOKUP(1, RELPATH)
66 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
67 SET_OUT_HEADER_LEN(out, entry);
68 out->body.entry.attr.mode = S_IFREG | 0644;
69 out->body.entry.nodeid = ino;
70 out->body.entry.entry_valid = UINT64_MAX;
73 EXPECT_CALL(*m_mock, process(
74 ResultOf([](auto in) {
75 return (in->header.opcode == FUSE_SETATTR &&
76 in->header.nodeid == ino);
79 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
80 SET_OUT_HEADER_LEN(out, attr);
81 out->body.attr.attr.ino = ino; // Must match nodeid
82 out->body.attr.attr.mode = S_IFREG | newmode;
83 out->body.attr.attr_valid = UINT64_MAX;
85 EXPECT_CALL(*m_mock, process(
86 ResultOf([](auto in) {
87 return (in->header.opcode == FUSE_GETATTR);
92 /* Set an attribute with SETATTR */
93 ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
95 /* The stat(2) should use cached attributes */
96 ASSERT_EQ(0, stat(FULLPATH, &sb));
97 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
100 /* Change the mode of a file */
101 TEST_F(Setattr, chmod)
103 const char FULLPATH[] = "mountpoint/some_file.txt";
104 const char RELPATH[] = "some_file.txt";
105 const uint64_t ino = 42;
106 const mode_t oldmode = 0755;
107 const mode_t newmode = 0644;
109 EXPECT_LOOKUP(1, RELPATH)
110 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
111 SET_OUT_HEADER_LEN(out, entry);
112 out->body.entry.attr.mode = S_IFREG | oldmode;
113 out->body.entry.nodeid = ino;
116 EXPECT_CALL(*m_mock, process(
117 ResultOf([](auto in) {
118 /* In protocol 7.23, ctime will be changed too */
119 uint32_t valid = FATTR_MODE;
120 return (in->header.opcode == FUSE_SETATTR &&
121 in->header.nodeid == ino &&
122 in->body.setattr.valid == valid &&
123 in->body.setattr.mode == newmode);
126 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
127 SET_OUT_HEADER_LEN(out, attr);
128 out->body.attr.attr.ino = ino; // Must match nodeid
129 out->body.attr.attr.mode = S_IFREG | newmode;
131 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
135 * Chmod a multiply-linked file with cached attributes. Check that both files'
136 * attributes have changed.
138 TEST_F(Setattr, chmod_multiply_linked)
140 const char FULLPATH0[] = "mountpoint/some_file.txt";
141 const char RELPATH0[] = "some_file.txt";
142 const char FULLPATH1[] = "mountpoint/other_file.txt";
143 const char RELPATH1[] = "other_file.txt";
145 const uint64_t ino = 42;
146 const mode_t oldmode = 0777;
147 const mode_t newmode = 0666;
149 EXPECT_LOOKUP(1, RELPATH0)
150 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
151 SET_OUT_HEADER_LEN(out, entry);
152 out->body.entry.attr.mode = S_IFREG | oldmode;
153 out->body.entry.nodeid = ino;
154 out->body.entry.attr.nlink = 2;
155 out->body.entry.attr_valid = UINT64_MAX;
156 out->body.entry.entry_valid = UINT64_MAX;
159 EXPECT_LOOKUP(1, RELPATH1)
160 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
161 SET_OUT_HEADER_LEN(out, entry);
162 out->body.entry.attr.mode = S_IFREG | oldmode;
163 out->body.entry.nodeid = ino;
164 out->body.entry.attr.nlink = 2;
165 out->body.entry.attr_valid = UINT64_MAX;
166 out->body.entry.entry_valid = UINT64_MAX;
169 EXPECT_CALL(*m_mock, process(
170 ResultOf([](auto in) {
171 uint32_t valid = FATTR_MODE;
172 return (in->header.opcode == FUSE_SETATTR &&
173 in->header.nodeid == ino &&
174 in->body.setattr.valid == valid &&
175 in->body.setattr.mode == newmode);
178 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
179 SET_OUT_HEADER_LEN(out, attr);
180 out->body.attr.attr.ino = ino;
181 out->body.attr.attr.mode = S_IFREG | newmode;
182 out->body.attr.attr.nlink = 2;
183 out->body.attr.attr_valid = UINT64_MAX;
186 /* For a lookup of the 2nd file to get it into the cache*/
187 ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
188 EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
190 ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno);
191 ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno);
192 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
193 ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
194 EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
198 /* Change the owner and group of a file */
199 TEST_F(Setattr, chown)
201 const char FULLPATH[] = "mountpoint/some_file.txt";
202 const char RELPATH[] = "some_file.txt";
203 const uint64_t ino = 42;
204 const gid_t oldgroup = 66;
205 const gid_t newgroup = 99;
206 const uid_t olduser = 33;
207 const uid_t newuser = 44;
209 EXPECT_LOOKUP(1, RELPATH)
210 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
211 SET_OUT_HEADER_LEN(out, entry);
212 out->body.entry.attr.mode = S_IFREG | 0644;
213 out->body.entry.nodeid = ino;
214 out->body.entry.attr.gid = oldgroup;
215 out->body.entry.attr.uid = olduser;
218 EXPECT_CALL(*m_mock, process(
219 ResultOf([](auto in) {
220 /* In protocol 7.23, ctime will be changed too */
221 uint32_t valid = FATTR_GID | FATTR_UID;
222 return (in->header.opcode == FUSE_SETATTR &&
223 in->header.nodeid == ino &&
224 in->body.setattr.valid == valid &&
225 in->body.setattr.uid == newuser &&
226 in->body.setattr.gid == newgroup);
229 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
230 SET_OUT_HEADER_LEN(out, attr);
231 out->body.attr.attr.ino = ino; // Must match nodeid
232 out->body.attr.attr.mode = S_IFREG | 0644;
233 out->body.attr.attr.uid = newuser;
234 out->body.attr.attr.gid = newgroup;
236 EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
242 * FUSE daemons are allowed to check permissions however they like. If the
243 * daemon returns EPERM, even if the file permissions "should" grant access,
244 * then fuse(4) should return EPERM too.
246 TEST_F(Setattr, eperm)
248 const char FULLPATH[] = "mountpoint/some_file.txt";
249 const char RELPATH[] = "some_file.txt";
250 const uint64_t ino = 42;
252 EXPECT_LOOKUP(1, RELPATH)
253 .WillOnce(Invoke(ReturnImmediate([=](auto in, auto out) {
254 SET_OUT_HEADER_LEN(out, entry);
255 out->body.entry.attr.mode = S_IFREG | 0777;
256 out->body.entry.nodeid = ino;
257 out->body.entry.attr.uid = in->header.uid;
258 out->body.entry.attr.gid = in->header.gid;
261 EXPECT_CALL(*m_mock, process(
262 ResultOf([](auto in) {
263 return (in->header.opcode == FUSE_SETATTR &&
264 in->header.nodeid == ino);
267 ).WillOnce(Invoke(ReturnErrno(EPERM)));
268 EXPECT_NE(0, truncate(FULLPATH, 10));
269 EXPECT_EQ(EPERM, errno);
272 /* Change the mode of an open file, by its file descriptor */
273 TEST_F(Setattr, fchmod)
275 const char FULLPATH[] = "mountpoint/some_file.txt";
276 const char RELPATH[] = "some_file.txt";
279 const mode_t oldmode = 0755;
280 const mode_t newmode = 0644;
282 EXPECT_LOOKUP(1, RELPATH)
283 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
284 SET_OUT_HEADER_LEN(out, entry);
285 out->body.entry.attr.mode = S_IFREG | oldmode;
286 out->body.entry.nodeid = ino;
287 out->body.entry.attr_valid = UINT64_MAX;
290 EXPECT_CALL(*m_mock, process(
291 ResultOf([=](auto in) {
292 return (in->header.opcode == FUSE_OPEN &&
293 in->header.nodeid == ino);
296 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
297 out->header.len = sizeof(out->header);
298 SET_OUT_HEADER_LEN(out, open);
301 EXPECT_CALL(*m_mock, process(
302 ResultOf([=](auto in) {
303 /* In protocol 7.23, ctime will be changed too */
304 uint32_t valid = FATTR_MODE;
305 return (in->header.opcode == FUSE_SETATTR &&
306 in->header.nodeid == ino &&
307 in->body.setattr.valid == valid &&
308 in->body.setattr.mode == newmode);
311 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
312 SET_OUT_HEADER_LEN(out, attr);
313 out->body.attr.attr.ino = ino; // Must match nodeid
314 out->body.attr.attr.mode = S_IFREG | newmode;
317 fd = open(FULLPATH, O_RDONLY);
318 ASSERT_LE(0, fd) << strerror(errno);
319 ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
320 /* Deliberately leak fd. close(2) will be tested in release.cc */
323 /* Change the size of an open file, by its file descriptor */
324 TEST_F(Setattr, ftruncate)
326 const char FULLPATH[] = "mountpoint/some_file.txt";
327 const char RELPATH[] = "some_file.txt";
330 uint64_t fh = 0xdeadbeef1a7ebabe;
331 const off_t oldsize = 99;
332 const off_t newsize = 12345;
334 EXPECT_LOOKUP(1, RELPATH)
335 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
336 SET_OUT_HEADER_LEN(out, entry);
337 out->body.entry.attr.mode = S_IFREG | 0755;
338 out->body.entry.nodeid = ino;
339 out->body.entry.attr_valid = UINT64_MAX;
340 out->body.entry.attr.size = oldsize;
343 EXPECT_CALL(*m_mock, process(
344 ResultOf([=](auto in) {
345 return (in->header.opcode == FUSE_OPEN &&
346 in->header.nodeid == ino);
349 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
350 out->header.len = sizeof(out->header);
351 SET_OUT_HEADER_LEN(out, open);
352 out->body.open.fh = fh;
355 EXPECT_CALL(*m_mock, process(
356 ResultOf([=](auto in) {
357 /* In protocol 7.23, ctime will be changed too */
358 uint32_t valid = FATTR_SIZE | FATTR_FH;
359 return (in->header.opcode == FUSE_SETATTR &&
360 in->header.nodeid == ino &&
361 in->body.setattr.valid == valid &&
362 in->body.setattr.fh == fh);
365 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
366 SET_OUT_HEADER_LEN(out, attr);
367 out->body.attr.attr.ino = ino; // Must match nodeid
368 out->body.attr.attr.mode = S_IFREG | 0755;
369 out->body.attr.attr.size = newsize;
372 fd = open(FULLPATH, O_RDWR);
373 ASSERT_LE(0, fd) << strerror(errno);
374 ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
375 /* Deliberately leak fd. close(2) will be tested in release.cc */
378 /* Change the size of the file */
379 TEST_F(Setattr, truncate) {
380 const char FULLPATH[] = "mountpoint/some_file.txt";
381 const char RELPATH[] = "some_file.txt";
382 const uint64_t ino = 42;
383 const uint64_t oldsize = 100'000'000;
384 const uint64_t newsize = 20'000'000;
386 EXPECT_LOOKUP(1, RELPATH)
387 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
388 SET_OUT_HEADER_LEN(out, entry);
389 out->body.entry.attr.mode = S_IFREG | 0644;
390 out->body.entry.nodeid = ino;
391 out->body.entry.attr.size = oldsize;
394 EXPECT_CALL(*m_mock, process(
395 ResultOf([](auto in) {
396 /* In protocol 7.23, ctime will be changed too */
397 uint32_t valid = FATTR_SIZE;
398 return (in->header.opcode == FUSE_SETATTR &&
399 in->header.nodeid == ino &&
400 in->body.setattr.valid == valid &&
401 in->body.setattr.size == newsize);
404 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
405 SET_OUT_HEADER_LEN(out, attr);
406 out->body.attr.attr.ino = ino; // Must match nodeid
407 out->body.attr.attr.mode = S_IFREG | 0644;
408 out->body.attr.attr.size = newsize;
410 EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
414 * Truncating a file should discard cached data past the truncation point.
415 * This is a regression test for bug 233783. The bug only applies when
416 * vfs.fusefs.data_cache_mode=1 or 2, but the test should pass regardless.
418 * There are two distinct failure modes. The first one is a failure to zero
419 * the portion of the file's final buffer past EOF. It can be reproduced by
420 * fsx -WR -P /tmp -S10 fsx.bin
422 * The second is a failure to drop buffers beyond that. It can be reproduced by
423 * fsx -WR -P /tmp -S18 -n fsx.bin
424 * Also reproducible in sh with:
425 * $> /path/to/libfuse/build/example/passthrough -d /tmp/mnt
427 * $> dd if=/dev/random of=randfile bs=1k count=192
428 * $> truncate -s 1k randfile && truncate -s 192k randfile
429 * $> xxd randfile | less # xxd will wrongly show random data at offset 0x8000
431 TEST_F(Setattr, truncate_discards_cached_data) {
432 const char FULLPATH[] = "mountpoint/some_file.txt";
433 const char RELPATH[] = "some_file.txt";
434 void *w0buf, *r0buf, *r1buf, *expected;
436 size_t w0_size = 0x30000;
438 off_t r0_size = w0_size;
439 size_t trunc0_size = 0x400;
440 size_t trunc1_size = w0_size;
441 off_t r1_offset = trunc0_size;
442 off_t r1_size = w0_size - trunc0_size;
444 const uint64_t ino = 42;
445 mode_t mode = S_IFREG | 0644;
447 bool should_have_data = false;
449 w0buf = malloc(w0_size);
450 ASSERT_NE(NULL, w0buf) << strerror(errno);
451 memset(w0buf, 'X', w0_size);
453 r0buf = malloc(r0_size);
454 ASSERT_NE(NULL, r0buf) << strerror(errno);
455 r1buf = malloc(r1_size);
456 ASSERT_NE(NULL, r1buf) << strerror(errno);
458 expected = malloc(r1_size);
459 ASSERT_NE(NULL, expected) << strerror(errno);
460 memset(expected, 0, r1_size);
462 expect_lookup(RELPATH, ino, mode, 0, 1);
463 expect_open(ino, O_RDWR, 1);
464 EXPECT_CALL(*m_mock, process(
465 ResultOf([=](auto in) {
466 return (in->header.opcode == FUSE_GETATTR &&
467 in->header.nodeid == ino);
470 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto out) {
471 SET_OUT_HEADER_LEN(out, attr);
472 out->body.attr.attr.ino = ino;
473 out->body.attr.attr.mode = mode;
474 out->body.attr.attr.size = cur_size;
477 * The exact pattern of FUSE_WRITE operations depends on the setting of
478 * vfs.fusefs.data_cache_mode. But it's not important for this test.
479 * Just set the mocks to accept anything
481 EXPECT_CALL(*m_mock, process(
482 ResultOf([=](auto in) {
483 return (in->header.opcode == FUSE_WRITE);
486 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto out) {
487 SET_OUT_HEADER_LEN(out, write);
488 out->body.attr.attr.ino = ino;
489 out->body.write.size = in->body.write.size;
490 cur_size = std::max(cur_size,
491 in->body.write.size + in->body.write.offset);
494 EXPECT_CALL(*m_mock, process(
495 ResultOf([=](auto in) {
496 return (in->header.opcode == FUSE_SETATTR &&
497 in->header.nodeid == ino &&
498 (in->body.setattr.valid & FATTR_SIZE));
501 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto out) {
502 auto trunc_size = in->body.setattr.size;
503 SET_OUT_HEADER_LEN(out, attr);
504 out->body.attr.attr.ino = ino;
505 out->body.attr.attr.mode = mode;
506 out->body.attr.attr.size = trunc_size;
507 cur_size = trunc_size;
510 /* exact pattern of FUSE_READ depends on vfs.fusefs.data_cache_mode */
511 EXPECT_CALL(*m_mock, process(
512 ResultOf([=](auto in) {
513 return (in->header.opcode == FUSE_READ);
516 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto out) {
517 auto osize = std::min(cur_size - in->body.read.offset,
518 (size_t)in->body.read.size);
519 out->header.len = sizeof(struct fuse_out_header) + osize;
520 if (should_have_data)
521 memset(out->body.bytes, 'X', osize);
523 bzero(out->body.bytes, osize);
526 fd = open(FULLPATH, O_RDWR, 0644);
527 ASSERT_LE(0, fd) << strerror(errno);
529 /* Fill the file with Xs */
530 ASSERT_EQ((ssize_t)w0_size, pwrite(fd, w0buf, w0_size, w0_offset));
531 should_have_data = true;
532 /* Fill the cache, if data_cache_mode == 1 */
533 ASSERT_EQ((ssize_t)r0_size, pread(fd, r0buf, r0_size, r0_offset));
534 /* 1st truncate should discard cached data */
535 EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
536 should_have_data = false;
537 /* 2nd truncate extends file into previously cached data */
538 EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
539 /* Read should return all zeros */
540 ASSERT_EQ((ssize_t)r1_size, pread(fd, r1buf, r1_size, r1_offset));
542 r = memcmp(expected, r1buf, r1_size);
551 /* Change a file's timestamps */
552 TEST_F(Setattr, utimensat) {
553 const char FULLPATH[] = "mountpoint/some_file.txt";
554 const char RELPATH[] = "some_file.txt";
555 const uint64_t ino = 42;
556 const timespec oldtimes[2] = {
557 {.tv_sec = 1, .tv_nsec = 2},
558 {.tv_sec = 3, .tv_nsec = 4},
560 const timespec newtimes[2] = {
561 {.tv_sec = 5, .tv_nsec = 6},
562 {.tv_sec = 7, .tv_nsec = 8},
565 EXPECT_LOOKUP(1, RELPATH)
566 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
567 SET_OUT_HEADER_LEN(out, entry);
568 out->body.entry.attr.mode = S_IFREG | 0644;
569 out->body.entry.nodeid = ino;
570 out->body.entry.attr_valid = UINT64_MAX;
571 out->body.entry.attr.atime = oldtimes[0].tv_sec;
572 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
573 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
574 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
577 EXPECT_CALL(*m_mock, process(
578 ResultOf([=](auto in) {
579 /* In protocol 7.23, ctime will be changed too */
580 uint32_t valid = FATTR_ATIME | FATTR_MTIME;
581 return (in->header.opcode == FUSE_SETATTR &&
582 in->header.nodeid == ino &&
583 in->body.setattr.valid == valid &&
584 in->body.setattr.atime == newtimes[0].tv_sec &&
585 in->body.setattr.atimensec ==
586 newtimes[0].tv_nsec &&
587 in->body.setattr.mtime == newtimes[1].tv_sec &&
588 in->body.setattr.mtimensec ==
589 newtimes[1].tv_nsec);
592 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
593 SET_OUT_HEADER_LEN(out, attr);
594 out->body.attr.attr.ino = ino; // Must match nodeid
595 out->body.attr.attr.mode = S_IFREG | 0644;
596 out->body.attr.attr.atime = newtimes[0].tv_sec;
597 out->body.attr.attr.atimensec = newtimes[0].tv_nsec;
598 out->body.attr.attr.mtime = newtimes[1].tv_sec;
599 out->body.attr.attr.mtimensec = newtimes[1].tv_nsec;
601 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
605 /* Change a file mtime but not its atime */
606 TEST_F(Setattr, utimensat_mtime_only) {
607 const char FULLPATH[] = "mountpoint/some_file.txt";
608 const char RELPATH[] = "some_file.txt";
609 const uint64_t ino = 42;
610 const timespec oldtimes[2] = {
611 {.tv_sec = 1, .tv_nsec = 2},
612 {.tv_sec = 3, .tv_nsec = 4},
614 const timespec newtimes[2] = {
615 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
616 {.tv_sec = 7, .tv_nsec = 8},
619 EXPECT_LOOKUP(1, RELPATH)
620 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
621 SET_OUT_HEADER_LEN(out, entry);
622 out->body.entry.attr.mode = S_IFREG | 0644;
623 out->body.entry.nodeid = ino;
624 out->body.entry.attr_valid = UINT64_MAX;
625 out->body.entry.attr.atime = oldtimes[0].tv_sec;
626 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
627 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
628 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
631 EXPECT_CALL(*m_mock, process(
632 ResultOf([=](auto in) {
633 /* In protocol 7.23, ctime will be changed too */
634 uint32_t valid = FATTR_MTIME;
635 return (in->header.opcode == FUSE_SETATTR &&
636 in->header.nodeid == ino &&
637 in->body.setattr.valid == valid &&
638 in->body.setattr.mtime == newtimes[1].tv_sec &&
639 in->body.setattr.mtimensec ==
640 newtimes[1].tv_nsec);
643 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
644 SET_OUT_HEADER_LEN(out, attr);
645 out->body.attr.attr.ino = ino; // Must match nodeid
646 out->body.attr.attr.mode = S_IFREG | 0644;
647 out->body.attr.attr.atime = oldtimes[0].tv_sec;
648 out->body.attr.attr.atimensec = oldtimes[0].tv_nsec;
649 out->body.attr.attr.mtime = newtimes[1].tv_sec;
650 out->body.attr.attr.mtimensec = newtimes[1].tv_nsec;
652 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
656 /* Set a file's mtime and atime to now */
657 /* TODO: enable this test after updating protocol to version 7.9 */
659 TEST_F(Setattr, utimensat_utime_now) {
660 const char FULLPATH[] = "mountpoint/some_file.txt";
661 const char RELPATH[] = "some_file.txt";
662 const uint64_t ino = 42;
663 const timespec oldtimes[2] = {
664 {.tv_sec = 1, .tv_nsec = 2},
665 {.tv_sec = 3, .tv_nsec = 4},
667 const timespec newtimes[2] = {
668 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
669 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
671 /* "now" is whatever the server says it is */
672 const timespec now[2] = {
673 {.tv_sec = 5, .tv_nsec = 7},
674 {.tv_sec = 6, .tv_nsec = 8},
678 EXPECT_LOOKUP(1, RELPATH)
679 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
680 SET_OUT_HEADER_LEN(out, entry);
681 out->body.entry.attr.mode = S_IFREG | 0644;
682 out->body.entry.nodeid = ino;
683 out->body.entry.attr_valid = UINT64_MAX;
684 out->body.entry.attr.atime = oldtimes[0].tv_sec;
685 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
686 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
687 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
690 EXPECT_CALL(*m_mock, process(
691 ResultOf([=](auto in) {
692 /* In protocol 7.23, ctime will be changed too */
693 uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW |
694 FATTR_MTIME | FATTR_MTIME_NOW;
695 return (in->header.opcode == FUSE_SETATTR &&
696 in->header.nodeid == ino &&
697 in->body.setattr.valid == valid);
700 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
701 SET_OUT_HEADER_LEN(out, attr);
702 out->body.attr.attr.ino = ino; // Must match nodeid
703 out->body.attr.attr.mode = S_IFREG | 0644;
704 out->body.attr.attr.atime = now[0].tv_sec;
705 out->body.attr.attr.atimensec = now[0].tv_nsec;
706 out->body.attr.attr.mtime = now[1].tv_sec;
707 out->body.attr.attr.mtimensec = now[1].tv_nsec;
708 out->body.attr.attr_valid = UINT64_MAX;
710 ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
712 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
713 EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec);
714 EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec);
715 EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec);
716 EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec);
720 /* On a read-only mount, no attributes may be changed */
721 TEST_F(RofsSetattr, erofs)
723 const char FULLPATH[] = "mountpoint/some_file.txt";
724 const char RELPATH[] = "some_file.txt";
725 const uint64_t ino = 42;
726 const mode_t oldmode = 0755;
727 const mode_t newmode = 0644;
729 EXPECT_LOOKUP(1, RELPATH)
730 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
731 SET_OUT_HEADER_LEN(out, entry);
732 out->body.entry.attr.mode = S_IFREG | oldmode;
733 out->body.entry.nodeid = ino;
736 ASSERT_EQ(-1, chmod(FULLPATH, newmode));
737 ASSERT_EQ(EROFS, errno);