Skip to content

Commit 17ba6f4

Browse files
CismonXbsdimp
authored andcommitted
fusefs: support FUSE_IOCTL
MFC After: 1 week Signed-off-by: CismonX <[email protected]> Reviewed by: imp Pull Request: #1470
1 parent 0a5535d commit 17ba6f4

File tree

7 files changed

+322
-9
lines changed

7 files changed

+322
-9
lines changed

sys/fs/fuse/fuse_internal.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,7 +1103,6 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td)
11031103
* FUSE_SPLICE_WRITE, FUSE_SPLICE_MOVE, FUSE_SPLICE_READ: FreeBSD
11041104
* doesn't have splice(2).
11051105
* FUSE_FLOCK_LOCKS: not yet implemented
1106-
* FUSE_HAS_IOCTL_DIR: not yet implemented
11071106
* FUSE_AUTO_INVAL_DATA: not yet implemented
11081107
* FUSE_DO_READDIRPLUS: not yet implemented
11091108
* FUSE_READDIRPLUS_AUTO: not yet implemented
@@ -1116,7 +1115,7 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td)
11161115
* FUSE_MAX_PAGES: not yet implemented
11171116
*/
11181117
fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT
1119-
| FUSE_BIG_WRITES | FUSE_WRITEBACK_CACHE
1118+
| FUSE_BIG_WRITES | FUSE_HAS_IOCTL_DIR | FUSE_WRITEBACK_CACHE
11201119
| FUSE_NO_OPEN_SUPPORT | FUSE_NO_OPENDIR_SUPPORT
11211120
| FUSE_SETXATTR_EXT;
11221121

sys/fs/fuse/fuse_ipc.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,10 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen)
835835
err = (blen == 0) ? 0 : EINVAL;
836836
break;
837837

838+
case FUSE_IOCTL:
839+
err = (blen >= sizeof(struct fuse_ioctl_out)) ? 0 : EINVAL;
840+
break;
841+
838842
case FUSE_FALLOCATE:
839843
err = (blen == 0) ? 0 : EINVAL;
840844
break;

sys/fs/fuse/fuse_vnops.c

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
#include <sys/vmmeter.h>
9292
#define EXTERR_CATEGORY EXTERR_CAT_FUSE_VNOPS
9393
#include <sys/exterrvar.h>
94+
#include <sys/sysent.h>
9495

9596
#include <vm/vm.h>
9697
#include <vm/vm_extern.h>
@@ -374,6 +375,84 @@ fuse_inval_buf_range(struct vnode *vp, off_t filesize, off_t start, off_t end)
374375
return (0);
375376
}
376377

378+
/* Send FUSE_IOCTL for this node */
379+
static int
380+
fuse_vnop_do_ioctl(struct vnode *vp, u_long cmd, void *arg, int fflag,
381+
struct ucred *cred, struct thread *td)
382+
{
383+
struct fuse_dispatcher fdi;
384+
struct fuse_ioctl_in *fii;
385+
struct fuse_ioctl_out *fio;
386+
struct fuse_filehandle *fufh;
387+
uint32_t flags = 0;
388+
uint32_t insize = 0;
389+
uint32_t outsize = 0;
390+
int err;
391+
392+
err = fuse_filehandle_getrw(vp, fflag, &fufh, cred, td->td_proc->p_pid);
393+
if (err != 0)
394+
return (err);
395+
396+
if (vnode_isdir(vp)) {
397+
struct fuse_data *data = fuse_get_mpdata(vnode_mount(vp));
398+
399+
if (!fuse_libabi_geq(data, 7, 18))
400+
return (ENOTTY);
401+
flags |= FUSE_IOCTL_DIR;
402+
}
403+
#ifdef __LP64__
404+
#ifdef COMPAT_FREEBSD32
405+
if (SV_PROC_FLAG(td->td_proc, SV_ILP32))
406+
flags |= FUSE_IOCTL_32BIT;
407+
#endif
408+
#else /* !defined(__LP64__) */
409+
flags |= FUSE_IOCTL_32BIT;
410+
#endif
411+
412+
if ((cmd & IOC_OUT) != 0)
413+
outsize = IOCPARM_LEN(cmd);
414+
/* _IOWINT() sets IOC_VOID */
415+
if ((cmd & (IOC_VOID | IOC_IN)) != 0)
416+
insize = IOCPARM_LEN(cmd);
417+
418+
fdisp_init(&fdi, sizeof(*fii) + insize);
419+
fdisp_make_vp(&fdi, FUSE_IOCTL, vp, td, cred);
420+
fii = fdi.indata;
421+
fii->fh = fufh->fh_id;
422+
fii->flags = flags;
423+
fii->cmd = cmd;
424+
fii->arg = (uintptr_t)arg;
425+
fii->in_size = insize;
426+
fii->out_size = outsize;
427+
if (insize > 0)
428+
memcpy((char *)fii + sizeof(*fii), arg, insize);
429+
430+
err = fdisp_wait_answ(&fdi);
431+
if (err != 0) {
432+
if (err == ENOSYS)
433+
err = ENOTTY;
434+
goto out;
435+
}
436+
437+
fio = fdi.answ;
438+
if (fdi.iosize > sizeof(*fio)) {
439+
size_t realoutsize = fdi.iosize - sizeof(*fio);
440+
441+
if (realoutsize > outsize) {
442+
err = EIO;
443+
goto out;
444+
}
445+
memcpy(arg, (char *)fio + sizeof(*fio), realoutsize);
446+
}
447+
if (fio->result > 0)
448+
td->td_retval[0] = fio->result;
449+
else
450+
err = -fio->result;
451+
452+
out:
453+
fdisp_destroy(&fdi);
454+
return (err);
455+
}
377456

378457
/* Send FUSE_LSEEK for this node */
379458
static int
@@ -1294,34 +1373,38 @@ fuse_vnop_ioctl(struct vop_ioctl_args *ap)
12941373
struct vnode *vp = ap->a_vp;
12951374
struct mount *mp = vnode_mount(vp);
12961375
struct ucred *cred = ap->a_cred;
1297-
off_t *offp;
1298-
pid_t pid = ap->a_td->td_proc->p_pid;
1376+
struct thread *td = ap->a_td;
12991377
int err;
13001378

1379+
if (fuse_isdeadfs(vp)) {
1380+
return (ENXIO);
1381+
}
1382+
13011383
switch (ap->a_command) {
13021384
case FIOSEEKDATA:
13031385
case FIOSEEKHOLE:
13041386
/* Call FUSE_LSEEK, if we can, or fall back to vop_stdioctl */
13051387
if (fsess_maybe_impl(mp, FUSE_LSEEK)) {
1388+
off_t *offp = ap->a_data;
1389+
pid_t pid = td->td_proc->p_pid;
13061390
int whence;
13071391

1308-
offp = ap->a_data;
13091392
if (ap->a_command == FIOSEEKDATA)
13101393
whence = SEEK_DATA;
13111394
else
13121395
whence = SEEK_HOLE;
13131396

13141397
vn_lock(vp, LK_SHARED | LK_RETRY);
1315-
err = fuse_vnop_do_lseek(vp, ap->a_td, cred, pid, offp,
1398+
err = fuse_vnop_do_lseek(vp, td, cred, pid, offp,
13161399
whence);
13171400
VOP_UNLOCK(vp);
13181401
}
13191402
if (fsess_not_impl(mp, FUSE_LSEEK))
13201403
err = vop_stdioctl(ap);
13211404
break;
13221405
default:
1323-
/* TODO: implement FUSE_IOCTL */
1324-
err = ENOTTY;
1406+
err = fuse_vnop_do_ioctl(vp, ap->a_command, ap->a_data,
1407+
ap->a_fflag, cred, td);
13251408
break;
13261409
}
13271410
return (err);

tests/sys/fs/fusefs/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ GTESTS+= fsyncdir
2929
GTESTS+= getattr
3030
GTESTS+= interrupt
3131
GTESTS+= io
32+
GTESTS+= ioctl
3233
GTESTS+= last_local_modify
3334
GTESTS+= link
3435
GTESTS+= locks

tests/sys/fs/fusefs/ioctl.cc

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*-
2+
* SPDX-License-Identifier: BSD-2-Clause
3+
*
4+
* Copyright (c) 2025 CismonX <[email protected]>
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+
extern "C" {
29+
#include <sys/types.h>
30+
#include <sys/ioctl.h>
31+
#include <fcntl.h>
32+
#include <string.h>
33+
}
34+
35+
#include "mockfs.hh"
36+
#include "utils.hh"
37+
38+
using namespace testing;
39+
40+
using IoctlTestProcT = std::function<void (int)>;
41+
42+
static const char INPUT_DATA[] = "input_data";
43+
static const char OUTPUT_DATA[] = "output_data";
44+
45+
class Ioctl: public FuseTest {
46+
public:
47+
void expect_ioctl(uint64_t ino, ProcessMockerT r)
48+
{
49+
EXPECT_CALL(*m_mock, process(
50+
ResultOf([=](auto in) {
51+
return (in.header.opcode == FUSE_IOCTL &&
52+
in.header.nodeid == ino);
53+
}, Eq(true)), _)
54+
).WillOnce(Invoke(r)).RetiresOnSaturation();
55+
}
56+
57+
void expect_ioctl_rw(uint64_t ino)
58+
{
59+
/*
60+
* _IOR(): Compare the input data with INPUT_DATA.
61+
* _IOW(): Copy out OUTPUT_DATA.
62+
* _IOWR(): Combination of above.
63+
* _IOWINT(): Return the integer argument value.
64+
*/
65+
expect_ioctl(ino, ReturnImmediate([](auto in, auto& out) {
66+
uint8_t *in_buf = in.body.bytes + sizeof(in.body.ioctl);
67+
uint8_t *out_buf = out.body.bytes + sizeof(out.body.ioctl);
68+
uint32_t cmd = in.body.ioctl.cmd;
69+
uint32_t arg_len = IOCPARM_LEN(cmd);
70+
int result = 0;
71+
72+
out.header.error = 0;
73+
SET_OUT_HEADER_LEN(out, ioctl);
74+
if ((cmd & IOC_VOID) != 0 && arg_len > 0) {
75+
memcpy(&result, in_buf, sizeof(int));
76+
goto out;
77+
}
78+
if ((cmd & IOC_IN) != 0) {
79+
if (0 != strncmp(INPUT_DATA, (char *)in_buf, arg_len)) {
80+
result = -EINVAL;
81+
goto out;
82+
}
83+
}
84+
if ((cmd & IOC_OUT) != 0) {
85+
memcpy(out_buf, OUTPUT_DATA, sizeof(OUTPUT_DATA));
86+
out.header.len += sizeof(OUTPUT_DATA);
87+
}
88+
89+
out:
90+
out.body.ioctl.result = result;
91+
}));
92+
}
93+
};
94+
95+
/**
96+
* If the server does not implement FUSE_IOCTL handler (returns ENOSYS),
97+
* the kernel should return ENOTTY to the user instead.
98+
*/
99+
TEST_F(Ioctl, enosys)
100+
{
101+
unsigned long req = _IO(0xff, 0);
102+
int fd;
103+
104+
expect_opendir(FUSE_ROOT_ID);
105+
expect_ioctl(FUSE_ROOT_ID, ReturnErrno(ENOSYS));
106+
107+
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
108+
ASSERT_LE(0, fd) << strerror(errno);
109+
110+
EXPECT_EQ(-1, ioctl(fd, req));
111+
EXPECT_EQ(ENOTTY, errno);
112+
113+
leak(fd);
114+
}
115+
116+
/*
117+
* For _IOR() and _IOWR(), The server is allowed to write fewer bytes
118+
* than IOCPARM_LEN(req).
119+
*/
120+
TEST_F(Ioctl, ior)
121+
{
122+
char buf[sizeof(OUTPUT_DATA) + 1] = { 0 };
123+
unsigned long req = _IOR(0xff, 1, buf);
124+
int fd;
125+
126+
expect_opendir(FUSE_ROOT_ID);
127+
expect_ioctl_rw(FUSE_ROOT_ID);
128+
129+
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
130+
ASSERT_LE(0, fd) << strerror(errno);
131+
132+
EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
133+
EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
134+
135+
leak(fd);
136+
}
137+
138+
/*
139+
* For _IOR() and _IOWR(), if the server attempts to write more bytes
140+
* than IOCPARM_LEN(req), the kernel should fail the syscall with EIO.
141+
*/
142+
TEST_F(Ioctl, ior_overflow)
143+
{
144+
char buf[sizeof(OUTPUT_DATA) - 1] = { 0 };
145+
unsigned long req = _IOR(0xff, 2, buf);
146+
int fd;
147+
148+
expect_opendir(FUSE_ROOT_ID);
149+
expect_ioctl_rw(FUSE_ROOT_ID);
150+
151+
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
152+
ASSERT_LE(0, fd) << strerror(errno);
153+
154+
EXPECT_EQ(-1, ioctl(fd, req, buf));
155+
EXPECT_EQ(EIO, errno);
156+
157+
leak(fd);
158+
}
159+
160+
TEST_F(Ioctl, iow)
161+
{
162+
unsigned long req = _IOW(0xff, 3, INPUT_DATA);
163+
int fd;
164+
165+
expect_opendir(FUSE_ROOT_ID);
166+
expect_ioctl_rw(FUSE_ROOT_ID);
167+
168+
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
169+
ASSERT_LE(0, fd) << strerror(errno);
170+
171+
EXPECT_EQ(0, ioctl(fd, req, INPUT_DATA)) << strerror(errno);
172+
173+
leak(fd);
174+
}
175+
176+
TEST_F(Ioctl, iowr)
177+
{
178+
char buf[std::max(sizeof(INPUT_DATA), sizeof(OUTPUT_DATA))] = { 0 };
179+
unsigned long req = _IOWR(0xff, 4, buf);
180+
int fd;
181+
182+
expect_opendir(FUSE_ROOT_ID);
183+
expect_ioctl_rw(FUSE_ROOT_ID);
184+
185+
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
186+
ASSERT_LE(0, fd) << strerror(errno);
187+
188+
memcpy(buf, INPUT_DATA, sizeof(INPUT_DATA));
189+
EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
190+
EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
191+
192+
leak(fd);
193+
}
194+
195+
TEST_F(Ioctl, iowint)
196+
{
197+
unsigned long req = _IOWINT(0xff, 5);
198+
int arg = 1337;
199+
int fd, r;
200+
201+
expect_opendir(FUSE_ROOT_ID);
202+
expect_ioctl_rw(FUSE_ROOT_ID);
203+
204+
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
205+
ASSERT_LE(0, fd) << strerror(errno);
206+
207+
/* The server is allowed to return a positive value on success */
208+
r = ioctl(fd, req, arg);
209+
EXPECT_LE(0, r) << strerror(errno);
210+
EXPECT_EQ(arg, r);
211+
212+
leak(fd);
213+
}

0 commit comments

Comments
 (0)