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