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