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