diff --git a/ableos/src/filesystem/errors.rs b/ableos/src/filesystem/errors.rs index 01b40a0..827bd95 100644 --- a/ableos/src/filesystem/errors.rs +++ b/ableos/src/filesystem/errors.rs @@ -6,4 +6,21 @@ pub enum FsError { UnsupportedOperation, + InvalidDevice, +} + +impl Into for ext2::error::Error { + fn into(self) -> FsError { + match self { + ext2::error::Error::Other(_) => todo!(), + ext2::error::Error::BadMagic { magic: _ } => todo!(), + ext2::error::Error::OutOfBounds { index: _ } => todo!(), + ext2::error::Error::AddressOutOfBounds { sector: _, offset: _, size: _ } => todo!(), + ext2::error::Error::BadBlockGroupCount { by_blocks: _, by_inodes: _ } => todo!(), + ext2::error::Error::InodeNotFound { inode: _ } => todo!(), + ext2::error::Error::NotADirectory { inode: _, name: _ } => todo!(), + ext2::error::Error::NotAbsolute { name: _ } => todo!(), + ext2::error::Error::NotFound { name: _ } => todo!(), + } + } } diff --git a/ableos/src/filesystem/ext2.rs b/ableos/src/filesystem/ext2.rs new file mode 100644 index 0000000..831f475 --- /dev/null +++ b/ableos/src/filesystem/ext2.rs @@ -0,0 +1,85 @@ +use ext2::fs::Ext2; +use ext2::sector::SectorSize; +use ext2::volume::Volume; + +use super::{FsResult as Result, StorageDevice}; + +pub struct Ext2StorageDevice +where + S: SectorSize, + V: Volume, +{ + fs: Ext2, +} + +impl Ext2StorageDevice +where + S: SectorSize, + V: Volume, +{ + pub fn new(volume: V) -> Result { + Ok(Self { + fs: Ext2::new(volume).map_err(|e| e.into())?, + }) + } +} + +impl StorageDevice for Ext2StorageDevice +where + S: SectorSize + Send, + V: Volume + Send, +{ + fn open(&self, node: &super::FsNode /* TODO: flags */) -> Result { + todo!() + } + + fn close(&self, node: &super::FsNode) -> Result<()> { + todo!() + } + + fn read(&self, node: &super::FsNode, offset: u32, size: u32) -> Result> { + todo!() + } + + fn write(&self, node: &super::FsNode, offset: u32, buffer: Box<[u8]>) -> Result<()> { + todo!() + } + + fn read_dir(&self, node: &super::FsNode, index: u32) -> Result { + todo!() + } + + fn find_dir(&self, node: &super::FsNode, name: &str) -> Result { + todo!() + } +} + +// fn load_fs() -> Arc>>> { +// let mut volume = Vec::new(); +// volume.extend_from_slice(include_bytes!("../../../userland/root_fs/ext2.img")); + +// Arc::>::new(volume).unwrap() +// } + +// pub fn walk>( +// fs: &Synced>, +// inode: Inode, +// name: String, +// ) { +// if let Some(dir) = inode.directory() { +// for entry in dir { +// assert!(entry.is_ok()); +// let entry = entry.unwrap(); +// let entry_name = String::from_utf8_lossy(&entry.name); + +// println!("{}/{} => {}", name, entry_name, entry.inode,); +// if entry_name != "." && entry_name != ".." { +// walk( +// fs, +// fs.inode_nth(entry.inode).unwrap(), +// format!("{}/{}", name, entry_name), +// ); +// } +// } +// } +// } diff --git a/ableos/src/filesystem/mod.rs b/ableos/src/filesystem/mod.rs index e32084d..36f3e57 100644 --- a/ableos/src/filesystem/mod.rs +++ b/ableos/src/filesystem/mod.rs @@ -5,34 +5,29 @@ */ pub mod errors; +pub mod ext2; use alloc::rc::Weak; use bitflags::bitflags; -use ext2::{ - fs::{ - sync::{Inode, Synced}, - Ext2, - }, - sector::{SectorSize, Size1024}, - volume::Volume, -}; -use spin::Lazy; -use crate::handle::Handle; +use crate::{handle::Handle, KERNEL_STATE}; use self::errors::FsError; use FsResult as Result; pub type FsResult = core::result::Result; -pub type FsOpenOperation = fn(node: &FsNode /* TODO: flags */) -> Result; -pub type FsCloseOperation = fn(node: &FsNode) -> Result<()>; -pub type FsReadOperation = fn(node: &FsNode, offset: u32, size: u32) - -> Result>; -pub type FsWriteOperation = fn(node: &FsNode, offset: u32, buffer: Box<[u8]>) - -> Result<()>; -pub type FsReaddirOperation = fn(node: &FsNode, index: u32) -> Result; -pub type FsFinddirOperation = fn(node: &FsNode, name: &str) -> Result; +pub trait StorageDevice +where + Self: Send, +{ + fn open(&self, node: &FsNode /* TODO: flags */) -> Result; + fn close(&self, node: &FsNode) -> Result<()>; + fn read(&self, node: &FsNode, offset: u32, size: u32) -> Result>; + fn write(&self, node: &FsNode, offset: u32, buffer: Box<[u8]>) -> Result<()>; + fn read_dir(&self, node: &FsNode, index: u32) -> Result; + fn find_dir(&self, node: &FsNode, name: &str) -> Result; +} /// A VFS node, that can either be a file or a directory. pub struct FsNode { @@ -44,12 +39,6 @@ pub struct FsNode { inode: u32, // implementation specific identifier for the node device_handle: Handle, // uniquely assigned device handle ptr: Weak, // used by mountpoints and symlinks - open: Option, - close: Option, - read: Option, - write: Option, - readdir: Option, - finddir: Option, // todo: permissions mask // todo: owning user/group } @@ -57,51 +46,57 @@ pub struct FsNode { impl FsNode { // TODO: make this take flags fn open(&self) -> Result { - if let Some(open) = self.open { - open(self) - } else { - Err(FsError::UnsupportedOperation) - } + let state = KERNEL_STATE.lock(); + let device = state + .get_storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + device.open(self) } fn close(&self) -> Result<()> { - if let Some(close) = self.close { - close(self) - } else { - Err(FsError::UnsupportedOperation) - } + let state = KERNEL_STATE.lock(); + let device = state + .get_storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + device.close(self) } fn read(&self, offset: u32, size: u32) -> Result> { - if let Some(read) = self.read { - read(self, offset, size) - } else { - Err(FsError::UnsupportedOperation) - } + let state = KERNEL_STATE.lock(); + let device = state + .get_storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + device.read(self, offset, size) } fn write(&self, offset: u32, buffer: Box<[u8]>) -> Result<()> { - if let Some(write) = self.write { - write(self, offset, buffer) - } else { - Err(FsError::UnsupportedOperation) - } + let state = KERNEL_STATE.lock(); + let device = state + .get_storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + device.write(self, offset, buffer) } - fn readdir(&self, index: u32) -> Result { - if let Some(readdir) = self.readdir { - readdir(self, index) - } else { - Err(FsError::UnsupportedOperation) - } + fn read_dir(&self, index: u32) -> Result { + let state = KERNEL_STATE.lock(); + let device = state + .get_storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + device.read_dir(self, index) } - fn finddir(&self, name: &str) -> Result { - if let Some(finddir) = self.finddir { - finddir(self, name) - } else { - Err(FsError::UnsupportedOperation) - } + fn find_dir(&self, name: &str) -> Result { + let state = KERNEL_STATE.lock(); + let device = state + .get_storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + device.find_dir(self, name) } } @@ -124,36 +119,3 @@ pub struct DirectoryEntry { name: String, inode: u32, } - -pub static FILE_SYSTEM: Lazy>>>> = - Lazy::new(|| spin::Mutex::new(load_fs())); - -pub fn walk>( - fs: &Synced>, - inode: Inode, - name: String, -) { - if let Some(dir) = inode.directory() { - for entry in dir { - assert!(entry.is_ok()); - let entry = entry.unwrap(); - let entry_name = String::from_utf8_lossy(&entry.name); - - println!("{}/{} => {}", name, entry_name, entry.inode,); - if entry_name != "." && entry_name != ".." { - walk( - fs, - fs.inode_nth(entry.inode).unwrap(), - format!("{}/{}", name, entry_name), - ); - } - } - } -} - -fn load_fs() -> Synced>> { - let mut volume = Vec::new(); - volume.extend_from_slice(include_bytes!("../../../userland/root_fs/ext2.img")); - - Synced::>::new(volume).unwrap() -} diff --git a/ableos/src/kernel_state.rs b/ableos/src/kernel_state.rs index 52ec371..25b27f4 100644 --- a/ableos/src/kernel_state.rs +++ b/ableos/src/kernel_state.rs @@ -1,10 +1,14 @@ +use hashbrown::HashMap; use spin::Lazy; +use crate::{handle::{Handle, HandleResource}, filesystem::StorageDevice}; + pub static KERNEL_STATE: Lazy> = Lazy::new(|| spin::Mutex::new(KernelInternalState::new())); pub struct KernelInternalState { pub hostname: String, + storage_devices: HashMap>, should_shutdown: bool, } @@ -12,6 +16,7 @@ impl KernelInternalState { pub fn new() -> Self { Self { should_shutdown: false, + storage_devices: HashMap::new(), hostname: "".to_string(), } } @@ -19,9 +24,19 @@ impl KernelInternalState { pub fn set_hostname(&mut self, hostname: String) { self.hostname = hostname; } + + pub fn add_storage_device(&mut self, device: impl StorageDevice + Send + 'static) { + self.storage_devices.insert(Handle::new(HandleResource::StorageDevice), Box::new(device)); + } + + pub fn get_storage_device(&self, handle: Handle) -> Option<&dyn StorageDevice> { + self.storage_devices.get(&handle).map(|d| &**d) + } + pub fn shutdown(&mut self) { self.should_shutdown = true; } + pub fn update_state(&mut self) { if self.should_shutdown { crate::arch::shutdown(); diff --git a/ableos/src/rhai_shell/mod.rs b/ableos/src/rhai_shell/mod.rs index 2e1f20b..9873aae 100644 --- a/ableos/src/rhai_shell/mod.rs +++ b/ableos/src/rhai_shell/mod.rs @@ -1,7 +1,7 @@ use crate::arch::drivers::sysinfo::master; use crate::systeminfo::{KERNEL_VERSION, RELEASE_TYPE}; -use crate::{filesystem::FILE_SYSTEM, time::fetch_time, KERNEL_STATE}; -use genfs::{Fs, OpenOptions}; +use crate::{time::fetch_time, KERNEL_STATE}; +// use genfs::{Fs, OpenOptions}; use kernel::allocator::ALLOCATOR; // use rhai::Engine; use spin::Lazy; @@ -103,23 +103,23 @@ pub fn poke_memory(ptr: i64, val: i64) { } } -pub fn ls() { - let current_dir = CURRENT_DIR.lock(); +// pub fn ls() { +// let current_dir = CURRENT_DIR.lock(); - let fs = &*FILE_SYSTEM.lock(); +// let fs = &*FILE_SYSTEM.lock(); - let file = fs - .open(current_dir.as_bytes(), OpenOptions::new().read(true)) - .unwrap(); +// let file = fs +// .open(current_dir.as_bytes(), OpenOptions::new().read(true)) +// .unwrap(); - let mut files = file.directory().unwrap(); - println!("current dir: {}", *current_dir); - while let Some(Ok(entry)) = files.next() { - let inode_name = entry.name; - let s = String::from_utf8_lossy(&inode_name); - println!("{}", s); - } -} +// let mut files = file.directory().unwrap(); +// println!("current dir: {}", *current_dir); +// while let Some(Ok(entry)) = files.next() { +// let inode_name = entry.name; +// let s = String::from_utf8_lossy(&inode_name); +// println!("{}", s); +// } +// } pub fn log_dump() { use crate::network::socket::SimpleSock; @@ -144,55 +144,55 @@ pub fn log_dump() { } } -pub fn echo_file(path: String) { - let mut current_dir = CURRENT_DIR.lock(); +// pub fn echo_file(path: String) { +// let mut current_dir = CURRENT_DIR.lock(); - let fs = &*FILE_SYSTEM.lock(); +// let fs = &*FILE_SYSTEM.lock(); - current_dir.push_str(&path); +// current_dir.push_str(&path); - let file = fs - .open(current_dir.as_bytes(), OpenOptions::new().read(true)) - .unwrap(); +// let file = fs +// .open(current_dir.as_bytes(), OpenOptions::new().read(true)) +// .unwrap(); - if file.is_dir() { - println!("{} is a directory", path); - } else { - let mut file_contents = Vec::new(); +// if file.is_dir() { +// println!("{} is a directory", path); +// } else { +// let mut file_contents = Vec::new(); - let _ret = file.read_to_end(&mut file_contents).unwrap(); +// let _ret = file.read_to_end(&mut file_contents).unwrap(); - let file_contents_str = String::from_utf8_lossy(&file_contents); +// let file_contents_str = String::from_utf8_lossy(&file_contents); - println!("{}", file_contents_str); - } -} +// println!("{}", file_contents_str); +// } +// } -pub fn change_directory(path: String) { - let mut current_dir = CURRENT_DIR.lock(); +// pub fn change_directory(path: String) { +// let mut current_dir = CURRENT_DIR.lock(); - let _fs = &*FILE_SYSTEM.lock(); - if path == "." || path == ".." { - let mut split_dir = current_dir.split('/').collect::>(); - let mut new_dir = String::new(); - split_dir.remove(split_dir.len() - 1); - println!("{:?}", split_dir); - if split_dir.is_empty() { - new_dir = "/".to_string(); - } else { - for x in split_dir { - new_dir.push_str(x); - new_dir.push('/'); - } - } - *current_dir = new_dir; - } else { - if !current_dir.ends_with('/') { - current_dir.push('/'); - } - current_dir.push_str(&path); - } -} +// let _fs = &*FILE_SYSTEM.lock(); +// if path == "." || path == ".." { +// let mut split_dir = current_dir.split('/').collect::>(); +// let mut new_dir = String::new(); +// split_dir.remove(split_dir.len() - 1); +// println!("{:?}", split_dir); +// if split_dir.is_empty() { +// new_dir = "/".to_string(); +// } else { +// for x in split_dir { +// new_dir.push_str(x); +// new_dir.push('/'); +// } +// } +// *current_dir = new_dir; +// } else { +// if !current_dir.ends_with('/') { +// current_dir.push('/'); +// } +// current_dir.push_str(&path); +// } +// } // fn engine_construction() -> Engine { // let mut engine = rhai::Engine::new(); diff --git a/ableos/src/scratchpad.rs b/ableos/src/scratchpad.rs index 2948cc9..0005606 100644 --- a/ableos/src/scratchpad.rs +++ b/ableos/src/scratchpad.rs @@ -11,8 +11,8 @@ use crate::systeminfo::{KERNEL_VERSION, RELEASE_TYPE}; use crate::time::fetch_time; use crate::KERNEL_STATE; use crate::{ - arch::shutdown, filesystem::FILE_SYSTEM, rhai_shell::KEYBUFF, vterm::VTerm, - wasm_jumploader::run_program, + arch::shutdown, rhai_shell::KEYBUFF, vterm::Term, + // wasm_jumploader::run_program, }; use acpi::{AcpiTables, PlatformInfo}; @@ -193,7 +193,7 @@ pub fn real_shell() { } pub fn command_parser(user: String, command: String) { - let fs = &*FILE_SYSTEM.lock(); + // let fs = &*FILE_SYSTEM.lock(); let mut iter = command.split_whitespace(); // TODO: update the open() function to take either a ableOS path or a b"/" type path @@ -225,41 +225,48 @@ pub fn command_parser(user: String, command: String) { // drop(fs); // shell(); // } - "list" | "ls" => { - for dir_entry in list_files_in_dir(fs, current_path) { - println!("{}", dir_entry.file_name_string()); - } - } + // "list" | "ls" => { + // for dir_entry in list_files_in_dir(fs, current_path) { + // println!("{}", dir_entry.file_name_string()); + // } + // } +<<<<<<< HEAD "echo" => match conf_args.1.arguments.get("p") { Some(path) => echo_file(path.to_string(), fs), None => println!("No path provided"), }, "test" => {} +======= + // "echo" => { + // echo_file(iter.next().unwrap().to_string(), fs); + // } +>>>>>>> 5149f26... vfs: move operations into trait StorageDevice, hold StorageDevices in KERNEL_STATE "quit" => shutdown(), _ => { - let mut options = OpenOptions::new(); - options.read(true); - let file = { - let path = format!("/home/{user}/bins/{bin_name}.wasm"); - if let Ok(file) = fs.open(&path.as_bytes(), &options) { - file - } else { - let path = format!("/shared/bins/{bin_name}.wasm"); - if let Ok(file) = fs.open(&path.as_bytes(), &options) { - file - } else { - let path = format!("/system/bins/{bin_name}.wasm"); - match fs.open(&path.as_bytes(), &options) { - Ok(file) => file, - Err(error) => { - trace!("{:?}", error); + // let mut options = OpenOptions::new(); + // options.read(true); + // let file = { + // let path = format!("/home/{user}/bins/{bin_name}.wasm"); + // if let Ok(file) = fs.open(&path.as_bytes(), &options) { + // file + // } else { + // let path = format!("/shared/bins/{bin_name}.wasm"); + // if let Ok(file) = fs.open(&path.as_bytes(), &options) { + // file + // } else { + // let path = format!("/system/bins/{bin_name}.wasm"); + // match fs.open(&path.as_bytes(), &options) { + // Ok(file) => file, + // Err(error) => { + // trace!("{:?}", error); println!("No such binary: {}", bin_name); error!("No such binary: {}", bin_name); return; +<<<<<<< HEAD } } } @@ -272,6 +279,20 @@ pub fn command_parser(user: String, command: String) { // let args = iter.collect::>(); // println!("{:?}", args); run_program(&binary); +======= + // } + // } + // } + // } + // }; + + // let mut binary = vec![]; + // file.read_to_end(&mut binary).unwrap(); + + // let args = iter.collect::>(); + // println!("{:?}", args); + // run_program(&binary); +>>>>>>> 5149f26... vfs: move operations into trait StorageDevice, hold StorageDevices in KERNEL_STATE } } } diff --git a/ableos/src/wasm_jumploader/mod.rs b/ableos/src/wasm_jumploader/mod.rs index ab5a674..06177dc 100644 --- a/ableos/src/wasm_jumploader/mod.rs +++ b/ableos/src/wasm_jumploader/mod.rs @@ -1,101 +1,101 @@ pub mod host_functions; -use crate::{filesystem::FILE_SYSTEM, wasm_jumploader::host_functions::HostExternals}; -use genfs::{Fs, OpenOptions}; +use crate::wasm_jumploader::host_functions::HostExternals; +// use genfs::{Fs, OpenOptions}; use wasmi::{ImportsBuilder, ModuleInstance}; pub fn interp() { trace!("Interpreting..."); - let fs = &*FILE_SYSTEM.lock(); - trace!("Got filesystem"); - let file = fs - .open( - b"/home/able/bins/aos_test.wasm", - OpenOptions::new().read(true), - ) - .unwrap(); + // let fs = &*FILE_SYSTEM.lock(); + // trace!("Got filesystem"); + // let file = fs + // .open( + // b"/home/able/bins/aos_test.wasm", + // OpenOptions::new().read(true), + // ) + // .unwrap(); - let mut wasm_binary = Vec::new(); + // let mut wasm_binary = Vec::new(); - let ret = file.read_to_end(&mut wasm_binary).unwrap(); - trace!("Binary size {}", ret); - // Load wasm binary and prepare it for instantiation. - let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); - trace!("Loaded wasm binary"); - let imports = ImportsBuilder::new().with_resolver("env", &host_functions::HostExternals {}); - trace!("Created imports"); + // let ret = file.read_to_end(&mut wasm_binary).unwrap(); + // trace!("Binary size {}", ret); + // // Load wasm binary and prepare it for instantiation. + // let module = wasmi::Module::from_buffer(&wasm_binary).expect("failed to load wasm"); + // trace!("Loaded wasm binary"); + // let imports = ImportsBuilder::new().with_resolver("env", &host_functions::HostExternals {}); + // trace!("Created imports"); - // Instantiate a module with empty imports and - // assert that there is no `start` function. - let instance = ModuleInstance::new(&module, &imports); // .expect("failed to instantiate wasm module") + // // Instantiate a module with empty imports and + // // assert that there is no `start` function. + // let instance = ModuleInstance::new(&module, &imports); // .expect("failed to instantiate wasm module") - match instance { - Ok(inst) => { - let instance = inst.assert_no_start(); - let mut is_driver = false; - let _is_program = false; - let mut has_driver_entry = false; - let mut has_driver_exit = false; - let mut has_start = false; + // match instance { + // Ok(inst) => { + // let instance = inst.assert_no_start(); + // let mut is_driver = false; + // let _is_program = false; + // let mut has_driver_entry = false; + // let mut has_driver_exit = false; + // let mut has_start = false; - if let Some(_val) = instance.export_by_name("driver_entry") { - has_driver_entry = true; - } + // if let Some(_val) = instance.export_by_name("driver_entry") { + // has_driver_entry = true; + // } - if let Some(_val) = instance.export_by_name("driver_exit") { - has_driver_exit = true; - } + // if let Some(_val) = instance.export_by_name("driver_exit") { + // has_driver_exit = true; + // } - match instance.export_by_name("start") { - Some(_val) => { - trace!("Program start function found"); - has_start = true; - } - None => debug!("No start function found"), - } + // match instance.export_by_name("start") { + // Some(_val) => { + // trace!("Program start function found"); + // has_start = true; + // } + // None => debug!("No start function found"), + // } - match instance.export_by_name("main") { - Some(_val) => { - trace!("Program main function found"); - has_start = true; - } - None => debug!("No main function found"), - } + // match instance.export_by_name("main") { + // Some(_val) => { + // trace!("Program main function found"); + // has_start = true; + // } + // None => debug!("No main function found"), + // } - match (has_driver_entry, has_driver_exit) { - (true, true) => { - trace!("Valid driver entry and exit functions found"); - is_driver = true; - } - (true, false) => error!("Driver entry function found but no driver exit function"), - (false, true) => error!("Driver exit function found but no driver entry function"), - (false, false) => { - trace!("No driver entry or exit functions found"); - } - } + // match (has_driver_entry, has_driver_exit) { + // (true, true) => { + // trace!("Valid driver entry and exit functions found"); + // is_driver = true; + // } + // (true, false) => error!("Driver entry function found but no driver exit function"), + // (false, true) => error!("Driver exit function found but no driver entry function"), + // (false, false) => { + // trace!("No driver entry or exit functions found"); + // } + // } - if has_start && has_driver_entry { - error!( - "A program should not have both a start function and a driver entry function. It Will be treated as a program." - ); - } + // if has_start && has_driver_entry { + // error!( + // "A program should not have both a start function and a driver entry function. It Will be treated as a program." + // ); + // } - if has_start { - let ret = instance - .invoke_export("start", &[], &mut HostExternals {}) - .expect("failed to execute export"); + // if has_start { + // let ret = instance + // .invoke_export("start", &[], &mut HostExternals {}) + // .expect("failed to execute export"); - println!("collected wasm return value: {:?}", ret); - } else if is_driver { - let ret = instance - .invoke_export("driver_entry", &[], &mut HostExternals {}) - .expect("failed to execute export"); + // println!("collected wasm return value: {:?}", ret); + // } else if is_driver { + // let ret = instance + // .invoke_export("driver_entry", &[], &mut HostExternals {}) + // .expect("failed to execute export"); - println!("collected wasm return value: {:?}", ret); - } - } - Err(err) => error!("{}", err), - } + // println!("collected wasm return value: {:?}", ret); + // } + // } + // Err(err) => error!("{}", err), + // } } pub fn run_program(program: &[u8]) { diff --git a/ext2-rs/src/error.rs b/ext2-rs/src/error.rs index 10cb16f..f506ae9 100644 --- a/ext2-rs/src/error.rs +++ b/ext2-rs/src/error.rs @@ -62,8 +62,8 @@ pub enum Error { /// inode name name: String, }, - #[cfg(any(test, not(feature = "no_std")))] - Io { inner: io::Error }, + // #[cfg(any(test, not(feature = "no_std")))] + // Io { inner: io::Error }, } impl Display for Error {