diff --git a/src/api/console.rs b/src/api/console.rs index 20bfeb9da..8b6ecb88b 100644 --- a/src/api/console.rs +++ b/src/api/console.rs @@ -79,10 +79,6 @@ fn color_to_bg(name: &str) -> Option { color_to_fg(name).map(|fg| fg + 10) } -pub fn read_char() -> Option { - Some(sys::console::get_char()) -} - pub fn is_printable(c: char) -> bool { if cfg!(feature = "video") { c.is_ascii() && sys::vga::is_printable(c as u8) diff --git a/src/api/fs.rs b/src/api/fs.rs index 32b80bab5..9f2a3284c 100644 --- a/src/api/fs.rs +++ b/src/api/fs.rs @@ -1,7 +1,46 @@ +use crate::api::syscall; +use crate::sys::fs::{OpenFlag, DeviceType}; use crate::sys; + +use alloc::format; use alloc::string::{String, ToString}; -use alloc::vec; use alloc::vec::Vec; +use alloc::vec; + +pub trait FileIO { + fn read(&mut self, buf: &mut [u8]) -> Result; + fn write(&mut self, buf: &[u8]) -> Result; +} + +pub fn dirname(pathname: &str) -> &str { + let n = pathname.len(); + let i = match pathname.rfind('/') { + Some(0) => 1, + Some(i) => i, + None => n, + }; + &pathname[0..i] +} + +pub fn filename(pathname: &str) -> &str { + let n = pathname.len(); + let i = match pathname.rfind('/') { + Some(i) => i + 1, + None => 0, + }; + &pathname[i..n] +} + +// Transform "foo.txt" into "/path/to/foo.txt" +pub fn realpath(pathname: &str) -> String { + if pathname.starts_with('/') { + pathname.into() + } else { + let dirname = sys::process::dir(); + let sep = if dirname.ends_with('/') { "" } else { "/" }; + format!("{}{}{}", dirname, sep, pathname) + } +} pub fn canonicalize(path: &str) -> Result { match sys::process::env("HOME") { @@ -18,19 +57,47 @@ pub fn canonicalize(path: &str) -> Result { } } -pub fn read_to_string(path: &str) -> Result { - let path = match canonicalize(path) { - Ok(path) => path, - Err(_) => return Err(()), - }; - match sys::fs::File::open(&path) { - Some(mut file) => { - Ok(file.read_to_string()) - }, - None => { - Err(()) - } +pub fn exists(path: &str) -> bool { + syscall::stat(path).is_some() +} + +pub fn open_file(path: &str) -> Option { + let flags = 0; + syscall::open(path, flags) +} + +pub fn create_file(path: &str) -> Option { + let flags = OpenFlag::Create as usize; + syscall::open(path, flags) +} + +pub fn open_dir(path: &str) -> Option { + let flags = OpenFlag::Dir as usize; + syscall::open(path, flags) +} + +pub fn create_dir(path: &str) -> Option { + let flags = OpenFlag::Create as usize | OpenFlag::Dir as usize; + syscall::open(path, flags) +} + +pub fn open_device(path: &str) -> Option { + let flags = OpenFlag::Device as usize; + syscall::open(path, flags) +} + +pub fn create_device(path: &str, kind: DeviceType) -> Option { + let flags = OpenFlag::Create as usize | OpenFlag::Device as usize; + if let Some(handle) = syscall::open(path, flags) { + let buf = [kind as u8; 1]; + return syscall::write(handle, &buf); } + None +} + +pub fn read_to_string(path: &str) -> Result { + let buf = read(path)?; + Ok(String::from_utf8_lossy(&buf).to_string()) } pub fn read(path: &str) -> Result, ()> { @@ -38,33 +105,48 @@ pub fn read(path: &str) -> Result, ()> { Ok(path) => path, Err(_) => return Err(()), }; - match sys::fs::File::open(&path) { - Some(mut file) => { - let mut buf = vec![0; file.size()]; - file.read(&mut buf); - Ok(buf) - }, - None => { - Err(()) + if let Some(stat) = syscall::stat(&path) { + let res = if stat.is_device() { open_device(&path) } else { open_file(&path) }; + if let Some(handle) = res { + let mut buf = vec![0; stat.size() as usize]; + if let Some(bytes) = syscall::read(handle, &mut buf) { + buf.resize(bytes, 0); + syscall::close(handle); + return Ok(buf) + } } } + Err(()) } -pub fn write(path: &str, buf: &[u8]) -> Result<(), ()> { +pub fn write(path: &str, buf: &[u8]) -> Result { let path = match canonicalize(path) { Ok(path) => path, Err(_) => return Err(()), }; - let mut file = match sys::fs::File::open(&path) { - None => match sys::fs::File::create(&path) { - None => return Err(()), - Some(file) => file, - }, - Some(file) => file, - }; - // TODO: add File::write_all to split buf if needed - match file.write(buf) { - Ok(_) => Ok(()), - Err(_) => Err(()), + if let Some(handle) = create_file(&path) { + if let Some(bytes) = syscall::write(handle, buf) { + syscall::close(handle); + return Ok(bytes) + } } + Err(()) +} + +#[test_case] +fn test_file() { + use crate::sys::fs::{mount_mem, format_mem, dismount}; + mount_mem(); + format_mem(); + + assert_eq!(open_file("/test"), None); + + // Write file + let input = "Hello, world!".as_bytes(); + assert_eq!(write("/test", &input), Ok(input.len())); + + // Read file + assert_eq!(read("/test"), Ok(input.to_vec())); + + dismount(); } diff --git a/src/api/io.rs b/src/api/io.rs new file mode 100644 index 000000000..8563dd27a --- /dev/null +++ b/src/api/io.rs @@ -0,0 +1,51 @@ +use crate::api::syscall; + +use alloc::vec; +use alloc::string::{String, ToString}; + +pub struct Stdin; +pub struct Stdout; + +impl Stdin { + fn new() -> Self { + Self {} + } + + pub fn read_char(&self) -> Option { + let mut buf = vec![0; 1]; + if let Some(bytes) = syscall::read(0, &mut buf) { + if bytes > 0 { + return Some(buf[0] as char); + } + } + None + } + + pub fn read_line(&self) -> String { + let mut buf = vec![0; 256]; + if let Some(bytes) = syscall::read(0, &mut buf) { + buf.resize(bytes, 0); + String::from_utf8_lossy(&buf).to_string() + } else { + String::new() + } + } +} + +impl Stdout { + fn new() -> Self { + Self {} + } + + pub fn write(&self, s: &str) { + syscall::write(1, s.as_bytes()); + } +} + +pub fn stdout() -> Stdout { + Stdout::new() +} + +pub fn stdin() -> Stdin { + Stdin::new() +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 196423c8b..1fcd91ae1 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,8 +1,9 @@ #[macro_export] macro_rules! print { ($($arg:tt)*) => ({ - // TODO: Use syscall instead - $crate::sys::console::print_fmt(format_args!($($arg)*)); + use alloc::format; + let s = format!("{}", format_args!($($arg)*)); + $crate::api::io::stdout().write(&s); }); } @@ -19,7 +20,9 @@ macro_rules! println { pub mod console; pub mod font; pub mod fs; +pub mod io; pub mod prompt; +pub mod random; pub mod regex; pub mod syscall; pub mod vga; diff --git a/src/api/prompt.rs b/src/api/prompt.rs index 92a3ec04b..2d766deca 100644 --- a/src/api/prompt.rs +++ b/src/api/prompt.rs @@ -1,5 +1,4 @@ -use crate::api::fs; -use crate::api::console; +use crate::api::{console, fs, io}; use alloc::boxed::Box; use alloc::string::{String, ToString}; use alloc::vec::Vec; @@ -31,7 +30,7 @@ impl Prompt { self.cursor = self.offset; self.line = String::new(); let mut parser = Parser::new(); - while let Some(c) = console::read_char() { + while let Some(c) = io::stdin().read_char() { match c { '\x03' => { // End of Text (^C) println!(); diff --git a/src/api/random.rs b/src/api/random.rs new file mode 100644 index 000000000..8a77c64d5 --- /dev/null +++ b/src/api/random.rs @@ -0,0 +1,22 @@ +use crate::api::syscall; +use crate::api::fs; + +pub fn get_u64() -> u64 { + let mut buf = [0; 8]; + if let Some(handle) = fs::open_device("/dev/random") { + if syscall::read(handle, &mut buf).is_some() { + return u64::from_be_bytes(buf); + } + } + 0 +} + +pub fn get_u16() -> u16 { + let mut buf = [0; 2]; + if let Some(handle) = fs::open_device("/dev/random") { + if syscall::read(handle, &mut buf).is_some() { + return u16::from_be_bytes(buf); + } + } + 0 +} diff --git a/src/api/syscall.rs b/src/api/syscall.rs index f0d530600..9944dc2d6 100644 --- a/src/api/syscall.rs +++ b/src/api/syscall.rs @@ -1,5 +1,6 @@ use crate::syscall; use crate::sys::syscall::number::*; +use crate::sys::fs::FileStat; pub fn sleep(seconds: f64) { unsafe { syscall!(SLEEP, seconds.to_bits()) }; @@ -14,3 +15,88 @@ pub fn realtime() -> f64 { let res = unsafe { syscall!(REALTIME) }; f64::from_bits(res as u64) } + +pub fn stat(path: &str) -> Option { + let path_ptr = path.as_ptr() as usize; + let path_len = path.len() as usize; + let mut stat = FileStat::new(); + let stat_ptr = &mut stat as *mut FileStat as usize; + let res = unsafe { syscall!(STAT, path_ptr, path_len, stat_ptr) } as isize; + if res.is_negative() { + None + } else { + Some(stat) + } +} + +pub fn open(path: &str, flags: usize) -> Option { + let ptr = path.as_ptr() as usize; + let len = path.len() as usize; + let res = unsafe { syscall!(OPEN, ptr, len, flags) } as isize; + if res.is_negative() { + None + } else { + Some(res as usize) + } +} + +pub fn read(handle: usize, buf: &mut [u8]) -> Option { + let ptr = buf.as_ptr() as usize; + let len = buf.len() as usize; + let res = unsafe { syscall!(READ, handle, ptr, len) } as isize; + if res.is_negative() { + None + } else { + Some(res as usize) + } +} + +pub fn write(handle: usize, buf: &[u8]) -> Option { + let ptr = buf.as_ptr() as usize; + let len = buf.len() as usize; + let res = unsafe { syscall!(WRITE, handle, ptr, len) } as isize; + if res.is_negative() { + None + } else { + Some(res as usize) + } +} + +pub fn close(handle: usize) { + unsafe { syscall!(CLOSE, handle as usize) }; +} + +#[test_case] +fn test_file() { + use crate::sys::fs::{mount_mem, format_mem, dismount, OpenFlag}; + use alloc::vec; + mount_mem(); + format_mem(); + + let flags = 0; + assert_eq!(open("/test", flags), None); + + // Write file + let flags = OpenFlag::Create as usize; + assert_eq!(open("/test", flags), Some(4)); + let input = "Hello, world!".as_bytes(); + assert_eq!(write(4, &input), Some(input.len())); + + // Read file + let flags = 0; + assert_eq!(open("/test", flags), Some(5)); + let mut output = vec![0; input.len()]; + assert_eq!(read(5, &mut output), Some(input.len())); + assert_eq!(output, input); + + close(4); + close(5); + + assert_eq!(open("/test", flags), Some(4)); + + close(4); + + //assert!(write(1, b"Hello, World\n").is_some()); + + dismount(); +} diff --git a/src/sys/console.rs b/src/sys/console.rs index d06616e59..aa52a0892 100644 --- a/src/sys/console.rs +++ b/src/sys/console.rs @@ -1,4 +1,5 @@ use crate::sys; +use crate::sys::fs::FileIO; use alloc::string::String; use alloc::string::ToString; use core::fmt; @@ -12,6 +13,36 @@ lazy_static! { pub static ref RAW: Mutex = Mutex::new(false); } +#[derive(Debug, Clone)] +pub struct Console; + +impl Console { + pub fn new() -> Self { + Self {} + } +} + +impl FileIO for Console { + fn read(&mut self, buf: &mut [u8]) -> Result { + let mut s = if buf.len() == 1 { + read_char().to_string() + } else { + read_line() + }; + s.truncate(buf.len()); + let n = s.len(); + buf[0..n].copy_from_slice(s.as_bytes()); + Ok(n) + } + + fn write(&mut self, buf: &[u8]) -> Result { + let s = String::from_utf8_lossy(buf); + let n = s.len(); + print_fmt(format_args!("{}", s)); + Ok(n) + } +} + pub fn cols() -> usize { if cfg!(feature = "video") { sys::vga::cols() @@ -110,8 +141,7 @@ pub fn drain() { }) } -// TODO: Rename to `read_char()` -pub fn get_char() -> char { +pub fn read_char() -> char { sys::console::disable_echo(); sys::console::enable_raw(); loop { @@ -132,8 +162,7 @@ pub fn get_char() -> char { } } -// TODO: Rename to `read_line()` -pub fn get_line() -> String { +pub fn read_line() -> String { loop { sys::time::halt(); let res = interrupts::without_interrupts(|| { diff --git a/src/sys/fs/device.rs b/src/sys/fs/device.rs new file mode 100644 index 000000000..f49eb4fd8 --- /dev/null +++ b/src/sys/fs/device.rs @@ -0,0 +1,76 @@ +use super::{dirname, filename, realpath, FileIO}; +use super::dir::Dir; +use super::file::File; +use super::block::Block; + +use crate::sys::console::Console; +use crate::sys::random::Random; + +#[repr(u8)] +pub enum DeviceType { + File = 0, + Console = 1, + Random = 2, +} + +#[derive(Debug, Clone)] +pub enum Device { + File(File), + Console(Console), + Random(Random), +} + +impl Device { + fn new(i: u8) -> Self { + match i { + i if i == DeviceType::Console as u8 => Device::Console(Console::new()), + i if i == DeviceType::Random as u8 => Device::Random(Random::new()), + _ => unimplemented!(), + } + } + + pub fn create(pathname: &str) -> Option { + let pathname = realpath(pathname); + let dirname = dirname(&pathname); + let filename = filename(&pathname); + if let Some(dir) = Dir::open(dirname) { + if let Some(dir_entry) = dir.create_device(filename) { + return Some(Device::File(dir_entry.into())) + } + } + None + } + + pub fn open(pathname: &str) -> Option { + let pathname = realpath(pathname); + let dirname = dirname(&pathname); + let filename = filename(&pathname); + if let Some(dir) = Dir::open(dirname) { + if let Some(dir_entry) = dir.find(filename) { + if dir_entry.is_device() { + let block = Block::read(dir_entry.addr()); + let data = block.data(); + return Some(Self::new(data[0])); + } + } + } + None + } +} + +impl FileIO for Device { + fn read(&mut self, buf: &mut [u8]) -> Result { + match self { + Device::File(io) => io.read(buf), + Device::Console(io) => io.read(buf), + Device::Random(io) => io.read(buf), + } + } + fn write(&mut self, buf: &[u8]) -> Result { + match self { + Device::File(io) => io.write(buf), + Device::Console(io) => io.write(buf), + Device::Random(io) => io.write(buf), + } + } +} diff --git a/src/sys/fs/dir.rs b/src/sys/fs/dir.rs index dcb20a825..3d4704bb0 100644 --- a/src/sys/fs/dir.rs +++ b/src/sys/fs/dir.rs @@ -1,4 +1,4 @@ -use super::{dirname, filename, realpath}; +use super::{dirname, filename, realpath, FileIO}; use super::dir_entry::DirEntry; use super::read_dir::ReadDir; use super::block_bitmap::BlockBitmap; @@ -9,7 +9,7 @@ use crate::sys; use alloc::string::String; use core::convert::From; -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub struct Dir { addr: u32, } @@ -71,7 +71,7 @@ impl Dir { } pub fn find(&self, name: &str) -> Option { - for entry in self.read() { + for entry in self.entries() { if entry.name() == name { return Some(entry); } @@ -88,24 +88,28 @@ impl Dir { self.create_entry(FileType::Dir, name) } + pub fn create_device(&self, name: &str) -> Option { + self.create_entry(FileType::Device, name) + } + fn create_entry(&self, kind: FileType, name: &str) -> Option { if self.find(name).is_some() { return None; } // Read the whole dir to add an entry at the end - let mut read_dir = self.read(); - while read_dir.next().is_some() {} + let mut entries = self.entries(); + while entries.next().is_some() {} // Allocate a new block for the dir if no space left for adding the new entry - let space_left = read_dir.block.data().len() - read_dir.block_data_offset(); + let space_left = entries.block.data().len() - entries.block_data_offset(); let entry_len = DirEntry::empty_len() + name.len(); if entry_len > space_left { - match read_dir.block.alloc_next() { + match entries.block.alloc_next() { None => return None, // Disk is full Some(new_block) => { - read_dir.block = new_block; - read_dir.block_data_offset = 0; + entries.block = new_block; + entries.block_data_offset = 0; }, } } @@ -118,8 +122,8 @@ impl Dir { let entry_time = sys::clock::realtime() as u64; let entry_name = truncate(name, u8::MAX as usize); let n = entry_name.len(); - let i = read_dir.block_data_offset(); - let data = read_dir.block.data_mut(); + let i = entries.block_data_offset(); + let data = entries.block.data_mut(); data[i] = entry_kind; data[(i + 1)..(i + 5)].clone_from_slice(&entry_addr.to_be_bytes()); @@ -128,7 +132,7 @@ impl Dir { data[i + 17] = n as u8; data[(i + 18)..(i + 18 + n)].clone_from_slice(&entry_name.as_bytes()); - read_dir.block.write(); + entries.block.write(); Some(DirEntry::new(*self, kind, entry_addr, entry_size, entry_time, name)) } @@ -136,17 +140,17 @@ impl Dir { // Deleting an entry is done by setting the entry address to 0 // TODO: If the entry is a directory, remove its entries recursively pub fn delete_entry(&mut self, name: &str) -> Result<(), ()> { - let mut read_dir = self.read(); - for entry in &mut read_dir { + let mut entries = self.entries(); + for entry in &mut entries { if entry.name() == name { // Zeroing entry addr - let i = read_dir.block_data_offset() - entry.len(); - let data = read_dir.block.data_mut(); + let i = entries.block_data_offset() - entry.len(); + let data = entries.block.data_mut(); data[i + 1] = 0; data[i + 2] = 0; data[i + 3] = 0; data[i + 4] = 0; - read_dir.block.write(); + entries.block.write(); // Freeing entry blocks let mut entry_block = Block::read(entry.addr()); @@ -166,20 +170,20 @@ impl Dir { pub fn update_entry(&mut self, name: &str, size: u32) { let time = sys::clock::realtime() as u64; - let mut read_dir = self.read(); - for entry in &mut read_dir { + let mut entries = self.entries(); + for entry in &mut entries { if entry.name() == name { - let i = read_dir.block_data_offset() - entry.len(); - let data = read_dir.block.data_mut(); + let i = entries.block_data_offset() - entry.len(); + let data = entries.block.data_mut(); data[(i + 5)..(i + 9)].clone_from_slice(&size.to_be_bytes()); data[(i + 9)..(i + 17)].clone_from_slice(&time.to_be_bytes()); - read_dir.block.write(); + entries.block.write(); break; } } } - pub fn read(&self) -> ReadDir { + pub fn entries(&self) -> ReadDir { ReadDir::from(self.clone()) } @@ -195,6 +199,15 @@ impl Dir { } } +impl FileIO for Dir { + fn read(&mut self, _buf: &mut [u8]) -> Result { + Err(()) + } + fn write(&mut self, _buf: &[u8]) -> Result { + Err(()) + } +} + // Truncate to the given number of bytes at most while respecting char boundaries fn truncate(s: &str, max: usize) -> String { s.char_indices().take_while(|(i, _)| *i <= max).map(|(_, c)| c).collect() diff --git a/src/sys/fs/dir_entry.rs b/src/sys/fs/dir_entry.rs index b571df7e3..72bae5fc6 100644 --- a/src/sys/fs/dir_entry.rs +++ b/src/sys/fs/dir_entry.rs @@ -1,4 +1,4 @@ -use super::FileType; +use super::{dirname, filename, realpath, FileType}; use super::dir::Dir; use alloc::string::String; @@ -13,6 +13,16 @@ pub struct DirEntry { } impl DirEntry { + pub fn open(pathname: &str) -> Option { + let pathname = realpath(pathname); + let dirname = dirname(&pathname); + let filename = filename(&pathname); + if let Some(dir) = Dir::open(dirname) { + return dir.find(filename); + } + None + } + pub fn new(dir: Dir, kind: FileType, addr: u32, size: u32, time: u64, name: &str) -> Self { let name = String::from(name); Self { dir, kind, addr, size, time, name } @@ -30,6 +40,10 @@ impl DirEntry { Self::empty_len() == self.len() } + pub fn kind(&self) -> FileType { + self.kind + } + pub fn is_dir(&self) -> bool { self.kind == FileType::Dir } @@ -38,6 +52,10 @@ impl DirEntry { self.kind == FileType::File } + pub fn is_device(&self) -> bool { + self.kind == FileType::Device + } + pub fn addr(&self) -> u32 { self.addr } @@ -57,4 +75,42 @@ impl DirEntry { pub fn time(&self) -> u64 { self.time } + + pub fn stat(&self) -> FileStat { + FileStat { kind: self.kind, size: self.size, time: self.time } + } +} + +#[derive(Debug)] +pub struct FileStat { + kind: FileType, + size: u32, + time: u64, +} + +impl FileStat { + pub fn new() -> Self { + Self { kind: FileType::File, size: 0, time: 0 } + } + + pub fn size(&self) -> u32 { + self.size + } + + pub fn time(&self) -> u64 { + self.time + } + + // TODO: Duplicated from dir entry + pub fn is_dir(&self) -> bool { + self.kind == FileType::Dir + } + + pub fn is_file(&self) -> bool { + self.kind == FileType::File + } + + pub fn is_device(&self) -> bool { + self.kind == FileType::Device + } } diff --git a/src/sys/fs/file.rs b/src/sys/fs/file.rs index e029d7f6e..11e737adb 100644 --- a/src/sys/fs/file.rs +++ b/src/sys/fs/file.rs @@ -1,4 +1,4 @@ -use super::{dirname, filename, realpath}; +use super::{dirname, filename, realpath, FileIO}; use super::dir::Dir; use super::block::Block; use super::dir_entry::DirEntry; @@ -13,7 +13,7 @@ pub enum SeekFrom { End(i32), } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct File { name: String, addr: u32, @@ -84,9 +84,35 @@ impl File { Ok(self.offset) } + // TODO: add `read_to_end(&self, buf: &mut Vec) -> Result` + + // TODO: `return Result` + pub fn read_to_string(&mut self) -> String { + let mut buf = vec![0; self.size()]; + if let Ok(bytes) = self.read(&mut buf) { + buf.resize(bytes, 0); + } + String::from_utf8_lossy(&buf).to_string() + } + + pub fn addr(&self) -> u32 { + self.addr + } + + pub fn delete(pathname: &str) -> Result<(), ()> { + let pathname = realpath(pathname); + let dirname = dirname(&pathname); + let filename = filename(&pathname); + if let Some(mut dir) = Dir::open(dirname) { + dir.delete_entry(filename) + } else { + Err(()) + } + } +} - // TODO: return `Result` - pub fn read(&mut self, buf: &mut [u8]) -> usize { +impl FileIO for File { + fn read(&mut self, buf: &mut [u8]) -> Result { let buf_len = buf.len(); let mut addr = self.addr; let mut bytes = 0; // Number of bytes read @@ -98,7 +124,7 @@ impl File { for i in 0..data_len { if pos == self.offset { if bytes == buf_len || pos as usize == self.size() { - return bytes; + return Ok(bytes); } buf[bytes] = data[i]; bytes += 1; @@ -108,22 +134,12 @@ impl File { } match block.next() { Some(next_block) => addr = next_block.addr(), - None => return bytes, + None => return Ok(bytes), } } } - // TODO: add `read_to_end(&self, buf: &mut Vec) -> Result` - - // TODO: `return Result` - pub fn read_to_string(&mut self) -> String { - let mut buf = vec![0; self.size()]; - let bytes = self.read(&mut buf); - buf.resize(bytes, 0); - String::from_utf8_lossy(&buf).to_string() - } - - pub fn write(&mut self, buf: &[u8]) -> Result { + fn write(&mut self, buf: &[u8]) -> Result { let buf_len = buf.len(); let mut addr = self.addr; let mut bytes = 0; // Number of bytes written @@ -172,21 +188,6 @@ impl File { self.dir.update_entry(&self.name, self.size); Ok(bytes) } - - pub fn addr(&self) -> u32 { - self.addr - } - - pub fn delete(pathname: &str) -> Result<(), ()> { - let pathname = realpath(pathname); - let dirname = dirname(&pathname); - let filename = filename(&pathname); - if let Some(mut dir) = Dir::open(dirname) { - dir.delete_entry(filename) - } else { - Err(()) - } - } } #[test_case] @@ -230,7 +231,7 @@ fn test_file_read() { let mut file = File::open("/test").unwrap(); let mut output = [0u8; 13]; - assert_eq!(file.read(&mut output), input.len()); + assert_eq!(file.read(&mut output), Ok(input.len())); assert_eq!(input, output); super::dismount(); } diff --git a/src/sys/fs/mod.rs b/src/sys/fs/mod.rs index 57c2d6342..e888b48a5 100644 --- a/src/sys/fs/mod.rs +++ b/src/sys/fs/mod.rs @@ -1,54 +1,95 @@ mod block; mod block_bitmap; mod block_device; +mod device; mod dir; mod dir_entry; mod file; mod read_dir; +pub use device::{Device, DeviceType}; pub use dir::Dir; +pub use dir_entry::FileStat; pub use file::{File, SeekFrom}; pub use block_device::{format_ata, format_mem, is_mounted, mount_ata, mount_mem, dismount}; +pub use crate::api::fs::{dirname, filename, realpath, FileIO}; use block_bitmap::BlockBitmap; +use dir_entry::DirEntry; -use crate::sys; -use alloc::format; -use alloc::string::String; +#[repr(u8)] +pub enum OpenFlag { + Read = 1, + Write = 2, + Create = 4, + Dir = 8, + Device = 16, +} + +impl OpenFlag { + fn is_set(self, flags: usize) -> bool { + flags & (self as usize) != 0 + } +} + +pub fn open(path: &str, flags: usize) -> Option { + if OpenFlag::Dir.is_set(flags) { + let res = Dir::open(path); + if res.is_none() && OpenFlag::Create.is_set(flags) { + Dir::create(path) + } else { + res + }.map(|r| Resource::Dir(r)) + } else if OpenFlag::Device.is_set(flags) { + let res = Device::open(path); + if res.is_none() && OpenFlag::Create.is_set(flags) { + Device::create(path) + } else { + res + }.map(|r| Resource::Device(r)) + } else { + let res = File::open(path); + if res.is_none() && OpenFlag::Create.is_set(flags) { + File::create(path) + } else { + res + }.map(|r| Resource::File(r)) + } +} + +pub fn stat(pathname: &str) -> Option { + DirEntry::open(pathname).map(|e| e.stat()) +} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum FileType { Dir = 0, File = 1, + Device = 2, } -pub fn dirname(pathname: &str) -> &str { - let n = pathname.len(); - let i = match pathname.rfind('/') { - Some(0) => 1, - Some(i) => i, - None => n, - }; - &pathname[0..i] +#[derive(Debug, Clone)] +pub enum Resource { + Dir(Dir), + File(File), + Device(Device), } -pub fn filename(pathname: &str) -> &str { - let n = pathname.len(); - let i = match pathname.rfind('/') { - Some(i) => i + 1, - None => 0, - }; - &pathname[i..n] -} +impl FileIO for Resource { + fn read(&mut self, buf: &mut [u8]) -> Result { + match self { + Resource::Dir(io) => io.read(buf), + Resource::File(io) => io.read(buf), + Resource::Device(io) => io.read(buf), + } + } -// Transform "foo.txt" into "/path/to/foo.txt" -pub fn realpath(pathname: &str) -> String { - if pathname.starts_with('/') { - pathname.into() - } else { - let dirname = sys::process::dir(); - let sep = if dirname.ends_with('/') { "" } else { "/" }; - format!("{}{}{}", dirname, sep, pathname) + fn write(&mut self, buf: &[u8]) -> Result { + match self { + Resource::Dir(io) => io.write(buf), + Resource::File(io) => io.write(buf), + Resource::Device(io) => io.write(buf), + } } } diff --git a/src/sys/fs/read_dir.rs b/src/sys/fs/read_dir.rs index ceb46f94d..6a07c15e0 100644 --- a/src/sys/fs/read_dir.rs +++ b/src/sys/fs/read_dir.rs @@ -74,6 +74,7 @@ impl Iterator for ReadDir { let entry_kind = match self.read_u8() { 0 => FileType::Dir, 1 => FileType::File, + 2 => FileType::Device, _ => { self.block_data_offset = offset; // Rewind the cursor break; diff --git a/src/sys/process.rs b/src/sys/process.rs index d7fd2ae0f..794a8e413 100644 --- a/src/sys/process.rs +++ b/src/sys/process.rs @@ -1,9 +1,15 @@ +use crate::sys::fs::{Resource, Device}; +use crate::sys::console::Console; use alloc::collections::btree_map::BTreeMap; use alloc::string::{String, ToString}; +use alloc::vec; +use alloc::vec::Vec; use core::sync::atomic::{AtomicUsize, Ordering}; use lazy_static::lazy_static; use spin::Mutex; +const MAX_FILE_HANDLES: usize = 1024; + lazy_static! { pub static ref PIDS: AtomicUsize = AtomicUsize::new(0); pub static ref PROCESS: Mutex = Mutex::new(Process::new("/", None)); // TODO @@ -14,6 +20,7 @@ pub struct Process { env: BTreeMap, dir: String, user: Option, + file_handles: Vec>, } impl Process { @@ -22,7 +29,11 @@ impl Process { let env = BTreeMap::new(); let dir = dir.to_string(); let user = user.map(String::from); - Self { id, env, dir, user } + let mut file_handles = vec![None; MAX_FILE_HANDLES]; + file_handles[0] = Some(Resource::Device(Device::Console(Console::new()))); + file_handles[1] = Some(Resource::Device(Device::Console(Console::new()))); + file_handles[2] = Some(Resource::Device(Device::Console(Console::new()))); + Self { id, env, dir, user, file_handles } } } @@ -57,3 +68,31 @@ pub fn set_dir(dir: &str) { pub fn set_user(user: &str) { PROCESS.lock().user = Some(user.into()) } + +pub fn create_file_handle(file: Resource) -> Result { + let min = 4; // The first 4 file handles are reserved + let max = MAX_FILE_HANDLES; + let proc = &mut *PROCESS.lock(); + for handle in min..max { + if proc.file_handles[handle].is_none() { + proc.file_handles[handle] = Some(file); + return Ok(handle); + } + } + Err(()) +} + +pub fn update_file_handle(handle: usize, file: Resource) { + let proc = &mut *PROCESS.lock(); + proc.file_handles[handle] = Some(file); +} + +pub fn delete_file_handle(handle: usize) { + let proc = &mut *PROCESS.lock(); + proc.file_handles[handle] = None; +} + +pub fn file_handle(handle: usize) -> Option { + let proc = &mut *PROCESS.lock(); + proc.file_handles[handle].clone() +} diff --git a/src/sys/random.rs b/src/sys/random.rs index 31f1d9529..177abd5e4 100644 --- a/src/sys/random.rs +++ b/src/sys/random.rs @@ -1,9 +1,33 @@ +use crate::sys::fs::FileIO; + #[cfg(not(debug_assertions))] use rand_chacha::ChaChaRng; #[cfg(not(debug_assertions))] use rand_core::{RngCore, SeedableRng}; use x86_64::instructions::random::RdRand; +#[derive(Debug, Clone)] +pub struct Random; + +impl Random { + pub fn new() -> Self { + Self {} + } +} + +impl FileIO for Random { + fn read(&mut self, buf: &mut [u8]) -> Result { + let n = buf.len(); + for i in 0..n { + buf[i] = get_u64() as u8; + } + Ok(n) + } + fn write(&mut self, _buf: &[u8]) -> Result { + unimplemented!(); + } +} + // FIXME: Compiling this with debug_assertions generate the following error: // LLVM ERROR: Do not know how to split the result of this operator! #[cfg(not(debug_assertions))] diff --git a/src/sys/syscall/mod.rs b/src/sys/syscall/mod.rs index 228800810..6688c275d 100644 --- a/src/sys/syscall/mod.rs +++ b/src/sys/syscall/mod.rs @@ -1,25 +1,57 @@ pub mod number; pub mod service; +use crate::sys::fs::FileStat; + /* * Dispatching system calls */ -pub fn dispatcher(n: usize, arg1: usize, _arg2: usize, _arg3: usize) -> usize { +pub fn dispatcher(n: usize, arg1: usize, arg2: usize, arg3: usize) -> usize { match n { number::SLEEP => { - // sleep(f64) service::sleep(f64::from_bits(arg1 as u64)); 0 } number::UPTIME => { - // uptime() -> f64 service::uptime().to_bits() as usize } number::REALTIME => { - // realtime() -> f64 service::realtime().to_bits() as usize } + number::STAT => { + let ptr = arg1 as *mut u8; + let len = arg2; + let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(ptr, len)) }; + let stat = unsafe { &mut *(arg3 as *mut FileStat) }; + service::stat(path, stat) as usize + } + number::OPEN => { + let ptr = arg1 as *mut u8; + let len = arg2; + let flags = arg3; + let path = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(ptr, len)) }; + service::open(path, flags) as usize + } + number::READ => { + let handle = arg1; + let ptr = arg2 as *mut u8; + let len = arg3; + let mut buf = unsafe { core::slice::from_raw_parts_mut(ptr, len) }; + service::read(handle, &mut buf) as usize + } + number::WRITE => { + let handle = arg1; + let ptr = arg2 as *mut u8; + let len = arg3; + let mut buf = unsafe { core::slice::from_raw_parts_mut(ptr, len) }; + service::write(handle, &mut buf) as usize + } + number::CLOSE => { + let handle = arg1; + service::close(handle); + 0 + } _ => { unimplemented!(); } diff --git a/src/sys/syscall/number.rs b/src/sys/syscall/number.rs index 2c0cb77fa..4e9c9c181 100644 --- a/src/sys/syscall/number.rs +++ b/src/sys/syscall/number.rs @@ -1,3 +1,8 @@ pub const SLEEP: usize = 0; pub const UPTIME: usize = 1; pub const REALTIME: usize = 2; +pub const OPEN: usize = 3; +pub const READ: usize = 4; +pub const WRITE: usize = 5; +pub const CLOSE: usize = 6; +pub const STAT: usize = 7; diff --git a/src/sys/syscall/service.rs b/src/sys/syscall/service.rs index ea6204d70..4edbb09b2 100644 --- a/src/sys/syscall/service.rs +++ b/src/sys/syscall/service.rs @@ -1,4 +1,6 @@ use crate::sys; +use crate::sys::fs::FileStat; +use crate::sys::fs::FileIO; pub fn sleep(seconds: f64) { unsafe { asm!("sti") }; // Restore interrupts @@ -13,3 +15,45 @@ pub fn uptime() -> f64 { pub fn realtime() -> f64 { sys::clock::realtime() } + +pub fn stat(path: &str, stat: &mut FileStat) -> isize { + if let Some(res) = sys::fs::stat(path) { + *stat = res; + 0 + } else { + -1 + } +} + +pub fn open(path: &str, flags: usize) -> isize { + if let Some(resource) = sys::fs::open(path, flags) { + if let Ok(handle) = sys::process::create_file_handle(resource) { + return handle as isize; + } + } + -1 +} + +pub fn read(handle: usize, buf: &mut [u8]) -> isize { + if let Some(mut file) = sys::process::file_handle(handle) { + if let Ok(bytes) = file.read(buf) { + sys::process::update_file_handle(handle, file); + return bytes as isize; + } + } + -1 +} + +pub fn write(handle: usize, buf: &mut [u8]) -> isize { + if let Some(mut file) = sys::process::file_handle(handle) { + if let Ok(bytes) = file.write(buf) { + sys::process::update_file_handle(handle, file); + return bytes as isize; + } + } + -1 +} + +pub fn close(handle: usize) { + sys::process::delete_file_handle(handle); +} diff --git a/src/sys/time.rs b/src/sys/time.rs index 66b6df194..726fd305e 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -30,7 +30,7 @@ pub fn last_rtc_update() -> usize { } pub fn halt() { - x86_64::instructions::hlt(); + x86_64::instructions::interrupts::enable_and_hlt(); } fn rdtsc() -> u64 { diff --git a/src/usr/copy.rs b/src/usr/copy.rs index cc5c918a2..f519aab80 100644 --- a/src/usr/copy.rs +++ b/src/usr/copy.rs @@ -1,5 +1,5 @@ -use crate::{sys, usr}; -use alloc::vec; +use crate::usr; +use crate::api::fs; pub fn main(args: &[&str]) -> usr::shell::ExitCode { if args.len() != 3 { @@ -15,21 +15,11 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { return usr::shell::ExitCode::CommandError; } - if let Some(mut source_file) = sys::fs::File::open(source) { - if let Some(mut dest_file) = sys::fs::File::create(dest) { - let mut buf = vec![0; source_file.size()]; - source_file.read(&mut buf); - match dest_file.write(&buf) { - Ok(_) => { - usr::shell::ExitCode::CommandSuccessful - }, - Err(()) => { - println!("Could not write to '{}'", dest); - usr::shell::ExitCode::CommandError - } - } + if let Ok(contents) = fs::read(source) { + if fs::write(dest, &contents).is_ok() { + usr::shell::ExitCode::CommandSuccessful } else { - println!("Permission denied to write to '{}'", dest); + println!("Could not write to '{}'", dest); usr::shell::ExitCode::CommandError } } else { diff --git a/src/usr/delete.rs b/src/usr/delete.rs index 00392a8e0..3f68b54c0 100644 --- a/src/usr/delete.rs +++ b/src/usr/delete.rs @@ -1,4 +1,5 @@ use crate::{sys, usr}; +use crate::api::fs; pub fn main(args: &[&str]) -> usr::shell::ExitCode { if args.len() != 2 { @@ -19,7 +20,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { } if let Some(dir) = sys::fs::Dir::open(pathname) { - if dir.read().count() == 0 { + if dir.entries().count() == 0 { if sys::fs::Dir::delete(pathname).is_ok() { usr::shell::ExitCode::CommandSuccessful } else { @@ -30,7 +31,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { println!("Directory '{}' not empty", pathname); usr::shell::ExitCode::CommandError } - } else if sys::fs::File::open(pathname).is_some() { + } else if fs::exists(pathname) { if sys::fs::File::delete(pathname).is_ok() { usr::shell::ExitCode::CommandSuccessful } else { diff --git a/src/usr/editor.rs b/src/usr/editor.rs index 10b4c57ba..6e8de47e5 100644 --- a/src/usr/editor.rs +++ b/src/usr/editor.rs @@ -1,5 +1,7 @@ use crate::{sys, usr}; +use crate::api::fs; use crate::api::console::Style; +use crate::api::io; use alloc::format; use alloc::string::{String, ToString}; use alloc::vec::Vec; @@ -20,7 +22,6 @@ struct EditorConfig { } pub struct Editor { - file: Option, pathname: String, lines: Vec, x: usize, @@ -39,23 +40,20 @@ impl Editor { let mut lines = Vec::new(); let config = EditorConfig { tab_size: 4 }; - let file = match sys::fs::File::open(pathname) { - Some(mut file) => { - let contents = file.read_to_string(); + match fs::read_to_string(pathname) { + Ok(contents) => { for line in contents.split('\n') { lines.push(line.into()); } - Some(file) }, - None => { + Err(_) => { lines.push(String::new()); - sys::fs::File::create(pathname) } }; let pathname = pathname.into(); - Self { file, pathname, lines, x, y, dx, dy, config } + Self { pathname, lines, x, y, dx, dy, config } } pub fn save(&mut self) -> usr::shell::ExitCode { @@ -68,9 +66,7 @@ impl Editor { } } - if let Some(file) = &mut self.file { - file.seek(sys::fs::SeekFrom::Start(0)).unwrap(); - file.write(contents.as_bytes()).unwrap(); + if fs::write(&self.pathname, contents.as_bytes()).is_ok() { let status = format!("Wrote {}L to '{}'", n, self.pathname); self.print_status(&status, "Yellow"); usr::shell::ExitCode::CommandSuccessful @@ -152,7 +148,7 @@ impl Editor { let mut escape = false; let mut csi = false; loop { - let c = sys::console::get_char(); + let c = io::stdin().read_char().unwrap_or('\0'); print!("\x1b[?25l"); // Disable cursor match c { '\x1B' => { // ESC diff --git a/src/usr/find.rs b/src/usr/find.rs index 0fa3080cf..bef495ef6 100644 --- a/src/usr/find.rs +++ b/src/usr/find.rs @@ -69,7 +69,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { fn print_matching_lines(path: &str, pattern: &str, state: &mut PrintingState) { if let Some(dir) = sys::fs::Dir::open(path) { state.is_recursive = true; - for file in dir.read() { + for file in dir.entries() { let file_path = format!("{}/{}", path, file.name()); if file.is_dir() { print_matching_lines(&file_path, pattern, state); @@ -77,7 +77,7 @@ fn print_matching_lines(path: &str, pattern: &str, state: &mut PrintingState) { print_matching_lines_in_file(&file_path, pattern, state); } } - } else if sys::fs::File::open(path).is_some() { + } else if fs::exists(path) { print_matching_lines_in_file(&path, pattern, state); } } diff --git a/src/usr/host.rs b/src/usr/host.rs index 38dca1c0e..621adc9f8 100644 --- a/src/usr/host.rs +++ b/src/usr/host.rs @@ -1,5 +1,6 @@ use crate::{sys, usr}; use crate::api::syscall; +use crate::api::random; use alloc::vec; use alloc::vec::Vec; use bit_field::BitField; @@ -59,7 +60,7 @@ impl Message { pub fn query(qname: &str, qtype: QueryType, qclass: QueryClass) -> Self { let mut datagram = Vec::new(); - let id = sys::random::get_u16(); + let id = random::get_u16(); for b in id.to_be_bytes().iter() { datagram.push(*b); // Transaction ID } @@ -125,7 +126,7 @@ pub fn resolve(name: &str) -> Result { let dns_port = 53; let server = IpEndpoint::new(dns_address, dns_port); - let local_port = 49152 + sys::random::get_u16() % 16384; + let local_port = 49152 + random::get_u16() % 16384; let client = IpEndpoint::new(IpAddress::Unspecified, local_port); let qname = name; diff --git a/src/usr/http.rs b/src/usr/http.rs index 034447898..4671bfcff 100644 --- a/src/usr/http.rs +++ b/src/usr/http.rs @@ -1,5 +1,6 @@ use crate::{sys, usr}; use crate::api::syscall; +use crate::api::random; use alloc::borrow::ToOwned; use alloc::string::{String, ToString}; use alloc::vec; @@ -133,7 +134,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { state = match state { State::Connect if !socket.is_active() => { - let local_port = 49152 + sys::random::get_u16() % 16384; + let local_port = 49152 + random::get_u16() % 16384; if is_verbose { println!("* Connecting to {}:{}", address, url.port); } diff --git a/src/usr/httpd.rs b/src/usr/httpd.rs index 3166c4267..60c7b2087 100644 --- a/src/usr/httpd.rs +++ b/src/usr/httpd.rs @@ -1,5 +1,6 @@ use crate::{sys, usr}; use crate::api::syscall; +use crate::api::fs; use crate::api::console::Style; use alloc::collections::vec_deque::VecDeque; use alloc::format; @@ -91,16 +92,16 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode { res.push_str(&format!("Location: {}\r\n", path.trim_end_matches('/'))); body = "

Moved Permanently

\r\n".to_string(); mime = "text/html"; - } else if let Some(mut file) = sys::fs::File::open(path) { + } else if let Ok(contents) = fs::read_to_string(path) { code = 200; res.push_str("HTTP/1.0 200 OK\r\n"); - body = file.read_to_string().replace("\n", "\r\n"); + body = contents.replace("\n", "\r\n"); mime = "text/plain"; } else if let Some(dir) = sys::fs::Dir::open(path) { code = 200; res.push_str("HTTP/1.0 200 OK\r\n"); body = format!("

Index of {}

\r\n", path); - let mut files: Vec<_> = dir.read().collect(); + let mut files: Vec<_> = dir.entries().collect(); files.sort_by_key(|f| f.name()); for file in files { let sep = if path == "/" { "" } else { "/" }; @@ -118,42 +119,30 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode { "PUT" => { if path.ends_with('/') { // Write directory let path = path.trim_end_matches('/'); - if sys::fs::Dir::open(path).is_some() { + if fs::exists(path) { code = 403; res.push_str("HTTP/1.0 403 Forbidden\r\n"); - } else if sys::fs::Dir::create(path).is_none() { - code = 500; - res.push_str("HTTP/1.0 500 Internal Server Error\r\n"); - } else { + } else if fs::create_dir(path).is_some() { code = 200; res.push_str("HTTP/1.0 200 OK\r\n"); + } else { + code = 500; + res.push_str("HTTP/1.0 500 Internal Server Error\r\n"); } } else { // Write file - let maybe_file = match sys::fs::File::open(path) { - Some(file) => Some(file), - None => sys::fs::File::create(path), - }; - match maybe_file { - Some(mut file) => { - if file.write(contents.as_bytes()).is_ok() { - code = 200; - res.push_str("HTTP/1.0 200 OK\r\n"); - } else { - code = 500; - res.push_str("HTTP/1.0 500 Internal Server Error\r\n"); - } - }, - None => { - code = 403; - res.push_str("HTTP/1.0 403 Forbidden\r\n"); - } + if fs::write(path, contents.as_bytes()).is_ok() { + code = 200; + res.push_str("HTTP/1.0 200 OK\r\n"); + } else { + code = 500; + res.push_str("HTTP/1.0 500 Internal Server Error\r\n"); } } body = "".to_string(); mime = "text/plain"; }, "DELETE" => { - if sys::fs::File::open(path).is_some() { + if fs::exists(path) { if sys::fs::File::delete(path).is_ok() { code = 200; res.push_str("HTTP/1.0 200 OK\r\n"); diff --git a/src/usr/install.rs b/src/usr/install.rs index 9a6ef8580..ad4215515 100644 --- a/src/usr/install.rs +++ b/src/usr/install.rs @@ -1,5 +1,8 @@ use crate::{sys, usr}; use crate::api::console::Style; +use crate::api::fs; +use crate::api::io; +use crate::api::syscall; use alloc::string::String; pub fn main(_args: &[&str]) -> usr::shell::ExitCode { @@ -9,7 +12,7 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode { println!(); print!("Proceed? [y/N] "); - if sys::console::get_line().trim() == "y" { + if io::stdin().read_line().trim() == "y" { println!(); if !sys::fs::is_mounted() { @@ -19,7 +22,7 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode { println!("{}Formatting disk ...{}", csi_color, csi_reset); print!("Enter path of disk to format: "); - let pathname = sys::console::get_line(); + let pathname = io::stdin().read_line(); let res = usr::disk::main(&["disk", "format", pathname.trim_end()]); if res == usr::shell::ExitCode::CommandError { return res; @@ -38,6 +41,20 @@ pub fn main(_args: &[&str]) -> usr::shell::ExitCode { create_dir("/usr"); // User directories create_dir("/var"); // Variables + create_dir("/dev/clk"); // Clocks + let pathname = "/dev/console"; + if syscall::stat(pathname).is_none() { + if fs::create_device(pathname, sys::fs::DeviceType::Console).is_some() { + println!("Created '{}'", pathname); + } + } + let pathname = "/dev/random"; + if syscall::stat(pathname).is_none() { + if fs::create_device(pathname, sys::fs::DeviceType::Random).is_some() { + println!("Created '{}'", pathname); + } + } + copy_file("/ini/boot.sh", include_bytes!("../../dsk/ini/boot.sh")); copy_file("/ini/banner.txt", include_bytes!("../../dsk/ini/banner.txt")); copy_file("/ini/version.txt", include_bytes!("../../dsk/ini/version.txt")); @@ -81,21 +98,19 @@ fn create_dir(pathname: &str) { } fn copy_file(pathname: &str, buf: &[u8]) { - if sys::fs::File::open(pathname).is_some() { + if fs::exists(pathname) { return; } - if let Some(mut file) = sys::fs::File::create(pathname) { - if pathname.ends_with(".txt") { - if let Ok(text) = String::from_utf8(buf.to_vec()) { - let text = text.replace("{x.x.x}", env!("CARGO_PKG_VERSION")); - file.write(text.as_bytes()).unwrap(); - } else { - file.write(buf).unwrap(); - } + if pathname.ends_with(".txt") { + if let Ok(text) = String::from_utf8(buf.to_vec()) { + let text = text.replace("{x.x.x}", env!("CARGO_PKG_VERSION")); + fs::write(pathname, text.as_bytes()).ok(); } else { - file.write(buf).unwrap(); + fs::write(pathname, buf).ok(); } - // TODO: add File::write_all to split buf if needed - println!("Copied '{}'", pathname); + } else { + fs::write(pathname, buf).ok(); } + // TODO: add File::write_all to split buf if needed + println!("Copied '{}'", pathname); } diff --git a/src/usr/lisp.rs b/src/usr/lisp.rs index f8a005643..67a0356cc 100644 --- a/src/usr/lisp.rs +++ b/src/usr/lisp.rs @@ -1,4 +1,4 @@ -use crate::{sys, usr}; +use crate::{api, usr}; use crate::api::console::Style; use crate::api::prompt::Prompt; use alloc::string::ToString; @@ -547,11 +547,11 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { }, 2 => { let pathname = args[1]; - if let Some(mut file) = sys::fs::File::open(pathname) { + if let Ok(code) = api::fs::read_to_string(pathname) { let mut block = String::new(); let mut opened = 0; let mut closed = 0; - for line in file.read_to_string().split('\n') { + for line in code.split('\n') { let line = strip_comments(line); if !line.is_empty() { opened += line.matches('(').count(); diff --git a/src/usr/list.rs b/src/usr/list.rs index 38eadcc55..00cfa11c5 100644 --- a/src/usr/list.rs +++ b/src/usr/list.rs @@ -36,7 +36,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { } if let Some(dir) = sys::fs::Dir::open(path) { - let mut files: Vec<_> = dir.read().collect(); + let mut files: Vec<_> = dir.entries().collect(); match sort { "name" => files.sort_by_key(|f| f.name()), @@ -54,12 +54,19 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { } let width = max_size.to_string().len(); - let csi_color = Style::color("Blue"); + let csi_dir_color = Style::color("Blue"); + let csi_dev_color = Style::color("Yellow"); let csi_reset = Style::reset(); for file in files { let date = OffsetDateTime::from_unix_timestamp(file.time() as i64); - let color = if file.is_dir() { csi_color } else { csi_reset }; + let color = if file.is_dir() { + csi_dir_color + } else if file.is_device() { + csi_dev_color + } else { + csi_reset + }; println!("{:width$} {} {}{}{}", file.size(), date.format("%F %H:%M:%S"), color, file.name(), csi_reset, width = width); } usr::shell::ExitCode::CommandSuccessful diff --git a/src/usr/read.rs b/src/usr/read.rs index 089bc7be4..31e4aa471 100644 --- a/src/usr/read.rs +++ b/src/usr/read.rs @@ -1,4 +1,5 @@ -use crate::{sys, usr}; +use crate::{api, sys, usr}; +use crate::api::fs; use crate::api::syscall; use crate::sys::cmos::CMOS; use alloc::borrow::ToOwned; @@ -29,19 +30,6 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { println!("{:.6}", syscall::uptime()); usr::shell::ExitCode::CommandSuccessful }, - "/dev/random" => { - loop { - // Generate ASCII graphic chars - let i = (sys::random::get_u32() % (0x72 - 0x20)) + 0x20; - if let Some(c) = core::char::from_u32(i) { - print!("{}", c); - } - if sys::console::end_of_text() { - println!(); - return usr::shell::ExitCode::CommandSuccessful; - } - } - }, _ => { if pathname.starts_with("/net/") { // Examples: @@ -75,11 +63,31 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { } } } - } else if pathname.ends_with('/') { - usr::list::main(args) - } else if let Some(mut file) = sys::fs::File::open(pathname) { - print!("{}", file.read_to_string()); - usr::shell::ExitCode::CommandSuccessful + } else if let Some(stat) = syscall::stat(pathname) { + if stat.is_file() { + if let Ok(contents) = api::fs::read_to_string(pathname) { + print!("{}", contents); + usr::shell::ExitCode::CommandSuccessful + } else { + println!("Could not read '{}'", pathname); + usr::shell::ExitCode::CommandError + } + } else if stat.is_dir() { + usr::list::main(args) + } else if stat.is_device() { + loop { + if let Ok(bytes) = fs::read(pathname) { + print!("{}", bytes[0] as char); + } + if sys::console::end_of_text() { + println!(); + return usr::shell::ExitCode::CommandSuccessful; + } + } + } else { + println!("Could not read type of '{}'", pathname); + usr::shell::ExitCode::CommandError + } } else { println!("File not found '{}'", pathname); usr::shell::ExitCode::CommandError diff --git a/src/usr/shell.rs b/src/usr/shell.rs index c05723f04..779a1c791 100644 --- a/src/usr/shell.rs +++ b/src/usr/shell.rs @@ -1,5 +1,6 @@ +use crate::{api, sys, usr}; +use crate::api::fs; use crate::api::prompt::Prompt; -use crate::{sys, usr}; use crate::api::console::Style; use alloc::format; use alloc::vec::Vec; @@ -34,12 +35,12 @@ fn shell_completer(line: &str) -> Vec { } } } else { // Autocomplete path - let pathname = sys::fs::realpath(args[i]); - let dirname = sys::fs::dirname(&pathname); - let filename = sys::fs::filename(&pathname); + let pathname = fs::realpath(args[i]); + let dirname = fs::dirname(&pathname); + let filename = fs::filename(&pathname); let sep = if dirname.ends_with('/') { "" } else { "/" }; if let Some(dir) = sys::fs::Dir::open(dirname) { - for entry in dir.read() { + for entry in dir.entries() { let name = entry.name(); if name.starts_with(filename) { let end = if entry.is_dir() { "/" } else { "" }; @@ -104,7 +105,7 @@ fn change_dir(args: &[&str]) -> ExitCode { ExitCode::CommandSuccessful }, 2 => { - let mut pathname = sys::fs::realpath(args[1]); + let mut pathname = fs::realpath(args[1]); if pathname.len() > 1 { pathname = pathname.trim_end_matches('/').into(); } @@ -221,8 +222,8 @@ pub fn main(args: &[&str]) -> ExitCode { }, 2 => { let pathname = args[1]; - if let Some(mut file) = sys::fs::File::open(pathname) { - for line in file.read_to_string().split('\n') { + if let Ok(contents) = api::fs::read_to_string(pathname) { + for line in contents.split('\n') { if !line.is_empty() { exec(line); } diff --git a/src/usr/tcp.rs b/src/usr/tcp.rs index a09b1763a..85b2e85d8 100644 --- a/src/usr/tcp.rs +++ b/src/usr/tcp.rs @@ -1,5 +1,6 @@ use crate::{sys, usr}; use crate::api::syscall; +use crate::api::random; use alloc::borrow::ToOwned; use alloc::string::{String, ToString}; use alloc::vec; @@ -94,7 +95,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { state = match state { State::Connect if !socket.is_active() => { - let local_port = 49152 + sys::random::get_u16() % 16384; + let local_port = 49152 + random::get_u16() % 16384; println!("Connecting to {}:{}", address, port); if socket.connect((address, port), local_port).is_err() { println!("Could not connect to {}:{}", address, port); diff --git a/src/usr/user.rs b/src/usr/user.rs index f75c25af4..b23b9e68e 100644 --- a/src/usr/user.rs +++ b/src/usr/user.rs @@ -1,8 +1,11 @@ -use crate::{sys, usr}; +use crate::{api, sys, usr}; +use crate::api::fs; +use crate::api::io; +use crate::api::random; use crate::api::syscall; use alloc::collections::btree_map::BTreeMap; use alloc::format; -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::vec::Vec; use core::convert::TryInto; use core::str; @@ -19,9 +22,9 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { let username: String = if args.len() == 2 { print!("Username: "); - sys::console::get_line().trim_end().into() + io::stdin().read_line().trim_end().to_string() } else { - args[2].into() + args[2].to_string() }; match args[1] { @@ -48,10 +51,9 @@ pub fn login(username: &str) -> usr::shell::ExitCode { Some(hash) => { print!("Password: "); sys::console::disable_echo(); - let mut password = sys::console::get_line(); + let password = io::stdin().read_line().trim_end().to_string(); sys::console::enable_echo(); println!(); - password.pop(); if !check(&password, &hash) { println!(); syscall::sleep(1.0); @@ -86,10 +88,9 @@ pub fn create(username: &str) -> usr::shell::ExitCode { print!("Password: "); sys::console::disable_echo(); - let mut password = sys::console::get_line(); + let password = io::stdin().read_line().trim_end().to_string(); sys::console::enable_echo(); println!(); - password.pop(); if password.is_empty() { return usr::shell::ExitCode::CommandError; @@ -97,10 +98,9 @@ pub fn create(username: &str) -> usr::shell::ExitCode { print!("Confirm: "); sys::console::disable_echo(); - let mut confirm = sys::console::get_line(); + let confirm = io::stdin().read_line().trim_end().to_string(); sys::console::enable_echo(); println!(); - confirm.pop(); if password != confirm { println!("Password confirmation failed"); @@ -148,7 +148,7 @@ pub fn hash(password: &str) -> String { // Generating salt for i in 0..2 { - let num = sys::random::get_u64(); + let num = random::get_u64(); let buf = num.to_be_bytes(); let n = buf.len(); for j in 0..n { @@ -173,8 +173,8 @@ pub fn hash(password: &str) -> String { fn read_hashed_passwords() -> BTreeMap { let mut hashed_passwords = BTreeMap::new(); - if let Some(mut file) = sys::fs::File::open(PASSWORDS) { - for line in file.read_to_string().split('\n') { + if let Ok(contents) = api::fs::read_to_string(PASSWORDS) { + for line in contents.split('\n') { let mut rows = line.split(','); if let Some(username) = rows.next() { if let Some(hash) = rows.next() { @@ -195,17 +195,10 @@ fn save_hashed_password(username: &str, hash: &str) -> Result { hashed_passwords.remove(username); hashed_passwords.insert(username.into(), hash.into()); - let mut file = match sys::fs::File::open(PASSWORDS) { - None => match sys::fs::File::create(PASSWORDS) { - None => return Err(()), - Some(file) => file, - }, - Some(file) => file, - }; - - let mut contents = String::new(); + let mut csv = String::new(); for (u, h) in hashed_passwords { - contents.push_str(&format!("{},{}\n", u, h)); + csv.push_str(&format!("{},{}\n", u, h)); } - file.write(contents.as_bytes()) + + fs::write(PASSWORDS, csv.as_bytes()) } diff --git a/src/usr/vga.rs b/src/usr/vga.rs index 23572bf8f..36fbd4bce 100644 --- a/src/usr/vga.rs +++ b/src/usr/vga.rs @@ -1,6 +1,6 @@ use crate::{api, sys, usr}; use crate::api::vga::palette; -use alloc::vec; +use crate::api::fs; pub fn main(args: &[&str]) -> usr::shell::ExitCode { if args.len() == 1 { @@ -10,9 +10,7 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { match args[1] { "set" => { if args.len() == 4 && args[2] == "font" { - if let Some(mut file) = sys::fs::File::open(args[3]) { - let mut buf = vec![0; file.size()]; - file.read(&mut buf); + if let Ok(buf) = fs::read(args[3]) { if let Ok(font) = api::font::from_bytes(&buf) { sys::vga::set_font(&font); } else { @@ -21,8 +19,8 @@ pub fn main(args: &[&str]) -> usr::shell::ExitCode { } } } else if args.len() == 4 && args[2] == "palette" { - if let Some(mut file) = sys::fs::File::open(args[3]) { - if let Ok(palette) = palette::from_csv(&file.read_to_string()) { + if let Ok(csv) = fs::read_to_string(args[3]) { + if let Ok(palette) = palette::from_csv(&csv) { sys::vga::set_palette(palette); // TODO: Instead of calling a kernel function we could // use the following ANSI OSC command to set a palette: