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