[libos] Re-implement ioctl commands
This commit is contained in:
		
							parent
							
								
									fd6e1bae45
								
							
						
					
					
						commit
						f3b1acf3ce
					
				| @ -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.
 | ||||
|  | ||||
							
								
								
									
										136
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/get_ifconf.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										136
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/get_ifconf.rs
									
									
									
									
									
										Normal 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(()) | ||||
| } | ||||
							
								
								
									
										77
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/get_ifreq.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										77
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/get_ifreq.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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> {} | ||||
| } | ||||
							
								
								
									
										28
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/get_readbuflen.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										28
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/get_readbuflen.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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(()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										310
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										310
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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=()> {} | ||||
| } | ||||
							
								
								
									
										24
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/set_nonblocking.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										24
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/set_nonblocking.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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(()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										64
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/winsize.rs
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										64
									
								
								src/libos/src/fs/file_ops/ioctl/builtin/winsize.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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); | ||||
|             return Ok(0); | ||||
|         } | ||||
|         IoctlCmd::FIOCLEX(_) => { | ||||
|             entry.set_close_on_spawn(true); | ||||
|             return Ok(0); | ||||
|         } | ||||
|         _ => return file_ref.ioctl(cmd), | ||||
|     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); | ||||
|     } | ||||
| 
 | ||||
|     file_ref.ioctl(cmd.as_mut())?; | ||||
|     raw_cmd.copy_output_from_safe(cmd.as_ref()); | ||||
|     Ok(0) | ||||
| } | ||||
| 
 | ||||
| extern "C" { | ||||
|  | ||||
| @ -69,8 +69,30 @@ 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 { | ||||
|     cmd_id: u8, | ||||
|  | ||||
| @ -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) | ||||
|     } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user