1*17ba6f42SCismonX /*-
2*17ba6f42SCismonX * SPDX-License-Identifier: BSD-2-Clause
3*17ba6f42SCismonX *
4*17ba6f42SCismonX * Copyright (c) 2025 CismonX <admin@cismon.net>
5*17ba6f42SCismonX *
6*17ba6f42SCismonX * Redistribution and use in source and binary forms, with or without
7*17ba6f42SCismonX * modification, are permitted provided that the following conditions
8*17ba6f42SCismonX * are met:
9*17ba6f42SCismonX * 1. Redistributions of source code must retain the above copyright
10*17ba6f42SCismonX * notice, this list of conditions and the following disclaimer.
11*17ba6f42SCismonX * 2. Redistributions in binary form must reproduce the above copyright
12*17ba6f42SCismonX * notice, this list of conditions and the following disclaimer in the
13*17ba6f42SCismonX * documentation and/or other materials provided with the distribution.
14*17ba6f42SCismonX *
15*17ba6f42SCismonX * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16*17ba6f42SCismonX * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17*17ba6f42SCismonX * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18*17ba6f42SCismonX * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19*17ba6f42SCismonX * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20*17ba6f42SCismonX * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21*17ba6f42SCismonX * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22*17ba6f42SCismonX * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23*17ba6f42SCismonX * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24*17ba6f42SCismonX * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25*17ba6f42SCismonX * SUCH DAMAGE.
26*17ba6f42SCismonX */
27*17ba6f42SCismonX
28*17ba6f42SCismonX extern "C" {
29*17ba6f42SCismonX #include <sys/types.h>
30*17ba6f42SCismonX #include <sys/ioctl.h>
31*17ba6f42SCismonX #include <fcntl.h>
32*17ba6f42SCismonX #include <string.h>
33*17ba6f42SCismonX }
34*17ba6f42SCismonX
35*17ba6f42SCismonX #include "mockfs.hh"
36*17ba6f42SCismonX #include "utils.hh"
37*17ba6f42SCismonX
38*17ba6f42SCismonX using namespace testing;
39*17ba6f42SCismonX
40*17ba6f42SCismonX using IoctlTestProcT = std::function<void (int)>;
41*17ba6f42SCismonX
42*17ba6f42SCismonX static const char INPUT_DATA[] = "input_data";
43*17ba6f42SCismonX static const char OUTPUT_DATA[] = "output_data";
44*17ba6f42SCismonX
45*17ba6f42SCismonX class Ioctl: public FuseTest {
46*17ba6f42SCismonX public:
expect_ioctl(uint64_t ino,ProcessMockerT r)47*17ba6f42SCismonX void expect_ioctl(uint64_t ino, ProcessMockerT r)
48*17ba6f42SCismonX {
49*17ba6f42SCismonX EXPECT_CALL(*m_mock, process(
50*17ba6f42SCismonX ResultOf([=](auto in) {
51*17ba6f42SCismonX return (in.header.opcode == FUSE_IOCTL &&
52*17ba6f42SCismonX in.header.nodeid == ino);
53*17ba6f42SCismonX }, Eq(true)), _)
54*17ba6f42SCismonX ).WillOnce(Invoke(r)).RetiresOnSaturation();
55*17ba6f42SCismonX }
56*17ba6f42SCismonX
expect_ioctl_rw(uint64_t ino)57*17ba6f42SCismonX void expect_ioctl_rw(uint64_t ino)
58*17ba6f42SCismonX {
59*17ba6f42SCismonX /*
60*17ba6f42SCismonX * _IOR(): Compare the input data with INPUT_DATA.
61*17ba6f42SCismonX * _IOW(): Copy out OUTPUT_DATA.
62*17ba6f42SCismonX * _IOWR(): Combination of above.
63*17ba6f42SCismonX * _IOWINT(): Return the integer argument value.
64*17ba6f42SCismonX */
65*17ba6f42SCismonX expect_ioctl(ino, ReturnImmediate([](auto in, auto& out) {
66*17ba6f42SCismonX uint8_t *in_buf = in.body.bytes + sizeof(in.body.ioctl);
67*17ba6f42SCismonX uint8_t *out_buf = out.body.bytes + sizeof(out.body.ioctl);
68*17ba6f42SCismonX uint32_t cmd = in.body.ioctl.cmd;
69*17ba6f42SCismonX uint32_t arg_len = IOCPARM_LEN(cmd);
70*17ba6f42SCismonX int result = 0;
71*17ba6f42SCismonX
72*17ba6f42SCismonX out.header.error = 0;
73*17ba6f42SCismonX SET_OUT_HEADER_LEN(out, ioctl);
74*17ba6f42SCismonX if ((cmd & IOC_VOID) != 0 && arg_len > 0) {
75*17ba6f42SCismonX memcpy(&result, in_buf, sizeof(int));
76*17ba6f42SCismonX goto out;
77*17ba6f42SCismonX }
78*17ba6f42SCismonX if ((cmd & IOC_IN) != 0) {
79*17ba6f42SCismonX if (0 != strncmp(INPUT_DATA, (char *)in_buf, arg_len)) {
80*17ba6f42SCismonX result = -EINVAL;
81*17ba6f42SCismonX goto out;
82*17ba6f42SCismonX }
83*17ba6f42SCismonX }
84*17ba6f42SCismonX if ((cmd & IOC_OUT) != 0) {
85*17ba6f42SCismonX memcpy(out_buf, OUTPUT_DATA, sizeof(OUTPUT_DATA));
86*17ba6f42SCismonX out.header.len += sizeof(OUTPUT_DATA);
87*17ba6f42SCismonX }
88*17ba6f42SCismonX
89*17ba6f42SCismonX out:
90*17ba6f42SCismonX out.body.ioctl.result = result;
91*17ba6f42SCismonX }));
92*17ba6f42SCismonX }
93*17ba6f42SCismonX };
94*17ba6f42SCismonX
95*17ba6f42SCismonX /**
96*17ba6f42SCismonX * If the server does not implement FUSE_IOCTL handler (returns ENOSYS),
97*17ba6f42SCismonX * the kernel should return ENOTTY to the user instead.
98*17ba6f42SCismonX */
TEST_F(Ioctl,enosys)99*17ba6f42SCismonX TEST_F(Ioctl, enosys)
100*17ba6f42SCismonX {
101*17ba6f42SCismonX unsigned long req = _IO(0xff, 0);
102*17ba6f42SCismonX int fd;
103*17ba6f42SCismonX
104*17ba6f42SCismonX expect_opendir(FUSE_ROOT_ID);
105*17ba6f42SCismonX expect_ioctl(FUSE_ROOT_ID, ReturnErrno(ENOSYS));
106*17ba6f42SCismonX
107*17ba6f42SCismonX fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
108*17ba6f42SCismonX ASSERT_LE(0, fd) << strerror(errno);
109*17ba6f42SCismonX
110*17ba6f42SCismonX EXPECT_EQ(-1, ioctl(fd, req));
111*17ba6f42SCismonX EXPECT_EQ(ENOTTY, errno);
112*17ba6f42SCismonX
113*17ba6f42SCismonX leak(fd);
114*17ba6f42SCismonX }
115*17ba6f42SCismonX
116*17ba6f42SCismonX /*
117*17ba6f42SCismonX * For _IOR() and _IOWR(), The server is allowed to write fewer bytes
118*17ba6f42SCismonX * than IOCPARM_LEN(req).
119*17ba6f42SCismonX */
TEST_F(Ioctl,ior)120*17ba6f42SCismonX TEST_F(Ioctl, ior)
121*17ba6f42SCismonX {
122*17ba6f42SCismonX char buf[sizeof(OUTPUT_DATA) + 1] = { 0 };
123*17ba6f42SCismonX unsigned long req = _IOR(0xff, 1, buf);
124*17ba6f42SCismonX int fd;
125*17ba6f42SCismonX
126*17ba6f42SCismonX expect_opendir(FUSE_ROOT_ID);
127*17ba6f42SCismonX expect_ioctl_rw(FUSE_ROOT_ID);
128*17ba6f42SCismonX
129*17ba6f42SCismonX fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
130*17ba6f42SCismonX ASSERT_LE(0, fd) << strerror(errno);
131*17ba6f42SCismonX
132*17ba6f42SCismonX EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
133*17ba6f42SCismonX EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
134*17ba6f42SCismonX
135*17ba6f42SCismonX leak(fd);
136*17ba6f42SCismonX }
137*17ba6f42SCismonX
138*17ba6f42SCismonX /*
139*17ba6f42SCismonX * For _IOR() and _IOWR(), if the server attempts to write more bytes
140*17ba6f42SCismonX * than IOCPARM_LEN(req), the kernel should fail the syscall with EIO.
141*17ba6f42SCismonX */
TEST_F(Ioctl,ior_overflow)142*17ba6f42SCismonX TEST_F(Ioctl, ior_overflow)
143*17ba6f42SCismonX {
144*17ba6f42SCismonX char buf[sizeof(OUTPUT_DATA) - 1] = { 0 };
145*17ba6f42SCismonX unsigned long req = _IOR(0xff, 2, buf);
146*17ba6f42SCismonX int fd;
147*17ba6f42SCismonX
148*17ba6f42SCismonX expect_opendir(FUSE_ROOT_ID);
149*17ba6f42SCismonX expect_ioctl_rw(FUSE_ROOT_ID);
150*17ba6f42SCismonX
151*17ba6f42SCismonX fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
152*17ba6f42SCismonX ASSERT_LE(0, fd) << strerror(errno);
153*17ba6f42SCismonX
154*17ba6f42SCismonX EXPECT_EQ(-1, ioctl(fd, req, buf));
155*17ba6f42SCismonX EXPECT_EQ(EIO, errno);
156*17ba6f42SCismonX
157*17ba6f42SCismonX leak(fd);
158*17ba6f42SCismonX }
159*17ba6f42SCismonX
TEST_F(Ioctl,iow)160*17ba6f42SCismonX TEST_F(Ioctl, iow)
161*17ba6f42SCismonX {
162*17ba6f42SCismonX unsigned long req = _IOW(0xff, 3, INPUT_DATA);
163*17ba6f42SCismonX int fd;
164*17ba6f42SCismonX
165*17ba6f42SCismonX expect_opendir(FUSE_ROOT_ID);
166*17ba6f42SCismonX expect_ioctl_rw(FUSE_ROOT_ID);
167*17ba6f42SCismonX
168*17ba6f42SCismonX fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
169*17ba6f42SCismonX ASSERT_LE(0, fd) << strerror(errno);
170*17ba6f42SCismonX
171*17ba6f42SCismonX EXPECT_EQ(0, ioctl(fd, req, INPUT_DATA)) << strerror(errno);
172*17ba6f42SCismonX
173*17ba6f42SCismonX leak(fd);
174*17ba6f42SCismonX }
175*17ba6f42SCismonX
TEST_F(Ioctl,iowr)176*17ba6f42SCismonX TEST_F(Ioctl, iowr)
177*17ba6f42SCismonX {
178*17ba6f42SCismonX char buf[std::max(sizeof(INPUT_DATA), sizeof(OUTPUT_DATA))] = { 0 };
179*17ba6f42SCismonX unsigned long req = _IOWR(0xff, 4, buf);
180*17ba6f42SCismonX int fd;
181*17ba6f42SCismonX
182*17ba6f42SCismonX expect_opendir(FUSE_ROOT_ID);
183*17ba6f42SCismonX expect_ioctl_rw(FUSE_ROOT_ID);
184*17ba6f42SCismonX
185*17ba6f42SCismonX fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
186*17ba6f42SCismonX ASSERT_LE(0, fd) << strerror(errno);
187*17ba6f42SCismonX
188*17ba6f42SCismonX memcpy(buf, INPUT_DATA, sizeof(INPUT_DATA));
189*17ba6f42SCismonX EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
190*17ba6f42SCismonX EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
191*17ba6f42SCismonX
192*17ba6f42SCismonX leak(fd);
193*17ba6f42SCismonX }
194*17ba6f42SCismonX
TEST_F(Ioctl,iowint)195*17ba6f42SCismonX TEST_F(Ioctl, iowint)
196*17ba6f42SCismonX {
197*17ba6f42SCismonX unsigned long req = _IOWINT(0xff, 5);
198*17ba6f42SCismonX int arg = 1337;
199*17ba6f42SCismonX int fd, r;
200*17ba6f42SCismonX
201*17ba6f42SCismonX expect_opendir(FUSE_ROOT_ID);
202*17ba6f42SCismonX expect_ioctl_rw(FUSE_ROOT_ID);
203*17ba6f42SCismonX
204*17ba6f42SCismonX fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
205*17ba6f42SCismonX ASSERT_LE(0, fd) << strerror(errno);
206*17ba6f42SCismonX
207*17ba6f42SCismonX /* The server is allowed to return a positive value on success */
208*17ba6f42SCismonX r = ioctl(fd, req, arg);
209*17ba6f42SCismonX EXPECT_LE(0, r) << strerror(errno);
210*17ba6f42SCismonX EXPECT_EQ(arg, r);
211*17ba6f42SCismonX
212*17ba6f42SCismonX leak(fd);
213*17ba6f42SCismonX }
214