]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/setattr.cc
fusefs: forward UTIME_NOW to the server
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / setattr.cc
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2019 The FreeBSD Foundation
5  *
6  * This software was developed by BFF Storage Systems, LLC under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
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.
17  *
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
28  * SUCH DAMAGE.
29  */
30
31 extern "C" {
32 #include <sys/stat.h>
33
34 #include <fcntl.h>
35 }
36
37 #include "mockfs.hh"
38 #include "utils.hh"
39
40 using namespace testing;
41
42 class Setattr : public FuseTest {};
43
44 class RofsSetattr: public Setattr {
45 public:
46 virtual void SetUp() {
47         m_ro = true;
48         Setattr::SetUp();
49 }
50 };
51
52 class Setattr_7_8: public Setattr {
53 public:
54 virtual void SetUp() {
55         m_kernel_minor_version = 8;
56         Setattr::SetUp();
57 }
58 };
59
60
61 /*
62  * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
63  * should use the cached attributes, rather than query the daemon
64  */
65 TEST_F(Setattr, attr_cache)
66 {
67         const char FULLPATH[] = "mountpoint/some_file.txt";
68         const char RELPATH[] = "some_file.txt";
69         const uint64_t ino = 42;
70         struct stat sb;
71         const mode_t newmode = 0644;
72
73         EXPECT_LOOKUP(1, RELPATH)
74         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
75                 SET_OUT_HEADER_LEN(out, entry);
76                 out->body.entry.attr.mode = S_IFREG | 0644;
77                 out->body.entry.nodeid = ino;
78                 out->body.entry.entry_valid = UINT64_MAX;
79         })));
80
81         EXPECT_CALL(*m_mock, process(
82                 ResultOf([](auto in) {
83                         return (in->header.opcode == FUSE_SETATTR &&
84                                 in->header.nodeid == ino);
85                 }, Eq(true)),
86                 _)
87         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
88                 SET_OUT_HEADER_LEN(out, attr);
89                 out->body.attr.attr.ino = ino;  // Must match nodeid
90                 out->body.attr.attr.mode = S_IFREG | newmode;
91                 out->body.attr.attr_valid = UINT64_MAX;
92         })));
93         EXPECT_CALL(*m_mock, process(
94                 ResultOf([](auto in) {
95                         return (in->header.opcode == FUSE_GETATTR);
96                 }, Eq(true)),
97                 _)
98         ).Times(0);
99
100         /* Set an attribute with SETATTR */
101         ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
102
103         /* The stat(2) should use cached attributes */
104         ASSERT_EQ(0, stat(FULLPATH, &sb));
105         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
106 }
107
108 /* Change the mode of a file */
109 TEST_F(Setattr, chmod)
110 {
111         const char FULLPATH[] = "mountpoint/some_file.txt";
112         const char RELPATH[] = "some_file.txt";
113         const uint64_t ino = 42;
114         const mode_t oldmode = 0755;
115         const mode_t newmode = 0644;
116
117         EXPECT_LOOKUP(1, RELPATH)
118         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
119                 SET_OUT_HEADER_LEN(out, entry);
120                 out->body.entry.attr.mode = S_IFREG | oldmode;
121                 out->body.entry.nodeid = ino;
122         })));
123
124         EXPECT_CALL(*m_mock, process(
125                 ResultOf([](auto in) {
126                         /* In protocol 7.23, ctime will be changed too */
127                         uint32_t valid = FATTR_MODE;
128                         return (in->header.opcode == FUSE_SETATTR &&
129                                 in->header.nodeid == ino &&
130                                 in->body.setattr.valid == valid &&
131                                 in->body.setattr.mode == newmode);
132                 }, Eq(true)),
133                 _)
134         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
135                 SET_OUT_HEADER_LEN(out, attr);
136                 out->body.attr.attr.ino = ino;  // Must match nodeid
137                 out->body.attr.attr.mode = S_IFREG | newmode;
138         })));
139         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
140 }
141
142 /* 
143  * Chmod a multiply-linked file with cached attributes.  Check that both files'
144  * attributes have changed.
145  */
146 TEST_F(Setattr, chmod_multiply_linked)
147 {
148         const char FULLPATH0[] = "mountpoint/some_file.txt";
149         const char RELPATH0[] = "some_file.txt";
150         const char FULLPATH1[] = "mountpoint/other_file.txt";
151         const char RELPATH1[] = "other_file.txt";
152         struct stat sb;
153         const uint64_t ino = 42;
154         const mode_t oldmode = 0777;
155         const mode_t newmode = 0666;
156
157         EXPECT_LOOKUP(1, RELPATH0)
158         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
159                 SET_OUT_HEADER_LEN(out, entry);
160                 out->body.entry.attr.mode = S_IFREG | oldmode;
161                 out->body.entry.nodeid = ino;
162                 out->body.entry.attr.nlink = 2;
163                 out->body.entry.attr_valid = UINT64_MAX;
164                 out->body.entry.entry_valid = UINT64_MAX;
165         })));
166
167         EXPECT_LOOKUP(1, RELPATH1)
168         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
169                 SET_OUT_HEADER_LEN(out, entry);
170                 out->body.entry.attr.mode = S_IFREG | oldmode;
171                 out->body.entry.nodeid = ino;
172                 out->body.entry.attr.nlink = 2;
173                 out->body.entry.attr_valid = UINT64_MAX;
174                 out->body.entry.entry_valid = UINT64_MAX;
175         })));
176
177         EXPECT_CALL(*m_mock, process(
178                 ResultOf([](auto in) {
179                         uint32_t valid = FATTR_MODE;
180                         return (in->header.opcode == FUSE_SETATTR &&
181                                 in->header.nodeid == ino &&
182                                 in->body.setattr.valid == valid &&
183                                 in->body.setattr.mode == newmode);
184                 }, Eq(true)),
185                 _)
186         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
187                 SET_OUT_HEADER_LEN(out, attr);
188                 out->body.attr.attr.ino = ino;
189                 out->body.attr.attr.mode = S_IFREG | newmode;
190                 out->body.attr.attr.nlink = 2;
191                 out->body.attr.attr_valid = UINT64_MAX;
192         })));
193
194         /* For a lookup of the 2nd file to get it into the cache*/
195         ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
196         EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
197
198         ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno);
199         ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno);
200         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
201         ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
202         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
203 }
204
205
206 /* Change the owner and group of a file */
207 TEST_F(Setattr, chown)
208 {
209         const char FULLPATH[] = "mountpoint/some_file.txt";
210         const char RELPATH[] = "some_file.txt";
211         const uint64_t ino = 42;
212         const gid_t oldgroup = 66;
213         const gid_t newgroup = 99;
214         const uid_t olduser = 33;
215         const uid_t newuser = 44;
216
217         EXPECT_LOOKUP(1, RELPATH)
218         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
219                 SET_OUT_HEADER_LEN(out, entry);
220                 out->body.entry.attr.mode = S_IFREG | 0644;
221                 out->body.entry.nodeid = ino;
222                 out->body.entry.attr.gid = oldgroup;
223                 out->body.entry.attr.uid = olduser;
224         })));
225
226         EXPECT_CALL(*m_mock, process(
227                 ResultOf([](auto in) {
228                         /* In protocol 7.23, ctime will be changed too */
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);
235                 }, Eq(true)),
236                 _)
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;
243         })));
244         EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
245 }
246
247
248
249 /* 
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.
253  */
254 TEST_F(Setattr, eperm)
255 {
256         const char FULLPATH[] = "mountpoint/some_file.txt";
257         const char RELPATH[] = "some_file.txt";
258         const uint64_t ino = 42;
259
260         EXPECT_LOOKUP(1, 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;
267         })));
268
269         EXPECT_CALL(*m_mock, process(
270                 ResultOf([](auto in) {
271                         return (in->header.opcode == FUSE_SETATTR &&
272                                 in->header.nodeid == ino);
273                 }, Eq(true)),
274                 _)
275         ).WillOnce(Invoke(ReturnErrno(EPERM)));
276         EXPECT_NE(0, truncate(FULLPATH, 10));
277         EXPECT_EQ(EPERM, errno);
278 }
279
280 /* Change the mode of an open file, by its file descriptor */
281 TEST_F(Setattr, fchmod)
282 {
283         const char FULLPATH[] = "mountpoint/some_file.txt";
284         const char RELPATH[] = "some_file.txt";
285         uint64_t ino = 42;
286         int fd;
287         const mode_t oldmode = 0755;
288         const mode_t newmode = 0644;
289
290         EXPECT_LOOKUP(1, 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;
296         })));
297
298         EXPECT_CALL(*m_mock, process(
299                 ResultOf([=](auto in) {
300                         return (in->header.opcode == FUSE_OPEN &&
301                                 in->header.nodeid == ino);
302                 }, Eq(true)),
303                 _)
304         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
305                 out->header.len = sizeof(out->header);
306                 SET_OUT_HEADER_LEN(out, open);
307         })));
308
309         EXPECT_CALL(*m_mock, process(
310                 ResultOf([=](auto in) {
311                         /* In protocol 7.23, ctime will be changed too */
312                         uint32_t valid = FATTR_MODE;
313                         return (in->header.opcode == FUSE_SETATTR &&
314                                 in->header.nodeid == ino &&
315                                 in->body.setattr.valid == valid &&
316                                 in->body.setattr.mode == newmode);
317                 }, Eq(true)),
318                 _)
319         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
320                 SET_OUT_HEADER_LEN(out, attr);
321                 out->body.attr.attr.ino = ino;  // Must match nodeid
322                 out->body.attr.attr.mode = S_IFREG | newmode;
323         })));
324
325         fd = open(FULLPATH, O_RDONLY);
326         ASSERT_LE(0, fd) << strerror(errno);
327         ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
328         /* Deliberately leak fd.  close(2) will be tested in release.cc */
329 }
330
331 /* Change the size of an open file, by its file descriptor */
332 TEST_F(Setattr, ftruncate)
333 {
334         const char FULLPATH[] = "mountpoint/some_file.txt";
335         const char RELPATH[] = "some_file.txt";
336         uint64_t ino = 42;
337         int fd;
338         uint64_t fh = 0xdeadbeef1a7ebabe;
339         const off_t oldsize = 99;
340         const off_t newsize = 12345;
341
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 | 0755;
346                 out->body.entry.nodeid = ino;
347                 out->body.entry.attr_valid = UINT64_MAX;
348                 out->body.entry.attr.size = oldsize;
349         })));
350
351         EXPECT_CALL(*m_mock, process(
352                 ResultOf([=](auto in) {
353                         return (in->header.opcode == FUSE_OPEN &&
354                                 in->header.nodeid == ino);
355                 }, Eq(true)),
356                 _)
357         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
358                 out->header.len = sizeof(out->header);
359                 SET_OUT_HEADER_LEN(out, open);
360                 out->body.open.fh = fh;
361         })));
362
363         EXPECT_CALL(*m_mock, process(
364                 ResultOf([=](auto in) {
365                         /* In protocol 7.23, ctime will be changed too */
366                         uint32_t valid = FATTR_SIZE | FATTR_FH;
367                         return (in->header.opcode == FUSE_SETATTR &&
368                                 in->header.nodeid == ino &&
369                                 in->body.setattr.valid == valid &&
370                                 in->body.setattr.fh == fh);
371                 }, Eq(true)),
372                 _)
373         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
374                 SET_OUT_HEADER_LEN(out, attr);
375                 out->body.attr.attr.ino = ino;  // Must match nodeid
376                 out->body.attr.attr.mode = S_IFREG | 0755;
377                 out->body.attr.attr.size = newsize;
378         })));
379
380         fd = open(FULLPATH, O_RDWR);
381         ASSERT_LE(0, fd) << strerror(errno);
382         ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
383         /* Deliberately leak fd.  close(2) will be tested in release.cc */
384 }
385
386 /* Change the size of the file */
387 TEST_F(Setattr, truncate) {
388         const char FULLPATH[] = "mountpoint/some_file.txt";
389         const char RELPATH[] = "some_file.txt";
390         const uint64_t ino = 42;
391         const uint64_t oldsize = 100'000'000;
392         const uint64_t newsize = 20'000'000;
393
394         EXPECT_LOOKUP(1, RELPATH)
395         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
396                 SET_OUT_HEADER_LEN(out, entry);
397                 out->body.entry.attr.mode = S_IFREG | 0644;
398                 out->body.entry.nodeid = ino;
399                 out->body.entry.attr.size = oldsize;
400         })));
401
402         EXPECT_CALL(*m_mock, process(
403                 ResultOf([](auto in) {
404                         /* In protocol 7.23, ctime will be changed too */
405                         uint32_t valid = FATTR_SIZE;
406                         return (in->header.opcode == FUSE_SETATTR &&
407                                 in->header.nodeid == ino &&
408                                 in->body.setattr.valid == valid &&
409                                 in->body.setattr.size == newsize);
410                 }, Eq(true)),
411                 _)
412         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
413                 SET_OUT_HEADER_LEN(out, attr);
414                 out->body.attr.attr.ino = ino;  // Must match nodeid
415                 out->body.attr.attr.mode = S_IFREG | 0644;
416                 out->body.attr.attr.size = newsize;
417         })));
418         EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
419 }
420
421 /*
422  * Truncating a file should discard cached data past the truncation point.
423  * This is a regression test for bug 233783.  The bug only applies when
424  * vfs.fusefs.data_cache_mode=1 or 2, but the test should pass regardless.
425  *
426  * There are two distinct failure modes.  The first one is a failure to zero
427  * the portion of the file's final buffer past EOF.  It can be reproduced by
428  * fsx -WR -P /tmp -S10 fsx.bin
429  *
430  * The second is a failure to drop buffers beyond that.  It can be reproduced by
431  * fsx -WR -P /tmp -S18 -n fsx.bin
432  * Also reproducible in sh with:
433  * $> /path/to/libfuse/build/example/passthrough -d /tmp/mnt
434  * $> cd /tmp/mnt/tmp
435  * $> dd if=/dev/random of=randfile bs=1k count=192
436  * $> truncate -s 1k randfile && truncate -s 192k randfile
437  * $> xxd randfile | less # xxd will wrongly show random data at offset 0x8000
438  */
439 TEST_F(Setattr, truncate_discards_cached_data) {
440         const char FULLPATH[] = "mountpoint/some_file.txt";
441         const char RELPATH[] = "some_file.txt";
442         void *w0buf, *r0buf, *r1buf, *expected;
443         off_t w0_offset = 0;
444         size_t w0_size = 0x30000;
445         off_t r0_offset = 0;
446         off_t r0_size = w0_size;
447         size_t trunc0_size = 0x400;
448         size_t trunc1_size = w0_size;
449         off_t r1_offset = trunc0_size;
450         off_t r1_size = w0_size - trunc0_size;
451         size_t cur_size = 0;
452         const uint64_t ino = 42;
453         mode_t mode = S_IFREG | 0644;
454         int fd, r;
455         bool should_have_data = false;
456
457         w0buf = malloc(w0_size);
458         ASSERT_NE(NULL, w0buf) << strerror(errno);
459         memset(w0buf, 'X', w0_size);
460
461         r0buf = malloc(r0_size);
462         ASSERT_NE(NULL, r0buf) << strerror(errno);
463         r1buf = malloc(r1_size);
464         ASSERT_NE(NULL, r1buf) << strerror(errno);
465
466         expected = malloc(r1_size);
467         ASSERT_NE(NULL, expected) << strerror(errno);
468         memset(expected, 0, r1_size);
469
470         expect_lookup(RELPATH, ino, mode, 0, 1);
471         expect_open(ino, O_RDWR, 1);
472         EXPECT_CALL(*m_mock, process(
473                 ResultOf([=](auto in) {
474                         return (in->header.opcode == FUSE_GETATTR &&
475                                 in->header.nodeid == ino);
476                 }, Eq(true)),
477                 _)
478         ).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto out) {
479                 SET_OUT_HEADER_LEN(out, attr);
480                 out->body.attr.attr.ino = ino;
481                 out->body.attr.attr.mode = mode;
482                 out->body.attr.attr.size = cur_size;
483         })));
484         /* 
485          * The exact pattern of FUSE_WRITE operations depends on the setting of
486          * vfs.fusefs.data_cache_mode.  But it's not important for this test.
487          * Just set the mocks to accept anything
488          */
489         EXPECT_CALL(*m_mock, process(
490                 ResultOf([=](auto in) {
491                         return (in->header.opcode == FUSE_WRITE);
492                 }, Eq(true)),
493                 _)
494         ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto out) {
495                 SET_OUT_HEADER_LEN(out, write);
496                 out->body.attr.attr.ino = ino;
497                 out->body.write.size = in->body.write.size;
498                 cur_size = std::max(cur_size,
499                         in->body.write.size + in->body.write.offset);
500         })));
501
502         EXPECT_CALL(*m_mock, process(
503                 ResultOf([=](auto in) {
504                         return (in->header.opcode == FUSE_SETATTR &&
505                                 in->header.nodeid == ino &&
506                                 (in->body.setattr.valid & FATTR_SIZE));
507                 }, Eq(true)),
508                 _)
509         ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto out) {
510                 auto trunc_size = in->body.setattr.size;
511                 SET_OUT_HEADER_LEN(out, attr);
512                 out->body.attr.attr.ino = ino;
513                 out->body.attr.attr.mode = mode;
514                 out->body.attr.attr.size = trunc_size;
515                 cur_size = trunc_size;
516         })));
517
518         /* exact pattern of FUSE_READ depends on vfs.fusefs.data_cache_mode */
519         EXPECT_CALL(*m_mock, process(
520                 ResultOf([=](auto in) {
521                         return (in->header.opcode == FUSE_READ);
522                 }, Eq(true)),
523                 _)
524         ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto out) {
525                 auto osize = std::min(cur_size - in->body.read.offset,
526                         (size_t)in->body.read.size);
527                 out->header.len = sizeof(struct fuse_out_header) + osize;
528                 if (should_have_data)
529                         memset(out->body.bytes, 'X', osize);
530                 else
531                         bzero(out->body.bytes, osize);
532         })));
533
534         fd = open(FULLPATH, O_RDWR, 0644);
535         ASSERT_LE(0, fd) << strerror(errno);
536
537         /* Fill the file with Xs */
538         ASSERT_EQ((ssize_t)w0_size, pwrite(fd, w0buf, w0_size, w0_offset));
539         should_have_data = true;
540         /* Fill the cache, if data_cache_mode == 1 */
541         ASSERT_EQ((ssize_t)r0_size, pread(fd, r0buf, r0_size, r0_offset));
542         /* 1st truncate should discard cached data */
543         EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
544         should_have_data = false;
545         /* 2nd truncate extends file into previously cached data */
546         EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
547         /* Read should return all zeros */
548         ASSERT_EQ((ssize_t)r1_size, pread(fd, r1buf, r1_size, r1_offset));
549
550         r = memcmp(expected, r1buf, r1_size);
551         ASSERT_EQ(0, r);
552
553         free(expected);
554         free(r1buf);
555         free(r0buf);
556         free(w0buf);
557 }
558
559 /* Change a file's timestamps */
560 TEST_F(Setattr, utimensat) {
561         const char FULLPATH[] = "mountpoint/some_file.txt";
562         const char RELPATH[] = "some_file.txt";
563         const uint64_t ino = 42;
564         const timespec oldtimes[2] = {
565                 {.tv_sec = 1, .tv_nsec = 2},
566                 {.tv_sec = 3, .tv_nsec = 4},
567         };
568         const timespec newtimes[2] = {
569                 {.tv_sec = 5, .tv_nsec = 6},
570                 {.tv_sec = 7, .tv_nsec = 8},
571         };
572
573         EXPECT_LOOKUP(1, RELPATH)
574         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
575                 SET_OUT_HEADER_LEN(out, entry);
576                 out->body.entry.attr.mode = S_IFREG | 0644;
577                 out->body.entry.nodeid = ino;
578                 out->body.entry.attr_valid = UINT64_MAX;
579                 out->body.entry.attr.atime = oldtimes[0].tv_sec;
580                 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
581                 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
582                 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
583         })));
584
585         EXPECT_CALL(*m_mock, process(
586                 ResultOf([=](auto in) {
587                         /* In protocol 7.23, ctime will be changed too */
588                         uint32_t valid = FATTR_ATIME | FATTR_MTIME;
589                         return (in->header.opcode == FUSE_SETATTR &&
590                                 in->header.nodeid == ino &&
591                                 in->body.setattr.valid == valid &&
592                                 in->body.setattr.atime == newtimes[0].tv_sec &&
593                                 in->body.setattr.atimensec ==
594                                         newtimes[0].tv_nsec &&
595                                 in->body.setattr.mtime == newtimes[1].tv_sec &&
596                                 in->body.setattr.mtimensec ==
597                                         newtimes[1].tv_nsec);
598                 }, Eq(true)),
599                 _)
600         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
601                 SET_OUT_HEADER_LEN(out, attr);
602                 out->body.attr.attr.ino = ino;  // Must match nodeid
603                 out->body.attr.attr.mode = S_IFREG | 0644;
604                 out->body.attr.attr.atime = newtimes[0].tv_sec;
605                 out->body.attr.attr.atimensec = newtimes[0].tv_nsec;
606                 out->body.attr.attr.mtime = newtimes[1].tv_sec;
607                 out->body.attr.attr.mtimensec = newtimes[1].tv_nsec;
608         })));
609         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
610                 << strerror(errno);
611 }
612
613 /* Change a file mtime but not its atime */
614 TEST_F(Setattr, utimensat_mtime_only) {
615         const char FULLPATH[] = "mountpoint/some_file.txt";
616         const char RELPATH[] = "some_file.txt";
617         const uint64_t ino = 42;
618         const timespec oldtimes[2] = {
619                 {.tv_sec = 1, .tv_nsec = 2},
620                 {.tv_sec = 3, .tv_nsec = 4},
621         };
622         const timespec newtimes[2] = {
623                 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
624                 {.tv_sec = 7, .tv_nsec = 8},
625         };
626
627         EXPECT_LOOKUP(1, RELPATH)
628         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
629                 SET_OUT_HEADER_LEN(out, entry);
630                 out->body.entry.attr.mode = S_IFREG | 0644;
631                 out->body.entry.nodeid = ino;
632                 out->body.entry.attr_valid = UINT64_MAX;
633                 out->body.entry.attr.atime = oldtimes[0].tv_sec;
634                 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
635                 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
636                 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
637         })));
638
639         EXPECT_CALL(*m_mock, process(
640                 ResultOf([=](auto in) {
641                         /* In protocol 7.23, ctime will be changed too */
642                         uint32_t valid = FATTR_MTIME;
643                         return (in->header.opcode == FUSE_SETATTR &&
644                                 in->header.nodeid == ino &&
645                                 in->body.setattr.valid == valid &&
646                                 in->body.setattr.mtime == newtimes[1].tv_sec &&
647                                 in->body.setattr.mtimensec ==
648                                         newtimes[1].tv_nsec);
649                 }, Eq(true)),
650                 _)
651         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
652                 SET_OUT_HEADER_LEN(out, attr);
653                 out->body.attr.attr.ino = ino;  // Must match nodeid
654                 out->body.attr.attr.mode = S_IFREG | 0644;
655                 out->body.attr.attr.atime = oldtimes[0].tv_sec;
656                 out->body.attr.attr.atimensec = oldtimes[0].tv_nsec;
657                 out->body.attr.attr.mtime = newtimes[1].tv_sec;
658                 out->body.attr.attr.mtimensec = newtimes[1].tv_nsec;
659         })));
660         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
661                 << strerror(errno);
662 }
663
664 /*
665  * Set a file's mtime and atime to now
666  *
667  * The design of FreeBSD's VFS does not allow fusefs to set just one of atime
668  * or mtime to UTIME_NOW; it's both or neither.
669  */
670 TEST_F(Setattr, utimensat_utime_now) {
671         const char FULLPATH[] = "mountpoint/some_file.txt";
672         const char RELPATH[] = "some_file.txt";
673         const uint64_t ino = 42;
674         const timespec oldtimes[2] = {
675                 {.tv_sec = 1, .tv_nsec = 2},
676                 {.tv_sec = 3, .tv_nsec = 4},
677         };
678         const timespec newtimes[2] = {
679                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
680                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
681         };
682         /* "now" is whatever the server says it is */
683         const timespec now[2] = {
684                 {.tv_sec = 5, .tv_nsec = 7},
685                 {.tv_sec = 6, .tv_nsec = 8},
686         };
687         struct stat sb;
688
689         EXPECT_LOOKUP(1, RELPATH)
690         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
691                 SET_OUT_HEADER_LEN(out, entry);
692                 out->body.entry.attr.mode = S_IFREG | 0644;
693                 out->body.entry.nodeid = ino;
694                 out->body.entry.attr_valid = UINT64_MAX;
695                 out->body.entry.entry_valid = UINT64_MAX;
696                 out->body.entry.attr.atime = oldtimes[0].tv_sec;
697                 out->body.entry.attr.atimensec = oldtimes[0].tv_nsec;
698                 out->body.entry.attr.mtime = oldtimes[1].tv_sec;
699                 out->body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
700         })));
701
702         EXPECT_CALL(*m_mock, process(
703                 ResultOf([=](auto in) {
704                         /* In protocol 7.23, ctime will be changed too */
705                         uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW |
706                                 FATTR_MTIME | FATTR_MTIME_NOW;
707                         return (in->header.opcode == FUSE_SETATTR &&
708                                 in->header.nodeid == ino &&
709                                 in->body.setattr.valid == valid);
710                 }, Eq(true)),
711                 _)
712         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
713                 SET_OUT_HEADER_LEN(out, attr);
714                 out->body.attr.attr.ino = ino;  // Must match nodeid
715                 out->body.attr.attr.mode = S_IFREG | 0644;
716                 out->body.attr.attr.atime = now[0].tv_sec;
717                 out->body.attr.attr.atimensec = now[0].tv_nsec;
718                 out->body.attr.attr.mtime = now[1].tv_sec;
719                 out->body.attr.attr.mtimensec = now[1].tv_nsec;
720                 out->body.attr.attr_valid = UINT64_MAX;
721         })));
722         ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
723                 << strerror(errno);
724         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
725         EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec);
726         EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec);
727         EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec);
728         EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec);
729 }
730
731 /* On a read-only mount, no attributes may be changed */
732 TEST_F(RofsSetattr, erofs)
733 {
734         const char FULLPATH[] = "mountpoint/some_file.txt";
735         const char RELPATH[] = "some_file.txt";
736         const uint64_t ino = 42;
737         const mode_t oldmode = 0755;
738         const mode_t newmode = 0644;
739
740         EXPECT_LOOKUP(1, RELPATH)
741         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
742                 SET_OUT_HEADER_LEN(out, entry);
743                 out->body.entry.attr.mode = S_IFREG | oldmode;
744                 out->body.entry.nodeid = ino;
745         })));
746
747         ASSERT_EQ(-1, chmod(FULLPATH, newmode));
748         ASSERT_EQ(EROFS, errno);
749 }
750
751 /* Change the mode of a file */
752 TEST_F(Setattr_7_8, chmod)
753 {
754         const char FULLPATH[] = "mountpoint/some_file.txt";
755         const char RELPATH[] = "some_file.txt";
756         const uint64_t ino = 42;
757         const mode_t oldmode = 0755;
758         const mode_t newmode = 0644;
759
760         EXPECT_LOOKUP(1, RELPATH)
761         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
762                 SET_OUT_HEADER_LEN(out, entry_7_8);
763                 out->body.entry.attr.mode = S_IFREG | oldmode;
764                 out->body.entry.nodeid = ino;
765         })));
766
767         EXPECT_CALL(*m_mock, process(
768                 ResultOf([](auto in) {
769                         /* In protocol 7.23, ctime will be changed too */
770                         uint32_t valid = FATTR_MODE;
771                         return (in->header.opcode == FUSE_SETATTR &&
772                                 in->header.nodeid == ino &&
773                                 in->body.setattr.valid == valid &&
774                                 in->body.setattr.mode == newmode);
775                 }, Eq(true)),
776                 _)
777         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) {
778                 SET_OUT_HEADER_LEN(out, attr_7_8);
779                 out->body.attr.attr.ino = ino;  // Must match nodeid
780                 out->body.attr.attr.mode = S_IFREG | newmode;
781         })));
782         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
783 }