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