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