xref: /freebsd/tests/sys/fs/fusefs/default_permissions.cc (revision 89d57b94d776877f77cc04752e449dac57a14618)
19821f1d3SAlan Somers /*-
29821f1d3SAlan Somers  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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.
291fa8ebfbSAlan Somers  *
301fa8ebfbSAlan Somers  * $FreeBSD$
319821f1d3SAlan Somers  */
329821f1d3SAlan Somers 
339821f1d3SAlan Somers /*
349821f1d3SAlan Somers  * Tests for the "default_permissions" mount option.  They must be in their own
359821f1d3SAlan Somers  * file so they can be run as an unprivileged user
369821f1d3SAlan Somers  */
379821f1d3SAlan Somers 
389821f1d3SAlan Somers extern "C" {
39ff4fbdf5SAlan Somers #include <sys/types.h>
40ff4fbdf5SAlan Somers #include <sys/extattr.h>
41ff4fbdf5SAlan Somers 
429821f1d3SAlan Somers #include <fcntl.h>
43331884f2SAlan Somers #include <semaphore.h>
449821f1d3SAlan Somers #include <unistd.h>
459821f1d3SAlan Somers }
469821f1d3SAlan Somers 
479821f1d3SAlan Somers #include "mockfs.hh"
489821f1d3SAlan Somers #include "utils.hh"
499821f1d3SAlan Somers 
509821f1d3SAlan Somers using namespace testing;
519821f1d3SAlan Somers 
529821f1d3SAlan Somers class DefaultPermissions: public FuseTest {
539821f1d3SAlan Somers 
549821f1d3SAlan Somers virtual void SetUp() {
55ff4fbdf5SAlan Somers 	m_default_permissions = true;
569821f1d3SAlan Somers 	FuseTest::SetUp();
57ff4fbdf5SAlan Somers 	if (HasFatalFailure() || IsSkipped())
58ff4fbdf5SAlan Somers 		return;
599821f1d3SAlan Somers 
609821f1d3SAlan Somers 	if (geteuid() == 0) {
619821f1d3SAlan Somers 		GTEST_SKIP() << "This test requires an unprivileged user";
629821f1d3SAlan Somers 	}
63ff4fbdf5SAlan Somers 
64ff4fbdf5SAlan Somers 	/* With -o default_permissions, FUSE_ACCESS should never be called */
65ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
66ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
6729edc611SAlan Somers 			return (in.header.opcode == FUSE_ACCESS);
68ff4fbdf5SAlan Somers 		}, Eq(true)),
69ff4fbdf5SAlan Somers 		_)
70ff4fbdf5SAlan Somers 	).Times(0);
719821f1d3SAlan Somers }
729821f1d3SAlan Somers 
739821f1d3SAlan Somers public:
7418a2264eSAlan Somers void expect_chmod(uint64_t ino, mode_t mode, uint64_t size = 0)
75a90e32deSAlan Somers {
76a90e32deSAlan Somers 	EXPECT_CALL(*m_mock, process(
77a90e32deSAlan Somers 		ResultOf([=](auto in) {
7829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
7929edc611SAlan Somers 				in.header.nodeid == ino &&
8029edc611SAlan Somers 				in.body.setattr.valid == FATTR_MODE &&
8129edc611SAlan Somers 				in.body.setattr.mode == mode);
82a90e32deSAlan Somers 		}, Eq(true)),
83a90e32deSAlan Somers 		_)
8429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
85a90e32deSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
8629edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
8729edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
8829edc611SAlan Somers 		out.body.attr.attr.size = size;
8929edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
90a90e32deSAlan Somers 	})));
91a90e32deSAlan Somers }
92a90e32deSAlan Somers 
933fa12789SAlan Somers void expect_create(const char *relpath, uint64_t ino)
943fa12789SAlan Somers {
953fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
963fa12789SAlan Somers 		ResultOf([=](auto in) {
9729edc611SAlan Somers 			const char *name = (const char*)in.body.bytes +
98a4856c96SAlan Somers 				sizeof(fuse_create_in);
9929edc611SAlan Somers 			return (in.header.opcode == FUSE_CREATE &&
1003fa12789SAlan Somers 				(0 == strcmp(relpath, name)));
1013fa12789SAlan Somers 		}, Eq(true)),
1023fa12789SAlan Somers 		_)
10329edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
1043fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, create);
10529edc611SAlan Somers 		out.body.create.entry.attr.mode = S_IFREG | 0644;
10629edc611SAlan Somers 		out.body.create.entry.nodeid = ino;
10729edc611SAlan Somers 		out.body.create.entry.entry_valid = UINT64_MAX;
10829edc611SAlan Somers 		out.body.create.entry.attr_valid = UINT64_MAX;
1093fa12789SAlan Somers 	})));
1103fa12789SAlan Somers }
1113fa12789SAlan Somers 
11292bbfe1fSAlan Somers void expect_copy_file_range(uint64_t ino_in, uint64_t off_in, uint64_t ino_out,
11392bbfe1fSAlan Somers     uint64_t off_out, uint64_t len)
11492bbfe1fSAlan Somers {
11592bbfe1fSAlan Somers 	EXPECT_CALL(*m_mock, process(
11692bbfe1fSAlan Somers 		ResultOf([=](auto in) {
11792bbfe1fSAlan Somers 			return (in.header.opcode == FUSE_COPY_FILE_RANGE &&
11892bbfe1fSAlan Somers 				in.header.nodeid == ino_in &&
11992bbfe1fSAlan Somers 				in.body.copy_file_range.off_in == off_in &&
12092bbfe1fSAlan Somers 				in.body.copy_file_range.nodeid_out == ino_out &&
12192bbfe1fSAlan Somers 				in.body.copy_file_range.off_out == off_out &&
12292bbfe1fSAlan Somers 				in.body.copy_file_range.len == len);
12392bbfe1fSAlan Somers 		}, Eq(true)),
12492bbfe1fSAlan Somers 		_)
12592bbfe1fSAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
12692bbfe1fSAlan Somers 		SET_OUT_HEADER_LEN(out, write);
12792bbfe1fSAlan Somers 		out.body.write.size = len;
12892bbfe1fSAlan Somers 	})));
12992bbfe1fSAlan Somers }
13092bbfe1fSAlan Somers 
131ff4fbdf5SAlan Somers void expect_getattr(uint64_t ino, mode_t mode, uint64_t attr_valid, int times,
132474ba6faSAlan Somers 	uid_t uid = 0, gid_t gid = 0)
1339821f1d3SAlan Somers {
134ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
135ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
13629edc611SAlan Somers 			return (in.header.opcode == FUSE_GETATTR &&
13729edc611SAlan Somers 				in.header.nodeid == ino);
138ff4fbdf5SAlan Somers 		}, Eq(true)),
139ff4fbdf5SAlan Somers 		_)
140ff4fbdf5SAlan Somers 	).Times(times)
14129edc611SAlan Somers 	.WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
142ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
14329edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
14429edc611SAlan Somers 		out.body.attr.attr.mode = mode;
14529edc611SAlan Somers 		out.body.attr.attr.size = 0;
14629edc611SAlan Somers 		out.body.attr.attr.uid = uid;
147a22a7807SAlan Somers 		out.body.attr.attr.gid = gid;
14829edc611SAlan Somers 		out.body.attr.attr_valid = attr_valid;
149ff4fbdf5SAlan Somers 	})));
150ff4fbdf5SAlan Somers }
151ff4fbdf5SAlan Somers 
152ff4fbdf5SAlan Somers void expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
153474ba6faSAlan Somers 	uint64_t attr_valid, uid_t uid = 0, gid_t gid = 0)
154ff4fbdf5SAlan Somers {
155474ba6faSAlan Somers 	FuseTest::expect_lookup(relpath, ino, mode, 0, 1, attr_valid, uid, gid);
1569821f1d3SAlan Somers }
1579821f1d3SAlan Somers 
1589821f1d3SAlan Somers };
1599821f1d3SAlan Somers 
1609821f1d3SAlan Somers class Access: public DefaultPermissions {};
161474ba6faSAlan Somers class Chown: public DefaultPermissions {};
162474ba6faSAlan Somers class Chgrp: public DefaultPermissions {};
16392bbfe1fSAlan Somers class CopyFileRange: public DefaultPermissions {};
164*89d57b94SAlan Somers class Fspacectl: public DefaultPermissions {};
165ff4fbdf5SAlan Somers class Lookup: public DefaultPermissions {};
1669821f1d3SAlan Somers class Open: public DefaultPermissions {};
167398c88c7SAlan Somers class PosixFallocate: public DefaultPermissions {};
168ff4fbdf5SAlan Somers class Setattr: public DefaultPermissions {};
169ff4fbdf5SAlan Somers class Unlink: public DefaultPermissions {};
170d943c93eSAlan Somers class Utimensat: public DefaultPermissions {};
171a90e32deSAlan Somers class Write: public DefaultPermissions {};
1729821f1d3SAlan Somers 
173ff4fbdf5SAlan Somers /*
174ff4fbdf5SAlan Somers  * Test permission handling during create, mkdir, mknod, link, symlink, and
175ff4fbdf5SAlan Somers  * rename vops (they all share a common path for permission checks in
176ff4fbdf5SAlan Somers  * VOP_LOOKUP)
177ff4fbdf5SAlan Somers  */
1783fa12789SAlan Somers class Create: public DefaultPermissions {};
179ff4fbdf5SAlan Somers 
180ff4fbdf5SAlan Somers class Deleteextattr: public DefaultPermissions {
181ff4fbdf5SAlan Somers public:
182ff4fbdf5SAlan Somers void expect_removexattr()
183ff4fbdf5SAlan Somers {
184ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
185ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
18629edc611SAlan Somers 			return (in.header.opcode == FUSE_REMOVEXATTR);
187ff4fbdf5SAlan Somers 		}, Eq(true)),
188ff4fbdf5SAlan Somers 		_)
189ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(0)));
190ff4fbdf5SAlan Somers }
191ff4fbdf5SAlan Somers };
192ff4fbdf5SAlan Somers 
193ff4fbdf5SAlan Somers class Getextattr: public DefaultPermissions {
194ff4fbdf5SAlan Somers public:
195ff4fbdf5SAlan Somers void expect_getxattr(ProcessMockerT r)
196ff4fbdf5SAlan Somers {
197ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
198ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
19929edc611SAlan Somers 			return (in.header.opcode == FUSE_GETXATTR);
200ff4fbdf5SAlan Somers 		}, Eq(true)),
201ff4fbdf5SAlan Somers 		_)
202ff4fbdf5SAlan Somers 	).WillOnce(Invoke(r));
203ff4fbdf5SAlan Somers }
204ff4fbdf5SAlan Somers };
205ff4fbdf5SAlan Somers 
206ff4fbdf5SAlan Somers class Listextattr: public DefaultPermissions {
207ff4fbdf5SAlan Somers public:
208ff4fbdf5SAlan Somers void expect_listxattr()
209ff4fbdf5SAlan Somers {
210ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
211ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
21229edc611SAlan Somers 			return (in.header.opcode == FUSE_LISTXATTR);
213ff4fbdf5SAlan Somers 		}, Eq(true)),
214ff4fbdf5SAlan Somers 		_)
21529edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) {
21629edc611SAlan Somers 		out.body.listxattr.size = 0;
217ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, listxattr);
218ff4fbdf5SAlan Somers 	})));
219ff4fbdf5SAlan Somers }
220ff4fbdf5SAlan Somers };
221ff4fbdf5SAlan Somers 
222ff4fbdf5SAlan Somers class Rename: public DefaultPermissions {
223ff4fbdf5SAlan Somers public:
224ff4fbdf5SAlan Somers 	/*
225ff4fbdf5SAlan Somers 	 * Expect a rename and respond with the given error.  Don't both to
226ff4fbdf5SAlan Somers 	 * validate arguments; the tests in rename.cc do that.
227ff4fbdf5SAlan Somers 	 */
228ff4fbdf5SAlan Somers 	void expect_rename(int error)
229ff4fbdf5SAlan Somers 	{
230ff4fbdf5SAlan Somers 		EXPECT_CALL(*m_mock, process(
231ff4fbdf5SAlan Somers 			ResultOf([=](auto in) {
23229edc611SAlan Somers 				return (in.header.opcode == FUSE_RENAME);
233ff4fbdf5SAlan Somers 			}, Eq(true)),
234ff4fbdf5SAlan Somers 			_)
235ff4fbdf5SAlan Somers 		).WillOnce(Invoke(ReturnErrno(error)));
236ff4fbdf5SAlan Somers 	}
237ff4fbdf5SAlan Somers };
238ff4fbdf5SAlan Somers 
239ff4fbdf5SAlan Somers class Setextattr: public DefaultPermissions {
240ff4fbdf5SAlan Somers public:
241ff4fbdf5SAlan Somers void expect_setxattr(int error)
242ff4fbdf5SAlan Somers {
243ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
244ff4fbdf5SAlan Somers 		ResultOf([=](auto in) {
24529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETXATTR);
246ff4fbdf5SAlan Somers 		}, Eq(true)),
247ff4fbdf5SAlan Somers 		_)
248ff4fbdf5SAlan Somers 	).WillOnce(Invoke(ReturnErrno(error)));
249ff4fbdf5SAlan Somers }
250ff4fbdf5SAlan Somers };
251ff4fbdf5SAlan Somers 
2528cfb4431SAlan Somers /* Return a group to which this user does not belong */
2538cfb4431SAlan Somers static gid_t excluded_group()
2548cfb4431SAlan Somers {
2558cfb4431SAlan Somers 	int i, ngroups = 64;
2568cfb4431SAlan Somers 	gid_t newgid, groups[ngroups];
2578cfb4431SAlan Somers 
2588cfb4431SAlan Somers 	getgrouplist(getlogin(), getegid(), groups, &ngroups);
2595a0b9a27SAlan Somers 	for (newgid = 0; ; newgid++) {
2608cfb4431SAlan Somers 		bool belongs = false;
2618cfb4431SAlan Somers 
2628cfb4431SAlan Somers 		for (i = 0; i < ngroups; i++) {
2638cfb4431SAlan Somers 			if (groups[i] == newgid)
2648cfb4431SAlan Somers 				belongs = true;
2658cfb4431SAlan Somers 		}
2668cfb4431SAlan Somers 		if (!belongs)
2678cfb4431SAlan Somers 			break;
2688cfb4431SAlan Somers 	}
2698cfb4431SAlan Somers 	/* newgid is now a group to which the current user does not belong */
2708cfb4431SAlan Somers 	return newgid;
2718cfb4431SAlan Somers }
2728cfb4431SAlan Somers 
273ff4fbdf5SAlan Somers TEST_F(Access, eacces)
2749821f1d3SAlan Somers {
2759821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
2769821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
2779821f1d3SAlan Somers 	uint64_t ino = 42;
2789821f1d3SAlan Somers 	mode_t	access_mode = X_OK;
2799821f1d3SAlan Somers 
280a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
281ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
282ff4fbdf5SAlan Somers 
283ff4fbdf5SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
284ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
285ff4fbdf5SAlan Somers }
286ff4fbdf5SAlan Somers 
287ff4fbdf5SAlan Somers TEST_F(Access, eacces_no_cached_attrs)
288ff4fbdf5SAlan Somers {
289ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
290ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
291ff4fbdf5SAlan Somers 	uint64_t ino = 42;
292ff4fbdf5SAlan Somers 	mode_t	access_mode = X_OK;
293ff4fbdf5SAlan Somers 
294a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, 0, 1);
295ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, 0);
296ff4fbdf5SAlan Somers 	expect_getattr(ino, S_IFREG | 0644, 0, 1);
2979821f1d3SAlan Somers 	/*
2989821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
2999821f1d3SAlan Somers 	 * another FUSE_GETATTR or something in here.  But there should not be
3009821f1d3SAlan Somers 	 * a FUSE_ACCESS
3019821f1d3SAlan Somers 	 */
3029821f1d3SAlan Somers 
3039821f1d3SAlan Somers 	ASSERT_NE(0, access(FULLPATH, access_mode));
3049821f1d3SAlan Somers 	ASSERT_EQ(EACCES, errno);
3059821f1d3SAlan Somers }
3069821f1d3SAlan Somers 
307ff4fbdf5SAlan Somers TEST_F(Access, ok)
3089821f1d3SAlan Somers {
3099821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3109821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
3119821f1d3SAlan Somers 	uint64_t ino = 42;
3129821f1d3SAlan Somers 	mode_t	access_mode = R_OK;
3139821f1d3SAlan Somers 
314a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
315ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
3169821f1d3SAlan Somers 	/*
3179821f1d3SAlan Somers 	 * Once default_permissions is properly implemented, there might be
31891ff3a0dSAlan Somers 	 * another FUSE_GETATTR or something in here.
3199821f1d3SAlan Somers 	 */
3209821f1d3SAlan Somers 
3219821f1d3SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, access_mode)) << strerror(errno);
3229821f1d3SAlan Somers }
3239821f1d3SAlan Somers 
3244e83d655SAlan Somers /* Unprivileged users may chown a file to their own uid */
3254e83d655SAlan Somers TEST_F(Chown, chown_to_self)
3264e83d655SAlan Somers {
3274e83d655SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
3284e83d655SAlan Somers 	const char RELPATH[] = "some_file.txt";
3294e83d655SAlan Somers 	const uint64_t ino = 42;
3304e83d655SAlan Somers 	const mode_t mode = 0755;
3314e83d655SAlan Somers 	uid_t uid;
3324e83d655SAlan Somers 
3334e83d655SAlan Somers 	uid = geteuid();
3344e83d655SAlan Somers 
335a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
3364e83d655SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid);
3374e83d655SAlan Somers 	/* The OS may optimize chown by omitting the redundant setattr */
3384e83d655SAlan Somers 	EXPECT_CALL(*m_mock, process(
3394e83d655SAlan Somers 		ResultOf([](auto in) {
34029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
3414e83d655SAlan Somers 		}, Eq(true)),
3424e83d655SAlan Somers 		_)
34329edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
3444e83d655SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
34529edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
34629edc611SAlan Somers 		out.body.attr.attr.uid = uid;
3474e83d655SAlan Somers 	})));
3484e83d655SAlan Somers 
3494e83d655SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
3504e83d655SAlan Somers }
3514e83d655SAlan Somers 
352a2bdd737SAlan Somers /*
353a2bdd737SAlan Somers  * A successful chown by a non-privileged non-owner should clear a file's SUID
354a2bdd737SAlan Somers  * bit
355a2bdd737SAlan Somers  */
356a2bdd737SAlan Somers TEST_F(Chown, clear_suid)
357a2bdd737SAlan Somers {
358a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
359a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
360a2bdd737SAlan Somers 	uint64_t ino = 42;
361a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
362a2bdd737SAlan Somers 	const mode_t newmode = 0755;
363a2bdd737SAlan Somers 	uid_t uid = geteuid();
364a2bdd737SAlan Somers 	uint32_t valid = FATTR_UID | FATTR_MODE;
365a2bdd737SAlan Somers 
366a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
367a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid);
368a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
369a2bdd737SAlan Somers 		ResultOf([=](auto in) {
37029edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
37129edc611SAlan Somers 				in.header.nodeid == ino &&
37229edc611SAlan Somers 				in.body.setattr.valid == valid &&
37329edc611SAlan Somers 				in.body.setattr.mode == newmode);
374a2bdd737SAlan Somers 		}, Eq(true)),
375a2bdd737SAlan Somers 		_)
37629edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
377a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
37829edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
37929edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
38029edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
381a2bdd737SAlan Somers 	})));
382a2bdd737SAlan Somers 
383a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, uid, -1)) << strerror(errno);
384a2bdd737SAlan Somers }
385a2bdd737SAlan Somers 
386a2bdd737SAlan Somers 
387474ba6faSAlan Somers /* Only root may change a file's owner */
388474ba6faSAlan Somers TEST_F(Chown, eperm)
389474ba6faSAlan Somers {
390474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
391474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
392474ba6faSAlan Somers 	const uint64_t ino = 42;
393474ba6faSAlan Somers 	const mode_t mode = 0755;
394474ba6faSAlan Somers 
395a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, geteuid());
396474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, geteuid());
397474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
398474ba6faSAlan Somers 		ResultOf([](auto in) {
39929edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
400474ba6faSAlan Somers 		}, Eq(true)),
401474ba6faSAlan Somers 		_)
402474ba6faSAlan Somers 	).Times(0);
403474ba6faSAlan Somers 
404474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, 0, -1));
405474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
406474ba6faSAlan Somers }
407474ba6faSAlan Somers 
408a2bdd737SAlan Somers /*
409a2bdd737SAlan Somers  * A successful chgrp by a non-privileged non-owner should clear a file's SUID
410a2bdd737SAlan Somers  * bit
411a2bdd737SAlan Somers  */
412a2bdd737SAlan Somers TEST_F(Chgrp, clear_suid)
413a2bdd737SAlan Somers {
414a2bdd737SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
415a2bdd737SAlan Somers 	const char RELPATH[] = "some_file.txt";
416a2bdd737SAlan Somers 	uint64_t ino = 42;
417a2bdd737SAlan Somers 	const mode_t oldmode = 06755;
418a2bdd737SAlan Somers 	const mode_t newmode = 0755;
419a2bdd737SAlan Somers 	uid_t uid = geteuid();
420a2bdd737SAlan Somers 	gid_t gid = getegid();
421a2bdd737SAlan Somers 	uint32_t valid = FATTR_GID | FATTR_MODE;
422a2bdd737SAlan Somers 
423a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid);
424a2bdd737SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
425a2bdd737SAlan Somers 	EXPECT_CALL(*m_mock, process(
426a2bdd737SAlan Somers 		ResultOf([=](auto in) {
42729edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
42829edc611SAlan Somers 				in.header.nodeid == ino &&
42929edc611SAlan Somers 				in.body.setattr.valid == valid &&
43029edc611SAlan Somers 				in.body.setattr.mode == newmode);
431a2bdd737SAlan Somers 		}, Eq(true)),
432a2bdd737SAlan Somers 		_)
43329edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
434a2bdd737SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
43529edc611SAlan Somers 		out.body.attr.attr.ino = ino;	// Must match nodeid
43629edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
43729edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
438a2bdd737SAlan Somers 	})));
439a2bdd737SAlan Somers 
440a2bdd737SAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, gid)) << strerror(errno);
441a2bdd737SAlan Somers }
442a2bdd737SAlan Somers 
443474ba6faSAlan Somers /* non-root users may only chgrp a file to a group they belong to */
444474ba6faSAlan Somers TEST_F(Chgrp, eperm)
445474ba6faSAlan Somers {
446474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
447474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
448474ba6faSAlan Somers 	const uint64_t ino = 42;
449474ba6faSAlan Somers 	const mode_t mode = 0755;
450474ba6faSAlan Somers 	uid_t uid;
451474ba6faSAlan Somers 	gid_t gid, newgid;
452474ba6faSAlan Somers 
453474ba6faSAlan Somers 	uid = geteuid();
454474ba6faSAlan Somers 	gid = getegid();
4558cfb4431SAlan Somers 	newgid = excluded_group();
456474ba6faSAlan Somers 
457a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
458474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
459474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
460474ba6faSAlan Somers 		ResultOf([](auto in) {
46129edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
462474ba6faSAlan Somers 		}, Eq(true)),
463474ba6faSAlan Somers 		_)
464474ba6faSAlan Somers 	).Times(0);
465474ba6faSAlan Somers 
466474ba6faSAlan Somers 	EXPECT_NE(0, chown(FULLPATH, -1, newgid));
467474ba6faSAlan Somers 	EXPECT_EQ(EPERM, errno);
468474ba6faSAlan Somers }
469474ba6faSAlan Somers 
470474ba6faSAlan Somers TEST_F(Chgrp, ok)
471474ba6faSAlan Somers {
472474ba6faSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
473474ba6faSAlan Somers 	const char RELPATH[] = "some_file.txt";
474474ba6faSAlan Somers 	const uint64_t ino = 42;
475474ba6faSAlan Somers 	const mode_t mode = 0755;
476474ba6faSAlan Somers 	uid_t uid;
477474ba6faSAlan Somers 	gid_t gid, newgid;
478474ba6faSAlan Somers 
479474ba6faSAlan Somers 	uid = geteuid();
480474ba6faSAlan Somers 	gid = 0;
481474ba6faSAlan Somers 	newgid = getegid();
482474ba6faSAlan Somers 
483a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, uid, gid);
484474ba6faSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, uid, gid);
4854e83d655SAlan Somers 	/* The OS may optimize chgrp by omitting the redundant setattr */
486474ba6faSAlan Somers 	EXPECT_CALL(*m_mock, process(
487474ba6faSAlan Somers 		ResultOf([](auto in) {
48829edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
48929edc611SAlan Somers 				in.header.nodeid == ino);
490474ba6faSAlan Somers 		}, Eq(true)),
491474ba6faSAlan Somers 		_)
49229edc611SAlan Somers 	).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out){
493474ba6faSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
49429edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
49529edc611SAlan Somers 		out.body.attr.attr.uid = uid;
49629edc611SAlan Somers 		out.body.attr.attr.gid = newgid;
497474ba6faSAlan Somers 	})));
498474ba6faSAlan Somers 
499474ba6faSAlan Somers 	EXPECT_EQ(0, chown(FULLPATH, -1, newgid)) << strerror(errno);
500474ba6faSAlan Somers }
501474ba6faSAlan Somers 
50292bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
503398c88c7SAlan Somers TEST_F(CopyFileRange, clear_sgid)
50492bbfe1fSAlan Somers {
50592bbfe1fSAlan Somers 	const char FULLPATH_IN[] = "mountpoint/in.txt";
50692bbfe1fSAlan Somers 	const char RELPATH_IN[] = "in.txt";
50792bbfe1fSAlan Somers 	const char FULLPATH_OUT[] = "mountpoint/out.txt";
50892bbfe1fSAlan Somers 	const char RELPATH_OUT[] = "out.txt";
50992bbfe1fSAlan Somers 	struct stat sb;
51092bbfe1fSAlan Somers 	uint64_t ino_in = 42;
51192bbfe1fSAlan Somers 	uint64_t ino_out = 43;
51292bbfe1fSAlan Somers 	mode_t oldmode = 02777;
51392bbfe1fSAlan Somers 	mode_t newmode = 0777;
51492bbfe1fSAlan Somers 	off_t fsize = 16;
51592bbfe1fSAlan Somers 	off_t off_in = 0;
51692bbfe1fSAlan Somers 	off_t off_out = 8;
51792bbfe1fSAlan Somers 	off_t len = 8;
51892bbfe1fSAlan Somers 	int fd_in, fd_out;
51992bbfe1fSAlan Somers 
52092bbfe1fSAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
52192bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
52292bbfe1fSAlan Somers 	    UINT64_MAX, 0, 0);
52392bbfe1fSAlan Somers 	expect_open(ino_in, 0, 1);
52492bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
52592bbfe1fSAlan Somers 	    1, UINT64_MAX, 0, 0);
52692bbfe1fSAlan Somers 	expect_open(ino_out, 0, 1);
52792bbfe1fSAlan Somers 	expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
52892bbfe1fSAlan Somers 	expect_chmod(ino_out, newmode, fsize);
52992bbfe1fSAlan Somers 
53092bbfe1fSAlan Somers 	fd_in = open(FULLPATH_IN, O_RDONLY);
53192bbfe1fSAlan Somers 	ASSERT_LE(0, fd_in) << strerror(errno);
53292bbfe1fSAlan Somers 	fd_out = open(FULLPATH_OUT, O_WRONLY);
53392bbfe1fSAlan Somers 	ASSERT_LE(0, fd_out) << strerror(errno);
53492bbfe1fSAlan Somers 	ASSERT_EQ(len,
53592bbfe1fSAlan Somers 	    copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
53692bbfe1fSAlan Somers 	    << strerror(errno);
53792bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
53892bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
53992bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
54092bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
54192bbfe1fSAlan Somers 
54292bbfe1fSAlan Somers 	leak(fd_in);
54392bbfe1fSAlan Somers 	leak(fd_out);
54492bbfe1fSAlan Somers }
54592bbfe1fSAlan Somers 
54692bbfe1fSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
54792bbfe1fSAlan Somers TEST_F(CopyFileRange, clear_suid)
54892bbfe1fSAlan Somers {
54992bbfe1fSAlan Somers 	const char FULLPATH_IN[] = "mountpoint/in.txt";
55092bbfe1fSAlan Somers 	const char RELPATH_IN[] = "in.txt";
55192bbfe1fSAlan Somers 	const char FULLPATH_OUT[] = "mountpoint/out.txt";
55292bbfe1fSAlan Somers 	const char RELPATH_OUT[] = "out.txt";
55392bbfe1fSAlan Somers 	struct stat sb;
55492bbfe1fSAlan Somers 	uint64_t ino_in = 42;
55592bbfe1fSAlan Somers 	uint64_t ino_out = 43;
55692bbfe1fSAlan Somers 	mode_t oldmode = 04777;
55792bbfe1fSAlan Somers 	mode_t newmode = 0777;
55892bbfe1fSAlan Somers 	off_t fsize = 16;
55992bbfe1fSAlan Somers 	off_t off_in = 0;
56092bbfe1fSAlan Somers 	off_t off_out = 8;
56192bbfe1fSAlan Somers 	off_t len = 8;
56292bbfe1fSAlan Somers 	int fd_in, fd_out;
56392bbfe1fSAlan Somers 
56492bbfe1fSAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
56592bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_IN, ino_in, S_IFREG | oldmode, fsize, 1,
56692bbfe1fSAlan Somers 	    UINT64_MAX, 0, 0);
56792bbfe1fSAlan Somers 	expect_open(ino_in, 0, 1);
56892bbfe1fSAlan Somers 	FuseTest::expect_lookup(RELPATH_OUT, ino_out, S_IFREG | oldmode, fsize,
56992bbfe1fSAlan Somers 	    1, UINT64_MAX, 0, 0);
57092bbfe1fSAlan Somers 	expect_open(ino_out, 0, 1);
57192bbfe1fSAlan Somers 	expect_copy_file_range(ino_in, off_in, ino_out, off_out, len);
57292bbfe1fSAlan Somers 	expect_chmod(ino_out, newmode, fsize);
57392bbfe1fSAlan Somers 
57492bbfe1fSAlan Somers 	fd_in = open(FULLPATH_IN, O_RDONLY);
57592bbfe1fSAlan Somers 	ASSERT_LE(0, fd_in) << strerror(errno);
57692bbfe1fSAlan Somers 	fd_out = open(FULLPATH_OUT, O_WRONLY);
57792bbfe1fSAlan Somers 	ASSERT_LE(0, fd_out) << strerror(errno);
57892bbfe1fSAlan Somers 	ASSERT_EQ(len,
57992bbfe1fSAlan Somers 	    copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0))
58092bbfe1fSAlan Somers 	    << strerror(errno);
58192bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_out, &sb)) << strerror(errno);
58292bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
58392bbfe1fSAlan Somers 	ASSERT_EQ(0, fstat(fd_in, &sb)) << strerror(errno);
58492bbfe1fSAlan Somers 	EXPECT_EQ(S_IFREG | oldmode, sb.st_mode);
58592bbfe1fSAlan Somers 
58692bbfe1fSAlan Somers 	leak(fd_in);
58792bbfe1fSAlan Somers 	leak(fd_out);
58892bbfe1fSAlan Somers }
58992bbfe1fSAlan Somers 
590ff4fbdf5SAlan Somers TEST_F(Create, ok)
591ff4fbdf5SAlan Somers {
592ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
593ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
594ff4fbdf5SAlan Somers 	uint64_t ino = 42;
595ff4fbdf5SAlan Somers 	int fd;
596ff4fbdf5SAlan Somers 
597a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
598a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
599a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
600ff4fbdf5SAlan Somers 	expect_create(RELPATH, ino);
601ff4fbdf5SAlan Somers 
602ff4fbdf5SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_EXCL, 0644);
603d2621689SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
6047fc0921dSAlan Somers 	leak(fd);
605ff4fbdf5SAlan Somers }
606ff4fbdf5SAlan Somers 
607ff4fbdf5SAlan Somers TEST_F(Create, eacces)
608ff4fbdf5SAlan Somers {
609ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
610ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
611ff4fbdf5SAlan Somers 
612a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
613a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
614a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
615ff4fbdf5SAlan Somers 
6168e765737SAlan Somers 	ASSERT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, 0644));
617ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
618ff4fbdf5SAlan Somers }
619ff4fbdf5SAlan Somers 
620ff4fbdf5SAlan Somers TEST_F(Deleteextattr, eacces)
621ff4fbdf5SAlan Somers {
622ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
623ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
624ff4fbdf5SAlan Somers 	uint64_t ino = 42;
625ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
626ff4fbdf5SAlan Somers 
627a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
628ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
629ff4fbdf5SAlan Somers 
630ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
631ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
632ff4fbdf5SAlan Somers }
633ff4fbdf5SAlan Somers 
634ff4fbdf5SAlan Somers TEST_F(Deleteextattr, ok)
635ff4fbdf5SAlan Somers {
636ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
637ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
638ff4fbdf5SAlan Somers 	uint64_t ino = 42;
639ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
640ff4fbdf5SAlan Somers 
641a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
642ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
643ff4fbdf5SAlan Somers 	expect_removexattr();
644ff4fbdf5SAlan Somers 
645ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
646ff4fbdf5SAlan Somers 		<< strerror(errno);
647ff4fbdf5SAlan Somers }
648ff4fbdf5SAlan Somers 
649ff4fbdf5SAlan Somers /* Delete system attributes requires superuser privilege */
650ff4fbdf5SAlan Somers TEST_F(Deleteextattr, system)
651ff4fbdf5SAlan Somers {
652ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
653ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
654ff4fbdf5SAlan Somers 	uint64_t ino = 42;
655ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
656ff4fbdf5SAlan Somers 
657a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
658ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
659ff4fbdf5SAlan Somers 
660ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_delete_file(FULLPATH, ns, "foo"));
661ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
662ff4fbdf5SAlan Somers }
663ff4fbdf5SAlan Somers 
664d943c93eSAlan Somers /* Anybody with write permission can set both timestamps to UTIME_NOW */
665d943c93eSAlan Somers TEST_F(Utimensat, utime_now)
666d943c93eSAlan Somers {
667d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
668d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
669d943c93eSAlan Somers 	const uint64_t ino = 42;
670d943c93eSAlan Somers 	/* Write permissions for everybody */
671d943c93eSAlan Somers 	const mode_t mode = 0666;
672d943c93eSAlan Somers 	uid_t owner = 0;
673d943c93eSAlan Somers 	const timespec times[2] = {
674d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
675d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_NOW},
676d943c93eSAlan Somers 	};
677d943c93eSAlan Somers 
678a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
679d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
680d943c93eSAlan Somers 	EXPECT_CALL(*m_mock, process(
681d943c93eSAlan Somers 		ResultOf([](auto in) {
68229edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
68329edc611SAlan Somers 				in.header.nodeid == ino &&
68429edc611SAlan Somers 				in.body.setattr.valid & FATTR_ATIME &&
68529edc611SAlan Somers 				in.body.setattr.valid & FATTR_MTIME);
686d943c93eSAlan Somers 		}, Eq(true)),
687d943c93eSAlan Somers 		_)
68829edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
689d943c93eSAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
69029edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
691d943c93eSAlan Somers 	})));
692d943c93eSAlan Somers 
693d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
694d943c93eSAlan Somers 		<< strerror(errno);
695d943c93eSAlan Somers }
696d943c93eSAlan Somers 
697d943c93eSAlan Somers /* Anybody can set both timestamps to UTIME_OMIT */
698d943c93eSAlan Somers TEST_F(Utimensat, utime_omit)
699d943c93eSAlan Somers {
700d943c93eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
701d943c93eSAlan Somers 	const char RELPATH[] = "some_file.txt";
702d943c93eSAlan Somers 	const uint64_t ino = 42;
703d943c93eSAlan Somers 	/* Write permissions for no one */
704d943c93eSAlan Somers 	const mode_t mode = 0444;
705d943c93eSAlan Somers 	uid_t owner = 0;
706d943c93eSAlan Somers 	const timespec times[2] = {
707d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
708d943c93eSAlan Somers 		{.tv_sec = 0, .tv_nsec = UTIME_OMIT},
709d943c93eSAlan Somers 	};
710d943c93eSAlan Somers 
711a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
712d943c93eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | mode, UINT64_MAX, owner);
713d943c93eSAlan Somers 
714d943c93eSAlan Somers 	ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &times[0], 0))
715d943c93eSAlan Somers 		<< strerror(errno);
716d943c93eSAlan Somers }
717d943c93eSAlan Somers 
718ff4fbdf5SAlan Somers /* Deleting user attributes merely requires WRITE privilege */
719ff4fbdf5SAlan Somers TEST_F(Deleteextattr, user)
720ff4fbdf5SAlan Somers {
721ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
722ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
723ff4fbdf5SAlan Somers 	uint64_t ino = 42;
724ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
725ff4fbdf5SAlan Somers 
726a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
727ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
728ff4fbdf5SAlan Somers 	expect_removexattr();
729ff4fbdf5SAlan Somers 
730ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_delete_file(FULLPATH, ns, "foo"))
731ff4fbdf5SAlan Somers 		<< strerror(errno);
732ff4fbdf5SAlan Somers }
733ff4fbdf5SAlan Somers 
734ff4fbdf5SAlan Somers TEST_F(Getextattr, eacces)
735ff4fbdf5SAlan Somers {
736ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
737ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
738ff4fbdf5SAlan Somers 	uint64_t ino = 42;
739ff4fbdf5SAlan Somers 	char data[80];
740ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
741ff4fbdf5SAlan Somers 
742a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
743ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
744ff4fbdf5SAlan Somers 
745ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
746ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
747ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
748ff4fbdf5SAlan Somers }
749ff4fbdf5SAlan Somers 
750ff4fbdf5SAlan Somers TEST_F(Getextattr, ok)
751ff4fbdf5SAlan Somers {
752ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
753ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
754ff4fbdf5SAlan Somers 	uint64_t ino = 42;
755ff4fbdf5SAlan Somers 	char data[80];
756ff4fbdf5SAlan Somers 	const char value[] = "whatever";
757ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
758ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
759ff4fbdf5SAlan Somers 	ssize_t r;
760ff4fbdf5SAlan Somers 
761a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
762ff4fbdf5SAlan Somers 	/* Getting user attributes only requires read access */
763ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0444, UINT64_MAX, 0);
764ff4fbdf5SAlan Somers 	expect_getxattr(
76529edc611SAlan Somers 		ReturnImmediate([&](auto in __unused, auto& out) {
76629edc611SAlan Somers 			memcpy((void*)out.body.bytes, value, value_len);
76729edc611SAlan Somers 			out.header.len = sizeof(out.header) + value_len;
768ff4fbdf5SAlan Somers 		})
769ff4fbdf5SAlan Somers 	);
770ff4fbdf5SAlan Somers 
771ff4fbdf5SAlan Somers 	r = extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data));
772ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r)  << strerror(errno);
773ff4fbdf5SAlan Somers 	EXPECT_STREQ(value, data);
774ff4fbdf5SAlan Somers }
775ff4fbdf5SAlan Somers 
776ff4fbdf5SAlan Somers /* Getting system attributes requires superuser privileges */
777ff4fbdf5SAlan Somers TEST_F(Getextattr, system)
778ff4fbdf5SAlan Somers {
779ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
780ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
781ff4fbdf5SAlan Somers 	uint64_t ino = 42;
782ff4fbdf5SAlan Somers 	char data[80];
783ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
784ff4fbdf5SAlan Somers 
785a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
786ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
787ff4fbdf5SAlan Somers 
788ff4fbdf5SAlan Somers 	ASSERT_EQ(-1,
789ff4fbdf5SAlan Somers 		extattr_get_file(FULLPATH, ns, "foo", data, sizeof(data)));
790ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
791ff4fbdf5SAlan Somers }
792ff4fbdf5SAlan Somers 
793ff4fbdf5SAlan Somers TEST_F(Listextattr, eacces)
794ff4fbdf5SAlan Somers {
795ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
796ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
797ff4fbdf5SAlan Somers 	uint64_t ino = 42;
798ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
799ff4fbdf5SAlan Somers 
800a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
801ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0600, UINT64_MAX, 0);
802ff4fbdf5SAlan Somers 
803ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
804ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
805ff4fbdf5SAlan Somers }
806ff4fbdf5SAlan Somers 
807ff4fbdf5SAlan Somers TEST_F(Listextattr, ok)
808ff4fbdf5SAlan Somers {
809ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
810ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
811ff4fbdf5SAlan Somers 	uint64_t ino = 42;
812ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
813ff4fbdf5SAlan Somers 
814a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
815ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
816ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
817ff4fbdf5SAlan Somers 	expect_listxattr();
818ff4fbdf5SAlan Somers 
819ff4fbdf5SAlan Somers 	ASSERT_EQ(0, extattr_list_file(FULLPATH, ns, NULL, 0))
820ff4fbdf5SAlan Somers 		<< strerror(errno);
821ff4fbdf5SAlan Somers }
822ff4fbdf5SAlan Somers 
823ff4fbdf5SAlan Somers /* Listing system xattrs requires superuser privileges */
824ff4fbdf5SAlan Somers TEST_F(Listextattr, system)
825ff4fbdf5SAlan Somers {
826ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
827ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
828ff4fbdf5SAlan Somers 	uint64_t ino = 42;
829ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
830ff4fbdf5SAlan Somers 
831a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
832ff4fbdf5SAlan Somers 	/* Listing user extended attributes merely requires read access */
833ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
834ff4fbdf5SAlan Somers 
835ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, extattr_list_file(FULLPATH, ns, NULL, 0));
836ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
837ff4fbdf5SAlan Somers }
838ff4fbdf5SAlan Somers 
839*89d57b94SAlan Somers /* A write by a non-owner should clear a file's SGID bit */
840*89d57b94SAlan Somers TEST_F(Fspacectl, clear_sgid)
841*89d57b94SAlan Somers {
842*89d57b94SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
843*89d57b94SAlan Somers 	const char RELPATH[] = "file.txt";
844*89d57b94SAlan Somers 	struct stat sb;
845*89d57b94SAlan Somers 	struct spacectl_range rqsr;
846*89d57b94SAlan Somers 	uint64_t ino = 42;
847*89d57b94SAlan Somers 	mode_t oldmode = 02777;
848*89d57b94SAlan Somers 	mode_t newmode = 0777;
849*89d57b94SAlan Somers 	off_t fsize = 16;
850*89d57b94SAlan Somers 	off_t off = 8;
851*89d57b94SAlan Somers 	off_t len = 8;
852*89d57b94SAlan Somers 	int fd;
853*89d57b94SAlan Somers 
854*89d57b94SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
855*89d57b94SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
856*89d57b94SAlan Somers 	    1, UINT64_MAX, 0, 0);
857*89d57b94SAlan Somers 	expect_open(ino, 0, 1);
858*89d57b94SAlan Somers 	expect_fallocate(ino, off, len,
859*89d57b94SAlan Somers 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
860*89d57b94SAlan Somers 	expect_chmod(ino, newmode, fsize);
861*89d57b94SAlan Somers 
862*89d57b94SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
863*89d57b94SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
864*89d57b94SAlan Somers 	rqsr.r_len = len;
865*89d57b94SAlan Somers 	rqsr.r_offset = off;
866*89d57b94SAlan Somers 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
867*89d57b94SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
868*89d57b94SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
869*89d57b94SAlan Somers 
870*89d57b94SAlan Somers 	leak(fd);
871*89d57b94SAlan Somers }
872*89d57b94SAlan Somers 
873*89d57b94SAlan Somers /* A write by a non-owner should clear a file's SUID bit */
874*89d57b94SAlan Somers TEST_F(Fspacectl, clear_suid)
875*89d57b94SAlan Somers {
876*89d57b94SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
877*89d57b94SAlan Somers 	const char RELPATH[] = "file.txt";
878*89d57b94SAlan Somers 	struct stat sb;
879*89d57b94SAlan Somers 	struct spacectl_range rqsr;
880*89d57b94SAlan Somers 	uint64_t ino = 42;
881*89d57b94SAlan Somers 	mode_t oldmode = 04777;
882*89d57b94SAlan Somers 	mode_t newmode = 0777;
883*89d57b94SAlan Somers 	off_t fsize = 16;
884*89d57b94SAlan Somers 	off_t off = 8;
885*89d57b94SAlan Somers 	off_t len = 8;
886*89d57b94SAlan Somers 	int fd;
887*89d57b94SAlan Somers 
888*89d57b94SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
889*89d57b94SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
890*89d57b94SAlan Somers 	    1, UINT64_MAX, 0, 0);
891*89d57b94SAlan Somers 	expect_open(ino, 0, 1);
892*89d57b94SAlan Somers 	expect_fallocate(ino, off, len,
893*89d57b94SAlan Somers 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
894*89d57b94SAlan Somers 	expect_chmod(ino, newmode, fsize);
895*89d57b94SAlan Somers 
896*89d57b94SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
897*89d57b94SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
898*89d57b94SAlan Somers 	rqsr.r_len = len;
899*89d57b94SAlan Somers 	rqsr.r_offset = off;
900*89d57b94SAlan Somers 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
901*89d57b94SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
902*89d57b94SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
903*89d57b94SAlan Somers 
904*89d57b94SAlan Somers 	leak(fd);
905*89d57b94SAlan Somers }
906*89d57b94SAlan Somers 
907*89d57b94SAlan Somers /*
908*89d57b94SAlan Somers  * fspacectl() of a file without writable permissions should succeed as
909*89d57b94SAlan Somers  * long as the file descriptor is writable.  This is important when combined
910*89d57b94SAlan Somers  * with O_CREAT
911*89d57b94SAlan Somers  */
912*89d57b94SAlan Somers TEST_F(Fspacectl, posix_fallocate_of_newly_created_file)
913*89d57b94SAlan Somers {
914*89d57b94SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
915*89d57b94SAlan Somers 	const char RELPATH[] = "some_file.txt";
916*89d57b94SAlan Somers 	struct spacectl_range rqsr;
917*89d57b94SAlan Somers 	const uint64_t ino = 42;
918*89d57b94SAlan Somers 	off_t off = 8;
919*89d57b94SAlan Somers 	off_t len = 8;
920*89d57b94SAlan Somers 	int fd;
921*89d57b94SAlan Somers 
922*89d57b94SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
923*89d57b94SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
924*89d57b94SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
925*89d57b94SAlan Somers 	expect_create(RELPATH, ino);
926*89d57b94SAlan Somers 	expect_fallocate(ino, off, len,
927*89d57b94SAlan Somers 		FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0);
928*89d57b94SAlan Somers 
929*89d57b94SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
930*89d57b94SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
931*89d57b94SAlan Somers 	rqsr.r_len = len;
932*89d57b94SAlan Somers 	rqsr.r_offset = off;
933*89d57b94SAlan Somers 	EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL));
934*89d57b94SAlan Somers 	leak(fd);
935*89d57b94SAlan Somers }
936*89d57b94SAlan Somers 
937ff4fbdf5SAlan Somers /* A component of the search path lacks execute permissions */
938ff4fbdf5SAlan Somers TEST_F(Lookup, eacces)
939ff4fbdf5SAlan Somers {
940ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_dir/some_file.txt";
941ff4fbdf5SAlan Somers 	const char RELDIRPATH[] = "some_dir";
942ff4fbdf5SAlan Somers 	uint64_t dir_ino = 42;
943ff4fbdf5SAlan Somers 
944a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
945ff4fbdf5SAlan Somers 	expect_lookup(RELDIRPATH, dir_ino, S_IFDIR | 0700, UINT64_MAX, 0);
946ff4fbdf5SAlan Somers 
947ff4fbdf5SAlan Somers 	EXPECT_EQ(-1, access(FULLPATH, F_OK));
948ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
949ff4fbdf5SAlan Somers }
950ff4fbdf5SAlan Somers 
951ff4fbdf5SAlan Somers TEST_F(Open, eacces)
952ff4fbdf5SAlan Somers {
953ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
954ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
955ff4fbdf5SAlan Somers 	uint64_t ino = 42;
956ff4fbdf5SAlan Somers 
957a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
958ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
959ff4fbdf5SAlan Somers 
9604ca1c0b7SAlan Somers 	EXPECT_EQ(-1, open(FULLPATH, O_RDWR));
961ff4fbdf5SAlan Somers 	EXPECT_EQ(EACCES, errno);
962ff4fbdf5SAlan Somers }
963ff4fbdf5SAlan Somers 
9649821f1d3SAlan Somers TEST_F(Open, ok)
9659821f1d3SAlan Somers {
9669821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
9679821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
9689821f1d3SAlan Somers 	uint64_t ino = 42;
9699821f1d3SAlan Somers 	int fd;
9709821f1d3SAlan Somers 
971a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
972ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX);
9739821f1d3SAlan Somers 	expect_open(ino, 0, 1);
9749821f1d3SAlan Somers 
9759821f1d3SAlan Somers 	fd = open(FULLPATH, O_RDONLY);
976d2621689SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
9777fc0921dSAlan Somers 	leak(fd);
9789821f1d3SAlan Somers }
9799821f1d3SAlan Somers 
980398c88c7SAlan Somers /* A write by a non-owner should clear a file's SGID bit */
981398c88c7SAlan Somers TEST_F(PosixFallocate, clear_sgid)
982398c88c7SAlan Somers {
983398c88c7SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
984398c88c7SAlan Somers 	const char RELPATH[] = "file.txt";
985398c88c7SAlan Somers 	struct stat sb;
986398c88c7SAlan Somers 	uint64_t ino = 42;
987398c88c7SAlan Somers 	mode_t oldmode = 02777;
988398c88c7SAlan Somers 	mode_t newmode = 0777;
989398c88c7SAlan Somers 	off_t fsize = 16;
990398c88c7SAlan Somers 	off_t off = 8;
991398c88c7SAlan Somers 	off_t len = 8;
992398c88c7SAlan Somers 	int fd;
993398c88c7SAlan Somers 
994398c88c7SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
995398c88c7SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
996398c88c7SAlan Somers 	    1, UINT64_MAX, 0, 0);
997398c88c7SAlan Somers 	expect_open(ino, 0, 1);
998398c88c7SAlan Somers 	expect_fallocate(ino, off, len, 0, 0);
999398c88c7SAlan Somers 	expect_chmod(ino, newmode, fsize);
1000398c88c7SAlan Somers 
1001398c88c7SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1002398c88c7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1003398c88c7SAlan Somers 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1004398c88c7SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1005398c88c7SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1006398c88c7SAlan Somers 
1007398c88c7SAlan Somers 	leak(fd);
1008398c88c7SAlan Somers }
1009398c88c7SAlan Somers 
1010398c88c7SAlan Somers /* A write by a non-owner should clear a file's SUID bit */
1011398c88c7SAlan Somers TEST_F(PosixFallocate, clear_suid)
1012398c88c7SAlan Somers {
1013398c88c7SAlan Somers 	const char FULLPATH[] = "mountpoint/file.txt";
1014398c88c7SAlan Somers 	const char RELPATH[] = "file.txt";
1015398c88c7SAlan Somers 	struct stat sb;
1016398c88c7SAlan Somers 	uint64_t ino = 42;
1017398c88c7SAlan Somers 	mode_t oldmode = 04777;
1018398c88c7SAlan Somers 	mode_t newmode = 0777;
1019398c88c7SAlan Somers 	off_t fsize = 16;
1020398c88c7SAlan Somers 	off_t off = 8;
1021398c88c7SAlan Somers 	off_t len = 8;
1022398c88c7SAlan Somers 	int fd;
1023398c88c7SAlan Somers 
1024398c88c7SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1025398c88c7SAlan Somers 	FuseTest::expect_lookup(RELPATH, ino, S_IFREG | oldmode, fsize,
1026398c88c7SAlan Somers 	    1, UINT64_MAX, 0, 0);
1027398c88c7SAlan Somers 	expect_open(ino, 0, 1);
1028398c88c7SAlan Somers 	expect_fallocate(ino, off, len, 0, 0);
1029398c88c7SAlan Somers 	expect_chmod(ino, newmode, fsize);
1030398c88c7SAlan Somers 
1031398c88c7SAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1032398c88c7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1033398c88c7SAlan Somers 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1034398c88c7SAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1035398c88c7SAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
1036398c88c7SAlan Somers 
1037398c88c7SAlan Somers 	leak(fd);
1038398c88c7SAlan Somers }
1039398c88c7SAlan Somers 
1040398c88c7SAlan Somers /*
1041*89d57b94SAlan Somers  * posix_fallocate() of a file without writable permissions should succeed as
1042398c88c7SAlan Somers  * long as the file descriptor is writable.  This is important when combined
1043398c88c7SAlan Somers  * with O_CREAT
1044398c88c7SAlan Somers  */
1045398c88c7SAlan Somers TEST_F(PosixFallocate, posix_fallocate_of_newly_created_file)
1046398c88c7SAlan Somers {
1047398c88c7SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1048398c88c7SAlan Somers 	const char RELPATH[] = "some_file.txt";
1049398c88c7SAlan Somers 	const uint64_t ino = 42;
1050398c88c7SAlan Somers 	off_t off = 8;
1051398c88c7SAlan Somers 	off_t len = 8;
1052398c88c7SAlan Somers 	int fd;
1053398c88c7SAlan Somers 
1054398c88c7SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1055398c88c7SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1056398c88c7SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1057398c88c7SAlan Somers 	expect_create(RELPATH, ino);
1058398c88c7SAlan Somers 	expect_fallocate(ino, off, len, 0, 0);
1059398c88c7SAlan Somers 
1060398c88c7SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
1061398c88c7SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1062398c88c7SAlan Somers 	EXPECT_EQ(0, posix_fallocate(fd, off, len)) << strerror(errno);
1063398c88c7SAlan Somers 	leak(fd);
1064398c88c7SAlan Somers }
1065398c88c7SAlan Somers 
1066ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_srcdir)
1067ff4fbdf5SAlan Somers {
1068ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1069ff4fbdf5SAlan Somers 	const char RELDST[] = "d/dst";
1070ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1071ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1072ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1073ff4fbdf5SAlan Somers 
1074a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1, 0);
1075ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1076a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1077ff4fbdf5SAlan Somers 		.Times(AnyNumber())
1078ff4fbdf5SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(ENOENT)));
1079ff4fbdf5SAlan Somers 
1080ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1081ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1082ff4fbdf5SAlan Somers }
1083ff4fbdf5SAlan Somers 
1084ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_creating)
1085ff4fbdf5SAlan Somers {
1086ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1087ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
1088ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1089ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1090ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1091ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
1092ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
1093ff4fbdf5SAlan Somers 
1094a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1095ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1096ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
1097ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1098ff4fbdf5SAlan Somers 
1099ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1100ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1101ff4fbdf5SAlan Somers }
1102ff4fbdf5SAlan Somers 
1103ff4fbdf5SAlan Somers TEST_F(Rename, eacces_on_dstdir_for_removing)
1104ff4fbdf5SAlan Somers {
1105ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1106ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
1107ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1108ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1109ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1110ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
1111ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
1112ff4fbdf5SAlan Somers 
1113a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1114ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1115ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0755, UINT64_MAX);
1116ff4fbdf5SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
1117ff4fbdf5SAlan Somers 
1118ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1119ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1120ff4fbdf5SAlan Somers }
1121ff4fbdf5SAlan Somers 
11226124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_srcdir)
1123ff4fbdf5SAlan Somers {
1124ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1125ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1126ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1127ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1128ff4fbdf5SAlan Somers 
1129a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1130ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1131ff4fbdf5SAlan Somers 
1132ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1133ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1134ff4fbdf5SAlan Somers }
1135ff4fbdf5SAlan Somers 
11368e45ec4eSAlan Somers /*
11378e45ec4eSAlan Somers  * A user cannot move out a subdirectory that he does not own, because that
11388e45ec4eSAlan Somers  * would require changing the subdirectory's ".." dirent
11398e45ec4eSAlan Somers  */
11408e45ec4eSAlan Somers TEST_F(Rename, eperm_for_subdirectory)
11418e45ec4eSAlan Somers {
11428e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
11438e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
11448e45ec4eSAlan Somers 	const char RELDSTDIR[] = "d";
11458e45ec4eSAlan Somers 	const char RELDST[] = "dst";
11468e45ec4eSAlan Somers 	const char RELSRC[] = "src";
11478e45ec4eSAlan Somers 	uint64_t ino = 42;
11488e45ec4eSAlan Somers 	uint64_t dstdir_ino = 43;
11498e45ec4eSAlan Somers 
1150a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
11518e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
11528e45ec4eSAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 0777, UINT64_MAX, 0);
11538e45ec4eSAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST).WillOnce(Invoke(ReturnErrno(ENOENT)));
11548e45ec4eSAlan Somers 
11558e45ec4eSAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
11568e45ec4eSAlan Somers 	ASSERT_EQ(EACCES, errno);
11578e45ec4eSAlan Somers }
11588e45ec4eSAlan Somers 
11598e45ec4eSAlan Somers /*
11608e45ec4eSAlan Somers  * A user _can_ rename a subdirectory to which he lacks write permissions, if
11618e45ec4eSAlan Somers  * it will keep the same parent
11628e45ec4eSAlan Somers  */
11638e45ec4eSAlan Somers TEST_F(Rename, subdirectory_to_same_dir)
11648e45ec4eSAlan Somers {
11658e45ec4eSAlan Somers 	const char FULLDST[] = "mountpoint/dst";
11668e45ec4eSAlan Somers 	const char FULLSRC[] = "mountpoint/src";
11678e45ec4eSAlan Somers 	const char RELDST[] = "dst";
11688e45ec4eSAlan Somers 	const char RELSRC[] = "src";
11698e45ec4eSAlan Somers 	uint64_t ino = 42;
11708e45ec4eSAlan Somers 
1171a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
11728e45ec4eSAlan Somers 	expect_lookup(RELSRC, ino, S_IFDIR | 0755, UINT64_MAX, 0);
1173a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1174a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
11758e45ec4eSAlan Somers 	expect_rename(0);
11768e45ec4eSAlan Somers 
11778e45ec4eSAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
11788e45ec4eSAlan Somers }
11798e45ec4eSAlan Somers 
11806124fd71SAlan Somers TEST_F(Rename, eperm_on_sticky_dstdir)
1181ff4fbdf5SAlan Somers {
1182ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/d/dst";
1183ff4fbdf5SAlan Somers 	const char RELDSTDIR[] = "d";
11846124fd71SAlan Somers 	const char RELDST[] = "dst";
1185ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1186ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1187ff4fbdf5SAlan Somers 	uint64_t src_ino = 42;
1188ff4fbdf5SAlan Somers 	uint64_t dstdir_ino = 43;
1189ff4fbdf5SAlan Somers 	uint64_t dst_ino = 44;
1190ff4fbdf5SAlan Somers 
1191a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, 0);
1192ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, src_ino, S_IFREG | 0644, UINT64_MAX);
1193ff4fbdf5SAlan Somers 	expect_lookup(RELDSTDIR, dstdir_ino, S_IFDIR | 01777, UINT64_MAX);
11946124fd71SAlan Somers 	EXPECT_LOOKUP(dstdir_ino, RELDST)
119529edc611SAlan Somers 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
11966124fd71SAlan Somers 		SET_OUT_HEADER_LEN(out, entry);
119729edc611SAlan Somers 		out.body.entry.attr.mode = S_IFREG | 0644;
119829edc611SAlan Somers 		out.body.entry.nodeid = dst_ino;
119929edc611SAlan Somers 		out.body.entry.attr_valid = UINT64_MAX;
120029edc611SAlan Somers 		out.body.entry.entry_valid = UINT64_MAX;
120129edc611SAlan Somers 		out.body.entry.attr.uid = 0;
12026124fd71SAlan Somers 	})));
1203ff4fbdf5SAlan Somers 
1204ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, rename(FULLSRC, FULLDST));
1205ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1206ff4fbdf5SAlan Somers }
1207ff4fbdf5SAlan Somers 
1208ff4fbdf5SAlan Somers /* Successfully rename a file, overwriting the destination */
1209ff4fbdf5SAlan Somers TEST_F(Rename, ok)
1210ff4fbdf5SAlan Somers {
1211ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1212ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1213ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1214ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1215ff4fbdf5SAlan Somers 	// The inode of the already-existing destination file
1216ff4fbdf5SAlan Somers 	uint64_t dst_ino = 2;
1217ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1218ff4fbdf5SAlan Somers 
1219a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1, geteuid());
1220ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX);
1221ff4fbdf5SAlan Somers 	expect_lookup(RELDST, dst_ino, S_IFREG | 0644, UINT64_MAX);
1222ff4fbdf5SAlan Somers 	expect_rename(0);
1223ff4fbdf5SAlan Somers 
1224ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1225ff4fbdf5SAlan Somers }
1226ff4fbdf5SAlan Somers 
1227ff4fbdf5SAlan Somers TEST_F(Rename, ok_to_remove_src_because_of_stickiness)
1228ff4fbdf5SAlan Somers {
1229ff4fbdf5SAlan Somers 	const char FULLDST[] = "mountpoint/dst";
1230ff4fbdf5SAlan Somers 	const char RELDST[] = "dst";
1231ff4fbdf5SAlan Somers 	const char FULLSRC[] = "mountpoint/src";
1232ff4fbdf5SAlan Somers 	const char RELSRC[] = "src";
1233ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1234ff4fbdf5SAlan Somers 
1235a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1, 0);
1236ff4fbdf5SAlan Somers 	expect_lookup(RELSRC, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1237a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDST)
1238a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
1239ff4fbdf5SAlan Somers 	expect_rename(0);
1240ff4fbdf5SAlan Somers 
1241ff4fbdf5SAlan Somers 	ASSERT_EQ(0, rename(FULLSRC, FULLDST)) << strerror(errno);
1242ff4fbdf5SAlan Somers }
1243ff4fbdf5SAlan Somers 
1244ff4fbdf5SAlan Somers TEST_F(Setattr, ok)
1245ff4fbdf5SAlan Somers {
1246ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1247ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1248ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
1249ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
1250ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
1251ff4fbdf5SAlan Somers 
1252a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1253ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1254ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
1255ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
125629edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
125729edc611SAlan Somers 				in.header.nodeid == ino &&
125829edc611SAlan Somers 				in.body.setattr.mode == newmode);
1259ff4fbdf5SAlan Somers 		}, Eq(true)),
1260ff4fbdf5SAlan Somers 		_)
126129edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) {
1262ff4fbdf5SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
126329edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | newmode;
1264ff4fbdf5SAlan Somers 	})));
1265ff4fbdf5SAlan Somers 
1266ff4fbdf5SAlan Somers 	EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno);
1267ff4fbdf5SAlan Somers }
1268ff4fbdf5SAlan Somers 
1269ff4fbdf5SAlan Somers TEST_F(Setattr, eacces)
1270ff4fbdf5SAlan Somers {
1271ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1272ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1273ff4fbdf5SAlan Somers 	const uint64_t ino = 42;
1274ff4fbdf5SAlan Somers 	const mode_t oldmode = 0755;
1275ff4fbdf5SAlan Somers 	const mode_t newmode = 0644;
1276ff4fbdf5SAlan Somers 
1277a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1278ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, 0);
1279ff4fbdf5SAlan Somers 	EXPECT_CALL(*m_mock, process(
1280ff4fbdf5SAlan Somers 		ResultOf([](auto in) {
128129edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1282ff4fbdf5SAlan Somers 		}, Eq(true)),
1283ff4fbdf5SAlan Somers 		_)
1284ff4fbdf5SAlan Somers 	).Times(0);
1285ff4fbdf5SAlan Somers 
1286ff4fbdf5SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1287ff4fbdf5SAlan Somers 	EXPECT_EQ(EPERM, errno);
1288ff4fbdf5SAlan Somers }
1289ff4fbdf5SAlan Somers 
12908cfb4431SAlan Somers /*
12913fa12789SAlan Somers  * ftruncate() of a file without writable permissions should succeed as long as
12923fa12789SAlan Somers  * the file descriptor is writable.  This is important when combined with
12933fa12789SAlan Somers  * O_CREAT
12943fa12789SAlan Somers  */
12953fa12789SAlan Somers TEST_F(Setattr, ftruncate_of_newly_created_file)
12963fa12789SAlan Somers {
12973fa12789SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
12983fa12789SAlan Somers 	const char RELPATH[] = "some_file.txt";
12993fa12789SAlan Somers 	const uint64_t ino = 42;
13003fa12789SAlan Somers 	const mode_t mode = 0000;
13013fa12789SAlan Somers 	int fd;
13023fa12789SAlan Somers 
1303a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1304a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
1305a34cdd26SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOENT)));
13063fa12789SAlan Somers 	expect_create(RELPATH, ino);
13073fa12789SAlan Somers 	EXPECT_CALL(*m_mock, process(
13083fa12789SAlan Somers 		ResultOf([](auto in) {
130929edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR &&
131029edc611SAlan Somers 				in.header.nodeid == ino &&
131129edc611SAlan Somers 				(in.body.setattr.valid & FATTR_SIZE));
13123fa12789SAlan Somers 		}, Eq(true)),
13133fa12789SAlan Somers 		_)
131429edc611SAlan Somers 	).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
13153fa12789SAlan Somers 		SET_OUT_HEADER_LEN(out, attr);
131629edc611SAlan Somers 		out.body.attr.attr.ino = ino;
131729edc611SAlan Somers 		out.body.attr.attr.mode = S_IFREG | mode;
131829edc611SAlan Somers 		out.body.attr.attr_valid = UINT64_MAX;
13193fa12789SAlan Somers 	})));
13203fa12789SAlan Somers 
13213fa12789SAlan Somers 	fd = open(FULLPATH, O_CREAT | O_RDWR, 0);
13223fa12789SAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
13233fa12789SAlan Somers 	ASSERT_EQ(0, ftruncate(fd, 100)) << strerror(errno);
13247fc0921dSAlan Somers 	leak(fd);
13253fa12789SAlan Somers }
13263fa12789SAlan Somers 
13273fa12789SAlan Somers /*
13288cfb4431SAlan Somers  * Setting the sgid bit should fail for an unprivileged user who doesn't belong
13298cfb4431SAlan Somers  * to the file's group
13308cfb4431SAlan Somers  */
13318cfb4431SAlan Somers TEST_F(Setattr, sgid_by_non_group_member)
13328cfb4431SAlan Somers {
13338cfb4431SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
13348cfb4431SAlan Somers 	const char RELPATH[] = "some_file.txt";
13358cfb4431SAlan Somers 	const uint64_t ino = 42;
13368cfb4431SAlan Somers 	const mode_t oldmode = 0755;
13378cfb4431SAlan Somers 	const mode_t newmode = 02755;
13388cfb4431SAlan Somers 	uid_t uid = geteuid();
13398cfb4431SAlan Somers 	gid_t gid = excluded_group();
13408cfb4431SAlan Somers 
1341a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
13428cfb4431SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, uid, gid);
13438cfb4431SAlan Somers 	EXPECT_CALL(*m_mock, process(
13448cfb4431SAlan Somers 		ResultOf([](auto in) {
134529edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
13468cfb4431SAlan Somers 		}, Eq(true)),
13478cfb4431SAlan Somers 		_)
13488cfb4431SAlan Somers 	).Times(0);
13498cfb4431SAlan Somers 
13508cfb4431SAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
13518cfb4431SAlan Somers 	EXPECT_EQ(EPERM, errno);
13528cfb4431SAlan Somers }
13538cfb4431SAlan Somers 
1354e5ff3a7eSAlan Somers /* Only the superuser may set the sticky bit on a non-directory */
1355e5ff3a7eSAlan Somers TEST_F(Setattr, sticky_regular_file)
1356e5ff3a7eSAlan Somers {
1357e5ff3a7eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1358e5ff3a7eSAlan Somers 	const char RELPATH[] = "some_file.txt";
1359e5ff3a7eSAlan Somers 	const uint64_t ino = 42;
1360e5ff3a7eSAlan Somers 	const mode_t oldmode = 0644;
1361e5ff3a7eSAlan Somers 	const mode_t newmode = 01644;
1362e5ff3a7eSAlan Somers 
1363a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1364e5ff3a7eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX, geteuid());
1365e5ff3a7eSAlan Somers 	EXPECT_CALL(*m_mock, process(
1366e5ff3a7eSAlan Somers 		ResultOf([](auto in) {
136729edc611SAlan Somers 			return (in.header.opcode == FUSE_SETATTR);
1368e5ff3a7eSAlan Somers 		}, Eq(true)),
1369e5ff3a7eSAlan Somers 		_)
1370e5ff3a7eSAlan Somers 	).Times(0);
1371e5ff3a7eSAlan Somers 
1372e5ff3a7eSAlan Somers 	EXPECT_NE(0, chmod(FULLPATH, newmode));
1373e5ff3a7eSAlan Somers 	EXPECT_EQ(EFTYPE, errno);
1374e5ff3a7eSAlan Somers }
1375e5ff3a7eSAlan Somers 
1376ff4fbdf5SAlan Somers TEST_F(Setextattr, ok)
1377ff4fbdf5SAlan Somers {
1378ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1379ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1380ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1381ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1382ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1383ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1384ff4fbdf5SAlan Somers 	ssize_t r;
1385ff4fbdf5SAlan Somers 
1386a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1387ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1388ff4fbdf5SAlan Somers 	expect_setxattr(0);
1389ff4fbdf5SAlan Somers 
13905a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
13915a0b9a27SAlan Somers 		value_len);
1392ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1393ff4fbdf5SAlan Somers }
1394ff4fbdf5SAlan Somers 
1395ff4fbdf5SAlan Somers TEST_F(Setextattr, eacces)
1396ff4fbdf5SAlan Somers {
1397ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1398ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1399ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1400ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1401ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1402ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1403ff4fbdf5SAlan Somers 
1404a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1405ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1406ff4fbdf5SAlan Somers 
14075a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
14085a0b9a27SAlan Somers 		value_len));
1409ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1410ff4fbdf5SAlan Somers }
1411ff4fbdf5SAlan Somers 
1412ff4fbdf5SAlan Somers // Setting system attributes requires superuser privileges
1413ff4fbdf5SAlan Somers TEST_F(Setextattr, system)
1414ff4fbdf5SAlan Somers {
1415ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1416ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1417ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1418ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1419ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1420ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_SYSTEM;
1421ff4fbdf5SAlan Somers 
1422a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1423ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, geteuid());
1424ff4fbdf5SAlan Somers 
14255a0b9a27SAlan Somers 	ASSERT_EQ(-1, extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
14265a0b9a27SAlan Somers 		value_len));
1427ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
1428ff4fbdf5SAlan Somers }
1429ff4fbdf5SAlan Somers 
1430ff4fbdf5SAlan Somers // Setting user attributes merely requires write privileges
1431ff4fbdf5SAlan Somers TEST_F(Setextattr, user)
1432ff4fbdf5SAlan Somers {
1433ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1434ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1435ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1436ff4fbdf5SAlan Somers 	const char value[] = "whatever";
1437ff4fbdf5SAlan Somers 	ssize_t value_len = strlen(value) + 1;
1438ff4fbdf5SAlan Somers 	int ns = EXTATTR_NAMESPACE_USER;
1439ff4fbdf5SAlan Somers 	ssize_t r;
1440ff4fbdf5SAlan Somers 
1441a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1442ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0666, UINT64_MAX, 0);
1443ff4fbdf5SAlan Somers 	expect_setxattr(0);
1444ff4fbdf5SAlan Somers 
14455a0b9a27SAlan Somers 	r = extattr_set_file(FULLPATH, ns, "foo", (const void*)value,
14465a0b9a27SAlan Somers 		value_len);
1447ff4fbdf5SAlan Somers 	ASSERT_EQ(value_len, r) << strerror(errno);
1448ff4fbdf5SAlan Somers }
1449ff4fbdf5SAlan Somers 
1450ff4fbdf5SAlan Somers TEST_F(Unlink, ok)
14519821f1d3SAlan Somers {
14529821f1d3SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
14539821f1d3SAlan Somers 	const char RELPATH[] = "some_file.txt";
14549821f1d3SAlan Somers 	uint64_t ino = 42;
1455331884f2SAlan Somers 	sem_t sem;
1456331884f2SAlan Somers 
1457331884f2SAlan Somers 	ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
14589821f1d3SAlan Somers 
1459a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0777, UINT64_MAX, 1);
1460ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1461a34cdd26SAlan Somers 	expect_unlink(FUSE_ROOT_ID, RELPATH, 0);
1462331884f2SAlan Somers 	expect_forget(ino, 1, &sem);
14639821f1d3SAlan Somers 
1464ff4fbdf5SAlan Somers 	ASSERT_EQ(0, unlink(FULLPATH)) << strerror(errno);
1465331884f2SAlan Somers 
1466331884f2SAlan Somers 	sem_wait(&sem);
1467331884f2SAlan Somers 	sem_destroy(&sem);
1468ff4fbdf5SAlan Somers }
1469ff4fbdf5SAlan Somers 
14706124fd71SAlan Somers /*
14716124fd71SAlan Somers  * Ensure that a cached name doesn't cause unlink to bypass permission checks
14726124fd71SAlan Somers  * in VOP_LOOKUP.
14736124fd71SAlan Somers  *
14746124fd71SAlan Somers  * This test should pass because lookup(9) purges the namecache entry by doing
14756124fd71SAlan Somers  * a vfs_cache_lookup with ~MAKEENTRY when nameiop == DELETE.
14766124fd71SAlan Somers  */
14776124fd71SAlan Somers TEST_F(Unlink, cached_unwritable_directory)
14786124fd71SAlan Somers {
14796124fd71SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
14806124fd71SAlan Somers 	const char RELPATH[] = "some_file.txt";
14816124fd71SAlan Somers 	uint64_t ino = 42;
14826124fd71SAlan Somers 
1483a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1484a34cdd26SAlan Somers 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH)
14856124fd71SAlan Somers 	.Times(AnyNumber())
14866124fd71SAlan Somers 	.WillRepeatedly(Invoke(
148729edc611SAlan Somers 		ReturnImmediate([=](auto i __unused, auto& out) {
14886124fd71SAlan Somers 			SET_OUT_HEADER_LEN(out, entry);
148929edc611SAlan Somers 			out.body.entry.attr.mode = S_IFREG | 0644;
149029edc611SAlan Somers 			out.body.entry.nodeid = ino;
149129edc611SAlan Somers 			out.body.entry.entry_valid = UINT64_MAX;
14926124fd71SAlan Somers 		}))
14936124fd71SAlan Somers 	);
14946124fd71SAlan Somers 
14956124fd71SAlan Somers 	/* Fill name cache */
14966124fd71SAlan Somers 	ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno);
14976124fd71SAlan Somers 	/* Despite cached name , unlink should fail */
14986124fd71SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
14996124fd71SAlan Somers 	ASSERT_EQ(EACCES, errno);
15006124fd71SAlan Somers }
15016124fd71SAlan Somers 
1502ff4fbdf5SAlan Somers TEST_F(Unlink, unwritable_directory)
1503ff4fbdf5SAlan Somers {
1504ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1505ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1506ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1507ff4fbdf5SAlan Somers 
1508a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1509ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, geteuid());
1510ff4fbdf5SAlan Somers 
1511ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1512ff4fbdf5SAlan Somers 	ASSERT_EQ(EACCES, errno);
1513ff4fbdf5SAlan Somers }
1514ff4fbdf5SAlan Somers 
15156124fd71SAlan Somers TEST_F(Unlink, sticky_directory)
1516ff4fbdf5SAlan Somers {
1517ff4fbdf5SAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1518ff4fbdf5SAlan Somers 	const char RELPATH[] = "some_file.txt";
1519ff4fbdf5SAlan Somers 	uint64_t ino = 42;
1520ff4fbdf5SAlan Somers 
1521a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 01777, UINT64_MAX, 1);
1522ff4fbdf5SAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | 0644, UINT64_MAX, 0);
1523ff4fbdf5SAlan Somers 
1524ff4fbdf5SAlan Somers 	ASSERT_EQ(-1, unlink(FULLPATH));
1525ff4fbdf5SAlan Somers 	ASSERT_EQ(EPERM, errno);
15269821f1d3SAlan Somers }
1527a90e32deSAlan Somers 
1528a90e32deSAlan Somers /* A write by a non-owner should clear a file's SUID bit */
1529a90e32deSAlan Somers TEST_F(Write, clear_suid)
1530a90e32deSAlan Somers {
1531a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1532a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1533a90e32deSAlan Somers 	struct stat sb;
1534a90e32deSAlan Somers 	uint64_t ino = 42;
1535a90e32deSAlan Somers 	mode_t oldmode = 04777;
1536a90e32deSAlan Somers 	mode_t newmode = 0777;
1537a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1538a90e32deSAlan Somers 	int fd;
1539a90e32deSAlan Somers 
1540a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1541a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1542a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1543bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
154418a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1545a90e32deSAlan Somers 
1546a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1547a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1548a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1549a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1550a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
15517fc0921dSAlan Somers 	leak(fd);
1552a90e32deSAlan Somers }
1553a90e32deSAlan Somers 
1554a90e32deSAlan Somers /* A write by a non-owner should clear a file's SGID bit */
1555a90e32deSAlan Somers TEST_F(Write, clear_sgid)
1556a90e32deSAlan Somers {
1557a90e32deSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
1558a90e32deSAlan Somers 	const char RELPATH[] = "some_file.txt";
1559a90e32deSAlan Somers 	struct stat sb;
1560a90e32deSAlan Somers 	uint64_t ino = 42;
1561a90e32deSAlan Somers 	mode_t oldmode = 02777;
1562a90e32deSAlan Somers 	mode_t newmode = 0777;
1563a90e32deSAlan Somers 	char wbuf[1] = {'x'};
1564a90e32deSAlan Somers 	int fd;
1565a90e32deSAlan Somers 
1566a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
1567a90e32deSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
1568a90e32deSAlan Somers 	expect_open(ino, 0, 1);
1569bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
157018a2264eSAlan Somers 	expect_chmod(ino, newmode, sizeof(wbuf));
1571a90e32deSAlan Somers 
1572a90e32deSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
1573a90e32deSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
1574a90e32deSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
1575a90e32deSAlan Somers 	ASSERT_EQ(0, fstat(fd, &sb)) << strerror(errno);
1576a90e32deSAlan Somers 	EXPECT_EQ(S_IFREG | newmode, sb.st_mode);
15777fc0921dSAlan Somers 	leak(fd);
1578a90e32deSAlan Somers }
157918a2264eSAlan Somers 
158018a2264eSAlan Somers /* Regression test for a specific recurse-of-nonrecursive-lock panic
158118a2264eSAlan Somers  *
158218a2264eSAlan Somers  * With writeback caching, we can't call vtruncbuf from fuse_io_strategy, or it
158318a2264eSAlan Somers  * may panic.  That happens if the FUSE_SETATTR response indicates that the
158418a2264eSAlan Somers  * file's size has changed since the write.
158518a2264eSAlan Somers  */
158618a2264eSAlan Somers TEST_F(Write, recursion_panic_while_clearing_suid)
158718a2264eSAlan Somers {
158818a2264eSAlan Somers 	const char FULLPATH[] = "mountpoint/some_file.txt";
158918a2264eSAlan Somers 	const char RELPATH[] = "some_file.txt";
159018a2264eSAlan Somers 	uint64_t ino = 42;
159118a2264eSAlan Somers 	mode_t oldmode = 04777;
159218a2264eSAlan Somers 	mode_t newmode = 0777;
159318a2264eSAlan Somers 	char wbuf[1] = {'x'};
159418a2264eSAlan Somers 	int fd;
159518a2264eSAlan Somers 
1596a34cdd26SAlan Somers 	expect_getattr(FUSE_ROOT_ID, S_IFDIR | 0755, UINT64_MAX, 1);
159718a2264eSAlan Somers 	expect_lookup(RELPATH, ino, S_IFREG | oldmode, UINT64_MAX);
159818a2264eSAlan Somers 	expect_open(ino, 0, 1);
1599bda39894SAlan Somers 	expect_write(ino, 0, sizeof(wbuf), sizeof(wbuf), 0, 0, wbuf);
160018a2264eSAlan Somers 	/* XXX Return a smaller file size than what we just wrote! */
160118a2264eSAlan Somers 	expect_chmod(ino, newmode, 0);
160218a2264eSAlan Somers 
160318a2264eSAlan Somers 	fd = open(FULLPATH, O_WRONLY);
160418a2264eSAlan Somers 	ASSERT_LE(0, fd) << strerror(errno);
160518a2264eSAlan Somers 	ASSERT_EQ(1, write(fd, wbuf, sizeof(wbuf))) << strerror(errno);
16067fc0921dSAlan Somers 	leak(fd);
160718a2264eSAlan Somers }
1608