mirror of
https://git.freebsd.org/src.git
synced 2026-01-11 19:57:22 +00:00
fusefs: support FUSE_IOCTL
MFC After: 1 week Signed-off-by: CismonX <admin@cismon.net> Reviewed by: imp Pull Request: https://github.com/freebsd/freebsd-src/pull/1470
This commit is contained in:
parent
0a5535d1c5
commit
17ba6f4286
7 changed files with 322 additions and 9 deletions
|
|
@ -1103,7 +1103,6 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td)
|
|||
* FUSE_SPLICE_WRITE, FUSE_SPLICE_MOVE, FUSE_SPLICE_READ: FreeBSD
|
||||
* doesn't have splice(2).
|
||||
* FUSE_FLOCK_LOCKS: not yet implemented
|
||||
* FUSE_HAS_IOCTL_DIR: not yet implemented
|
||||
* FUSE_AUTO_INVAL_DATA: not yet implemented
|
||||
* FUSE_DO_READDIRPLUS: not yet implemented
|
||||
* FUSE_READDIRPLUS_AUTO: not yet implemented
|
||||
|
|
@ -1116,7 +1115,7 @@ fuse_internal_send_init(struct fuse_data *data, struct thread *td)
|
|||
* FUSE_MAX_PAGES: not yet implemented
|
||||
*/
|
||||
fiii->flags = FUSE_ASYNC_READ | FUSE_POSIX_LOCKS | FUSE_EXPORT_SUPPORT
|
||||
| FUSE_BIG_WRITES | FUSE_WRITEBACK_CACHE
|
||||
| FUSE_BIG_WRITES | FUSE_HAS_IOCTL_DIR | FUSE_WRITEBACK_CACHE
|
||||
| FUSE_NO_OPEN_SUPPORT | FUSE_NO_OPENDIR_SUPPORT
|
||||
| FUSE_SETXATTR_EXT;
|
||||
|
||||
|
|
|
|||
|
|
@ -835,6 +835,10 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen)
|
|||
err = (blen == 0) ? 0 : EINVAL;
|
||||
break;
|
||||
|
||||
case FUSE_IOCTL:
|
||||
err = (blen >= sizeof(struct fuse_ioctl_out)) ? 0 : EINVAL;
|
||||
break;
|
||||
|
||||
case FUSE_FALLOCATE:
|
||||
err = (blen == 0) ? 0 : EINVAL;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@
|
|||
#include <sys/vmmeter.h>
|
||||
#define EXTERR_CATEGORY EXTERR_CAT_FUSE_VNOPS
|
||||
#include <sys/exterrvar.h>
|
||||
#include <sys/sysent.h>
|
||||
|
||||
#include <vm/vm.h>
|
||||
#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)
|
|||
return (0);
|
||||
}
|
||||
|
||||
/* Send FUSE_IOCTL for this node */
|
||||
static int
|
||||
fuse_vnop_do_ioctl(struct vnode *vp, u_long cmd, void *arg, int fflag,
|
||||
struct ucred *cred, struct thread *td)
|
||||
{
|
||||
struct fuse_dispatcher fdi;
|
||||
struct fuse_ioctl_in *fii;
|
||||
struct fuse_ioctl_out *fio;
|
||||
struct fuse_filehandle *fufh;
|
||||
uint32_t flags = 0;
|
||||
uint32_t insize = 0;
|
||||
uint32_t outsize = 0;
|
||||
int err;
|
||||
|
||||
err = fuse_filehandle_getrw(vp, fflag, &fufh, cred, td->td_proc->p_pid);
|
||||
if (err != 0)
|
||||
return (err);
|
||||
|
||||
if (vnode_isdir(vp)) {
|
||||
struct fuse_data *data = fuse_get_mpdata(vnode_mount(vp));
|
||||
|
||||
if (!fuse_libabi_geq(data, 7, 18))
|
||||
return (ENOTTY);
|
||||
flags |= FUSE_IOCTL_DIR;
|
||||
}
|
||||
#ifdef __LP64__
|
||||
#ifdef COMPAT_FREEBSD32
|
||||
if (SV_PROC_FLAG(td->td_proc, SV_ILP32))
|
||||
flags |= FUSE_IOCTL_32BIT;
|
||||
#endif
|
||||
#else /* !defined(__LP64__) */
|
||||
flags |= FUSE_IOCTL_32BIT;
|
||||
#endif
|
||||
|
||||
if ((cmd & IOC_OUT) != 0)
|
||||
outsize = IOCPARM_LEN(cmd);
|
||||
/* _IOWINT() sets IOC_VOID */
|
||||
if ((cmd & (IOC_VOID | IOC_IN)) != 0)
|
||||
insize = IOCPARM_LEN(cmd);
|
||||
|
||||
fdisp_init(&fdi, sizeof(*fii) + insize);
|
||||
fdisp_make_vp(&fdi, FUSE_IOCTL, vp, td, cred);
|
||||
fii = fdi.indata;
|
||||
fii->fh = fufh->fh_id;
|
||||
fii->flags = flags;
|
||||
fii->cmd = cmd;
|
||||
fii->arg = (uintptr_t)arg;
|
||||
fii->in_size = insize;
|
||||
fii->out_size = outsize;
|
||||
if (insize > 0)
|
||||
memcpy((char *)fii + sizeof(*fii), arg, insize);
|
||||
|
||||
err = fdisp_wait_answ(&fdi);
|
||||
if (err != 0) {
|
||||
if (err == ENOSYS)
|
||||
err = ENOTTY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
fio = fdi.answ;
|
||||
if (fdi.iosize > sizeof(*fio)) {
|
||||
size_t realoutsize = fdi.iosize - sizeof(*fio);
|
||||
|
||||
if (realoutsize > outsize) {
|
||||
err = EIO;
|
||||
goto out;
|
||||
}
|
||||
memcpy(arg, (char *)fio + sizeof(*fio), realoutsize);
|
||||
}
|
||||
if (fio->result > 0)
|
||||
td->td_retval[0] = fio->result;
|
||||
else
|
||||
err = -fio->result;
|
||||
|
||||
out:
|
||||
fdisp_destroy(&fdi);
|
||||
return (err);
|
||||
}
|
||||
|
||||
/* Send FUSE_LSEEK for this node */
|
||||
static int
|
||||
|
|
@ -1294,25 +1373,29 @@ fuse_vnop_ioctl(struct vop_ioctl_args *ap)
|
|||
struct vnode *vp = ap->a_vp;
|
||||
struct mount *mp = vnode_mount(vp);
|
||||
struct ucred *cred = ap->a_cred;
|
||||
off_t *offp;
|
||||
pid_t pid = ap->a_td->td_proc->p_pid;
|
||||
struct thread *td = ap->a_td;
|
||||
int err;
|
||||
|
||||
if (fuse_isdeadfs(vp)) {
|
||||
return (ENXIO);
|
||||
}
|
||||
|
||||
switch (ap->a_command) {
|
||||
case FIOSEEKDATA:
|
||||
case FIOSEEKHOLE:
|
||||
/* Call FUSE_LSEEK, if we can, or fall back to vop_stdioctl */
|
||||
if (fsess_maybe_impl(mp, FUSE_LSEEK)) {
|
||||
off_t *offp = ap->a_data;
|
||||
pid_t pid = td->td_proc->p_pid;
|
||||
int whence;
|
||||
|
||||
offp = ap->a_data;
|
||||
if (ap->a_command == FIOSEEKDATA)
|
||||
whence = SEEK_DATA;
|
||||
else
|
||||
whence = SEEK_HOLE;
|
||||
|
||||
vn_lock(vp, LK_SHARED | LK_RETRY);
|
||||
err = fuse_vnop_do_lseek(vp, ap->a_td, cred, pid, offp,
|
||||
err = fuse_vnop_do_lseek(vp, td, cred, pid, offp,
|
||||
whence);
|
||||
VOP_UNLOCK(vp);
|
||||
}
|
||||
|
|
@ -1320,8 +1403,8 @@ fuse_vnop_ioctl(struct vop_ioctl_args *ap)
|
|||
err = vop_stdioctl(ap);
|
||||
break;
|
||||
default:
|
||||
/* TODO: implement FUSE_IOCTL */
|
||||
err = ENOTTY;
|
||||
err = fuse_vnop_do_ioctl(vp, ap->a_command, ap->a_data,
|
||||
ap->a_fflag, cred, td);
|
||||
break;
|
||||
}
|
||||
return (err);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ GTESTS+= fsyncdir
|
|||
GTESTS+= getattr
|
||||
GTESTS+= interrupt
|
||||
GTESTS+= io
|
||||
GTESTS+= ioctl
|
||||
GTESTS+= last_local_modify
|
||||
GTESTS+= link
|
||||
GTESTS+= locks
|
||||
|
|
|
|||
213
tests/sys/fs/fusefs/ioctl.cc
Normal file
213
tests/sys/fs/fusefs/ioctl.cc
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
/*-
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* Copyright (c) 2025 CismonX <admin@cismon.net>
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
extern "C" {
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
}
|
||||
|
||||
#include "mockfs.hh"
|
||||
#include "utils.hh"
|
||||
|
||||
using namespace testing;
|
||||
|
||||
using IoctlTestProcT = std::function<void (int)>;
|
||||
|
||||
static const char INPUT_DATA[] = "input_data";
|
||||
static const char OUTPUT_DATA[] = "output_data";
|
||||
|
||||
class Ioctl: public FuseTest {
|
||||
public:
|
||||
void expect_ioctl(uint64_t ino, ProcessMockerT r)
|
||||
{
|
||||
EXPECT_CALL(*m_mock, process(
|
||||
ResultOf([=](auto in) {
|
||||
return (in.header.opcode == FUSE_IOCTL &&
|
||||
in.header.nodeid == ino);
|
||||
}, Eq(true)), _)
|
||||
).WillOnce(Invoke(r)).RetiresOnSaturation();
|
||||
}
|
||||
|
||||
void expect_ioctl_rw(uint64_t ino)
|
||||
{
|
||||
/*
|
||||
* _IOR(): Compare the input data with INPUT_DATA.
|
||||
* _IOW(): Copy out OUTPUT_DATA.
|
||||
* _IOWR(): Combination of above.
|
||||
* _IOWINT(): Return the integer argument value.
|
||||
*/
|
||||
expect_ioctl(ino, ReturnImmediate([](auto in, auto& out) {
|
||||
uint8_t *in_buf = in.body.bytes + sizeof(in.body.ioctl);
|
||||
uint8_t *out_buf = out.body.bytes + sizeof(out.body.ioctl);
|
||||
uint32_t cmd = in.body.ioctl.cmd;
|
||||
uint32_t arg_len = IOCPARM_LEN(cmd);
|
||||
int result = 0;
|
||||
|
||||
out.header.error = 0;
|
||||
SET_OUT_HEADER_LEN(out, ioctl);
|
||||
if ((cmd & IOC_VOID) != 0 && arg_len > 0) {
|
||||
memcpy(&result, in_buf, sizeof(int));
|
||||
goto out;
|
||||
}
|
||||
if ((cmd & IOC_IN) != 0) {
|
||||
if (0 != strncmp(INPUT_DATA, (char *)in_buf, arg_len)) {
|
||||
result = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
if ((cmd & IOC_OUT) != 0) {
|
||||
memcpy(out_buf, OUTPUT_DATA, sizeof(OUTPUT_DATA));
|
||||
out.header.len += sizeof(OUTPUT_DATA);
|
||||
}
|
||||
|
||||
out:
|
||||
out.body.ioctl.result = result;
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If the server does not implement FUSE_IOCTL handler (returns ENOSYS),
|
||||
* the kernel should return ENOTTY to the user instead.
|
||||
*/
|
||||
TEST_F(Ioctl, enosys)
|
||||
{
|
||||
unsigned long req = _IO(0xff, 0);
|
||||
int fd;
|
||||
|
||||
expect_opendir(FUSE_ROOT_ID);
|
||||
expect_ioctl(FUSE_ROOT_ID, ReturnErrno(ENOSYS));
|
||||
|
||||
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
|
||||
ASSERT_LE(0, fd) << strerror(errno);
|
||||
|
||||
EXPECT_EQ(-1, ioctl(fd, req));
|
||||
EXPECT_EQ(ENOTTY, errno);
|
||||
|
||||
leak(fd);
|
||||
}
|
||||
|
||||
/*
|
||||
* For _IOR() and _IOWR(), The server is allowed to write fewer bytes
|
||||
* than IOCPARM_LEN(req).
|
||||
*/
|
||||
TEST_F(Ioctl, ior)
|
||||
{
|
||||
char buf[sizeof(OUTPUT_DATA) + 1] = { 0 };
|
||||
unsigned long req = _IOR(0xff, 1, buf);
|
||||
int fd;
|
||||
|
||||
expect_opendir(FUSE_ROOT_ID);
|
||||
expect_ioctl_rw(FUSE_ROOT_ID);
|
||||
|
||||
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
|
||||
ASSERT_LE(0, fd) << strerror(errno);
|
||||
|
||||
EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
|
||||
EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
|
||||
|
||||
leak(fd);
|
||||
}
|
||||
|
||||
/*
|
||||
* For _IOR() and _IOWR(), if the server attempts to write more bytes
|
||||
* than IOCPARM_LEN(req), the kernel should fail the syscall with EIO.
|
||||
*/
|
||||
TEST_F(Ioctl, ior_overflow)
|
||||
{
|
||||
char buf[sizeof(OUTPUT_DATA) - 1] = { 0 };
|
||||
unsigned long req = _IOR(0xff, 2, buf);
|
||||
int fd;
|
||||
|
||||
expect_opendir(FUSE_ROOT_ID);
|
||||
expect_ioctl_rw(FUSE_ROOT_ID);
|
||||
|
||||
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
|
||||
ASSERT_LE(0, fd) << strerror(errno);
|
||||
|
||||
EXPECT_EQ(-1, ioctl(fd, req, buf));
|
||||
EXPECT_EQ(EIO, errno);
|
||||
|
||||
leak(fd);
|
||||
}
|
||||
|
||||
TEST_F(Ioctl, iow)
|
||||
{
|
||||
unsigned long req = _IOW(0xff, 3, INPUT_DATA);
|
||||
int fd;
|
||||
|
||||
expect_opendir(FUSE_ROOT_ID);
|
||||
expect_ioctl_rw(FUSE_ROOT_ID);
|
||||
|
||||
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
|
||||
ASSERT_LE(0, fd) << strerror(errno);
|
||||
|
||||
EXPECT_EQ(0, ioctl(fd, req, INPUT_DATA)) << strerror(errno);
|
||||
|
||||
leak(fd);
|
||||
}
|
||||
|
||||
TEST_F(Ioctl, iowr)
|
||||
{
|
||||
char buf[std::max(sizeof(INPUT_DATA), sizeof(OUTPUT_DATA))] = { 0 };
|
||||
unsigned long req = _IOWR(0xff, 4, buf);
|
||||
int fd;
|
||||
|
||||
expect_opendir(FUSE_ROOT_ID);
|
||||
expect_ioctl_rw(FUSE_ROOT_ID);
|
||||
|
||||
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
|
||||
ASSERT_LE(0, fd) << strerror(errno);
|
||||
|
||||
memcpy(buf, INPUT_DATA, sizeof(INPUT_DATA));
|
||||
EXPECT_EQ(0, ioctl(fd, req, buf)) << strerror(errno);
|
||||
EXPECT_EQ(0, memcmp(buf, OUTPUT_DATA, sizeof(OUTPUT_DATA)));
|
||||
|
||||
leak(fd);
|
||||
}
|
||||
|
||||
TEST_F(Ioctl, iowint)
|
||||
{
|
||||
unsigned long req = _IOWINT(0xff, 5);
|
||||
int arg = 1337;
|
||||
int fd, r;
|
||||
|
||||
expect_opendir(FUSE_ROOT_ID);
|
||||
expect_ioctl_rw(FUSE_ROOT_ID);
|
||||
|
||||
fd = open("mountpoint", O_RDONLY | O_DIRECTORY);
|
||||
ASSERT_LE(0, fd) << strerror(errno);
|
||||
|
||||
/* The server is allowed to return a positive value on success */
|
||||
r = ioctl(fd, req, arg);
|
||||
EXPECT_LE(0, r) << strerror(errno);
|
||||
EXPECT_EQ(arg, r);
|
||||
|
||||
leak(fd);
|
||||
}
|
||||
|
|
@ -241,6 +241,12 @@ void MockFS::debug_request(const mockfs_buf_in &in, ssize_t buflen)
|
|||
case FUSE_INTERRUPT:
|
||||
printf(" unique=%" PRIu64, in.body.interrupt.unique);
|
||||
break;
|
||||
case FUSE_IOCTL:
|
||||
printf(" flags=%#x cmd=%#x in_size=%" PRIu32
|
||||
" out_size=%" PRIu32,
|
||||
in.body.ioctl.flags, in.body.ioctl.cmd,
|
||||
in.body.ioctl.in_size, in.body.ioctl.out_size);
|
||||
break;
|
||||
case FUSE_LINK:
|
||||
printf(" oldnodeid=%" PRIu64, in.body.link.oldnodeid);
|
||||
break;
|
||||
|
|
@ -678,6 +684,12 @@ void MockFS::audit_request(const mockfs_buf_in &in, ssize_t buflen) {
|
|||
EXPECT_EQ(inlen, fih + sizeof(in.body.init));
|
||||
EXPECT_EQ((size_t)buflen, inlen);
|
||||
break;
|
||||
case FUSE_IOCTL:
|
||||
EXPECT_GE(inlen, fih + sizeof(in.body.ioctl));
|
||||
EXPECT_EQ(inlen,
|
||||
fih + sizeof(in.body.ioctl) + in.body.ioctl.in_size);
|
||||
EXPECT_EQ((size_t)buflen, inlen);
|
||||
break;
|
||||
case FUSE_OPENDIR:
|
||||
EXPECT_EQ(inlen, fih + sizeof(in.body.opendir));
|
||||
EXPECT_EQ((size_t)buflen, inlen);
|
||||
|
|
@ -733,7 +745,6 @@ void MockFS::audit_request(const mockfs_buf_in &in, ssize_t buflen) {
|
|||
break;
|
||||
case FUSE_NOTIFY_REPLY:
|
||||
case FUSE_BATCH_FORGET:
|
||||
case FUSE_IOCTL:
|
||||
case FUSE_POLL:
|
||||
case FUSE_READDIRPLUS:
|
||||
FAIL() << "Unsupported opcode?";
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ union fuse_payloads_in {
|
|||
fuse_forget_in forget;
|
||||
fuse_getattr_in getattr;
|
||||
fuse_interrupt_in interrupt;
|
||||
fuse_ioctl_in ioctl;
|
||||
fuse_lk_in getlk;
|
||||
fuse_getxattr_in getxattr;
|
||||
fuse_init_in init;
|
||||
|
|
@ -222,6 +223,7 @@ union fuse_payloads_out {
|
|||
fuse_listxattr_out listxattr;
|
||||
fuse_open_out open;
|
||||
fuse_statfs_out statfs;
|
||||
fuse_ioctl_out ioctl;
|
||||
/*
|
||||
* The protocol places no limits on the length of the string. This is
|
||||
* merely convenient for testing.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue