]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/setattr.cc
ssh: Update to OpenSSH 9.3p1
[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                 assert(osize <= sizeof(out.body.bytes));
534                 out.header.len = sizeof(struct fuse_out_header) + osize;
535                 if (should_have_data)
536                         memset(out.body.bytes, 'X', osize);
537                 else
538                         bzero(out.body.bytes, osize);
539         })));
540
541         fd = open(FULLPATH, O_RDWR, 0644);
542         ASSERT_LE(0, fd) << strerror(errno);
543
544         /* Fill the file with Xs */
545         ASSERT_EQ(static_cast<ssize_t>(w0_size),
546                 pwrite(fd, w0buf, w0_size, w0_offset));
547         should_have_data = true;
548         /* Fill the cache */
549         ASSERT_EQ(static_cast<ssize_t>(r0_size),
550                 pread(fd, r0buf, r0_size, r0_offset));
551         /* 1st truncate should discard cached data */
552         EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
553         should_have_data = false;
554         /* 2nd truncate extends file into previously cached data */
555         EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
556         /* Read should return all zeros */
557         ASSERT_EQ(static_cast<ssize_t>(r1_size),
558                 pread(fd, r1buf, r1_size, r1_offset));
559
560         r = memcmp(expected, r1buf, r1_size);
561         ASSERT_EQ(0, r);
562
563         free(expected);
564         free(r1buf);
565         free(r0buf);
566         free(w0buf);
567
568         leak(fd);
569 }
570
571 /* truncate should fail if it would cause the file to exceed RLIMIT_FSIZE */
572 TEST_F(Setattr, truncate_rlimit_rsize)
573 {
574         const char FULLPATH[] = "mountpoint/some_file.txt";
575         const char RELPATH[] = "some_file.txt";
576         struct rlimit rl;
577         const uint64_t ino = 42;
578         const uint64_t oldsize = 0;
579         const uint64_t newsize = 100'000'000;
580
581         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
582         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
583                 SET_OUT_HEADER_LEN(out, entry);
584                 out.body.entry.attr.mode = S_IFREG | 0644;
585                 out.body.entry.nodeid = ino;
586                 out.body.entry.attr.size = oldsize;
587         })));
588
589         rl.rlim_cur = newsize / 2;
590         rl.rlim_max = 10 * newsize;
591         ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
592         ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
593
594         EXPECT_EQ(-1, truncate(FULLPATH, newsize));
595         EXPECT_EQ(EFBIG, errno);
596         EXPECT_EQ(1, s_sigxfsz);
597 }
598
599 /* Change a file's timestamps */
600 TEST_F(Setattr, utimensat) {
601         const char FULLPATH[] = "mountpoint/some_file.txt";
602         const char RELPATH[] = "some_file.txt";
603         const uint64_t ino = 42;
604         const timespec oldtimes[2] = {
605                 {.tv_sec = 1, .tv_nsec = 2},
606                 {.tv_sec = 3, .tv_nsec = 4},
607         };
608         const timespec newtimes[2] = {
609                 {.tv_sec = 5, .tv_nsec = 6},
610                 {.tv_sec = 7, .tv_nsec = 8},
611         };
612
613         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
614         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
615                 SET_OUT_HEADER_LEN(out, entry);
616                 out.body.entry.attr.mode = S_IFREG | 0644;
617                 out.body.entry.nodeid = ino;
618                 out.body.entry.attr_valid = UINT64_MAX;
619                 out.body.entry.attr.atime = oldtimes[0].tv_sec;
620                 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
621                 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
622                 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
623         })));
624
625         EXPECT_CALL(*m_mock, process(
626                 ResultOf([=](auto in) {
627                         uint32_t valid = FATTR_ATIME | FATTR_MTIME;
628                         return (in.header.opcode == FUSE_SETATTR &&
629                                 in.header.nodeid == ino &&
630                                 in.body.setattr.valid == valid &&
631                                 (time_t)in.body.setattr.atime ==
632                                         newtimes[0].tv_sec &&
633                                 (long)in.body.setattr.atimensec ==
634                                         newtimes[0].tv_nsec &&
635                                 (time_t)in.body.setattr.mtime ==
636                                         newtimes[1].tv_sec &&
637                                 (long)in.body.setattr.mtimensec ==
638                                         newtimes[1].tv_nsec);
639                 }, Eq(true)),
640                 _)
641         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
642                 SET_OUT_HEADER_LEN(out, attr);
643                 out.body.attr.attr.ino = ino;   // Must match nodeid
644                 out.body.attr.attr.mode = S_IFREG | 0644;
645                 out.body.attr.attr.atime = newtimes[0].tv_sec;
646                 out.body.attr.attr.atimensec = newtimes[0].tv_nsec;
647                 out.body.attr.attr.mtime = newtimes[1].tv_sec;
648                 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
649         })));
650         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
651                 << strerror(errno);
652 }
653
654 /* Change a file mtime but not its atime */
655 TEST_F(Setattr, utimensat_mtime_only) {
656         const char FULLPATH[] = "mountpoint/some_file.txt";
657         const char RELPATH[] = "some_file.txt";
658         const uint64_t ino = 42;
659         const timespec oldtimes[2] = {
660                 {.tv_sec = 1, .tv_nsec = 2},
661                 {.tv_sec = 3, .tv_nsec = 4},
662         };
663         const timespec newtimes[2] = {
664                 {.tv_sec = 5, .tv_nsec = UTIME_OMIT},
665                 {.tv_sec = 7, .tv_nsec = 8},
666         };
667
668         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
669         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
670                 SET_OUT_HEADER_LEN(out, entry);
671                 out.body.entry.attr.mode = S_IFREG | 0644;
672                 out.body.entry.nodeid = ino;
673                 out.body.entry.attr_valid = UINT64_MAX;
674                 out.body.entry.attr.atime = oldtimes[0].tv_sec;
675                 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
676                 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
677                 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
678         })));
679
680         EXPECT_CALL(*m_mock, process(
681                 ResultOf([=](auto in) {
682                         uint32_t valid = FATTR_MTIME;
683                         return (in.header.opcode == FUSE_SETATTR &&
684                                 in.header.nodeid == ino &&
685                                 in.body.setattr.valid == valid &&
686                                 (time_t)in.body.setattr.mtime ==
687                                         newtimes[1].tv_sec &&
688                                 (long)in.body.setattr.mtimensec ==
689                                         newtimes[1].tv_nsec);
690                 }, Eq(true)),
691                 _)
692         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
693                 SET_OUT_HEADER_LEN(out, attr);
694                 out.body.attr.attr.ino = ino;   // Must match nodeid
695                 out.body.attr.attr.mode = S_IFREG | 0644;
696                 out.body.attr.attr.atime = oldtimes[0].tv_sec;
697                 out.body.attr.attr.atimensec = oldtimes[0].tv_nsec;
698                 out.body.attr.attr.mtime = newtimes[1].tv_sec;
699                 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
700         })));
701         EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
702                 << strerror(errno);
703 }
704
705 /*
706  * Set a file's mtime and atime to now
707  *
708  * The design of FreeBSD's VFS does not allow fusefs to set just one of atime
709  * or mtime to UTIME_NOW; it's both or neither.
710  */
711 TEST_F(Setattr, utimensat_utime_now) {
712         const char FULLPATH[] = "mountpoint/some_file.txt";
713         const char RELPATH[] = "some_file.txt";
714         const uint64_t ino = 42;
715         const timespec oldtimes[2] = {
716                 {.tv_sec = 1, .tv_nsec = 2},
717                 {.tv_sec = 3, .tv_nsec = 4},
718         };
719         const timespec newtimes[2] = {
720                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
721                 {.tv_sec = 0, .tv_nsec = UTIME_NOW},
722         };
723         /* "now" is whatever the server says it is */
724         const timespec now[2] = {
725                 {.tv_sec = 5, .tv_nsec = 7},
726                 {.tv_sec = 6, .tv_nsec = 8},
727         };
728         struct stat sb;
729
730         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
731         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
732                 SET_OUT_HEADER_LEN(out, entry);
733                 out.body.entry.attr.mode = S_IFREG | 0644;
734                 out.body.entry.nodeid = ino;
735                 out.body.entry.attr_valid = UINT64_MAX;
736                 out.body.entry.entry_valid = UINT64_MAX;
737                 out.body.entry.attr.atime = oldtimes[0].tv_sec;
738                 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
739                 out.body.entry.attr.mtime = oldtimes[1].tv_sec;
740                 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
741         })));
742
743         EXPECT_CALL(*m_mock, process(
744                 ResultOf([=](auto in) {
745                         uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW |
746                                 FATTR_MTIME | FATTR_MTIME_NOW;
747                         return (in.header.opcode == FUSE_SETATTR &&
748                                 in.header.nodeid == ino &&
749                                 in.body.setattr.valid == valid);
750                 }, Eq(true)),
751                 _)
752         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
753                 SET_OUT_HEADER_LEN(out, attr);
754                 out.body.attr.attr.ino = ino;   // Must match nodeid
755                 out.body.attr.attr.mode = S_IFREG | 0644;
756                 out.body.attr.attr.atime = now[0].tv_sec;
757                 out.body.attr.attr.atimensec = now[0].tv_nsec;
758                 out.body.attr.attr.mtime = now[1].tv_sec;
759                 out.body.attr.attr.mtimensec = now[1].tv_nsec;
760                 out.body.attr.attr_valid = UINT64_MAX;
761         })));
762         ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
763                 << strerror(errno);
764         ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
765         EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec);
766         EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec);
767         EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec);
768         EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec);
769 }
770
771 /*
772  * FUSE_SETATTR returns a different file type, even though the entry cache
773  * hasn't expired.  This is a server bug!  It probably means that the server
774  * removed the file and recreated it with the same inode but a different vtyp.
775  * The best thing fusefs can do is return ENOENT to the caller.  After all, the
776  * entry must not have existed recently.
777  */
778 TEST_F(Setattr, vtyp_conflict)
779 {
780         const char FULLPATH[] = "mountpoint/some_file.txt";
781         const char RELPATH[] = "some_file.txt";
782         const uint64_t ino = 42;
783         uid_t newuser = 12345;
784         sem_t sem;
785
786         ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
787
788         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
789         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
790                 SET_OUT_HEADER_LEN(out, entry);
791                 out.body.entry.attr.mode = S_IFREG | 0777;
792                 out.body.entry.nodeid = ino;
793                 out.body.entry.entry_valid = UINT64_MAX;
794         })));
795
796         EXPECT_CALL(*m_mock, process(
797                 ResultOf([](auto in) {
798                         return (in.header.opcode == FUSE_SETATTR &&
799                                 in.header.nodeid == ino);
800                 }, Eq(true)),
801                 _)
802         ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
803                 SET_OUT_HEADER_LEN(out, attr);
804                 out.body.attr.attr.ino = ino;
805                 out.body.attr.attr.mode = S_IFDIR | 0777;       // Changed!
806                 out.body.attr.attr.uid = newuser;
807         })));
808         // We should reclaim stale vnodes
809         expect_forget(ino, 1, &sem);
810
811         EXPECT_NE(0, chown(FULLPATH, newuser, -1));
812         EXPECT_EQ(ENOENT, errno);
813
814         sem_wait(&sem);
815         sem_destroy(&sem);
816 }
817
818 /* On a read-only mount, no attributes may be changed */
819 TEST_F(RofsSetattr, erofs)
820 {
821         const char FULLPATH[] = "mountpoint/some_file.txt";
822         const char RELPATH[] = "some_file.txt";
823         const uint64_t ino = 42;
824         const mode_t oldmode = 0755;
825         const mode_t newmode = 0644;
826
827         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
828         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
829                 SET_OUT_HEADER_LEN(out, entry);
830                 out.body.entry.attr.mode = S_IFREG | oldmode;
831                 out.body.entry.nodeid = ino;
832         })));
833
834         ASSERT_EQ(-1, chmod(FULLPATH, newmode));
835         ASSERT_EQ(EROFS, errno);
836 }
837
838 /* Change the mode of a file */
839 TEST_F(Setattr_7_8, chmod)
840 {
841         const char FULLPATH[] = "mountpoint/some_file.txt";
842         const char RELPATH[] = "some_file.txt";
843         const uint64_t ino = 42;
844         const mode_t oldmode = 0755;
845         const mode_t newmode = 0644;
846
847         EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
848         .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
849                 SET_OUT_HEADER_LEN(out, entry_7_8);
850                 out.body.entry.attr.mode = S_IFREG | oldmode;
851                 out.body.entry.nodeid = ino;
852         })));
853
854         EXPECT_CALL(*m_mock, process(
855                 ResultOf([](auto in) {
856                         uint32_t valid = FATTR_MODE;
857                         return (in.header.opcode == FUSE_SETATTR &&
858                                 in.header.nodeid == ino &&
859                                 in.body.setattr.valid == valid &&
860                                 in.body.setattr.mode == newmode);
861                 }, Eq(true)),
862                 _)
863         ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
864                 SET_OUT_HEADER_LEN(out, attr_7_8);
865                 out.body.attr.attr.ino = ino;   // Must match nodeid
866                 out.body.attr.attr.mode = S_IFREG | newmode;
867         })));
868         EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
869 }