xref: /freebsd-src/tests/sys/fs/fusefs/setattr.cc (revision 8bae22bbbe6571da9259e0d43ffa8a56f4b3e171)
19821f1d3SAlan Somers /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
39821f1d3SAlan Somers  *
49821f1d3SAlan Somers  * Copyright (c) 2019 The FreeBSD Foundation
59821f1d3SAlan Somers  *
69821f1d3SAlan Somers  * This software was developed by BFF Storage Systems, LLC under sponsorship
79821f1d3SAlan Somers  * from the FreeBSD Foundation.
89821f1d3SAlan Somers  *
99821f1d3SAlan Somers  * Redistribution and use in source and binary forms, with or without
109821f1d3SAlan Somers  * modification, are permitted provided that the following conditions
119821f1d3SAlan Somers  * are met:
129821f1d3SAlan Somers  * 1. Redistributions of source code must retain the above copyright
139821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer.
149821f1d3SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
159821f1d3SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
169821f1d3SAlan Somers  *    documentation and/or other materials provided with the distribution.
179821f1d3SAlan Somers  *
189821f1d3SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
199821f1d3SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
209821f1d3SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
219821f1d3SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
229821f1d3SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
239821f1d3SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
249821f1d3SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
259821f1d3SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
269821f1d3SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
279821f1d3SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
289821f1d3SAlan Somers  * SUCH DAMAGE.
299821f1d3SAlan Somers  */
309821f1d3SAlan Somers 
319821f1d3SAlan Somers extern "C" {
320a192b3aSAlan Somers #include <sys/types.h>
330a192b3aSAlan Somers #include <sys/resource.h>
349821f1d3SAlan Somers #include <sys/stat.h>
350a192b3aSAlan Somers #include <sys/time.h>
369821f1d3SAlan Somers 
379821f1d3SAlan Somers #include <fcntl.h>
38c9cabf9aSDimitry Andric #include <semaphore.h>
390a192b3aSAlan Somers #include <signal.h>
409821f1d3SAlan Somers }
419821f1d3SAlan Somers 
429821f1d3SAlan Somers #include "mockfs.hh"
439821f1d3SAlan Somers #include "utils.hh"
449821f1d3SAlan Somers 
459821f1d3SAlan Somers using namespace testing;
469821f1d3SAlan Somers 
470a192b3aSAlan Somers class Setattr : public FuseTest {
480a192b3aSAlan Somers public:
490a192b3aSAlan Somers static sig_atomic_t s_sigxfsz;
500a192b3aSAlan Somers };
519821f1d3SAlan Somers 
52666f8543SAlan Somers class RofsSetattr: public Setattr {
53666f8543SAlan Somers public:
SetUp()54666f8543SAlan Somers virtual void SetUp() {
550a192b3aSAlan Somers 	s_sigxfsz = 0;
56666f8543SAlan Somers 	m_ro = true;
57666f8543SAlan Somers 	Setattr::SetUp();
58666f8543SAlan Somers }
59666f8543SAlan Somers };
60666f8543SAlan Somers 
6116bd2d47SAlan Somers class Setattr_7_8: public Setattr {
6216bd2d47SAlan Somers public:
SetUp()6316bd2d47SAlan Somers virtual void SetUp() {
6416bd2d47SAlan Somers 	m_kernel_minor_version = 8;
6516bd2d47SAlan Somers 	Setattr::SetUp();
6616bd2d47SAlan Somers }
6716bd2d47SAlan Somers };
6816bd2d47SAlan Somers 
699821f1d3SAlan Somers 
700a192b3aSAlan Somers sig_atomic_t Setattr::s_sigxfsz = 0;
710a192b3aSAlan Somers 
sigxfsz_handler(int __unused sig)720a192b3aSAlan Somers void sigxfsz_handler(int __unused sig) {
730a192b3aSAlan Somers 	Setattr::s_sigxfsz = 1;
740a192b3aSAlan Somers }
750a192b3aSAlan Somers 
769821f1d3SAlan Somers /*
779821f1d3SAlan Somers  * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs
789821f1d3SAlan Somers  * should use the cached attributes, rather than query the daemon
799821f1d3SAlan Somers  */
TEST_F(Setattr,attr_cache)80cad67791SAlan Somers TEST_F(Setattr, attr_cache)
819821f1d3SAlan Somers {
829821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
839821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
849821f1d3SAlan Somers 	const uint64_t ino = 42;
859821f1d3SAlan Somers 	struct stat sb;
869821f1d3SAlan Somers 	const mode_t newmode = 0644;
879821f1d3SAlan Somers 
88a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
8929edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
909821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
9129edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
9229edc611SAlan Somers 		out.body.entry.nodeid = ino;
9329edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
949821f1d3SAlan Somers 	})));
959821f1d3SAlan Somers 
969821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
979821f1d3SAlan Somers 		ResultOf([](auto in) {
9829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
9929edc611SAlan Somers 				in.header.nodeid == ino);
1009821f1d3SAlan Somers 		}, Eq(true)),
1019821f1d3SAlan Somers 		_)
10229edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1039821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
10429edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
10529edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
10629edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
1079821f1d3SAlan Somers 	})));
1089821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1099821f1d3SAlan Somers 		ResultOf([](auto in) {
11029edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR);
1119821f1d3SAlan Somers 		}, Eq(true)),
1129821f1d3SAlan Somers 		_)
1139821f1d3SAlan Somers 	).Times(0);
1149821f1d3SAlan Somers 
1159821f1d3SAlan Somers 	/* Set an attribute with SETATTR */
1169821f1d3SAlan Somers 	ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1179821f1d3SAlan Somers 
1189821f1d3SAlan Somers 	/* The stat(2) should use cached attributes */
1199821f1d3SAlan Somers 	ASSERT_EQ(0, stat(FULLPATH, &sb));
1209821f1d3SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1219821f1d3SAlan Somers }
1229821f1d3SAlan Somers 
1239821f1d3SAlan Somers /* Change the mode of a file */
TEST_F(Setattr,chmod)1249821f1d3SAlan Somers TEST_F(Setattr, chmod)
1259821f1d3SAlan Somers {
1269821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1279821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
1289821f1d3SAlan Somers 	const uint64_t ino = 42;
1299821f1d3SAlan Somers 	const mode_t oldmode = 0755;
1309821f1d3SAlan Somers 	const mode_t newmode = 0644;
1319821f1d3SAlan Somers 
132a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
13329edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1349821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
13529edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | oldmode;
13629edc611SAlan Somers 		out.body.entry.nodeid = ino;
1379821f1d3SAlan Somers 	})));
1389821f1d3SAlan Somers 
1399821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
1409821f1d3SAlan Somers 		ResultOf([](auto in) {
1419821f1d3SAlan Somers 			uint32_t valid = FATTR_MODE;
14229edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
14329edc611SAlan Somers 				in.header.nodeid == ino &&
14429edc611SAlan Somers 				in.body.setattr.valid == valid &&
14529edc611SAlan Somers 				in.body.setattr.mode == newmode);
1469821f1d3SAlan Somers 		}, Eq(true)),
1479821f1d3SAlan Somers 		_)
14829edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1499821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
15029edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
15129edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
1529821f1d3SAlan Somers 	})));
1539821f1d3SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1549821f1d3SAlan Somers }
1559821f1d3SAlan Somers 
1564ae3a56cSAlan Somers /*
1574ae3a56cSAlan Somers  * Chmod a multiply-linked file with cached attributes.  Check that both files'
1584ae3a56cSAlan Somers  * attributes have changed.
1594ae3a56cSAlan Somers  */
TEST_F(Setattr,chmod_multiply_linked)1604ae3a56cSAlan Somers TEST_F(Setattr, chmod_multiply_linked)
1614ae3a56cSAlan Somers {
1624ae3a56cSAlan Somers 	const char FULLPATH0[] = "mountpoint/some_file.txt";
1634ae3a56cSAlan Somers 	const char RELPATH0[] = "some_file.txt";
1644ae3a56cSAlan Somers 	const char FULLPATH1[] = "mountpoint/other_file.txt";
1654ae3a56cSAlan Somers 	const char RELPATH1[] = "other_file.txt";
1664ae3a56cSAlan Somers 	struct stat sb;
1674ae3a56cSAlan Somers 	const uint64_t ino = 42;
1684ae3a56cSAlan Somers 	const mode_t oldmode = 0777;
1694ae3a56cSAlan Somers 	const mode_t newmode = 0666;
1704ae3a56cSAlan Somers 
171a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0)
17229edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1734ae3a56cSAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
17429edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | oldmode;
17529edc611SAlan Somers 		out.body.entry.nodeid = ino;
17629edc611SAlan Somers 		out.body.entry.attr.nlink = 2;
17729edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
17829edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
1794ae3a56cSAlan Somers 	})));
1804ae3a56cSAlan Somers 
181a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1)
18229edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1834ae3a56cSAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
18429edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | oldmode;
18529edc611SAlan Somers 		out.body.entry.nodeid = ino;
18629edc611SAlan Somers 		out.body.entry.attr.nlink = 2;
18729edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
18829edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
1894ae3a56cSAlan Somers 	})));
1904ae3a56cSAlan Somers 
1914ae3a56cSAlan Somers 	EXPECT_CALL(*m_mock, process(
1924ae3a56cSAlan Somers 		ResultOf([](auto in) {
1934ae3a56cSAlan Somers 			uint32_t valid = FATTR_MODE;
19429edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
19529edc611SAlan Somers 				in.header.nodeid == ino &&
19629edc611SAlan Somers 				in.body.setattr.valid == valid &&
19729edc611SAlan Somers 				in.body.setattr.mode == newmode);
1984ae3a56cSAlan Somers 		}, Eq(true)),
1994ae3a56cSAlan Somers 		_)
20029edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
2014ae3a56cSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
20229edc611SAlan Somers 		out.body.attr.attr.ino = ino;
20329edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
20429edc611SAlan Somers 		out.body.attr.attr.nlink = 2;
20529edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
2064ae3a56cSAlan Somers 	})));
2074ae3a56cSAlan Somers 
2084ae3a56cSAlan Somers 	/* For a lookup of the 2nd file to get it into the cache*/
2094ae3a56cSAlan Somers 	ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
2104ae3a56cSAlan Somers 	EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
2114ae3a56cSAlan Somers 
2124ae3a56cSAlan Somers 	ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno);
2134ae3a56cSAlan Somers 	ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno);
2144ae3a56cSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
2154ae3a56cSAlan Somers 	ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno);
2164ae3a56cSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
2174ae3a56cSAlan Somers }
2184ae3a56cSAlan Somers 
2194ae3a56cSAlan Somers 
2209821f1d3SAlan Somers /* Change the owner and group of a file */
TEST_F(Setattr,chown)2219821f1d3SAlan Somers TEST_F(Setattr, chown)
2229821f1d3SAlan Somers {
2239821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2249821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2259821f1d3SAlan Somers 	const uint64_t ino = 42;
2269821f1d3SAlan Somers 	const gid_t oldgroup = 66;
2279821f1d3SAlan Somers 	const gid_t newgroup = 99;
2289821f1d3SAlan Somers 	const uid_t olduser = 33;
2299821f1d3SAlan Somers 	const uid_t newuser = 44;
2309821f1d3SAlan Somers 
231a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
23229edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
2339821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
23429edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
23529edc611SAlan Somers 		out.body.entry.nodeid = ino;
23629edc611SAlan Somers 		out.body.entry.attr.gid = oldgroup;
23729edc611SAlan Somers 		out.body.entry.attr.uid = olduser;
2389821f1d3SAlan Somers 	})));
2399821f1d3SAlan Somers 
2409821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
2419821f1d3SAlan Somers 		ResultOf([](auto in) {
2429821f1d3SAlan Somers 			uint32_t valid = FATTR_GID | FATTR_UID;
24329edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
24429edc611SAlan Somers 				in.header.nodeid == ino &&
24529edc611SAlan Somers 				in.body.setattr.valid == valid &&
24629edc611SAlan Somers 				in.body.setattr.uid == newuser &&
24729edc611SAlan Somers 				in.body.setattr.gid == newgroup);
2489821f1d3SAlan Somers 		}, Eq(true)),
2499821f1d3SAlan Somers 		_)
25029edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
2519821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
25229edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
25329edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
25429edc611SAlan Somers 		out.body.attr.attr.uid = newuser;
25529edc611SAlan Somers 		out.body.attr.attr.gid = newgroup;
2569821f1d3SAlan Somers 	})));
2579821f1d3SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno);
2589821f1d3SAlan Somers }
2599821f1d3SAlan Somers 
2609821f1d3SAlan Somers 
2619821f1d3SAlan Somers 
2629821f1d3SAlan Somers /*
2639821f1d3SAlan Somers  * FUSE daemons are allowed to check permissions however they like.  If the
2649821f1d3SAlan Somers  * daemon returns EPERM, even if the file permissions "should" grant access,
2659821f1d3SAlan Somers  * then fuse(4) should return EPERM too.
2669821f1d3SAlan Somers  */
TEST_F(Setattr,eperm)2679821f1d3SAlan Somers TEST_F(Setattr, eperm)
2689821f1d3SAlan Somers {
2699821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2709821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2719821f1d3SAlan Somers 	const uint64_t ino = 42;
2729821f1d3SAlan Somers 
273a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
27429edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) {
2759821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
27629edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0777;
27729edc611SAlan Somers 		out.body.entry.nodeid = ino;
27829edc611SAlan Somers 		out.body.entry.attr.uid = in.header.uid;
27929edc611SAlan Somers 		out.body.entry.attr.gid = in.header.gid;
2809821f1d3SAlan Somers 	})));
2819821f1d3SAlan Somers 
2829821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
2839821f1d3SAlan Somers 		ResultOf([](auto in) {
28429edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
28529edc611SAlan Somers 				in.header.nodeid == ino);
2869821f1d3SAlan Somers 		}, Eq(true)),
2879821f1d3SAlan Somers 		_)
2889821f1d3SAlan Somers 	).WillOnce(Invoke(ReturnErrno(EPERM)));
2899821f1d3SAlan Somers 	EXPECT_NE(0, truncate(FULLPATH, 10));
2909821f1d3SAlan Somers 	EXPECT_EQ(EPERM, errno);
2919821f1d3SAlan Somers }
2929821f1d3SAlan Somers 
2939821f1d3SAlan Somers /* Change the mode of an open file, by its file descriptor */
TEST_F(Setattr,fchmod)2949821f1d3SAlan Somers TEST_F(Setattr, fchmod)
2959821f1d3SAlan Somers {
2969821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2979821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2989821f1d3SAlan Somers 	uint64_t ino = 42;
2999821f1d3SAlan Somers 	int fd;
3009821f1d3SAlan Somers 	const mode_t oldmode = 0755;
3019821f1d3SAlan Somers 	const mode_t newmode = 0644;
3029821f1d3SAlan Somers 
303a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
30429edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
3059821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
30629edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | oldmode;
30729edc611SAlan Somers 		out.body.entry.nodeid = ino;
30829edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
3099821f1d3SAlan Somers 	})));
3109821f1d3SAlan Somers 
3119821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
3129821f1d3SAlan Somers 		ResultOf([=](auto in) {
31329edc611SAlan Somers 			return (in.header.opcode == FUSE_OPEN &&
31429edc611SAlan Somers 				in.header.nodeid == ino);
3159821f1d3SAlan Somers 		}, Eq(true)),
3169821f1d3SAlan Somers 		_)
31729edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
31829edc611SAlan Somers 		out.header.len = sizeof(out.header);
3199821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, open);
3209821f1d3SAlan Somers 	})));
3219821f1d3SAlan Somers 
3229821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
3239821f1d3SAlan Somers 		ResultOf([=](auto in) {
3249821f1d3SAlan Somers 			uint32_t valid = FATTR_MODE;
32529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
32629edc611SAlan Somers 				in.header.nodeid == ino &&
32729edc611SAlan Somers 				in.body.setattr.valid == valid &&
32829edc611SAlan Somers 				in.body.setattr.mode == newmode);
3299821f1d3SAlan Somers 		}, Eq(true)),
3309821f1d3SAlan Somers 		_)
33129edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
3329821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
33329edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
33429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
3359821f1d3SAlan Somers 	})));
3369821f1d3SAlan Somers 
3379821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
3389821f1d3SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
3399821f1d3SAlan Somers 	ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno);
3407fc0921dSAlan Somers 	leak(fd);
3419821f1d3SAlan Somers }
3429821f1d3SAlan Somers 
3439821f1d3SAlan Somers /* Change the size of an open file, by its file descriptor */
TEST_F(Setattr,ftruncate)3449821f1d3SAlan Somers TEST_F(Setattr, ftruncate)
3459821f1d3SAlan Somers {
3469821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3479821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
3489821f1d3SAlan Somers 	uint64_t ino = 42;
3499821f1d3SAlan Somers 	int fd;
3509821f1d3SAlan Somers 	uint64_t fh = 0xdeadbeef1a7ebabe;
3519821f1d3SAlan Somers 	const off_t oldsize = 99;
3529821f1d3SAlan Somers 	const off_t newsize = 12345;
3539821f1d3SAlan Somers 
354a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
35529edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
3569821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
35729edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0755;
35829edc611SAlan Somers 		out.body.entry.nodeid = ino;
35929edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
36029edc611SAlan Somers 		out.body.entry.attr.size = oldsize;
3619821f1d3SAlan Somers 	})));
3629821f1d3SAlan Somers 
3639821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
3649821f1d3SAlan Somers 		ResultOf([=](auto in) {
36529edc611SAlan Somers 			return (in.header.opcode == FUSE_OPEN &&
36629edc611SAlan Somers 				in.header.nodeid == ino);
3679821f1d3SAlan Somers 		}, Eq(true)),
3689821f1d3SAlan Somers 		_)
36929edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
37029edc611SAlan Somers 		out.header.len = sizeof(out.header);
3719821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, open);
37229edc611SAlan Somers 		out.body.open.fh = fh;
3739821f1d3SAlan Somers 	})));
3749821f1d3SAlan Somers 
3759821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
3769821f1d3SAlan Somers 		ResultOf([=](auto in) {
3779821f1d3SAlan Somers 			uint32_t valid = FATTR_SIZE | FATTR_FH;
37829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
37929edc611SAlan Somers 				in.header.nodeid == ino &&
38029edc611SAlan Somers 				in.body.setattr.valid == valid &&
38129edc611SAlan Somers 				in.body.setattr.fh == fh);
3829821f1d3SAlan Somers 		}, Eq(true)),
3839821f1d3SAlan Somers 		_)
38429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
3859821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
38629edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
38729edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0755;
38829edc611SAlan Somers 		out.body.attr.attr.size = newsize;
3899821f1d3SAlan Somers 	})));
3909821f1d3SAlan Somers 
3919821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDWR);
3929821f1d3SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
3939821f1d3SAlan Somers 	ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno);
3947fc0921dSAlan Somers 	leak(fd);
3959821f1d3SAlan Somers }
3969821f1d3SAlan Somers 
3979821f1d3SAlan Somers /* Change the size of the file */
TEST_F(Setattr,truncate)3989821f1d3SAlan Somers TEST_F(Setattr, truncate) {
3999821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
4009821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
4019821f1d3SAlan Somers 	const uint64_t ino = 42;
4029821f1d3SAlan Somers 	const uint64_t oldsize = 100'000'000;
4039821f1d3SAlan Somers 	const uint64_t newsize = 20'000'000;
4049821f1d3SAlan Somers 
405a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
40629edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
4079821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
40829edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
40929edc611SAlan Somers 		out.body.entry.nodeid = ino;
41029edc611SAlan Somers 		out.body.entry.attr.size = oldsize;
4119821f1d3SAlan Somers 	})));
4129821f1d3SAlan Somers 
4139821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
4149821f1d3SAlan Somers 		ResultOf([](auto in) {
4159821f1d3SAlan Somers 			uint32_t valid = FATTR_SIZE;
41629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
41729edc611SAlan Somers 				in.header.nodeid == ino &&
41829edc611SAlan Somers 				in.body.setattr.valid == valid &&
41929edc611SAlan Somers 				in.body.setattr.size == newsize);
4209821f1d3SAlan Somers 		}, Eq(true)),
4219821f1d3SAlan Somers 		_)
42229edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
4239821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
42429edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
42529edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
42629edc611SAlan Somers 		out.body.attr.attr.size = newsize;
4279821f1d3SAlan Somers 	})));
4289821f1d3SAlan Somers 	EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno);
4299821f1d3SAlan Somers }
4309821f1d3SAlan Somers 
431e312493bSAlan Somers /*
432e312493bSAlan Somers  * Truncating a file should discard cached data past the truncation point.
433f8ebf1cdSAlan Somers  * This is a regression test for bug 233783.
43477b82478SAlan Somers  *
43577b82478SAlan Somers  * There are two distinct failure modes.  The first one is a failure to zero
43677b82478SAlan Somers  * the portion of the file's final buffer past EOF.  It can be reproduced by
43777b82478SAlan Somers  * fsx -WR -P /tmp -S10 fsx.bin
43877b82478SAlan Somers  *
43977b82478SAlan Somers  * The second is a failure to drop buffers beyond that.  It can be reproduced by
44077b82478SAlan Somers  * fsx -WR -P /tmp -S18 -n fsx.bin
44177b82478SAlan Somers  * Also reproducible in sh with:
44277b82478SAlan Somers  * $> /path/to/libfuse/build/example/passthrough -d /tmp/mnt
44377b82478SAlan Somers  * $> cd /tmp/mnt/tmp
44477b82478SAlan Somers  * $> dd if=/dev/random of=randfile bs=1k count=192
44577b82478SAlan Somers  * $> truncate -s 1k randfile && truncate -s 192k randfile
44677b82478SAlan Somers  * $> xxd randfile | less # xxd will wrongly show random data at offset 0x8000
447e312493bSAlan Somers  */
TEST_F(Setattr,truncate_discards_cached_data)448e312493bSAlan Somers TEST_F(Setattr, truncate_discards_cached_data) {
449e312493bSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
450e312493bSAlan Somers 	const char RELPATH[] = "some_file.txt";
451*8bae22bbSAlan Somers 	char *w0buf, *r0buf, *r1buf, *expected;
45277b82478SAlan Somers 	off_t w0_offset = 0;
45377b82478SAlan Somers 	size_t w0_size = 0x30000;
45477b82478SAlan Somers 	off_t r0_offset = 0;
45577b82478SAlan Somers 	off_t r0_size = w0_size;
45677b82478SAlan Somers 	size_t trunc0_size = 0x400;
45777b82478SAlan Somers 	size_t trunc1_size = w0_size;
45877b82478SAlan Somers 	off_t r1_offset = trunc0_size;
45977b82478SAlan Somers 	off_t r1_size = w0_size - trunc0_size;
460e312493bSAlan Somers 	size_t cur_size = 0;
461e312493bSAlan Somers 	const uint64_t ino = 42;
462e312493bSAlan Somers 	mode_t mode = S_IFREG | 0644;
46377b82478SAlan Somers 	int fd, r;
46477b82478SAlan Somers 	bool should_have_data = false;
465e312493bSAlan Somers 
466*8bae22bbSAlan Somers 	w0buf = new char[w0_size];
467e312493bSAlan Somers 	memset(w0buf, 'X', w0_size);
468e312493bSAlan Somers 
469*8bae22bbSAlan Somers 	r0buf = new char[r0_size];
470*8bae22bbSAlan Somers 	r1buf = new char[r1_size];
471e312493bSAlan Somers 
472*8bae22bbSAlan Somers 	expected = new char[r1_size]();
473e312493bSAlan Somers 
474e312493bSAlan Somers 	expect_lookup(RELPATH, ino, mode, 0, 1);
475e312493bSAlan Somers 	expect_open(ino, O_RDWR, 1);
476e312493bSAlan Somers 	EXPECT_CALL(*m_mock, process(
477e312493bSAlan Somers 		ResultOf([=](auto in) {
47829edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
47929edc611SAlan Somers 				in.header.nodeid == ino);
480e312493bSAlan Somers 		}, Eq(true)),
481e312493bSAlan Somers 		_)
48229edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto& out) {
483e312493bSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
48429edc611SAlan Somers 		out.body.attr.attr.ino = ino;
48529edc611SAlan Somers 		out.body.attr.attr.mode = mode;
48629edc611SAlan Somers 		out.body.attr.attr.size = cur_size;
487e312493bSAlan Somers 	})));
488e312493bSAlan Somers 	EXPECT_CALL(*m_mock, process(
489e312493bSAlan Somers 		ResultOf([=](auto in) {
49029edc611SAlan Somers 			return (in.header.opcode == FUSE_WRITE);
491e312493bSAlan Somers 		}, Eq(true)),
492e312493bSAlan Somers 		_)
49329edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
494e312493bSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
49529edc611SAlan Somers 		out.body.attr.attr.ino = ino;
49629edc611SAlan Somers 		out.body.write.size = in.body.write.size;
49729edc611SAlan Somers 		cur_size = std::max(static_cast<uint64_t>(cur_size),
49829edc611SAlan Somers 			in.body.write.size + in.body.write.offset);
499e312493bSAlan Somers 	})));
500e312493bSAlan Somers 
501e312493bSAlan Somers 	EXPECT_CALL(*m_mock, process(
502e312493bSAlan Somers 		ResultOf([=](auto in) {
50329edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
50429edc611SAlan Somers 				in.header.nodeid == ino &&
50529edc611SAlan Somers 				(in.body.setattr.valid & FATTR_SIZE));
506e312493bSAlan Somers 		}, Eq(true)),
507e312493bSAlan Somers 		_)
50829edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
50929edc611SAlan Somers 		auto trunc_size = in.body.setattr.size;
510e312493bSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
51129edc611SAlan Somers 		out.body.attr.attr.ino = ino;
51229edc611SAlan Somers 		out.body.attr.attr.mode = mode;
51329edc611SAlan Somers 		out.body.attr.attr.size = trunc_size;
514e312493bSAlan Somers 		cur_size = trunc_size;
515e312493bSAlan Somers 	})));
516e312493bSAlan Somers 
517e312493bSAlan Somers 	EXPECT_CALL(*m_mock, process(
518e312493bSAlan Somers 		ResultOf([=](auto in) {
51929edc611SAlan Somers 			return (in.header.opcode == FUSE_READ);
520e312493bSAlan Somers 		}, Eq(true)),
521e312493bSAlan Somers 		_)
52229edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) {
52329edc611SAlan Somers 		auto osize = std::min(
52429edc611SAlan Somers 			static_cast<uint64_t>(cur_size) - in.body.read.offset,
52529edc611SAlan Somers 			static_cast<uint64_t>(in.body.read.size));
5260c9df4afSAlan Somers 		assert(osize <= sizeof(out.body.bytes));
52729edc611SAlan Somers 		out.header.len = sizeof(struct fuse_out_header) + osize;
52877b82478SAlan Somers 		if (should_have_data)
52929edc611SAlan Somers 			memset(out.body.bytes, 'X', osize);
53077b82478SAlan Somers 		else
53129edc611SAlan Somers 			bzero(out.body.bytes, osize);
532e312493bSAlan Somers 	})));
533e312493bSAlan Somers 
534e312493bSAlan Somers 	fd = open(FULLPATH, O_RDWR, 0644);
535e312493bSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
536e312493bSAlan Somers 
53777b82478SAlan Somers 	/* Fill the file with Xs */
53829edc611SAlan Somers 	ASSERT_EQ(static_cast<ssize_t>(w0_size),
53929edc611SAlan Somers 		pwrite(fd, w0buf, w0_size, w0_offset));
54077b82478SAlan Somers 	should_have_data = true;
541f8ebf1cdSAlan Somers 	/* Fill the cache */
54229edc611SAlan Somers 	ASSERT_EQ(static_cast<ssize_t>(r0_size),
54329edc611SAlan Somers 		pread(fd, r0buf, r0_size, r0_offset));
544e312493bSAlan Somers 	/* 1st truncate should discard cached data */
545e312493bSAlan Somers 	EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno);
54677b82478SAlan Somers 	should_have_data = false;
547e312493bSAlan Somers 	/* 2nd truncate extends file into previously cached data */
548e312493bSAlan Somers 	EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno);
549e312493bSAlan Somers 	/* Read should return all zeros */
55029edc611SAlan Somers 	ASSERT_EQ(static_cast<ssize_t>(r1_size),
55129edc611SAlan Somers 		pread(fd, r1buf, r1_size, r1_offset));
552e312493bSAlan Somers 
55377b82478SAlan Somers 	r = memcmp(expected, r1buf, r1_size);
55477b82478SAlan Somers 	ASSERT_EQ(0, r);
555e312493bSAlan Somers 
556*8bae22bbSAlan Somers 	delete[] expected;
557*8bae22bbSAlan Somers 	delete[] r1buf;
558*8bae22bbSAlan Somers 	delete[] r0buf;
559*8bae22bbSAlan Somers 	delete[] w0buf;
5608e765737SAlan Somers 
5618e765737SAlan Somers 	leak(fd);
562e312493bSAlan Somers }
563e312493bSAlan Somers 
5640a192b3aSAlan Somers /* truncate should fail if it would cause the file to exceed RLIMIT_FSIZE */
TEST_F(Setattr,truncate_rlimit_rsize)5650a192b3aSAlan Somers TEST_F(Setattr, truncate_rlimit_rsize)
5660a192b3aSAlan Somers {
5670a192b3aSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
5680a192b3aSAlan Somers 	const char RELPATH[] = "some_file.txt";
5690a192b3aSAlan Somers 	struct rlimit rl;
5700a192b3aSAlan Somers 	const uint64_t ino = 42;
5710a192b3aSAlan Somers 	const uint64_t oldsize = 0;
5720a192b3aSAlan Somers 	const uint64_t newsize = 100'000'000;
5730a192b3aSAlan Somers 
5740a192b3aSAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
5750a192b3aSAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
5760a192b3aSAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
5770a192b3aSAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
5780a192b3aSAlan Somers 		out.body.entry.nodeid = ino;
5790a192b3aSAlan Somers 		out.body.entry.attr.size = oldsize;
5800a192b3aSAlan Somers 	})));
5810a192b3aSAlan Somers 
5820a192b3aSAlan Somers 	rl.rlim_cur = newsize / 2;
5830a192b3aSAlan Somers 	rl.rlim_max = 10 * newsize;
5840a192b3aSAlan Somers 	ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno);
5850a192b3aSAlan Somers 	ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno);
5860a192b3aSAlan Somers 
5870a192b3aSAlan Somers 	EXPECT_EQ(-1, truncate(FULLPATH, newsize));
5880a192b3aSAlan Somers 	EXPECT_EQ(EFBIG, errno);
5890a192b3aSAlan Somers 	EXPECT_EQ(1, s_sigxfsz);
5900a192b3aSAlan Somers }
5910a192b3aSAlan Somers 
5929821f1d3SAlan Somers /* Change a file's timestamps */
TEST_F(Setattr,utimensat)5939821f1d3SAlan Somers TEST_F(Setattr, utimensat) {
5949821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
5959821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
5969821f1d3SAlan Somers 	const uint64_t ino = 42;
5979821f1d3SAlan Somers 	const timespec oldtimes[2] = {
5989821f1d3SAlan Somers 		{.tv_sec = 1, .tv_nsec = 2},
5999821f1d3SAlan Somers 		{.tv_sec = 3, .tv_nsec = 4},
6009821f1d3SAlan Somers 	};
6019821f1d3SAlan Somers 	const timespec newtimes[2] = {
6029821f1d3SAlan Somers 		{.tv_sec = 5, .tv_nsec = 6},
6039821f1d3SAlan Somers 		{.tv_sec = 7, .tv_nsec = 8},
6049821f1d3SAlan Somers 	};
6059821f1d3SAlan Somers 
606a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
60729edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
6089821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
60929edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
61029edc611SAlan Somers 		out.body.entry.nodeid = ino;
61129edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
61229edc611SAlan Somers 		out.body.entry.attr.atime = oldtimes[0].tv_sec;
61329edc611SAlan Somers 		out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
61429edc611SAlan Somers 		out.body.entry.attr.mtime = oldtimes[1].tv_sec;
61529edc611SAlan Somers 		out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
6169821f1d3SAlan Somers 	})));
6179821f1d3SAlan Somers 
6189821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
6199821f1d3SAlan Somers 		ResultOf([=](auto in) {
6209821f1d3SAlan Somers 			uint32_t valid = FATTR_ATIME | FATTR_MTIME;
62129edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
62229edc611SAlan Somers 				in.header.nodeid == ino &&
62329edc611SAlan Somers 				in.body.setattr.valid == valid &&
6245a0b9a27SAlan Somers 				(time_t)in.body.setattr.atime ==
6255a0b9a27SAlan Somers 					newtimes[0].tv_sec &&
62653f82128SKyle Evans 				(long)in.body.setattr.atimensec ==
6279821f1d3SAlan Somers 					newtimes[0].tv_nsec &&
6285a0b9a27SAlan Somers 				(time_t)in.body.setattr.mtime ==
6295a0b9a27SAlan Somers 					newtimes[1].tv_sec &&
63053f82128SKyle Evans 				(long)in.body.setattr.mtimensec ==
6319821f1d3SAlan Somers 					newtimes[1].tv_nsec);
6329821f1d3SAlan Somers 		}, Eq(true)),
6339821f1d3SAlan Somers 		_)
63429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
6359821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
63629edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
63729edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
63829edc611SAlan Somers 		out.body.attr.attr.atime = newtimes[0].tv_sec;
63929edc611SAlan Somers 		out.body.attr.attr.atimensec = newtimes[0].tv_nsec;
64029edc611SAlan Somers 		out.body.attr.attr.mtime = newtimes[1].tv_sec;
64129edc611SAlan Somers 		out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
6429821f1d3SAlan Somers 	})));
6439821f1d3SAlan Somers 	EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
6449821f1d3SAlan Somers 		<< strerror(errno);
6459821f1d3SAlan Somers }
6469821f1d3SAlan Somers 
6479821f1d3SAlan Somers /* Change a file mtime but not its atime */
TEST_F(Setattr,utimensat_mtime_only)6489821f1d3SAlan Somers TEST_F(Setattr, utimensat_mtime_only) {
6499821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
6509821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
6519821f1d3SAlan Somers 	const uint64_t ino = 42;
6529821f1d3SAlan Somers 	const timespec oldtimes[2] = {
6539821f1d3SAlan Somers 		{.tv_sec = 1, .tv_nsec = 2},
6549821f1d3SAlan Somers 		{.tv_sec = 3, .tv_nsec = 4},
6559821f1d3SAlan Somers 	};
6569821f1d3SAlan Somers 	const timespec newtimes[2] = {
6579821f1d3SAlan Somers 		{.tv_sec = 5, .tv_nsec = UTIME_OMIT},
6589821f1d3SAlan Somers 		{.tv_sec = 7, .tv_nsec = 8},
6599821f1d3SAlan Somers 	};
6609821f1d3SAlan Somers 
661a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
66229edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
6639821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
66429edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
66529edc611SAlan Somers 		out.body.entry.nodeid = ino;
66629edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
66729edc611SAlan Somers 		out.body.entry.attr.atime = oldtimes[0].tv_sec;
66829edc611SAlan Somers 		out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
66929edc611SAlan Somers 		out.body.entry.attr.mtime = oldtimes[1].tv_sec;
67029edc611SAlan Somers 		out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
6719821f1d3SAlan Somers 	})));
6729821f1d3SAlan Somers 
6739821f1d3SAlan Somers 	EXPECT_CALL(*m_mock, process(
6749821f1d3SAlan Somers 		ResultOf([=](auto in) {
6759821f1d3SAlan Somers 			uint32_t valid = FATTR_MTIME;
67629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
67729edc611SAlan Somers 				in.header.nodeid == ino &&
67829edc611SAlan Somers 				in.body.setattr.valid == valid &&
6795a0b9a27SAlan Somers 				(time_t)in.body.setattr.mtime ==
6805a0b9a27SAlan Somers 					newtimes[1].tv_sec &&
68153f82128SKyle Evans 				(long)in.body.setattr.mtimensec ==
6829821f1d3SAlan Somers 					newtimes[1].tv_nsec);
6839821f1d3SAlan Somers 		}, Eq(true)),
6849821f1d3SAlan Somers 		_)
68529edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
6869821f1d3SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
68729edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
68829edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
68929edc611SAlan Somers 		out.body.attr.attr.atime = oldtimes[0].tv_sec;
69029edc611SAlan Somers 		out.body.attr.attr.atimensec = oldtimes[0].tv_nsec;
69129edc611SAlan Somers 		out.body.attr.attr.mtime = newtimes[1].tv_sec;
69229edc611SAlan Somers 		out.body.attr.attr.mtimensec = newtimes[1].tv_nsec;
6939821f1d3SAlan Somers 	})));
6949821f1d3SAlan Somers 	EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
6959821f1d3SAlan Somers 		<< strerror(errno);
6969821f1d3SAlan Somers }
697ff4fbdf5SAlan Somers 
698fe221e01SAlan Somers /*
699fe221e01SAlan Somers  * Set a file's mtime and atime to now
700fe221e01SAlan Somers  *
701fe221e01SAlan Somers  * The design of FreeBSD's VFS does not allow fusefs to set just one of atime
702fe221e01SAlan Somers  * or mtime to UTIME_NOW; it's both or neither.
703fe221e01SAlan Somers  */
TEST_F(Setattr,utimensat_utime_now)704b349700aSAlan Somers TEST_F(Setattr, utimensat_utime_now) {
705b349700aSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
706b349700aSAlan Somers 	const char RELPATH[] = "some_file.txt";
707b349700aSAlan Somers 	const uint64_t ino = 42;
708b349700aSAlan Somers 	const timespec oldtimes[2] = {
709b349700aSAlan Somers 		{.tv_sec = 1, .tv_nsec = 2},
710b349700aSAlan Somers 		{.tv_sec = 3, .tv_nsec = 4},
711b349700aSAlan Somers 	};
712b349700aSAlan Somers 	const timespec newtimes[2] = {
713b349700aSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
714b349700aSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
715b349700aSAlan Somers 	};
716b349700aSAlan Somers 	/* "now" is whatever the server says it is */
717b349700aSAlan Somers 	const timespec now[2] = {
718b349700aSAlan Somers 		{.tv_sec = 5, .tv_nsec = 7},
719b349700aSAlan Somers 		{.tv_sec = 6, .tv_nsec = 8},
720b349700aSAlan Somers 	};
721b349700aSAlan Somers 	struct stat sb;
722b349700aSAlan Somers 
723a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
72429edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
725b349700aSAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
72629edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
72729edc611SAlan Somers 		out.body.entry.nodeid = ino;
72829edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
72929edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
73029edc611SAlan Somers 		out.body.entry.attr.atime = oldtimes[0].tv_sec;
73129edc611SAlan Somers 		out.body.entry.attr.atimensec = oldtimes[0].tv_nsec;
73229edc611SAlan Somers 		out.body.entry.attr.mtime = oldtimes[1].tv_sec;
73329edc611SAlan Somers 		out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec;
734b349700aSAlan Somers 	})));
735b349700aSAlan Somers 
736b349700aSAlan Somers 	EXPECT_CALL(*m_mock, process(
737b349700aSAlan Somers 		ResultOf([=](auto in) {
738b349700aSAlan Somers 			uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW |
739b349700aSAlan Somers 				FATTR_MTIME | FATTR_MTIME_NOW;
74029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
74129edc611SAlan Somers 				in.header.nodeid == ino &&
74229edc611SAlan Somers 				in.body.setattr.valid == valid);
743b349700aSAlan Somers 		}, Eq(true)),
744b349700aSAlan Somers 		_)
74529edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
746b349700aSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
74729edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
74829edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | 0644;
74929edc611SAlan Somers 		out.body.attr.attr.atime = now[0].tv_sec;
75029edc611SAlan Somers 		out.body.attr.attr.atimensec = now[0].tv_nsec;
75129edc611SAlan Somers 		out.body.attr.attr.mtime = now[1].tv_sec;
75229edc611SAlan Somers 		out.body.attr.attr.mtimensec = now[1].tv_nsec;
75329edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
754b349700aSAlan Somers 	})));
755b349700aSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0))
756b349700aSAlan Somers 		<< strerror(errno);
757b349700aSAlan Somers 	ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno);
758b349700aSAlan Somers 	EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec);
759b349700aSAlan Somers 	EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec);
760b349700aSAlan Somers 	EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec);
761b349700aSAlan Somers 	EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec);
762b349700aSAlan Somers }
763b349700aSAlan Somers 
76425927e06SAlan Somers /*
76525927e06SAlan Somers  * FUSE_SETATTR returns a different file type, even though the entry cache
76625927e06SAlan Somers  * hasn't expired.  This is a server bug!  It probably means that the server
76725927e06SAlan Somers  * removed the file and recreated it with the same inode but a different vtyp.
76825927e06SAlan Somers  * The best thing fusefs can do is return ENOENT to the caller.  After all, the
76925927e06SAlan Somers  * entry must not have existed recently.
77025927e06SAlan Somers  */
TEST_F(Setattr,vtyp_conflict)77125927e06SAlan Somers TEST_F(Setattr, vtyp_conflict)
77225927e06SAlan Somers {
77325927e06SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
77425927e06SAlan Somers 	const char RELPATH[] = "some_file.txt";
77525927e06SAlan Somers 	const uint64_t ino = 42;
77625927e06SAlan Somers 	uid_t newuser = 12345;
77725927e06SAlan Somers 	sem_t sem;
77825927e06SAlan Somers 
77925927e06SAlan Somers 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
78025927e06SAlan Somers 
78125927e06SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
78225927e06SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
78325927e06SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
78425927e06SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0777;
78525927e06SAlan Somers 		out.body.entry.nodeid = ino;
78625927e06SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
78725927e06SAlan Somers 	})));
78825927e06SAlan Somers 
78925927e06SAlan Somers 	EXPECT_CALL(*m_mock, process(
79025927e06SAlan Somers 		ResultOf([](auto in) {
79125927e06SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
79225927e06SAlan Somers 				in.header.nodeid == ino);
79325927e06SAlan Somers 		}, Eq(true)),
79425927e06SAlan Somers 		_)
79525927e06SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
79625927e06SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
79725927e06SAlan Somers 		out.body.attr.attr.ino = ino;
79825927e06SAlan Somers 		out.body.attr.attr.mode = S_IFDIR | 0777;	// Changed!
79925927e06SAlan Somers 		out.body.attr.attr.uid = newuser;
80025927e06SAlan Somers 	})));
80125927e06SAlan Somers 	// We should reclaim stale vnodes
80225927e06SAlan Somers 	expect_forget(ino, 1, &sem);
80325927e06SAlan Somers 
80425927e06SAlan Somers 	EXPECT_NE(0, chown(FULLPATH, newuser, -1));
80525927e06SAlan Somers 	EXPECT_EQ(ENOENT, errno);
80625927e06SAlan Somers 
80725927e06SAlan Somers 	sem_wait(&sem);
80825927e06SAlan Somers 	sem_destroy(&sem);
80925927e06SAlan Somers }
81025927e06SAlan Somers 
811666f8543SAlan Somers /* On a read-only mount, no attributes may be changed */
TEST_F(RofsSetattr,erofs)812666f8543SAlan Somers TEST_F(RofsSetattr, erofs)
813666f8543SAlan Somers {
814666f8543SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
815666f8543SAlan Somers 	const char RELPATH[] = "some_file.txt";
816666f8543SAlan Somers 	const uint64_t ino = 42;
817666f8543SAlan Somers 	const mode_t oldmode = 0755;
818666f8543SAlan Somers 	const mode_t newmode = 0644;
819666f8543SAlan Somers 
820a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
82129edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
822666f8543SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
82329edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | oldmode;
82429edc611SAlan Somers 		out.body.entry.nodeid = ino;
825666f8543SAlan Somers 	})));
826666f8543SAlan Somers 
827666f8543SAlan Somers 	ASSERT_EQ(-1, chmod(FULLPATH, newmode));
828666f8543SAlan Somers 	ASSERT_EQ(EROFS, errno);
829666f8543SAlan Somers }
83016bd2d47SAlan Somers 
83116bd2d47SAlan Somers /* Change the mode of a file */
TEST_F(Setattr_7_8,chmod)83216bd2d47SAlan Somers TEST_F(Setattr_7_8, chmod)
83316bd2d47SAlan Somers {
83416bd2d47SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
83516bd2d47SAlan Somers 	const char RELPATH[] = "some_file.txt";
83616bd2d47SAlan Somers 	const uint64_t ino = 42;
83716bd2d47SAlan Somers 	const mode_t oldmode = 0755;
83816bd2d47SAlan Somers 	const mode_t newmode = 0644;
83916bd2d47SAlan Somers 
840a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
84129edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
84216bd2d47SAlan Somers 		SET_OUT_HEADER_LEN(out, entry_7_8);
84329edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | oldmode;
84429edc611SAlan Somers 		out.body.entry.nodeid = ino;
84516bd2d47SAlan Somers 	})));
84616bd2d47SAlan Somers 
84716bd2d47SAlan Somers 	EXPECT_CALL(*m_mock, process(
84816bd2d47SAlan Somers 		ResultOf([](auto in) {
84916bd2d47SAlan Somers 			uint32_t valid = FATTR_MODE;
85029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
85129edc611SAlan Somers 				in.header.nodeid == ino &&
85229edc611SAlan Somers 				in.body.setattr.valid == valid &&
85329edc611SAlan Somers 				in.body.setattr.mode == newmode);
85416bd2d47SAlan Somers 		}, Eq(true)),
85516bd2d47SAlan Somers 		_)
85629edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
85716bd2d47SAlan Somers 		SET_OUT_HEADER_LEN(out, attr_7_8);
85829edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
85929edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
86016bd2d47SAlan Somers 	})));
86116bd2d47SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
86216bd2d47SAlan Somers }
863