]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/setattr.cc
fusefs: updated cached attributes during VOP_LINK.
[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
53 /*
54  * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
55  * should use the cached attributes, rather than query the daemon
56  */
57 TEST_F(Setattr, attr_cache)
58 {
59         const char FULLPATH[] = "mountpoint/some_file.txt";
60         const char RELPATH[] = "some_file.txt";
61         const uint64_t ino = 42;
62         struct stat sb;
63         const mode_t newmode = 0644;
64
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;
71         })));
72
73         EXPECT_CALL(*m_mock, process(
74                 ResultOf([](auto in) {
75                         return (in->header.opcode == FUSE_SETATTR &&
76                                 in->header.nodeid == ino);
77                 }, Eq(true)),
78                 _)
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;
84         })));
85         EXPECT_CALL(*m_mock, process(
86                 ResultOf([](auto in) {
87                         return (in->header.opcode == FUSE_GETATTR);
88                 }, Eq(true)),
89                 _)
90         ).Times(0);
91
92         /* Set an attribute with SETATTR */
93         ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
94
95         /* The stat(2) should use cached attributes */
96         ASSERT_EQ(0, stat(FULLPATH, &sb));
97         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
98 }
99
100 /* Change the mode of a file */
101 TEST_F(Setattr, chmod)
102 {
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;
108
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;
114         })));
115
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);
124                 }, Eq(true)),
125                 _)
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;
130         })));
131         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
132 }
133
134 /* 
135  * Chmod a multiply-linked file with cached attributes.  Check that both files'
136  * attributes have changed.
137  */
138 TEST_F(Setattr, chmod_multiply_linked)
139 {
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";
144         struct stat sb;
145         const uint64_t ino = 42;
146         const mode_t oldmode = 0777;
147         const mode_t newmode = 0666;
148
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;
157         })));
158
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;
167         })));
168
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);
176                 }, Eq(true)),
177                 _)
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;
184         })));
185
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);
189
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);
195 }
196
197
198 /* Change the owner and group of a file */
199 TEST_F(Setattr, chown)
200 {
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;
208
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;
216         })));
217
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);
227                 }, Eq(true)),
228                 _)
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;
235         })));
236         EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
237 }
238
239
240
241 /* 
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.
245  */
246 TEST_F(Setattr, eperm)
247 {
248         const char FULLPATH[] = "mountpoint/some_file.txt";
249         const char RELPATH[] = "some_file.txt";
250         const uint64_t ino = 42;
251
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;
259         })));
260
261         EXPECT_CALL(*m_mock, process(
262                 ResultOf([](auto in) {
263                         return (in->header.opcode == FUSE_SETATTR &&
264                                 in->header.nodeid == ino);
265                 }, Eq(true)),
266                 _)
267         ).WillOnce(Invoke(ReturnErrno(EPERM)));
268         EXPECT_NE(0, truncate(FULLPATH, 10));
269         EXPECT_EQ(EPERM, errno);
270 }
271
272 /* Change the mode of an open file, by its file descriptor */
273 TEST_F(Setattr, fchmod)
274 {
275         const char FULLPATH[] = "mountpoint/some_file.txt";
276         const char RELPATH[] = "some_file.txt";
277         uint64_t ino = 42;
278         int fd;
279         const mode_t oldmode = 0755;
280         const mode_t newmode = 0644;
281
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;
288         })));
289
290         EXPECT_CALL(*m_mock, process(
291                 ResultOf([=](auto in) {
292                         return (in->header.opcode == FUSE_OPEN &&
293                                 in->header.nodeid == ino);
294                 }, Eq(true)),
295                 _)
296         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
297                 out->header.len = sizeof(out->header);
298                 SET_OUT_HEADER_LEN(out, open);
299         })));
300
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);
309                 }, Eq(true)),
310                 _)
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;
315         })));
316
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 */
321 }
322
323 /* Change the size of an open file, by its file descriptor */
324 TEST_F(Setattr, ftruncate)
325 {
326         const char FULLPATH[] = "mountpoint/some_file.txt";
327         const char RELPATH[] = "some_file.txt";
328         uint64_t ino = 42;
329         int fd;
330         uint64_t fh = 0xdeadbeef1a7ebabe;
331         const off_t oldsize = 99;
332         const off_t newsize = 12345;
333
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;
341         })));
342
343         EXPECT_CALL(*m_mock, process(
344                 ResultOf([=](auto in) {
345                         return (in->header.opcode == FUSE_OPEN &&
346                                 in->header.nodeid == ino);
347                 }, Eq(true)),
348                 _)
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;
353         })));
354
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);
363                 }, Eq(true)),
364                 _)
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;
370         })));
371
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 */
376 }
377
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;
385
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;
392         })));
393
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);
402                 }, Eq(true)),
403                 _)
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;
409         })));
410         EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
411 }
412
413 /*
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.
417  *
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
421  *
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
426  * $> cd /tmp/mnt/tmp
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
430  */
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;
435         off_t w0_offset = 0;
436         size_t w0_size = 0x30000;
437         off_t r0_offset = 0;
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;
443         size_t cur_size = 0;
444         const uint64_t ino = 42;
445         mode_t mode = S_IFREG | 0644;
446         int fd, r;
447         bool should_have_data = false;
448
449         w0buf = malloc(w0_size);
450         ASSERT_NE(NULL, w0buf) << strerror(errno);
451         memset(w0buf, 'X', w0_size);
452
453         r0buf = malloc(r0_size);
454         ASSERT_NE(NULL, r0buf) << strerror(errno);
455         r1buf = malloc(r1_size);
456         ASSERT_NE(NULL, r1buf) << strerror(errno);
457
458         expected = malloc(r1_size);
459         ASSERT_NE(NULL, expected) << strerror(errno);
460         memset(expected, 0, r1_size);
461
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);
468                 }, Eq(true)),
469                 _)
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;
475         })));
476         /* 
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
480          */
481         EXPECT_CALL(*m_mock, process(
482                 ResultOf([=](auto in) {
483                         return (in->header.opcode == FUSE_WRITE);
484                 }, Eq(true)),
485                 _)
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);
492         })));
493
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));
499                 }, Eq(true)),
500                 _)
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;
508         })));
509
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);
514                 }, Eq(true)),
515                 _)
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);
522                 else
523                         bzero(out->body.bytes, osize);
524         })));
525
526         fd = open(FULLPATH, O_RDWR, 0644);
527         ASSERT_LE(0, fd) << strerror(errno);
528
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));
541
542         r = memcmp(expected, r1buf, r1_size);
543         ASSERT_EQ(0, r);
544
545         free(expected);
546         free(r1buf);
547         free(r0buf);
548         free(w0buf);
549 }
550
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},
559         };
560         const timespec newtimes[2] = {
561                 {.tv_sec = 5, .tv_nsec = 6},
562                 {.tv_sec = 7, .tv_nsec = 8},
563         };
564
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;
575         })));
576
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);
590                 }, Eq(true)),
591                 _)
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;
600         })));
601         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
602                 << strerror(errno);
603 }
604
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},
613         };
614         const timespec newtimes[2] = {
615                 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
616                 {.tv_sec = 7, .tv_nsec = 8},
617         };
618
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;
629         })));
630
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);
641                 }, Eq(true)),
642                 _)
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;
651         })));
652         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
653                 << strerror(errno);
654 }
655
656 /* Set a file's mtime and atime to now */
657 /* TODO: enable this test after updating protocol to version 7.9 */
658 #if 0
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},
666         };
667         const timespec newtimes[2] = {
668                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
669                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
670         };
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},
675         };
676         struct stat sb;
677
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;
688         })));
689
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);
698                 }, Eq(true)),
699                 _)
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;
709         })));
710         ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
711                 << strerror(errno);
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);
717 }
718 #endif
719
720 /* On a read-only mount, no attributes may be changed */
721 TEST_F(RofsSetattr, erofs)
722 {
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;
728
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;
734         })));
735
736         ASSERT_EQ(-1, chmod(FULLPATH, newmode));
737         ASSERT_EQ(EROFS, errno);
738 }