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