[libos] Re-implement ioctl commands

This commit is contained in:
ClawSeven 2024-04-30 11:41:44 +08:00 committed by volcano
parent fd6e1bae45
commit f3b1acf3ce
20 changed files with 906 additions and 250 deletions

@ -45,7 +45,7 @@ impl INode for DevSgx {
fn io_control(&self, _cmd: u32, _data: usize) -> vfs::Result<()> {
let mut ioctl_cmd =
unsafe { IoctlCmd::new(_cmd, _data as *mut u8).map_err(|_| FsError::InvalidParam)? };
unsafe { IoctlRawCmd::new(_cmd, _data as *mut u8).map_err(|_| FsError::InvalidParam)? };
self.ioctl(&mut ioctl_cmd).map_err(|e| {
error!("{}", e.backtrace());
FsError::IOCTLError
@ -59,9 +59,9 @@ impl INode for DevSgx {
}
impl DevSgx {
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
fn ioctl(&self, cmd: &mut IoctlRawCmd) -> Result<i32> {
let nonbuiltin_cmd = match cmd {
IoctlCmd::NonBuiltin(nonbuiltin_cmd) => nonbuiltin_cmd,
IoctlRawCmd::NonBuiltin(nonbuiltin_cmd) => nonbuiltin_cmd,
_ => return_errno!(EINVAL, "unknown ioctl cmd for /dev/sgx"),
};
let cmd_num = nonbuiltin_cmd.cmd_num().as_u32();

@ -1,4 +1,5 @@
use super::*;
use crate::fs::IoctlCmd;
macro_rules! return_op_unsupported_error {
($op_name: expr, $errno: expr) => {{
@ -79,8 +80,8 @@ pub trait File: Debug + Sync + Send + Any {
Ok(())
}
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
return_op_unsupported_error!("ioctl")
fn ioctl(&self, _cmd: &mut dyn IoctlCmd) -> Result<()> {
return_op_unsupported_error!("ioctl", EINVAL);
}
fn access_mode(&self) -> Result<AccessMode> {
@ -142,7 +143,7 @@ pub trait File: Debug + Sync + Send + Any {
/// Return the host fd, if the file is backed by an underlying host file.
fn host_fd(&self) -> Option<&HostFd> {
return None;
None
}
/// Update the ready events of a host file.

@ -0,0 +1,136 @@
use super::IoctlCmd;
use crate::prelude::*;
#[derive(Debug)]
#[repr(C)]
pub struct IfConf {
pub ifc_len: i32,
pub ifc_buf: *const u8,
}
#[derive(Debug)]
pub enum GetIfConf {
IfConfBuf(Vec<u8>),
IfConfLen(usize),
}
impl GetIfConf {
pub fn new(ifconf: &IfConf) -> Self {
if ifconf.ifc_buf.is_null() {
Self::IfConfLen(ifconf.ifc_len as usize)
} else {
let buf = {
let mut buf = Vec::with_capacity(ifconf.ifc_len as usize);
buf.resize(ifconf.ifc_len as usize, 0);
buf
};
Self::IfConfBuf(buf)
}
}
pub fn execute(&mut self, fd: FileDesc) -> Result<()> {
if let Self::IfConfBuf(buf) = self {
if buf.len() == 0 {
return Ok(());
}
}
let mut if_conf = self.to_raw_ifconf();
get_ifconf_by_host(fd, &mut if_conf)?;
self.set_len(if_conf.ifc_len as usize);
Ok(())
}
fn set_len(&mut self, new_len: usize) {
match self {
Self::IfConfLen(len) => {
*len = new_len;
}
Self::IfConfBuf(buf) => {
buf.resize(new_len, 0);
}
}
}
pub fn len(&self) -> usize {
match self {
Self::IfConfLen(len) => *len,
Self::IfConfBuf(buf) => buf.len(),
}
}
pub fn as_slice(&self) -> Option<&[u8]> {
match self {
Self::IfConfBuf(buf) => Some(buf.as_slice()),
Self::IfConfLen(_) => None,
}
}
pub fn to_raw_ifconf(&self) -> IfConf {
match self {
Self::IfConfLen(len) => IfConf {
ifc_buf: std::ptr::null(),
ifc_len: *len as i32,
},
Self::IfConfBuf(buf) => IfConf {
ifc_buf: buf.as_ptr(),
ifc_len: buf.len() as i32,
},
}
}
}
impl IoctlCmd for GetIfConf {}
const SIOCGIFCONF: u32 = 0x8912;
fn get_ifconf_by_host(fd: FileDesc, if_conf: &mut IfConf) -> Result<()> {
extern "C" {
// Used to ioctl arguments with pointer members.
//
// Before the call the area the pointers points to should be assembled into
// one continuous memory block. Then the block is repacked to ioctl arguments
// in the ocall implementation in host.
//
// ret: holds the return value of ioctl in host
// fd: the host fd for the device
// cmd_num: request number of the ioctl
// buf: the data to exchange with host
// len: the size of the buf
// recv_len: accepts transferred data length when buf is used to get data from host
//
fn socket_ocall_ioctl_repack(
ret: *mut i32,
fd: i32,
cmd_num: i32,
buf: *const u8,
len: i32,
recv_len: *mut i32,
) -> sgx_types::sgx_status_t;
}
try_libc!({
let mut recv_len: i32 = 0;
let mut retval: i32 = 0;
let status = socket_ocall_ioctl_repack(
&mut retval as *mut i32,
fd as _,
SIOCGIFCONF as _,
if_conf.ifc_buf,
if_conf.ifc_len,
&mut recv_len as *mut i32,
);
assert!(status == sgx_types::sgx_status_t::SGX_SUCCESS);
// If ifc_req is NULL, SIOCGIFCONF returns the necessary buffer
// size in bytes for receiving all available addresses in ifc_len
// which is irrelevant to the orginal ifc_len.
if !if_conf.ifc_buf.is_null() {
assert!(if_conf.ifc_len >= recv_len);
}
if_conf.ifc_len = recv_len;
retval
});
Ok(())
}

@ -0,0 +1,77 @@
use crate::prelude::*;
const IFNAMSIZ: usize = 16;
use super::IoctlCmd;
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct IfReq {
pub ifr_name: [u8; IFNAMSIZ],
pub ifr_union: [u8; 24],
}
/// Many of the socket ioctl commands use `IfReq` as the structure to get configuration
/// of network devices. The only difference is the command number.
///
/// This structure wraps the `GetIfReq` and the command number as the `IoctlCmd`.
#[derive(Debug)]
pub struct GetIfReqWithRawCmd {
inner: GetIfReq,
raw_cmd: u32,
}
impl GetIfReqWithRawCmd {
pub fn new(raw_cmd: u32, req: IfReq) -> Self {
Self {
inner: GetIfReq::new(req),
raw_cmd,
}
}
pub fn output(&self) -> Option<&IfReq> {
self.inner.output()
}
pub fn execute(&mut self, fd: FileDesc) -> Result<()> {
let input_if_req = self.inner.input();
let output_if_req = get_ifreq_by_host(fd, self.raw_cmd, input_if_req)?;
self.inner.set_output(output_if_req);
Ok(())
}
}
fn get_ifreq_by_host(fd: FileDesc, cmd: u32, req: &IfReq) -> Result<IfReq> {
let mut if_req: IfReq = req.clone();
try_libc!({
let mut retval: i32 = 0;
extern "C" {
pub fn occlum_ocall_ioctl(
ret: *mut i32,
fd: c_int,
request: c_int,
arg: *mut c_void,
len: size_t,
) -> sgx_types::sgx_status_t;
}
use libc::{c_int, c_void, size_t};
use occlum_ocall_ioctl as do_ioctl;
let status = do_ioctl(
&mut retval as *mut i32,
fd as i32,
cmd as i32,
&mut if_req as *mut IfReq as *mut c_void,
std::mem::size_of::<IfReq>(),
);
assert!(status == sgx_types::sgx_status_t::SGX_SUCCESS);
retval
});
Ok(if_req)
}
impl IoctlCmd for GetIfReqWithRawCmd {}
impl_ioctl_cmd! {
pub struct GetIfReq<Input=IfReq, Output=IfReq> {}
}

@ -0,0 +1,28 @@
use super::*;
impl_ioctl_cmd! {
pub struct GetReadBufLen<Input=(), Output=i32> {}
}
impl GetReadBufLen {
pub fn execute(&mut self, host_fd: FileDesc) -> Result<()> {
let mut buflen: i32 = 0;
try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_fd as i32,
BuiltinIoctlNum::FIONREAD as i32,
&mut buflen as *mut i32 as *mut c_void,
std::mem::size_of::<i32>(),
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
trace!("read buf len = {:?}", buflen);
self.set_output(buflen);
Ok(())
}
}

@ -0,0 +1,310 @@
//! An ioctl-style, extensible API for file types.
//!
//! # Motiviation
//!
//! Let's consider three classic versatile APIs for files in Unix-like
//! systems: `ioctl`, `fcntl`, and `getsockopt`/`setsockopt`.
//!
//! ```c
//! int fcntl(int fd, int cmd, ... /* arg */ );
//!
//! int ioctl(int fd, unsigned long request, ... /* arg */);
//!
//! int getsockopt(int sockfd, int level, int optname,
//! void *restrict optval, socklen_t *restrict optlen);
//! int setsockopt(int sockfd, int level, int optname,
//! const void *optval, socklen_t optlen);
//! ```
//!
//! The three APIs have two properties in common: _extensibility_ and
//! _type erasure_. By extensibility, we mean that it is quite easy to add
//! new sub-commands that are specific to a device or file. And type erasure
//! underscores the fact that since the input and output arguments of a future
//! sub-command cannot be known beforehand, the concrete types of these
//! arguments have to be "erased" from the interface using `...` or `void*`.
//!
//! So how do we support these three C APIs in our Rust types for files?
//!
//! Specifically, do we need to add three separate Rust APIs corresponding to
//! their C counterparts? And what is the best way to express type-erased
//! arguments in the type-safe language of Rust? And most importantly, how
//! can we add new kinds of sub-commands and handle all kinds of sub-commands
//! as painless as possible?
//!
//! Our solution is the `IoctlCmd` trait and its companion macros.
//!
//! # Usage
//!
//! Here is a simple program to demonstrate the usage of `IoctlCmd`.
//!
//! ```rust
//! use net::{impl_ioctl_cmd, match_ioctl_cmd_mut};
//! use net::ioctl::{IoctlCmd};
//!
//! /// A trait to abstract any file-like type.
//! ///
//! /// For our purpose, it suffices for the trait to have only one API,
//! /// which takes a mutable trait object of `IoctlCmd` as its argument.
//! /// Thanks to `IoctlCmd`'s capability of downcasting itself to the
//! /// concrete type that implements the trait, it becomes easy to accept
//! /// all kinds of commands without breaking changes to the API signature.
//! pub trait File {
//! fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()>;
//! }
//!
//! // A typical command consists of an input and an output. For such commands,
//! // you can use the `impl_ioctl_cmd` to define them handily.
//! //
//! // Here, three structs are defined, implementing the `IoctlCmd` trait.
//! // Each of them represent a different command, having different
//! // input and output arguments.
//! //
//! // Note that while the trait is named `IoctlCmd`, it does not
//! // preclude using the trait to abstract `fcntl` or `getsocktopt`
//! // commands.
//! impl_ioctl_cmd! {
//! pub struct CommonCmd<Input=(), Output=bool> {}
//! }
//!
//! impl_ioctl_cmd! {
//! pub struct FooCmd<Input=i32, Output=()> {}
//! }
//!
//! impl_ioctl_cmd! {
//! pub struct BarCmd<Input=i32, Output=String> {}
//! }
//!
//! // Of course. You can implement an ioctl command manually, without
//! // using the `impl_ioctl_cmd` macro.
//! #[derive(Debug)]
//! pub struct ComplexCmd { /* customized memory layout */ };
//! impl IoctlCmd for ComplexCmd {}
//!
//! pub struct FooFile;
//! impl File for FooFile {
//! fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
//! // Only handle the interesting commands. The trait object
//! // is automatically downcasted to the concrete struct that
//! // represents the command.
//! match_ioctl_cmd_mut!(&mut *cmd, {
//! cmd: CommonCmd => {
//! println!("Accepted CommonCmd: {:?}", cmd);
//! Ok(())
//! },
//! cmd: FooCmd => {
//! println!("FooCmd's input: {}", cmd.input());
//! Ok(())
//! },
//! _ => {
//! Err(errno!(EINVAL, "unknown command"))
//! }
//! })
//! }
//! }
//!
//! pub struct BarFile;
//! impl File for BarFile {
//! fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
//! match_ioctl_cmd_mut!(&mut *cmd, {
//! cmd: CommonCmd => {
//! println!("Accepted CommonCmd: {:?}", cmd);
//! Ok(())
//! },
//! cmd: BarCmd => {
//! cmd.set_output("Bar Result".to_string());
//! println!("BarCmd's output: {}", cmd.output().unwrap());
//! Ok(())
//! },
//! cmd: ComplexCmd => {
//! println!("Accepted ComplexCmd: {:?}", cmd);
//! Ok(())
//! },
//! _ => {
//! Err(errno!(EINVAL, "unknown command"))
//! }
//! })
//! }
//! }
//! ```
/// A trait to unify all concrete types representing ioctl commands.
///
/// The most useful property of this trait is that it supports downcasting
/// to the concrete type that actually implements the trait with the
/// `downcast_ref` and `downcast_mut` methods.
///
/// ```rust
/// use async_io::ioctl::IoctlCmd;
///
/// #[derive(Debug)]
/// pub struct DummyCmd;
/// impl IoctlCmd for DummyCmd {}
///
/// let dummy : Box<dyn IoctlCmd> = Box::new(DummyCmd);
/// assert!(dummy.downcast_ref::<DummyCmd>().is_some());
/// ```
pub trait IoctlCmd: Downcast + Debug + Sync + Send {}
impl_downcast!(IoctlCmd);
/// A convenient macro to define a struct for some type of ioctl command.
///
/// Typcially, an ioctl command consists of an input argument and an output
/// output. As such, the struct defined by this macro has two fields: the
/// input and the output.
///
/// The struct defined by this macro automatically implements the `IoctlCmd`
/// trait.
#[macro_export]
macro_rules! impl_ioctl_cmd {
(
$(#[$outer:meta])*
pub struct $CmdName:ident <Input=$Input:ty, Output=$Output:ty> {}
) => {
$(#[$outer])*
pub struct $CmdName {
input: $Input,
output: Option<$Output>,
}
#[allow(dead_code)]
impl $CmdName {
pub fn new(input: $Input) -> Self {
Self {
input,
output: None,
}
}
pub fn input(&self) -> &$Input {
&self.input
}
pub fn output(&self) -> Option<&$Output> {
self.output.as_ref()
}
pub fn set_output(&mut self, output: $Output) {
self.output = Some(output)
}
pub fn take_output(&mut self) -> Option<$Output> {
self.output.take()
}
}
impl crate::fs::IoctlCmd for $CmdName {}
impl std::fmt::Debug for $CmdName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(stringify!($CmdName))
.field("input", self.input())
.field("output", &self.output())
.finish()
}
}
}
}
use super::*;
pub use self::get_ifconf::{GetIfConf, IfConf};
pub use self::get_ifreq::{GetIfReq, GetIfReqWithRawCmd, IfReq};
pub use self::get_readbuflen::GetReadBufLen;
pub use self::set_close_on_exec::*;
pub use self::set_nonblocking::SetNonBlocking;
pub use self::termios::*;
pub use self::winsize::*;
pub use net::socket::sockopt::SetSockOptRawCmd;
mod get_ifconf;
mod get_ifreq;
mod get_readbuflen;
mod set_close_on_exec;
mod set_nonblocking;
mod termios;
mod winsize;
use downcast_rs::{impl_downcast, Downcast};
use std::fmt::Debug;
#[macro_export]
macro_rules! match_ioctl_cmd_ref {
(
$cmd:expr,
{
$( $bind:ident : $ty:ty => $arm:expr ),*,
_ => $default:expr
}
) => {{
let __cmd : &dyn crate::fs::IoctlCmd = $cmd;
$(
if __cmd.is::<$ty>() {
let $bind = __cmd.downcast_ref::<$ty>().unwrap();
$arm
} else
)*
{
$default
}
}}
}
#[macro_export]
macro_rules! match_ioctl_cmd_mut {
(
$cmd:expr,
{
$( $bind:ident : $ty:ty => $arm:expr ),*,
_ => $default:expr
}
) => {{
let __cmd : &mut dyn crate::fs::IoctlCmd = $cmd;
$(
if __cmd.is::<$ty>() {
let $bind = __cmd.downcast_mut::<$ty>().unwrap();
$arm
} else
)*
{
$default
}
}}
}
// Macro for ioctl auto error number.
// If the corresponding cmds are not defined, a default error number will be return
#[macro_export]
macro_rules! match_ioctl_cmd_auto_error {
(
$cmd:expr,
{
$( $bind:ident : $ty:ty => $arm:expr ),* $(,)?
}
) => {{
use crate::fs::*;
let __cmd : &mut dyn crate::fs::IoctlCmd = $cmd;
$(
if __cmd.is::<$ty>() {
let $bind = __cmd.downcast_mut::<$ty>().unwrap();
$arm
} else
)*
// If the corresponding cmds are not defined, it will go here for default error
if __cmd.is::<TcGets>() {
return_errno!(Errno::ENOTTY, "not tty device");
}
else if __cmd.is::<TcSets>() {
return_errno!(Errno::ENOTTY, "not tty device");
}
else if __cmd.is::<GetWinSize>() {
return_errno!(Errno::ENOTTY, "not tty device");
}
else if __cmd.is::<SetWinSize>() {
return_errno!(Errno::ENOTTY, "not tty device");
}
else {
// Default branch
return_errno!(EINVAL, "unsupported ioctl cmd");
}
}}
}

@ -0,0 +1,5 @@
use super::*;
impl_ioctl_cmd! {
pub struct SetCloseOnExec<Input=bool, Output=()> {}
}

@ -0,0 +1,24 @@
use super::*;
impl_ioctl_cmd! {
pub struct SetNonBlocking<Input=i32, Output=()> {}
}
impl SetNonBlocking {
pub fn execute(&mut self, host_fd: FileDesc) -> Result<()> {
try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_fd as i32,
BuiltinIoctlNum::FIONBIO as i32,
self.input() as *const i32 as *mut c_void,
std::mem::size_of::<i32>(),
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
Ok(())
}
}

@ -1,31 +1,5 @@
//! Built-in ioctls.
use super::*;
#[derive(Debug)]
#[repr(C)]
pub struct WinSize {
pub ws_row: u16,
pub ws_col: u16,
pub ws_xpixel: u16,
pub ws_ypixel: u16,
}
#[derive(Debug)]
#[repr(C)]
pub struct IfConf {
pub ifc_len: i32,
pub ifc_buf: *const u8,
}
const IFNAMSIZ: usize = 16;
#[derive(Debug)]
#[repr(C)]
pub struct IfReq {
pub ifr_name: [u8; IFNAMSIZ],
pub ifr_union: [u8; 24],
}
/*
The termios structure used in the Linux kernel is not the same as we use in the glibc. Thus, we have two
definitions here.
@ -78,49 +52,10 @@ impl KernelTermios {
c_ospeed: 0,
}
}
pub fn execute_tcgets(&mut self, host_fd: i32, cmd_num: i32) -> Result<i32> {
debug_assert!(cmd_num == 0x5401);
let mut termios = self.to_termios();
let len = std::mem::size_of::<Termios>();
let ret = try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_fd,
cmd_num,
&mut termios as *const Termios as *mut c_void,
len,
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
*self = termios.to_kernel_termios();
Ok(ret)
}
pub fn execute_tcsets(&self, host_fd: i32, cmd_num: i32) -> Result<i32> {
debug_assert!(cmd_num == 0x5402);
let termios = self.to_termios();
let len = std::mem::size_of::<Termios>();
let ret = try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_fd,
cmd_num,
&termios as *const Termios as *mut c_void,
len,
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
Ok(ret)
}
}
impl Termios {
pub fn to_kernel_termios(&self) -> KernelTermios {
fn to_kernel_termios(&self) -> KernelTermios {
let mut c_cc = [0; KERNEL_NCCS];
c_cc.copy_from_slice(&self.c_cc[..KERNEL_NCCS]);
@ -134,3 +69,55 @@ impl Termios {
}
}
}
impl_ioctl_cmd! {
pub struct TcGets<Input=(), Output=KernelTermios> {}
}
impl TcGets {
pub fn execute(&mut self, host_fd: FileDesc) -> Result<()> {
let mut termios: Termios = Default::default();
try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_fd as i32,
BuiltinIoctlNum::TCGETS as i32,
&mut termios as *mut Termios as *mut c_void,
std::mem::size_of::<Termios>(),
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
let kernel_termios = termios.to_kernel_termios();
trace!("kernel termios = {:?}", kernel_termios);
self.set_output(kernel_termios);
Ok(())
}
}
impl_ioctl_cmd! {
pub struct TcSets<Input=KernelTermios, Output=()> {}
}
impl TcSets {
pub fn execute(&self, host_fd: FileDesc) -> Result<()> {
let kernel_termios = self.input();
let termios = kernel_termios.to_termios();
try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_fd as i32,
BuiltinIoctlNum::TCSETS as i32,
&termios as *const Termios as *mut c_void,
std::mem::size_of::<Termios>(),
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
Ok(())
}
}

@ -0,0 +1,64 @@
use super::*;
#[derive(Default, Clone, Copy, Debug)]
#[repr(C)]
pub struct WinSize {
pub ws_row: u16,
pub ws_col: u16,
pub ws_xpixel: u16,
pub ws_ypixel: u16,
}
impl_ioctl_cmd! {
pub struct SetWinSize<Input=WinSize, Output=()> {}
}
impl SetWinSize {
pub fn execute(&self, host_fd: FileDesc) -> Result<()> {
try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_fd as i32,
BuiltinIoctlNum::TIOCSWINSZ as i32,
self.input() as *const WinSize as *mut c_void,
std::mem::size_of::<WinSize>(),
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
Ok(())
}
}
impl_ioctl_cmd! {
pub struct GetWinSize<Input=(), Output=WinSize> {}
}
impl GetWinSize {
pub fn execute(&mut self, host_fd: FileDesc) -> Result<()> {
let mut winsize: WinSize = Default::default();
try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_fd as i32,
BuiltinIoctlNum::TIOCGWINSZ as i32,
&mut winsize as *mut WinSize as *mut c_void,
std::mem::size_of::<WinSize>(),
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
if winsize.ws_row == 0 || winsize.ws_col == 0 {
warn!(
"window size: row: {:?}, col: {:?}",
winsize.ws_row, winsize.ws_col
);
}
self.set_output(winsize);
Ok(())
}
}

@ -1,7 +1,7 @@
//! Macros to implement `BuiltinIoctlNum` and `IoctlCmd` given a list of ioctl
//! Macros to implement `BuiltinIoctlNum` and `IoctlRawCmd` given a list of ioctl
//! names, numbers, and argument types.
/// Implement `BuiltinIoctlNum` and `IoctlCmd`.
/// Implement `BuiltinIoctlNum` and `IoctlRawCmd`.
#[macro_export]
macro_rules! impl_ioctl_nums_and_cmds {
($( $ioctl_name: ident => ( $ioctl_num: expr, $($ioctl_type_tt: tt)* ) ),+,) => {
@ -12,7 +12,7 @@ macro_rules! impl_ioctl_nums_and_cmds {
)*
}
// Implement IoctlCmd given ioctl names and their argument types
// Implement IoctlRawCmd given ioctl names and their argument types
impl_ioctl_cmds! {
$(
$ioctl_name => ( $($ioctl_type_tt)*),
@ -57,21 +57,21 @@ macro_rules! impl_builtin_ioctl_nums {
}
////////////////////////////////////////////////////////////////////////////////
// IoctlCmd
// IoctlRawCmd
////////////////////////////////////////////////////////////////////////////////
macro_rules! impl_ioctl_cmds {
($( $ioctl_name: ident => ( $($ioctl_type_tt: tt)* ) ),+,) => {
#[derive(Debug)]
pub enum IoctlCmd<'a> {
pub enum IoctlRawCmd<'a> {
$(
$ioctl_name( get_arg_type!($($ioctl_type_tt)*) ),
)*
NonBuiltin(NonBuiltinIoctlCmd<'a>),
}
impl<'a> IoctlCmd<'a> {
pub unsafe fn new(cmd_num: u32, arg_ptr: *mut u8) -> Result<IoctlCmd<'a>> {
impl<'a> IoctlRawCmd<'a> {
pub unsafe fn new(cmd_num: u32, arg_ptr: *mut u8) -> Result<IoctlRawCmd<'a>> {
if let Some(builtin_cmd_num) = BuiltinIoctlNum::from_u32(cmd_num) {
unsafe { Self::new_builtin_cmd(builtin_cmd_num, arg_ptr) }
} else {
@ -79,7 +79,7 @@ macro_rules! impl_ioctl_cmds {
}
}
unsafe fn new_builtin_cmd(cmd_num: BuiltinIoctlNum, arg_ptr: *mut u8) -> Result<IoctlCmd<'a>> {
unsafe fn new_builtin_cmd(cmd_num: BuiltinIoctlNum, arg_ptr: *mut u8) -> Result<IoctlRawCmd<'a>> {
if cmd_num.require_arg() && arg_ptr.is_null() {
return_errno!(EINVAL, "arg_ptr cannot be null");
}
@ -90,43 +90,43 @@ macro_rules! impl_ioctl_cmds {
$(
BuiltinIoctlNum::$ioctl_name => {
let arg = get_arg!($($ioctl_type_tt)*, arg_ptr);
IoctlCmd::$ioctl_name(arg)
IoctlRawCmd::$ioctl_name(arg)
}
)*
};
Ok(cmd)
}
unsafe fn new_nonbuiltin_cmd(cmd_num: u32, arg_ptr: *mut u8) -> Result<IoctlCmd<'a>> {
unsafe fn new_nonbuiltin_cmd(cmd_num: u32, arg_ptr: *mut u8) -> Result<IoctlRawCmd<'a>> {
let structured_cmd_num = StructuredIoctlNum::from_u32(cmd_num)?;
let inner_cmd = unsafe { NonBuiltinIoctlCmd::new(structured_cmd_num, arg_ptr)? };
Ok(IoctlCmd::NonBuiltin(inner_cmd))
Ok(IoctlRawCmd::NonBuiltin(inner_cmd))
}
pub fn arg_ptr(&self) -> *const u8 {
match self {
$(
IoctlCmd::$ioctl_name(arg_ref) => get_arg_ptr!($($ioctl_type_tt)*, arg_ref),
IoctlRawCmd::$ioctl_name(arg_ref) => get_arg_ptr!($($ioctl_type_tt)*, arg_ref),
)*
IoctlCmd::NonBuiltin(inner) => inner.arg_ptr(),
IoctlRawCmd::NonBuiltin(inner) => inner.arg_ptr(),
}
}
pub fn arg_len(&self) -> usize {
match self {
$(
IoctlCmd::$ioctl_name(_) => get_arg_len!($($ioctl_type_tt)*),
IoctlRawCmd::$ioctl_name(_) => get_arg_len!($($ioctl_type_tt)*),
)*
IoctlCmd::NonBuiltin(inner) => inner.arg_len(),
IoctlRawCmd::NonBuiltin(inner) => inner.arg_len(),
}
}
pub fn cmd_num(&self) -> u32 {
match self {
$(
IoctlCmd::$ioctl_name(_) => BuiltinIoctlNum::$ioctl_name as u32,
IoctlRawCmd::$ioctl_name(_) => BuiltinIoctlNum::$ioctl_name as u32,
)*
IoctlCmd::NonBuiltin(inner) => inner.cmd_num().as_u32(),
IoctlRawCmd::NonBuiltin(inner) => inner.cmd_num().as_u32(),
}
}
}

@ -6,8 +6,13 @@
use super::*;
pub use self::builtin::*;
use self::builtin::*;
pub use self::builtin::{
GetIfConf, GetIfReqWithRawCmd, GetReadBufLen, GetWinSize, IfConf, IoctlCmd, SetNonBlocking,
SetWinSize, TcGets, TcSets,
};
pub use self::non_builtin::{NonBuiltinIoctlCmd, StructuredIoctlArgType, StructuredIoctlNum};
use crate::util::mem_util::from_user;
#[macro_use]
mod macros;
@ -18,7 +23,7 @@ mod non_builtin;
///
/// By giving the names, numbers, and argument types of built-in ioctls,
/// the macro below generates the corresponding code of `BuiltinIoctlNum` and
/// `IoctlCmd`.
/// `IoctlRawCmd`.
///
/// To add a new built-in ioctl, just follow the convention as shown
/// by existing built-in ioctls.
@ -68,51 +73,121 @@ impl_ioctl_nums_and_cmds! {
///
/// Sanity checks are mostly useful when the argument values are returned from
/// the untrusted host OS.
impl<'a> IoctlCmd<'a> {
pub fn validate_arg_and_ret_vals(&self, ret: i32) -> Result<()> {
impl<'a> IoctlRawCmd<'a> {
pub fn to_safe_ioctlcmd(&self) -> Result<Box<dyn IoctlCmd>> {
Ok(match self {
IoctlRawCmd::TCGETS(_) => Box::new(TcGets::new(())),
IoctlRawCmd::TCSETS(termios_ref) => {
let termios = **termios_ref;
Box::new(TcSets::new(termios))
}
IoctlRawCmd::TIOCGWINSZ(_) => Box::new(GetWinSize::new(())),
IoctlRawCmd::TIOCSWINSZ(winsize_ref) => {
let winsize = **winsize_ref;
Box::new(SetWinSize::new(winsize))
}
IoctlRawCmd::NonBuiltin(inner) => {
let nonbuiltin_cmd =
unsafe { NonBuiltinIoctlCmd::new(*inner.cmd_num(), inner.arg_ptr() as _)? };
Box::new(nonbuiltin_cmd)
}
IoctlRawCmd::FIONBIO(non_blocking) => Box::new(SetNonBlocking::new(**non_blocking)),
IoctlRawCmd::FIONREAD(_) => Box::new(GetReadBufLen::new(())),
IoctlRawCmd::FIONCLEX(_) => Box::new(SetCloseOnExec::new(false)),
IoctlRawCmd::FIOCLEX(_) => Box::new(SetCloseOnExec::new(true)),
IoctlRawCmd::SIOCGIFCONF(ifconf_mut) => {
if !ifconf_mut.ifc_buf.is_null() {
if ifconf_mut.ifc_len < 0 {
return_errno!(EINVAL, "invalid ifc_len");
}
from_user::check_array(ifconf_mut.ifc_buf, ifconf_mut.ifc_len as usize)?;
}
Box::new(GetIfConf::new(ifconf_mut))
}
IoctlRawCmd::SIOCGIFFLAGS(req)
| IoctlRawCmd::SIOCGIFNAME(req)
| IoctlRawCmd::SIOCGIFADDR(req)
| IoctlRawCmd::SIOCGIFDSTADDR(req)
| IoctlRawCmd::SIOCGIFBRDADDR(req)
| IoctlRawCmd::SIOCGIFNETMASK(req)
| IoctlRawCmd::SIOCGIFMTU(req)
| IoctlRawCmd::SIOCGIFHWADDR(req)
| IoctlRawCmd::SIOCGIFINDEX(req)
| IoctlRawCmd::SIOCGIFPFLAGS(req)
| IoctlRawCmd::SIOCGIFTXQLEN(req)
| IoctlRawCmd::SIOCGIFMAP(req) => {
Box::new(GetIfReqWithRawCmd::new(self.cmd_num(), **req))
}
_ => {
return_errno!(EINVAL, "unsupported cmd");
}
})
}
pub fn copy_output_from_safe(&mut self, cmd: &dyn IoctlCmd) {
match self {
IoctlCmd::TIOCGWINSZ(winsize_ref) => {
// ws_row and ws_col are usually not zeros
if winsize_ref.ws_row == 0 || winsize_ref.ws_col == 0 {
warn!(
"window size: row: {:?}, col: {:?}",
winsize_ref.ws_row, winsize_ref.ws_col
);
IoctlRawCmd::TCGETS(termios_mut) => {
let cmd = cmd.downcast_ref::<TcGets>().unwrap();
**termios_mut = *cmd.output().unwrap();
}
IoctlRawCmd::TIOCGWINSZ(winsize_mut) => {
let cmd = cmd.downcast_ref::<GetWinSize>().unwrap();
**winsize_mut = *cmd.output().unwrap();
}
IoctlRawCmd::FIONREAD(len_mut) => {
let cmd = cmd.downcast_ref::<GetReadBufLen>().unwrap();
**len_mut = *cmd.output().unwrap();
}
IoctlRawCmd::SIOCGIFCONF(ifconf_mut) => {
let cmd = cmd.downcast_ref::<GetIfConf>().unwrap();
ifconf_mut.ifc_len = cmd.len() as i32;
if !ifconf_mut.ifc_buf.is_null() {
let mut raw_buf = unsafe {
std::slice::from_raw_parts_mut(
ifconf_mut.ifc_buf as _,
ifconf_mut.ifc_len as _,
)
};
raw_buf.copy_from_slice(cmd.as_slice().unwrap());
}
}
IoctlCmd::FIONREAD(nread_ref) => {
if (**nread_ref < 0) {
return_errno!(EINVAL, "invalid data from host");
}
IoctlRawCmd::SIOCGIFNAME(ifreq_mut)
| IoctlRawCmd::SIOCGIFFLAGS(ifreq_mut)
| IoctlRawCmd::SIOCGIFADDR(ifreq_mut)
| IoctlRawCmd::SIOCGIFDSTADDR(ifreq_mut)
| IoctlRawCmd::SIOCGIFBRDADDR(ifreq_mut)
| IoctlRawCmd::SIOCGIFNETMASK(ifreq_mut)
| IoctlRawCmd::SIOCGIFMTU(ifreq_mut)
| IoctlRawCmd::SIOCGIFHWADDR(ifreq_mut)
| IoctlRawCmd::SIOCGIFINDEX(ifreq_mut)
| IoctlRawCmd::SIOCGIFPFLAGS(ifreq_mut)
| IoctlRawCmd::SIOCGIFTXQLEN(ifreq_mut)
| IoctlRawCmd::SIOCGIFMAP(ifreq_mut) => {
let cmd = cmd.downcast_ref::<GetIfReqWithRawCmd>().unwrap();
**ifreq_mut = *cmd.output().unwrap();
}
_ => {}
}
// Current ioctl commands all return zero
if ret != 0 {
return_errno!(EINVAL, "return value should be zero");
}
Ok(())
}
}
pub fn do_ioctl(fd: FileDesc, cmd: &mut IoctlCmd) -> Result<i32> {
debug!("ioctl: fd: {}, cmd: {:?}", fd, cmd);
pub fn do_ioctl(fd: FileDesc, raw_cmd: &mut IoctlRawCmd<'_>) -> Result<i32> {
debug!("ioctl: fd: {}, cmd: {:?}", fd, raw_cmd);
let current = current!();
let file_ref = current.file(fd)?;
let mut file_table = current.files().lock().unwrap();
let mut entry = file_table.get_entry_mut(fd)?;
match cmd {
IoctlCmd::FIONCLEX(_) => {
entry.set_close_on_spawn(false);
let mut cmd = raw_cmd.to_safe_ioctlcmd()?;
if cmd.is::<SetCloseOnExec>() {
let is_close_on_exec = cmd.downcast_ref::<SetCloseOnExec>().unwrap().input();
let mut file_table = current.files().lock();
let entry = file_table.get_entry_mut(fd)?;
entry.set_close_on_spawn(*is_close_on_exec);
return Ok(0);
}
IoctlCmd::FIOCLEX(_) => {
entry.set_close_on_spawn(true);
return Ok(0);
}
_ => return file_ref.ioctl(cmd),
}
file_ref.ioctl(cmd.as_mut())?;
raw_cmd.copy_output_from_safe(cmd.as_ref());
Ok(0)
}
extern "C" {

@ -69,7 +69,29 @@ impl<'a> NonBuiltinIoctlCmd<'a> {
pub fn arg_len(&self) -> usize {
self.cmd_num.arg_size()
}
pub fn execute(&self, host_fd: FileDesc) -> Result<()> {
let cmd_num = self.cmd_num().as_u32() as c_int;
let cmd_arg_ptr = self.arg_ptr() as *mut c_void;
let ret = try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_fd as i32,
cmd_num,
cmd_arg_ptr,
self.arg_len(),
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
Ok(())
}
}
impl IoctlCmd for NonBuiltinIoctlCmd<'static> {}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct StructuredIoctlNum {

@ -14,8 +14,9 @@ pub use self::fspath::{get_abs_path_by_fd, FsPath, AT_FDCWD};
pub use self::fsync::{do_fdatasync, do_fsync};
pub use self::getdents::{do_getdents, do_getdents64};
pub use self::ioctl::{
do_ioctl, occlum_ocall_ioctl, BuiltinIoctlNum, IfConf, IoctlCmd, StructuredIoctlArgType,
StructuredIoctlNum,
do_ioctl, occlum_ocall_ioctl, BuiltinIoctlNum, GetIfConf, GetIfReqWithRawCmd, GetReadBufLen,
GetWinSize, IfConf, IoctlCmd, IoctlRawCmd, NonBuiltinIoctlCmd, SetNonBlocking, SetWinSize,
StructuredIoctlArgType, StructuredIoctlNum, TcGets, TcSets,
};
pub use self::link::{do_linkat, LinkFlags};
pub use self::lseek::do_lseek;

@ -1,4 +1,5 @@
use super::*;
use crate::fs::IoctlCmd;
use crate::net::PollEventFlags;
use crate::process::do_getuid;
use rcore_fs::vfs::FallocateMode;
@ -275,16 +276,13 @@ impl File for INodeFile {
self.unlock_range_lock(&range_lock)
}
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
match cmd {
IoctlCmd::TCGETS(_) => return_errno!(ENOTTY, "not tty device"),
IoctlCmd::TCSETS(_) => return_errno!(ENOTTY, "not tty device"),
_ => {}
};
let cmd_num = cmd.cmd_num();
let cmd_argp = cmd.arg_ptr() as usize;
self.inode.io_control(cmd_num, cmd_argp)?;
Ok(0)
fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
match_ioctl_cmd_auto_error!(cmd, {
cmd : NonBuiltinIoctlCmd => {
self.inode.io_control(cmd.cmd_num().as_u32(), cmd.arg_ptr() as usize)?;
},
});
Ok(())
}
fn poll_new(&self) -> IoEvents {

@ -19,8 +19,9 @@ pub use self::events::{AtomicIoEvents, IoEvents, IoNotifier};
pub use self::file::{File, FileRef};
pub use self::file_ops::{
occlum_ocall_ioctl, utimbuf_t, AccessMode, BuiltinIoctlNum, CreationFlags, FallocateFlags,
FileMode, IfConf, IoctlCmd, Stat, StatusFlags, StructuredIoctlArgType, StructuredIoctlNum,
STATUS_FLAGS_MASK,
FileMode, GetIfConf, GetIfReqWithRawCmd, GetReadBufLen, GetWinSize, IfConf, IoctlCmd,
IoctlRawCmd, NonBuiltinIoctlCmd, SetNonBlocking, SetWinSize, Stat, StatusFlags,
StructuredIoctlArgType, StructuredIoctlNum, TcGets, TcSets, STATUS_FLAGS_MASK,
};
pub use self::file_table::{FileDesc, FileTable, FileTableEvent, FileTableNotifier};
pub use self::fs_ops::Statfs;

@ -1,8 +1,10 @@
use atomic::{Atomic, Ordering};
use net::PollEventFlags;
use super::channel::{Channel, Consumer, Producer};
use super::*;
use net::PollEventFlags;
use crate::fs::{GetReadBufLen, IoctlCmd};
// TODO: Add F_SETPIPE_SZ in fcntl to dynamically change the size of pipe
// to improve memory efficiency. This value is got from /proc/sys/fs/pipe-max-size on linux.
@ -107,18 +109,14 @@ impl File for PipeReader {
self
}
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
match cmd {
IoctlCmd::TCGETS(_) => return_errno!(ENOTTY, "not tty device"),
IoctlCmd::TCSETS(_) => return_errno!(ENOTTY, "not tty device"),
IoctlCmd::FIONREAD(arg) => {
let ready_len = self.get_ready_len().min(std::i32::MAX as usize) as i32;
**arg = ready_len;
return Ok(0);
}
_ => return_errno!(ENOSYS, "not supported"),
};
unreachable!();
fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
match_ioctl_cmd_auto_error!(cmd, {
cmd : GetReadBufLen => {
let read_buf_len = self.consumer.ready_len();
cmd.set_output(read_buf_len as _);
},
});
Ok(())
}
}
@ -209,13 +207,9 @@ impl File for PipeWriter {
self
}
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
match cmd {
IoctlCmd::TCGETS(_) => return_errno!(ENOTTY, "not tty device"),
IoctlCmd::TCSETS(_) => return_errno!(ENOTTY, "not tty device"),
_ => return_errno!(ENOSYS, "not supported"),
};
unreachable!();
fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
match_ioctl_cmd_auto_error!(cmd, {});
Ok(())
}
}

@ -4,6 +4,9 @@ use core::cmp;
use std::io::{BufReader, LineWriter};
use std::sync::SgxMutex;
use crate::fs::file_ops::{GetWinSize, SetWinSize, TcGets, TcSets};
use crate::fs::IoctlCmd;
macro_rules! try_libc_stdio {
($ret: expr) => {{
let ret = unsafe { $ret };
@ -169,50 +172,6 @@ impl File for StdoutFile {
Ok(())
}
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
let host_stdout_fd = self.host_fd() as i32;
let cmd_bits = cmd.cmd_num() as c_int;
// Handle special case for TCGETS/TCSETS which use different structures
// in linux kernel and libc
match cmd {
IoctlCmd::TCGETS(kernel_termios) => {
return kernel_termios.execute_tcgets(host_stdout_fd, cmd_bits);
}
IoctlCmd::TCSETS(kernel_termios) => {
return kernel_termios.execute_tcsets(host_stdout_fd, cmd_bits);
}
_ => {}
};
let can_delegate_to_host = match cmd {
IoctlCmd::TIOCGWINSZ(_) => true,
IoctlCmd::TIOCSWINSZ(_) => true,
_ => false,
};
if !can_delegate_to_host {
return_errno!(EINVAL, "unknown ioctl cmd for stdout");
}
let cmd_arg_ptr = cmd.arg_ptr() as *mut c_void;
let cmd_arg_len = cmd.arg_len();
let ret = try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_stdout_fd,
cmd_bits,
cmd_arg_ptr,
cmd_arg_len,
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
cmd.validate_arg_and_ret_vals(ret)?;
Ok(ret)
}
fn status_flags(&self) -> Result<StatusFlags> {
let ret = try_libc!(libc::ocall::fcntl_arg0(
self.host_fd() as i32,
@ -359,48 +318,8 @@ impl File for StdinFile {
})
}
fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
let host_stdin_fd = self.host_fd() as i32;
let cmd_bits = cmd.cmd_num() as c_int;
// Handle special case for TCGETS/TCSETS which use different structures
// in linux kernel and libc
match cmd {
IoctlCmd::TCGETS(kernel_termios) => {
return kernel_termios.execute_tcgets(host_stdin_fd, cmd_bits);
}
IoctlCmd::TCSETS(kernel_termios) => {
return kernel_termios.execute_tcsets(host_stdin_fd, cmd_bits);
}
_ => {}
};
let can_delegate_to_host = match cmd {
IoctlCmd::TIOCGWINSZ(_) => true,
IoctlCmd::TIOCSWINSZ(_) => true,
_ => false,
};
if !can_delegate_to_host {
return_errno!(EINVAL, "unknown ioctl cmd for stdin");
}
let cmd_arg_ptr = cmd.arg_ptr() as *mut c_void;
let cmd_arg_len = cmd.arg_len();
let ret = try_libc!({
let mut retval: i32 = 0;
let status = occlum_ocall_ioctl(
&mut retval as *mut i32,
host_stdin_fd,
cmd_bits,
cmd_arg_ptr,
cmd_arg_len,
);
assert!(status == sgx_status_t::SGX_SUCCESS);
retval
});
cmd.validate_arg_and_ret_vals(ret)?;
Ok(ret)
fn ioctl(&self, cmd: &mut dyn IoctlCmd) -> Result<()> {
stdio_ioctl(cmd, self.host_fd())
}
fn status_flags(&self) -> Result<StatusFlags> {
@ -444,3 +363,22 @@ impl Debug for StdinFile {
unsafe impl Send for StdinFile {}
unsafe impl Sync for StdinFile {}
fn stdio_ioctl(cmd: &mut dyn IoctlCmd, host_fd: FileDesc) -> Result<()> {
debug!("stdio ioctl: cmd: {:?}", cmd);
match_ioctl_cmd_auto_error!(cmd, {
cmd : TcGets => {
cmd.execute(host_fd)?
},
cmd : TcSets => {
cmd.execute(host_fd)?
},
cmd : SetWinSize => {
cmd.execute(host_fd)?
},
cmd : GetWinSize => {
cmd.execute(host_fd)?
},
});
Ok(())
}

@ -673,7 +673,7 @@ pub fn do_ioctl(fd: FileDesc, cmd: u32, argp: *mut u8) -> Result<isize> {
if argp.is_null() == false {
from_user::check_mut_ptr(argp)?;
}
IoctlCmd::new(cmd, argp)?
IoctlRawCmd::new(cmd, argp)?
};
file_ops::do_ioctl(fd, &mut ioctl_cmd)?;
Ok(0)

@ -115,11 +115,6 @@ impl File for TimerFile {
Ok(ret)
}
// TODO: implement ioctl
// fn ioctl(&self, cmd: &mut IoctlCmd) -> Result<i32> {
// self.ioctl_impl(cmd)
// }
fn access_mode(&self) -> Result<AccessMode> {
Ok(AccessMode::O_RDWR)
}