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