xref: /freebsd/tests/sys/fs/fusefs/misc.cc (revision c503d23a6ccb8090edabc1c90981f9b8f202e093)
1ee1c3d38SAlan Somers /*-
2ee1c3d38SAlan Somers  * SPDX-License-Identifier: BSD-2-Clause
3ee1c3d38SAlan Somers  *
4ee1c3d38SAlan Somers  * Copyright (c) 2026 Alan Somers
5ee1c3d38SAlan Somers  *
6ee1c3d38SAlan Somers  * Redistribution and use in source and binary forms, with or without
7ee1c3d38SAlan Somers  * modification, are permitted provided that the following conditions
8ee1c3d38SAlan Somers  * are met:
9ee1c3d38SAlan Somers  * 1. Redistributions of source code must retain the above copyright
10ee1c3d38SAlan Somers  *    notice, this list of conditions and the following disclaimer.
11ee1c3d38SAlan Somers  * 2. Redistributions in binary form must reproduce the above copyright
12ee1c3d38SAlan Somers  *    notice, this list of conditions and the following disclaimer in the
13ee1c3d38SAlan Somers  *    documentation and/or other materials provided with the distribution.
14ee1c3d38SAlan Somers  *
15ee1c3d38SAlan Somers  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16ee1c3d38SAlan Somers  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17ee1c3d38SAlan Somers  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18ee1c3d38SAlan Somers  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19ee1c3d38SAlan Somers  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20ee1c3d38SAlan Somers  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21ee1c3d38SAlan Somers  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22ee1c3d38SAlan Somers  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23ee1c3d38SAlan Somers  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24ee1c3d38SAlan Somers  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25ee1c3d38SAlan Somers  * SUCH DAMAGE.
26ee1c3d38SAlan Somers  */
27ee1c3d38SAlan Somers 
28ee1c3d38SAlan Somers /* Miscellaneous tests that don't relate to any particular fuse operation */
29ee1c3d38SAlan Somers 
30ee1c3d38SAlan Somers extern "C" {
31ee1c3d38SAlan Somers #include <sys/wait.h>
32ee1c3d38SAlan Somers 
33ee1c3d38SAlan Somers #include <fcntl.h>
34ee1c3d38SAlan Somers #include <unistd.h>
35ee1c3d38SAlan Somers }
36ee1c3d38SAlan Somers 
37ee1c3d38SAlan Somers #include "mockfs.hh"
38ee1c3d38SAlan Somers #include "utils.hh"
39ee1c3d38SAlan Somers 
40ee1c3d38SAlan Somers using namespace testing;
41ee1c3d38SAlan Somers 
42ee1c3d38SAlan Somers 
43ee1c3d38SAlan Somers class Execv: public FuseTest {
44ee1c3d38SAlan Somers public:
SetUp()45ee1c3d38SAlan Somers virtual void SetUp() {
46ee1c3d38SAlan Somers 	/* Enable FUSE_ASYNC_READ to allow shared vnode locks */
47ee1c3d38SAlan Somers 	m_init_flags = FUSE_ASYNC_READ;
48ee1c3d38SAlan Somers 	FuseTest::SetUp();
49ee1c3d38SAlan Somers }
50ee1c3d38SAlan Somers };
51ee1c3d38SAlan Somers class Fexecv: public Execv {};
52ee1c3d38SAlan Somers 
53ee1c3d38SAlan Somers class FexecvDefaultPermissions: public Fexecv {
SetUp()54ee1c3d38SAlan Somers virtual void SetUp() {
55ee1c3d38SAlan Somers 	m_default_permissions = true;
56ee1c3d38SAlan Somers 	Fexecv::SetUp();
57ee1c3d38SAlan Somers }
58ee1c3d38SAlan Somers };
59ee1c3d38SAlan Somers 
60ee1c3d38SAlan Somers /*
61ee1c3d38SAlan Somers  * Execute a file mounted on a fusefs file system.  The server should get the
62ee1c3d38SAlan Somers  * FUSE_RELEASE request when sys_fexecve closes the file.
63ee1c3d38SAlan Somers  *
64ee1c3d38SAlan Somers  * Crucially, execve ignores the file system's MNTK_EXTENDED_SHARED flag.
65ee1c3d38SAlan Somers  *
66ee1c3d38SAlan Somers  * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=295957
67ee1c3d38SAlan Somers  */
TEST_F(Execv,close)68ee1c3d38SAlan Somers TEST_F(Execv, close)
69ee1c3d38SAlan Somers {
70ee1c3d38SAlan Somers 	const static char FULLPATH[] = "mountpoint/true";
71ee1c3d38SAlan Somers 	const static char RELPATH[] = "true";
72ee1c3d38SAlan Somers 	const static size_t BUFSIZE = 16384;
73ee1c3d38SAlan Somers 	FILE *true_file;
74ee1c3d38SAlan Somers 	uint64_t ino = 42;
75ee1c3d38SAlan Somers 	size_t true_len;
76ee1c3d38SAlan Somers 	int status;
77ee1c3d38SAlan Somers 	char *buf;
78ee1c3d38SAlan Somers 
79ee1c3d38SAlan Somers 	buf = new char[BUFSIZE];
80ee1c3d38SAlan Somers 	true_file = fopen("/usr/bin/true", "r");
81ee1c3d38SAlan Somers 	ASSERT_TRUE(true_file) << strerror(errno);
82ee1c3d38SAlan Somers 	true_len = fread(buf, 1, BUFSIZE, true_file);
83ee1c3d38SAlan Somers 	ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
84ee1c3d38SAlan Somers 	fclose(true_file);
85ee1c3d38SAlan Somers 
86ee1c3d38SAlan Somers 	fork(false, &status, [&] {
87ee1c3d38SAlan Somers 		expect_lookup(RELPATH, ino, S_IFREG | 0755, true_len, 1,
88ee1c3d38SAlan Somers 				UINT64_MAX);
89ee1c3d38SAlan Somers 		expect_open(ino, 0, 1);
90ee1c3d38SAlan Somers 		expect_read(ino, 0, true_len, true_len, buf);
91ee1c3d38SAlan Somers 		expect_flush(ino, 1, ReturnErrno(ENOSYS));
92ee1c3d38SAlan Somers 		EXPECT_CALL(*m_mock, process(
93ee1c3d38SAlan Somers 			ResultOf([=](auto in) {
94ee1c3d38SAlan Somers 				return (in.header.opcode == FUSE_RELEASE &&
95ee1c3d38SAlan Somers 					in.header.nodeid == ino);
96ee1c3d38SAlan Somers 			}, Eq(true)),
97ee1c3d38SAlan Somers 			_)
98ee1c3d38SAlan Somers 		).Times(1)
99ee1c3d38SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(0)));
100ee1c3d38SAlan Somers 
101ee1c3d38SAlan Somers 	}, [&] {
102ee1c3d38SAlan Somers 		char *const argv[] = {__DECONST(char *, "true"), NULL};
103ee1c3d38SAlan Somers 		char *const env[] = {NULL};
104ee1c3d38SAlan Somers 
105ee1c3d38SAlan Somers 		execve(FULLPATH, argv, env);
106ee1c3d38SAlan Somers 		fprintf(stderr, "execv: %s\n", strerror(errno));
107ee1c3d38SAlan Somers 		return 1;
108ee1c3d38SAlan Somers 	});
109ee1c3d38SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
110ee1c3d38SAlan Somers 
111ee1c3d38SAlan Somers 	delete[] buf;
112ee1c3d38SAlan Somers }
113ee1c3d38SAlan Somers 
TEST_F(Fexecv,close)114ee1c3d38SAlan Somers TEST_F(Fexecv, close)
115ee1c3d38SAlan Somers {
116ee1c3d38SAlan Somers 	const static char FULLPATH[] = "mountpoint/true";
117ee1c3d38SAlan Somers 	const static char RELPATH[] = "true";
118ee1c3d38SAlan Somers 	const static size_t BUFSIZE = 16384;
119ee1c3d38SAlan Somers 	FILE *true_file;
120ee1c3d38SAlan Somers 	uint64_t ino = 42;
121ee1c3d38SAlan Somers 	size_t true_len;
122ee1c3d38SAlan Somers 	int status;
123ee1c3d38SAlan Somers 	char *buf;
124ee1c3d38SAlan Somers 
125ee1c3d38SAlan Somers 	buf = new char[BUFSIZE];
126ee1c3d38SAlan Somers 	true_file = fopen("/usr/bin/true", "r");
127ee1c3d38SAlan Somers 	ASSERT_TRUE(true_file) << strerror(errno);
128ee1c3d38SAlan Somers 	true_len = fread(buf, 1, BUFSIZE, true_file);
129ee1c3d38SAlan Somers 	ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
130ee1c3d38SAlan Somers 	fclose(true_file);
131ee1c3d38SAlan Somers 
132ee1c3d38SAlan Somers 	fork(false, &status, [&] {
133ee1c3d38SAlan Somers 		expect_lookup(RELPATH, ino, S_IFREG | 0755, true_len, 1,
134ee1c3d38SAlan Somers 				UINT64_MAX);
135ee1c3d38SAlan Somers 		expect_open(ino, 0, 2);
136ee1c3d38SAlan Somers 		expect_read(ino, 0, true_len, true_len, buf);
137ee1c3d38SAlan Somers 		expect_flush(ino, 1, ReturnErrno(ENOSYS));
138ee1c3d38SAlan Somers 		EXPECT_CALL(*m_mock, process(
139ee1c3d38SAlan Somers 			ResultOf([=](auto in) {
140ee1c3d38SAlan Somers 				return (in.header.opcode == FUSE_RELEASE &&
141ee1c3d38SAlan Somers 					in.header.nodeid == ino);
142ee1c3d38SAlan Somers 			}, Eq(true)),
143ee1c3d38SAlan Somers 			_)
144ee1c3d38SAlan Somers 		).Times(2)
145ee1c3d38SAlan Somers 		.WillRepeatedly(Invoke(ReturnErrno(0)));
146ee1c3d38SAlan Somers 
147ee1c3d38SAlan Somers 	}, [&] {
148ee1c3d38SAlan Somers 		char *const argv[] = {__DECONST(char *, "true"), NULL};
149ee1c3d38SAlan Somers 		char *const env[] = {NULL};
150ee1c3d38SAlan Somers 		int fd;
151ee1c3d38SAlan Somers 
152ee1c3d38SAlan Somers 		fd = open(FULLPATH, O_EXEC);
153ee1c3d38SAlan Somers 		if (fd < 0) {
154ee1c3d38SAlan Somers 			fprintf(stderr, "open: %s\n", strerror(errno));
155ee1c3d38SAlan Somers 			return 1;
156ee1c3d38SAlan Somers 		}
157ee1c3d38SAlan Somers 		fexecve(fd, argv, env);
158ee1c3d38SAlan Somers 		fprintf(stderr, "execv: %s\n", strerror(errno));
159ee1c3d38SAlan Somers 		return 1;
160ee1c3d38SAlan Somers 	});
161ee1c3d38SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
162ee1c3d38SAlan Somers 
163ee1c3d38SAlan Somers 	delete[] buf;
164ee1c3d38SAlan Somers }
165ee1c3d38SAlan Somers 
166ee1c3d38SAlan Somers /*
167ee1c3d38SAlan Somers  * Execute a file stored on a fusefs file system that does not implement
168ee1c3d38SAlan Somers  * FUSE_OPEN
169ee1c3d38SAlan Somers  */
TEST_F(Fexecv,close_noopen)170ee1c3d38SAlan Somers TEST_F(Fexecv, close_noopen)
171ee1c3d38SAlan Somers {
172ee1c3d38SAlan Somers 	const static char FULLPATH[] = "mountpoint/true";
173ee1c3d38SAlan Somers 	const static char RELPATH[] = "true";
174ee1c3d38SAlan Somers 	const static size_t BUFSIZE = 16384;
175ee1c3d38SAlan Somers 	FILE *true_file;
176ee1c3d38SAlan Somers 	uint64_t ino = 42;
177ee1c3d38SAlan Somers 	size_t true_len;
178ee1c3d38SAlan Somers 	int status;
179ee1c3d38SAlan Somers 	char *buf;
180ee1c3d38SAlan Somers 
181ee1c3d38SAlan Somers 	buf = new char[BUFSIZE];
182ee1c3d38SAlan Somers 	true_file = fopen("/usr/bin/true", "r");
183ee1c3d38SAlan Somers 	ASSERT_TRUE(true_file) << strerror(errno);
184ee1c3d38SAlan Somers 	true_len = fread(buf, 1, BUFSIZE, true_file);
185ee1c3d38SAlan Somers 	ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
186ee1c3d38SAlan Somers 	fclose(true_file);
187ee1c3d38SAlan Somers 
188ee1c3d38SAlan Somers 	fork(false, &status, [&] {
189ee1c3d38SAlan Somers 		expect_lookup(RELPATH, ino, S_IFREG | 0755, true_len, 1,
190ee1c3d38SAlan Somers 				UINT64_MAX);
191ee1c3d38SAlan Somers 		EXPECT_CALL(*m_mock, process(
192ee1c3d38SAlan Somers 			ResultOf([=](auto in) {
193ee1c3d38SAlan Somers 				return (in.header.opcode == FUSE_OPEN &&
194ee1c3d38SAlan Somers 					in.header.nodeid == ino);
195ee1c3d38SAlan Somers 			}, Eq(true)),
196ee1c3d38SAlan Somers 			_)
197ee1c3d38SAlan Somers 		).Times(1)
198ee1c3d38SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOSYS)));
199ee1c3d38SAlan Somers 		expect_read(ino, 0, true_len, true_len, buf, -1, 0);
200ee1c3d38SAlan Somers 		expect_flush(ino, 1, ReturnErrno(ENOSYS));
201ee1c3d38SAlan Somers 	}, [&] {
202ee1c3d38SAlan Somers 		char *const argv[] = {__DECONST(char *, "true"), NULL};
203ee1c3d38SAlan Somers 		char *const env[] = {NULL};
204ee1c3d38SAlan Somers 		int fd;
205ee1c3d38SAlan Somers 
206ee1c3d38SAlan Somers 		fd = open(FULLPATH, O_EXEC);
207ee1c3d38SAlan Somers 		if (fd < 0) {
208ee1c3d38SAlan Somers 			fprintf(stderr, "open: %s\n", strerror(errno));
209ee1c3d38SAlan Somers 			return 1;
210ee1c3d38SAlan Somers 		}
211ee1c3d38SAlan Somers 		fexecve(fd, argv, env);
212ee1c3d38SAlan Somers 		fprintf(stderr, "execv: %s\n", strerror(errno));
213ee1c3d38SAlan Somers 		return 1;
214ee1c3d38SAlan Somers 	});
215ee1c3d38SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
216ee1c3d38SAlan Somers 
217ee1c3d38SAlan Somers 	delete[] buf;
218ee1c3d38SAlan Somers }
219ee1c3d38SAlan Somers 
220ee1c3d38SAlan Somers /*
221ee1c3d38SAlan Somers  * When execute a file with a dirty atime, fusefs must send FUSE_SETATTR to the
222ee1c3d38SAlan Somers  * daemon during close.
223ee1c3d38SAlan Somers  */
TEST_F(FexecvDefaultPermissions,atime)224ee1c3d38SAlan Somers TEST_F(FexecvDefaultPermissions, atime)
225ee1c3d38SAlan Somers {
226ee1c3d38SAlan Somers 	const static char FULLPATH[] = "mountpoint/true";
227ee1c3d38SAlan Somers 	const static char RELPATH[] = "true";
228ee1c3d38SAlan Somers 	const static size_t BUFSIZE = 16384;
229ee1c3d38SAlan Somers 	FILE *true_file;
230ee1c3d38SAlan Somers 	uint64_t ino = 42;
231ee1c3d38SAlan Somers 	size_t true_len;
232ee1c3d38SAlan Somers 	int status;
233ee1c3d38SAlan Somers 	char *buf;
234ee1c3d38SAlan Somers 
235ee1c3d38SAlan Somers 	buf = new char[BUFSIZE];
236ee1c3d38SAlan Somers 	true_file = fopen("/usr/bin/true", "r");
237ee1c3d38SAlan Somers 	ASSERT_TRUE(true_file) << strerror(errno);
238ee1c3d38SAlan Somers 	true_len = fread(buf, 1, BUFSIZE, true_file);
239ee1c3d38SAlan Somers 	ASSERT_LT(true_len, BUFSIZE) << "Must increase BUFSIZE";
240ee1c3d38SAlan Somers 	fclose(true_file);
241ee1c3d38SAlan Somers 
242ee1c3d38SAlan Somers 	fork(false, &status, [&] {
243ee1c3d38SAlan Somers 		expect_lookup(RELPATH, ino, S_IFREG | 0777, true_len, 1,
244ee1c3d38SAlan Somers 				UINT64_MAX);
245ee1c3d38SAlan Somers 		EXPECT_CALL(*m_mock, process(
246ee1c3d38SAlan Somers 			ResultOf([=](auto in) {
247ee1c3d38SAlan Somers 				return (in.header.opcode == FUSE_GETATTR &&
248ee1c3d38SAlan Somers 					in.header.nodeid == FUSE_ROOT_ID);
249ee1c3d38SAlan Somers 			}, Eq(true)),
250ee1c3d38SAlan Somers 			_)
251ee1c3d38SAlan Somers 		).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) {
252ee1c3d38SAlan Somers 			SET_OUT_HEADER_LEN(out, attr);
253ee1c3d38SAlan Somers 			out.body.attr.attr.ino = FUSE_ROOT_ID;
254ee1c3d38SAlan Somers 			out.body.attr.attr.mode = S_IFDIR | 0777;
255ee1c3d38SAlan Somers 			out.body.attr.attr.size = 0;
256ee1c3d38SAlan Somers 			out.body.attr.attr_valid = UINT64_MAX;
257ee1c3d38SAlan Somers 		})));
258ee1c3d38SAlan Somers 		EXPECT_CALL(*m_mock, process(
259ee1c3d38SAlan Somers 			ResultOf([=](auto in) {
260ee1c3d38SAlan Somers 				return (in.header.opcode == FUSE_OPEN &&
261ee1c3d38SAlan Somers 					in.header.nodeid == ino);
262ee1c3d38SAlan Somers 			}, Eq(true)),
263ee1c3d38SAlan Somers 			_)
264ee1c3d38SAlan Somers 		).Times(1)
265ee1c3d38SAlan Somers 		.WillOnce(Invoke(ReturnErrno(ENOSYS)));
266ee1c3d38SAlan Somers 		expect_read(ino, 0, true_len, true_len, buf, -1, 0);
267ee1c3d38SAlan Somers 		expect_flush(ino, 1, ReturnErrno(ENOSYS));
268ee1c3d38SAlan Somers 		EXPECT_CALL(*m_mock, process(
269ee1c3d38SAlan Somers 			ResultOf([&](auto in) {
270ee1c3d38SAlan Somers 				return (in.header.opcode == FUSE_SETATTR &&
271ee1c3d38SAlan Somers 					in.header.nodeid == ino &&
272ee1c3d38SAlan Somers 					in.body.setattr.valid == FATTR_ATIME);
273ee1c3d38SAlan Somers 			}, Eq(true)),
274ee1c3d38SAlan Somers 			_)
275ee1c3d38SAlan Somers 		).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
276ee1c3d38SAlan Somers 			SET_OUT_HEADER_LEN(out, attr);
277ee1c3d38SAlan Somers 			out.body.attr.attr.ino = ino;
278ee1c3d38SAlan Somers 			out.body.attr.attr.mode = S_IFREG | 0777;
279ee1c3d38SAlan Somers 		})));
280ee1c3d38SAlan Somers 	}, [&] {
281ee1c3d38SAlan Somers 		char *const argv[] = {__DECONST(char *, "true"), NULL};
282ee1c3d38SAlan Somers 		char *const env[] = {NULL};
283*c503d23aSRyan Libby 		char cbuf[8];
284ee1c3d38SAlan Somers 		int fd;
285ee1c3d38SAlan Somers 
286ee1c3d38SAlan Somers 		/* Note that fexecve doesn't actually require O_EXEC */
287ee1c3d38SAlan Somers 		fd = open(FULLPATH, O_RDONLY);
288ee1c3d38SAlan Somers 		if (fd < 0) {
289ee1c3d38SAlan Somers 			fprintf(stderr, "open: %s\n", strerror(errno));
290ee1c3d38SAlan Somers 			return 1;
291ee1c3d38SAlan Somers 		}
292ee1c3d38SAlan Somers 		/* Read a few bytes, just to dirty the file's atime */
293*c503d23aSRyan Libby 		if (read(fd, cbuf, sizeof(cbuf)) < 0) {
294ee1c3d38SAlan Somers 			fprintf(stderr, "read: %s\n", strerror(errno));
295ee1c3d38SAlan Somers 			return 1;
296ee1c3d38SAlan Somers 		}
297ee1c3d38SAlan Somers 		fexecve(fd, argv, env);
298ee1c3d38SAlan Somers 		fprintf(stderr, "execv: %s\n", strerror(errno));
299ee1c3d38SAlan Somers 		return 1;
300ee1c3d38SAlan Somers 	});
301ee1c3d38SAlan Somers 	ASSERT_EQ(0, WEXITSTATUS(status));
302ee1c3d38SAlan Somers 
303ee1c3d38SAlan Somers 	delete[] buf;
304ee1c3d38SAlan Somers }
305