Skip to content

Commit 0922143

Browse files
committed
feat: add generic ioctl support
1 parent 916c0d1 commit 0922143

File tree

10 files changed

+200
-23
lines changed

10 files changed

+200
-23
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ time = { version = "0.3", default-features = false }
138138
volatile = "0.6"
139139
zerocopy = { version = "0.8", default-features = false }
140140
uhyve-interface = "0.1.3"
141+
bitfield-struct = "0.11.0"
141142

142143
[dependencies.smoltcp]
143144
version = "0.12"

src/fd/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,15 @@ pub(crate) trait ObjectInterface: Sync + Send + core::fmt::Debug {
268268
Err(Errno::Nosys)
269269
}
270270

271+
/// Handles an ioctl
272+
fn handle_ioctl(
273+
&self,
274+
_cmd: crate::fs::ioctl::IoCtlCall,
275+
_argp: *mut core::ffi::c_void,
276+
) -> io::Result<()> {
277+
Err(Errno::Nosys)
278+
}
279+
271280
/// `isatty` returns `true` for a terminal device
272281
async fn isatty(&self) -> io::Result<bool> {
273282
Ok(false)

src/fd/socket/mod.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,33 @@
1+
use crate::executor::block_on;
2+
use crate::fd::ObjectInterface;
3+
use crate::{fd, io};
4+
use crate::errno::Errno;
5+
16
#[cfg(feature = "tcp")]
27
pub(crate) mod tcp;
38
#[cfg(feature = "udp")]
49
pub(crate) mod udp;
510
#[cfg(feature = "vsock")]
611
pub(crate) mod vsock;
12+
13+
/// Handles an ioctl (general function)
14+
fn socket_handle_ioctl(
15+
this: &dyn ObjectInterface,
16+
cmd: crate::fs::ioctl::IoCtlCall,
17+
argp: *mut core::ffi::c_void,
18+
) -> io::Result<()> {
19+
const FIONBIO: u32 = 0x8008_667eu32;
20+
21+
if cmd.into_bits() == FIONBIO {
22+
let value = unsafe { *(argp as *const i32) };
23+
let status_flags = if value != 0 {
24+
fd::StatusFlags::O_NONBLOCK
25+
} else {
26+
fd::StatusFlags::empty()
27+
};
28+
29+
block_on(this.set_status_flags(status_flags), None)
30+
} else {
31+
Err(Errno::Inval)
32+
}
33+
}

src/fd/socket/tcp.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use alloc::boxed::Box;
22
use alloc::collections::BTreeSet;
33
use alloc::sync::Arc;
4+
use core::ffi::c_void;
45
use core::future;
56
use core::mem::MaybeUninit;
67
use core::sync::atomic::{AtomicU16, Ordering};
@@ -533,4 +534,8 @@ impl ObjectInterface for async_lock::RwLock<Socket> {
533534
async fn set_status_flags(&self, status_flags: fd::StatusFlags) -> io::Result<()> {
534535
self.write().await.set_status_flags(status_flags).await
535536
}
537+
538+
fn handle_ioctl(&self, cmd: crate::fs::ioctl::IoCtlCall, argp: *mut c_void) -> io::Result<()> {
539+
super::socket_handle_ioctl(self, cmd, argp)
540+
}
536541
}

src/fd/socket/udp.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use alloc::boxed::Box;
2+
use core::ffi::c_void;
23
use core::future;
34
use core::mem::MaybeUninit;
45
use core::task::Poll;
@@ -293,4 +294,8 @@ impl ObjectInterface for async_lock::RwLock<Socket> {
293294
async fn set_status_flags(&self, status_flags: fd::StatusFlags) -> io::Result<()> {
294295
self.write().await.set_status_flags(status_flags).await
295296
}
297+
298+
fn handle_ioctl(&self, cmd: crate::fs::ioctl::IoCtlCall, argp: *mut c_void) -> io::Result<()> {
299+
super::socket_handle_ioctl(self, cmd, argp)
300+
}
296301
}

src/fd/socket/vsock.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use alloc::boxed::Box;
22
use alloc::sync::Arc;
33
use alloc::vec::Vec;
4+
use core::ffi::c_void;
45
use core::future;
56
use core::mem::MaybeUninit;
67
use core::task::Poll;
@@ -471,4 +472,8 @@ impl ObjectInterface for async_lock::RwLock<Socket> {
471472
async fn set_status_flags(&self, status_flags: fd::StatusFlags) -> io::Result<()> {
472473
self.write().await.set_status_flags(status_flags).await
473474
}
475+
476+
fn handle_ioctl(&self, cmd: crate::fs::ioctl::IoCtlCall, argp: *mut c_void) -> io::Result<()> {
477+
super::socket_handle_ioctl(self, cmd, argp)
478+
}
474479
}

src/fs/ioctl.rs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//! A module for custom IOCTL objects
2+
3+
use alloc::boxed::Box;
4+
use alloc::sync::Arc;
5+
use alloc::vec::Vec;
6+
use core::fmt::Debug;
7+
8+
use bitfield_struct::bitfield;
9+
10+
use crate::fd::{AccessPermission, ObjectInterface};
11+
use crate::fs::{NodeKind, VfsNode};
12+
use crate::io;
13+
14+
/// Encoding for an IOCTL command, as done in the Linux Kernel.
15+
///
16+
/// See [relevant kernel header](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/ioctl.h?h=v6.15) for reference.
17+
///
18+
/// The goal of this interface is to easily support linux applications that communicate via IOCTL,
19+
/// so linux compatibility is an intended and explicit goal here.
20+
#[bitfield(u32)]
21+
pub struct IoCtlCall {
22+
call_nr: u8,
23+
24+
call_type: u8,
25+
26+
#[bits(2, from = IoCtlDirection::from_bits_truncate, default = IoCtlDirection::empty())]
27+
call_dir: IoCtlDirection,
28+
29+
#[bits(14)]
30+
call_size: u16,
31+
}
32+
33+
bitflags! {
34+
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
35+
pub struct IoCtlDirection: u8 {
36+
const IOC_WRITE = 1;
37+
const IOC_READ = 2;
38+
}
39+
}
40+
41+
impl IoCtlDirection {
42+
// Required for IoCtlCall
43+
const fn into_bits(self) -> u8 {
44+
self.bits()
45+
}
46+
}
47+
48+
#[derive(Debug)]
49+
struct IoCtlNode(Arc<dyn ObjectInterface>);
50+
51+
impl VfsNode for IoCtlNode {
52+
fn get_kind(&self) -> NodeKind {
53+
NodeKind::File
54+
}
55+
56+
fn get_object(&self) -> io::Result<Arc<dyn ObjectInterface>> {
57+
Ok(self.0.clone())
58+
}
59+
}
60+
61+
/// Register a custom object to handle IOCTLS at a given path,
62+
///
63+
/// This call mounts a trivial VfsNode that opens to the provided ioctl_object at the given path.
64+
#[allow(dead_code)]
65+
pub(crate) fn register_ioctl(path: &str, ioctl_object: Arc<dyn ObjectInterface>) {
66+
assert!(path.starts_with("/"));
67+
let mut path: Vec<&str> = path.split("/").skip(1).collect();
68+
assert!(!path.is_empty());
69+
70+
let fs = super::FILESYSTEM
71+
.get()
72+
.expect("Failed to mount ioctl: filesystem is not yet initialized");
73+
74+
// Create parent directory
75+
let mut directory: Vec<&str> = path.clone();
76+
directory.pop().unwrap(); // remove file name
77+
directory.reverse();
78+
79+
if !directory.is_empty() {
80+
let _ = fs
81+
.root
82+
.traverse_mkdir(&mut directory, AccessPermission::all()); // ignore possible errors at this step
83+
}
84+
85+
// Mount the file
86+
path.reverse();
87+
fs.root
88+
.traverse_mount(&mut path, Box::new(IoCtlNode(ioctl_object)))
89+
.expect("Failed to mount ioctl: filesystem error");
90+
}
91+
92+
#[cfg(all(test, not(target_os = "none")))]
93+
mod tests {
94+
use super::{IoCtlCall, IoCtlDirection};
95+
96+
#[test]
97+
fn ioctl_call_correctly_written() {
98+
let call_nr = 0x12u8;
99+
let call_type = 0x78u8;
100+
let call_dir = IoCtlDirection::IOC_WRITE;
101+
let call_size = 0x423u16;
102+
103+
let ioctl_call_number = (u32::from(call_size) << 18)
104+
| (u32::from(call_dir.bits()) << 16)
105+
| (u32::from(call_type) << 8)
106+
| u32::from(call_nr);
107+
108+
let call = IoCtlCall::new()
109+
.with_call_nr(call_nr)
110+
.with_call_type(call_type)
111+
.with_call_dir(call_dir)
112+
.with_call_size(call_size);
113+
114+
assert_eq!(ioctl_call_number, call.into_bits());
115+
}
116+
#[test]
117+
fn ioctl_call_correctly_parsed() {
118+
let call_nr = 0x12u8;
119+
let call_type = 0x78u8;
120+
let call_dir = IoCtlDirection::IOC_WRITE;
121+
let call_size = 0x423u16;
122+
123+
let ioctl_call_number = (u32::from(call_size) << 18)
124+
| (u32::from(call_dir.bits()) << 16)
125+
| (u32::from(call_type) << 8)
126+
| u32::from(call_nr);
127+
128+
let parsed = IoCtlCall::from_bits(ioctl_call_number);
129+
130+
assert_eq!(call_nr, parsed.call_nr());
131+
assert_eq!(call_type, parsed.call_type());
132+
assert_eq!(call_dir, parsed.call_dir());
133+
assert_eq!(call_size, parsed.call_size());
134+
}
135+
}

src/fs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg(all(feature = "fuse", feature = "pci"))]
22
pub(crate) mod fuse;
3+
pub mod ioctl;
34
mod mem;
45
mod uhyve;
56

src/syscalls/mod.rs

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#[cfg(all(target_os = "none", not(feature = "common-os")))]
44
use core::alloc::{GlobalAlloc, Layout};
5-
use core::ffi::{CStr, c_char};
5+
use core::ffi::{CStr, c_char, c_ulong};
66
use core::marker::PhantomData;
77

88
use dirent_display::Dirent64Display;
@@ -26,6 +26,7 @@ use crate::fd::{
2626
self, AccessPermission, EventFlags, FileDescriptor, OpenOption, PollFd, dup_object,
2727
dup_object2, get_object, isatty, remove_object,
2828
};
29+
use crate::fs::ioctl::IoCtlCall;
2930
use crate::fs::{self, FileAttr, SeekWhence};
3031
#[cfg(all(target_os = "none", not(feature = "common-os")))]
3132
use crate::mm::ALLOCATOR;
@@ -476,30 +477,17 @@ pub unsafe extern "C" fn sys_writev(fd: FileDescriptor, iov: *const iovec, iovcn
476477
#[unsafe(no_mangle)]
477478
pub unsafe extern "C" fn sys_ioctl(
478479
fd: FileDescriptor,
479-
cmd: i32,
480+
cmd: c_ulong,
480481
argp: *mut core::ffi::c_void,
481482
) -> i32 {
482-
const FIONBIO: i32 = 0x8008_667eu32 as i32;
483-
484-
if cmd == FIONBIO {
485-
let value = unsafe { *(argp as *const i32) };
486-
let status_flags = if value != 0 {
487-
fd::StatusFlags::O_NONBLOCK
488-
} else {
489-
fd::StatusFlags::empty()
490-
};
491-
492-
let obj = get_object(fd);
493-
obj.map_or_else(
494-
|e| -i32::from(e),
495-
|v| {
496-
block_on((*v).set_status_flags(status_flags), None)
497-
.map_or_else(|e| -i32::from(e), |()| 0)
498-
},
499-
)
500-
} else {
501-
-i32::from(Errno::Inval)
502-
}
483+
let obj = get_object(fd);
484+
obj.map_or_else(
485+
|e| -i32::from(e),
486+
|v| {
487+
(*v).handle_ioctl(IoCtlCall::from_bits(cmd as u32), argp)
488+
.map_or_else(|e| -i32::from(e), |()| 0)
489+
},
490+
)
503491
}
504492

505493
/// manipulate file descriptor

0 commit comments

Comments
 (0)