]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/setattr.cc
zfs: merge openzfs/zfs@b9d98453f
[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 #include <semaphore.h>
38 }
39
40 #include "mockfs.hh"
41 #include "utils.hh"
42
43 using namespace testing;
44
45 class Setattr : public FuseTest {};
46
47 class RofsSetattr: public Setattr {
48 public:
49 virtual void SetUp() {
50         m_ro = true;
51         Setattr::SetUp();
52 }
53 };
54
55 class Setattr_7_8: public Setattr {
56 public:
57 virtual void SetUp() {
58         m_kernel_minor_version = 8;
59         Setattr::SetUp();
60 }
61 };
62
63
64 /*
65  * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
66  * should use the cached attributes, rather than query the daemon
67  */
68 TEST_F(Setattr, attr_cache)
69 {
70         const char FULLPATH[] = "mountpoint/some_file.txt";
71         const char RELPATH[] = "some_file.txt";
72         const uint64_t ino = 42;
73         struct stat sb;
74         const mode_t newmode = 0644;
75
76         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
77         .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
78                 SET_OUT_HEADER_LEN(out, entry);
79                 out.body.entry.attr.mode = S_IFREG | 0644;
80                 out.body.entry.nodeid = ino;
81                 out.body.entry.entry_valid = UINT64_MAX;
82         })));
83
84         EXPECT_CALL(*m_mock, process(
85                 ResultOf([](auto in) {
86                         return (in.header.opcode == FUSE_SETATTR &&
87                                 in.header.nodeid == ino);
88                 }, Eq(true)),
89                 _)
90         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
91                 SET_OUT_HEADER_LEN(out, attr);
92                 out.body.attr.attr.ino = ino;   // Must match nodeid
93                 out.body.attr.attr.mode = S_IFREG | newmode;
94                 out.body.attr.attr_valid = UINT64_MAX;
95         })));
96         EXPECT_CALL(*m_mock, process(
97                 ResultOf([](auto in) {
98                         return (in.header.opcode == FUSE_GETATTR);
99                 }, Eq(true)),
100                 _)
101         ).Times(0);
102
103         /* Set an attribute with SETATTR */
104         ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
105
106         /* The stat(2) should use cached attributes */
107         ASSERT_EQ(0, stat(FULLPATH, &sb));
108         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
109 }
110
111 /* Change the mode of a file */
112 TEST_F(Setattr, chmod)
113 {
114         const char FULLPATH[] = "mountpoint/some_file.txt";
115         const char RELPATH[] = "some_file.txt";
116         const uint64_t ino = 42;
117         const mode_t oldmode = 0755;
118         const mode_t newmode = 0644;
119
120         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
121         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
122                 SET_OUT_HEADER_LEN(out, entry);
123                 out.body.entry.attr.mode = S_IFREG | oldmode;
124                 out.body.entry.nodeid = ino;
125         })));
126
127         EXPECT_CALL(*m_mock, process(
128                 ResultOf([](auto in) {
129                         uint32_t valid = FATTR_MODE;
130                         return (in.header.opcode == FUSE_SETATTR &&
131                                 in.header.nodeid == ino &&
132                                 in.body.setattr.valid == valid &&
133                                 in.body.setattr.mode == newmode);
134                 }, Eq(true)),
135                 _)
136         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
137                 SET_OUT_HEADER_LEN(out, attr);
138                 out.body.attr.attr.ino = ino;   // Must match nodeid
139                 out.body.attr.attr.mode = S_IFREG | newmode;
140         })));
141         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
142 }
143
144 /* 
145  * Chmod a multiply-linked file with cached attributes.  Check that both files'
146  * attributes have changed.
147  */
148 TEST_F(Setattr, chmod_multiply_linked)
149 {
150         const char FULLPATH0[] = "mountpoint/some_file.txt";
151         const char RELPATH0[] = "some_file.txt";
152         const char FULLPATH1[] = "mountpoint/other_file.txt";
153         const char RELPATH1[] = "other_file.txt";
154         struct stat sb;
155         const uint64_t ino = 42;
156         const mode_t oldmode = 0777;
157         const mode_t newmode = 0666;
158
159         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0)
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_LOOKUP(FUSE_ROOT_ID, RELPATH1)
170         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
171                 SET_OUT_HEADER_LEN(out, entry);
172                 out.body.entry.attr.mode = S_IFREG | oldmode;
173                 out.body.entry.nodeid = ino;
174                 out.body.entry.attr.nlink = 2;
175                 out.body.entry.attr_valid = UINT64_MAX;
176                 out.body.entry.entry_valid = UINT64_MAX;
177         })));
178
179         EXPECT_CALL(*m_mock, process(
180                 ResultOf([](auto in) {
181                         uint32_t valid = FATTR_MODE;
182                         return (in.header.opcode == FUSE_SETATTR &&
183                                 in.header.nodeid == ino &&
184                                 in.body.setattr.valid == valid &&
185                                 in.body.setattr.mode == newmode);
186                 }, Eq(true)),
187                 _)
188         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
189                 SET_OUT_HEADER_LEN(out, attr);
190                 out.body.attr.attr.ino = ino;
191                 out.body.attr.attr.mode = S_IFREG | newmode;
192                 out.body.attr.attr.nlink = 2;
193                 out.body.attr.attr_valid = UINT64_MAX;
194         })));
195
196         /* For a lookup of the 2nd file to get it into the cache*/
197         ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
198         EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
199
200         ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno);
201         ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno);
202         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
203         ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
204         EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
205 }
206
207
208 /* Change the owner and group of a file */
209 TEST_F(Setattr, chown)
210 {
211         const char FULLPATH[] = "mountpoint/some_file.txt";
212         const char RELPATH[] = "some_file.txt";
213         const uint64_t ino = 42;
214         const gid_t oldgroup = 66;
215         const gid_t newgroup = 99;
216         const uid_t olduser = 33;
217         const uid_t newuser = 44;
218
219         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
220         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
221                 SET_OUT_HEADER_LEN(out, entry);
222                 out.body.entry.attr.mode = S_IFREG | 0644;
223                 out.body.entry.nodeid = ino;
224                 out.body.entry.attr.gid = oldgroup;
225                 out.body.entry.attr.uid = olduser;
226         })));
227
228         EXPECT_CALL(*m_mock, process(
229                 ResultOf([](auto in) {
230                         uint32_t valid = FATTR_GID | FATTR_UID;
231                         return (in.header.opcode == FUSE_SETATTR &&
232                                 in.header.nodeid == ino &&
233                                 in.body.setattr.valid == valid &&
234                                 in.body.setattr.uid == newuser &&
235                                 in.body.setattr.gid == newgroup);
236                 }, Eq(true)),
237                 _)
238         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
239                 SET_OUT_HEADER_LEN(out, attr);
240                 out.body.attr.attr.ino = ino;   // Must match nodeid
241                 out.body.attr.attr.mode = S_IFREG | 0644;
242                 out.body.attr.attr.uid = newuser;
243                 out.body.attr.attr.gid = newgroup;
244         })));
245         EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
246 }
247
248
249
250 /* 
251  * FUSE daemons are allowed to check permissions however they like.  If the
252  * daemon returns EPERM, even if the file permissions "should" grant access,
253  * then fuse(4) should return EPERM too.
254  */
255 TEST_F(Setattr, eperm)
256 {
257         const char FULLPATH[] = "mountpoint/some_file.txt";
258         const char RELPATH[] = "some_file.txt";
259         const uint64_t ino = 42;
260
261         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
262         .WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
263                 SET_OUT_HEADER_LEN(out, entry);
264                 out.body.entry.attr.mode = S_IFREG | 0777;
265                 out.body.entry.nodeid = ino;
266                 out.body.entry.attr.uid = in.header.uid;
267                 out.body.entry.attr.gid = in.header.gid;
268         })));
269
270         EXPECT_CALL(*m_mock, process(
271                 ResultOf([](auto in) {
272                         return (in.header.opcode == FUSE_SETATTR &&
273                                 in.header.nodeid == ino);
274                 }, Eq(true)),
275                 _)
276         ).WillOnce(Invoke(ReturnErrno(EPERM)));
277         EXPECT_NE(0, truncate(FULLPATH, 10));
278         EXPECT_EQ(EPERM, errno);
279 }
280
281 /* Change the mode of an open file, by its file descriptor */
282 TEST_F(Setattr, fchmod)
283 {
284         const char FULLPATH[] = "mountpoint/some_file.txt";
285         const char RELPATH[] = "some_file.txt";
286         uint64_t ino = 42;
287         int fd;
288         const mode_t oldmode = 0755;
289         const mode_t newmode = 0644;
290
291         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
292         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
293                 SET_OUT_HEADER_LEN(out, entry);
294                 out.body.entry.attr.mode = S_IFREG | oldmode;
295                 out.body.entry.nodeid = ino;
296                 out.body.entry.attr_valid = UINT64_MAX;
297         })));
298
299         EXPECT_CALL(*m_mock, process(
300                 ResultOf([=](auto in) {
301                         return (in.header.opcode == FUSE_OPEN &&
302                                 in.header.nodeid == ino);
303                 }, Eq(true)),
304                 _)
305         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
306                 out.header.len = sizeof(out.header);
307                 SET_OUT_HEADER_LEN(out, open);
308         })));
309
310         EXPECT_CALL(*m_mock, process(
311                 ResultOf([=](auto in) {
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         leak(fd);
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(FUSE_ROOT_ID, 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                         uint32_t valid = FATTR_SIZE | FATTR_FH;
366                         return (in.header.opcode == FUSE_SETATTR &&
367                                 in.header.nodeid == ino &&
368                                 in.body.setattr.valid == valid &&
369                                 in.body.setattr.fh == fh);
370                 }, Eq(true)),
371                 _)
372         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
373                 SET_OUT_HEADER_LEN(out, attr);
374                 out.body.attr.attr.ino = ino;   // Must match nodeid
375                 out.body.attr.attr.mode = S_IFREG | 0755;
376                 out.body.attr.attr.size = newsize;
377         })));
378
379         fd = open(FULLPATH, O_RDWR);
380         ASSERT_LE(0, fd) << strerror(errno);
381         ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
382         leak(fd);
383 }
384
385 /* Change the size of the file */
386 TEST_F(Setattr, truncate) {
387         const char FULLPATH[] = "mountpoint/some_file.txt";
388         const char RELPATH[] = "some_file.txt";
389         const uint64_t ino = 42;
390         const uint64_t oldsize = 100'000'000;
391         const uint64_t newsize = 20'000'000;
392
393         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
394         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
395                 SET_OUT_HEADER_LEN(out, entry);
396                 out.body.entry.attr.mode = S_IFREG | 0644;
397                 out.body.entry.nodeid = ino;
398                 out.body.entry.attr.size = oldsize;
399         })));
400
401         EXPECT_CALL(*m_mock, process(
402                 ResultOf([](auto in) {
403                         uint32_t valid = FATTR_SIZE;
404                         return (in.header.opcode == FUSE_SETATTR &&
405                                 in.header.nodeid == ino &&
406                                 in.body.setattr.valid == valid &&
407                                 in.body.setattr.size == newsize);
408                 }, Eq(true)),
409                 _)
410         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
411                 SET_OUT_HEADER_LEN(out, attr);
412                 out.body.attr.attr.ino = ino;   // Must match nodeid
413                 out.body.attr.attr.mode = S_IFREG | 0644;
414                 out.body.attr.attr.size = newsize;
415         })));
416         EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
417 }
418
419 /*
420  * Truncating a file should discard cached data past the truncation point.
421  * This is a regression test for bug 233783.
422  *
423  * There are two distinct failure modes.  The first one is a failure to zero
424  * the portion of the file's final buffer past EOF.  It can be reproduced by
425  * fsx -WR -P /tmp -S10 fsx.bin
426  *
427  * The second is a failure to drop buffers beyond that.  It can be reproduced by
428  * fsx -WR -P /tmp -S18 -n fsx.bin
429  * Also reproducible in sh with:
430  * $> /path/to/libfuse/build/example/passthrough -d /tmp/mnt
431  * $> cd /tmp/mnt/tmp
432  * $> dd if=/dev/random of=randfile bs=1k count=192
433  * $> truncate -s 1k randfile && truncate -s 192k randfile
434  * $> xxd randfile | less # xxd will wrongly show random data at offset 0x8000
435  */
436 TEST_F(Setattr, truncate_discards_cached_data) {
437         const char FULLPATH[] = "mountpoint/some_file.txt";
438         const char RELPATH[] = "some_file.txt";
439         void *w0buf, *r0buf, *r1buf, *expected;
440         off_t w0_offset = 0;
441         size_t w0_size = 0x30000;
442         off_t r0_offset = 0;
443         off_t r0_size = w0_size;
444         size_t trunc0_size = 0x400;
445         size_t trunc1_size = w0_size;
446         off_t r1_offset = trunc0_size;
447         off_t r1_size = w0_size - trunc0_size;
448         size_t cur_size = 0;
449         const uint64_t ino = 42;
450         mode_t mode = S_IFREG | 0644;
451         int fd, r;
452         bool should_have_data = false;
453
454         w0buf = malloc(w0_size);
455         ASSERT_NE(nullptr, w0buf) << strerror(errno);
456         memset(w0buf, 'X', w0_size);
457
458         r0buf = malloc(r0_size);
459         ASSERT_NE(nullptr, r0buf) << strerror(errno);
460         r1buf = malloc(r1_size);
461         ASSERT_NE(nullptr, r1buf) << strerror(errno);
462
463         expected = malloc(r1_size);
464         ASSERT_NE(nullptr, expected) << strerror(errno);
465         memset(expected, 0, r1_size);
466
467         expect_lookup(RELPATH, ino, mode, 0, 1);
468         expect_open(ino, O_RDWR, 1);
469         EXPECT_CALL(*m_mock, process(
470                 ResultOf([=](auto in) {
471                         return (in.header.opcode == FUSE_GETATTR &&
472                                 in.header.nodeid == ino);
473                 }, Eq(true)),
474                 _)
475         ).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto& out) {
476                 SET_OUT_HEADER_LEN(out, attr);
477                 out.body.attr.attr.ino = ino;
478                 out.body.attr.attr.mode = mode;
479                 out.body.attr.attr.size = cur_size;
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(static_cast<uint64_t>(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         EXPECT_CALL(*m_mock, process(
511                 ResultOf([=](auto in) {
512                         return (in.header.opcode == FUSE_READ);
513                 }, Eq(true)),
514                 _)
515         ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
516                 auto osize = std::min(
517                         static_cast<uint64_t>(cur_size) - in.body.read.offset,
518                         static_cast<uint64_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(static_cast<ssize_t>(w0_size),
531                 pwrite(fd, w0buf, w0_size, w0_offset));
532         should_have_data = true;
533         /* Fill the cache */
534         ASSERT_EQ(static_cast<ssize_t>(r0_size),
535                 pread(fd, r0buf, r0_size, r0_offset));
536         /* 1st truncate should discard cached data */
537         EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
538         should_have_data = false;
539         /* 2nd truncate extends file into previously cached data */
540         EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
541         /* Read should return all zeros */
542         ASSERT_EQ(static_cast<ssize_t>(r1_size),
543                 pread(fd, r1buf, r1_size, r1_offset));
544
545         r = memcmp(expected, r1buf, r1_size);
546         ASSERT_EQ(0, r);
547
548         free(expected);
549         free(r1buf);
550         free(r0buf);
551         free(w0buf);
552
553         leak(fd);
554 }
555
556 /* Change a file's timestamps */
557 TEST_F(Setattr, utimensat) {
558         const char FULLPATH[] = "mountpoint/some_file.txt";
559         const char RELPATH[] = "some_file.txt";
560         const uint64_t ino = 42;
561         const timespec oldtimes[2] = {
562                 {.tv_sec = 1, .tv_nsec = 2},
563                 {.tv_sec = 3, .tv_nsec = 4},
564         };
565         const timespec newtimes[2] = {
566                 {.tv_sec = 5, .tv_nsec = 6},
567                 {.tv_sec = 7, .tv_nsec = 8},
568         };
569
570         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
571         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
572                 SET_OUT_HEADER_LEN(out, entry);
573                 out.body.entry.attr.mode = S_IFREG | 0644;
574                 out.body.entry.nodeid = ino;
575                 out.body.entry.attr_valid = UINT64_MAX;
576                 out.body.entry.attr.atime = oldtimes[0].tv_sec;
577                 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
578                 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
579                 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
580         })));
581
582         EXPECT_CALL(*m_mock, process(
583                 ResultOf([=](auto in) {
584                         uint32_t valid = FATTR_ATIME | FATTR_MTIME;
585                         return (in.header.opcode == FUSE_SETATTR &&
586                                 in.header.nodeid == ino &&
587                                 in.body.setattr.valid == valid &&
588                                 (time_t)in.body.setattr.atime ==
589                                         newtimes[0].tv_sec &&
590                                 (long)in.body.setattr.atimensec ==
591                                         newtimes[0].tv_nsec &&
592                                 (time_t)in.body.setattr.mtime ==
593                                         newtimes[1].tv_sec &&
594                                 (long)in.body.setattr.mtimensec ==
595                                         newtimes[1].tv_nsec);
596                 }, Eq(true)),
597                 _)
598         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
599                 SET_OUT_HEADER_LEN(out, attr);
600                 out.body.attr.attr.ino = ino;   // Must match nodeid
601                 out.body.attr.attr.mode = S_IFREG | 0644;
602                 out.body.attr.attr.atime = newtimes[0].tv_sec;
603                 out.body.attr.attr.atimensec = newtimes[0].tv_nsec;
604                 out.body.attr.attr.mtime = newtimes[1].tv_sec;
605                 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
606         })));
607         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
608                 << strerror(errno);
609 }
610
611 /* Change a file mtime but not its atime */
612 TEST_F(Setattr, utimensat_mtime_only) {
613         const char FULLPATH[] = "mountpoint/some_file.txt";
614         const char RELPATH[] = "some_file.txt";
615         const uint64_t ino = 42;
616         const timespec oldtimes[2] = {
617                 {.tv_sec = 1, .tv_nsec = 2},
618                 {.tv_sec = 3, .tv_nsec = 4},
619         };
620         const timespec newtimes[2] = {
621                 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
622                 {.tv_sec = 7, .tv_nsec = 8},
623         };
624
625         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
626         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
627                 SET_OUT_HEADER_LEN(out, entry);
628                 out.body.entry.attr.mode = S_IFREG | 0644;
629                 out.body.entry.nodeid = ino;
630                 out.body.entry.attr_valid = UINT64_MAX;
631                 out.body.entry.attr.atime = oldtimes[0].tv_sec;
632                 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
633                 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
634                 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
635         })));
636
637         EXPECT_CALL(*m_mock, process(
638                 ResultOf([=](auto in) {
639                         uint32_t valid = FATTR_MTIME;
640                         return (in.header.opcode == FUSE_SETATTR &&
641                                 in.header.nodeid == ino &&
642                                 in.body.setattr.valid == valid &&
643                                 (time_t)in.body.setattr.mtime ==
644                                         newtimes[1].tv_sec &&
645                                 (long)in.body.setattr.mtimensec ==
646                                         newtimes[1].tv_nsec);
647                 }, Eq(true)),
648                 _)
649         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
650                 SET_OUT_HEADER_LEN(out, attr);
651                 out.body.attr.attr.ino = ino;   // Must match nodeid
652                 out.body.attr.attr.mode = S_IFREG | 0644;
653                 out.body.attr.attr.atime = oldtimes[0].tv_sec;
654                 out.body.attr.attr.atimensec = oldtimes[0].tv_nsec;
655                 out.body.attr.attr.mtime = newtimes[1].tv_sec;
656                 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
657         })));
658         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
659                 << strerror(errno);
660 }
661
662 /*
663  * Set a file's mtime and atime to now
664  *
665  * The design of FreeBSD's VFS does not allow fusefs to set just one of atime
666  * or mtime to UTIME_NOW; it's both or neither.
667  */
668 TEST_F(Setattr, utimensat_utime_now) {
669         const char FULLPATH[] = "mountpoint/some_file.txt";
670         const char RELPATH[] = "some_file.txt";
671         const uint64_t ino = 42;
672         const timespec oldtimes[2] = {
673                 {.tv_sec = 1, .tv_nsec = 2},
674                 {.tv_sec = 3, .tv_nsec = 4},
675         };
676         const timespec newtimes[2] = {
677                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
678                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
679         };
680         /* "now" is whatever the server says it is */
681         const timespec now[2] = {
682                 {.tv_sec = 5, .tv_nsec = 7},
683                 {.tv_sec = 6, .tv_nsec = 8},
684         };
685         struct stat sb;
686
687         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
688         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
689                 SET_OUT_HEADER_LEN(out, entry);
690                 out.body.entry.attr.mode = S_IFREG | 0644;
691                 out.body.entry.nodeid = ino;
692                 out.body.entry.attr_valid = UINT64_MAX;
693                 out.body.entry.entry_valid = UINT64_MAX;
694                 out.body.entry.attr.atime = oldtimes[0].tv_sec;
695                 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
696                 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
697                 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
698         })));
699
700         EXPECT_CALL(*m_mock, process(
701                 ResultOf([=](auto in) {
702                         uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW |
703                                 FATTR_MTIME | FATTR_MTIME_NOW;
704                         return (in.header.opcode == FUSE_SETATTR &&
705                                 in.header.nodeid == ino &&
706                                 in.body.setattr.valid == valid);
707                 }, Eq(true)),
708                 _)
709         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
710                 SET_OUT_HEADER_LEN(out, attr);
711                 out.body.attr.attr.ino = ino;   // Must match nodeid
712                 out.body.attr.attr.mode = S_IFREG | 0644;
713                 out.body.attr.attr.atime = now[0].tv_sec;
714                 out.body.attr.attr.atimensec = now[0].tv_nsec;
715                 out.body.attr.attr.mtime = now[1].tv_sec;
716                 out.body.attr.attr.mtimensec = now[1].tv_nsec;
717                 out.body.attr.attr_valid = UINT64_MAX;
718         })));
719         ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
720                 << strerror(errno);
721         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
722         EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec);
723         EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec);
724         EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec);
725         EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec);
726 }
727
728 /*
729  * FUSE_SETATTR returns a different file type, even though the entry cache
730  * hasn't expired.  This is a server bug!  It probably means that the server
731  * removed the file and recreated it with the same inode but a different vtyp.
732  * The best thing fusefs can do is return ENOENT to the caller.  After all, the
733  * entry must not have existed recently.
734  */
735 TEST_F(Setattr, vtyp_conflict)
736 {
737         const char FULLPATH[] = "mountpoint/some_file.txt";
738         const char RELPATH[] = "some_file.txt";
739         const uint64_t ino = 42;
740         uid_t newuser = 12345;
741         sem_t sem;
742
743         ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
744
745         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
746         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
747                 SET_OUT_HEADER_LEN(out, entry);
748                 out.body.entry.attr.mode = S_IFREG | 0777;
749                 out.body.entry.nodeid = ino;
750                 out.body.entry.entry_valid = UINT64_MAX;
751         })));
752
753         EXPECT_CALL(*m_mock, process(
754                 ResultOf([](auto in) {
755                         return (in.header.opcode == FUSE_SETATTR &&
756                                 in.header.nodeid == ino);
757                 }, Eq(true)),
758                 _)
759         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
760                 SET_OUT_HEADER_LEN(out, attr);
761                 out.body.attr.attr.ino = ino;
762                 out.body.attr.attr.mode = S_IFDIR | 0777;       // Changed!
763                 out.body.attr.attr.uid = newuser;
764         })));
765         // We should reclaim stale vnodes
766         expect_forget(ino, 1, &sem);
767
768         EXPECT_NE(0, chown(FULLPATH, newuser, -1));
769         EXPECT_EQ(ENOENT, errno);
770
771         sem_wait(&sem);
772         sem_destroy(&sem);
773 }
774
775 /* On a read-only mount, no attributes may be changed */
776 TEST_F(RofsSetattr, erofs)
777 {
778         const char FULLPATH[] = "mountpoint/some_file.txt";
779         const char RELPATH[] = "some_file.txt";
780         const uint64_t ino = 42;
781         const mode_t oldmode = 0755;
782         const mode_t newmode = 0644;
783
784         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
785         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
786                 SET_OUT_HEADER_LEN(out, entry);
787                 out.body.entry.attr.mode = S_IFREG | oldmode;
788                 out.body.entry.nodeid = ino;
789         })));
790
791         ASSERT_EQ(-1, chmod(FULLPATH, newmode));
792         ASSERT_EQ(EROFS, errno);
793 }
794
795 /* Change the mode of a file */
796 TEST_F(Setattr_7_8, chmod)
797 {
798         const char FULLPATH[] = "mountpoint/some_file.txt";
799         const char RELPATH[] = "some_file.txt";
800         const uint64_t ino = 42;
801         const mode_t oldmode = 0755;
802         const mode_t newmode = 0644;
803
804         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
805         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
806                 SET_OUT_HEADER_LEN(out, entry_7_8);
807                 out.body.entry.attr.mode = S_IFREG | oldmode;
808                 out.body.entry.nodeid = ino;
809         })));
810
811         EXPECT_CALL(*m_mock, process(
812                 ResultOf([](auto in) {
813                         uint32_t valid = FATTR_MODE;
814                         return (in.header.opcode == FUSE_SETATTR &&
815                                 in.header.nodeid == ino &&
816                                 in.body.setattr.valid == valid &&
817                                 in.body.setattr.mode == newmode);
818                 }, Eq(true)),
819                 _)
820         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
821                 SET_OUT_HEADER_LEN(out, attr_7_8);
822                 out.body.attr.attr.ino = ino;   // Must match nodeid
823                 out.body.attr.attr.mode = S_IFREG | newmode;
824         })));
825         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
826 }