diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2024-08-06 19:11:46 +1200 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2024-08-06 19:12:43 +1200 |
commit | 65b53003e8de9543ba25a3b3d3cace399b92dc1d (patch) | |
tree | 90ba625f4522553136e55b452c9f2f91f29d9753 /src/devices/file.rs | |
parent | 406b4d3ea56a19c0389b4214a7d8d15bf3028eb8 (diff) | |
download | bedrock-pc-65b53003e8de9543ba25a3b3d3cace399b92dc1d.zip |
Refactor the file device
The major feature of this refactor is the creation of BedrockFilePath,
a type which can be losslessly converted into an operating-system
specific path or a Bedrock-style byte path. It also prevents file paths
which can't be represented as Bedrock-style byte paths from being
constructed, and allows the use of a base directory which acts as an
inescapable sandbox.
BedrockFilePath will support the creation of a virtual root directory
on Windows which will allow navigation between drive letters via the
standard hierarchical navigation ports.
This commit has been tested on Linux, but not yet Windows. Further work
is expected before the new code will work on Windows.
Diffstat (limited to 'src/devices/file.rs')
-rw-r--r-- | src/devices/file.rs | 201 |
1 files changed, 82 insertions, 119 deletions
diff --git a/src/devices/file.rs b/src/devices/file.rs index d53db0b..26e14da 100644 --- a/src/devices/file.rs +++ b/src/devices/file.rs @@ -1,42 +1,23 @@ +mod bedrock_file_path; mod buffered_file; mod circular_path_buffer; -mod directory_entry; +mod directory_child; mod directory_listing; mod entry; mod operations; +pub use bedrock_file_path::*; pub use buffered_file::*; pub use circular_path_buffer::*; -pub use directory_entry::*; +pub use directory_child::*; pub use directory_listing::*; pub use entry::*; use operations::*; -#[cfg(target_family = "unix")] -use std::os::unix::ffi::OsStrExt; -use std::fs::{OpenOptions, metadata}; use std::path::{Component, Path, PathBuf}; -fn is_blank_path(path: &Path) -> bool { - path == PathBuf::new() -} - -fn bytes_to_path(bytes: &[u8]) -> PathBuf { - #[cfg(target_family = "unix")] - let os_string: std::ffi::OsString = { - std::os::unix::ffi::OsStringExt::from_vec(Vec::from(bytes)) - }; - #[cfg(target_family = "windows")] - let os_string: std::ffi::OsString = { - let wide: Vec<u16> = bytes.iter().map(|b| *b as u16).collect(); - std::os::windows::ffi::OsStringExt::from_wide(&wide) - }; - os_string.into() -} pub struct FileDevice { - /// The path to which the file device is confined. Files and directories - /// outside of this directory cannot be accessed. pub base_path: PathBuf, pub default_path: PathBuf, @@ -44,108 +25,104 @@ pub struct FileDevice { pub move_buffer: CircularPathBuffer, pub name_buffer: CircularPathBuffer, - pub entry: Option<(Entry, PathBuf)>, + pub entry: Option<(Entry, BedrockFilePath)>, - pub op_success: bool, - pub new_pointer: u32, - pub new_length: u32, + pub success: bool, + pub pointer: u32, + pub length: u32, + pub enable_read: bool, + pub enable_write: bool, pub enable_create: bool, pub enable_move: bool, pub enable_delete: bool, - pub enable_read: bool, - pub enable_write: bool, } impl FileDevice { pub fn new() -> Self { + #[cfg(target_family = "unix")] + let default_base: PathBuf = PathBuf::from("/"); + #[cfg(target_family = "windows")] + let default_base: PathBuf = PathBuf::from(""); + Self { - base_path: PathBuf::from("/"), + base_path: default_base, default_path: match std::env::current_dir() { Ok(dir) => PathBuf::from(dir), - Err(_) => PathBuf::from("/"), + Err(_) => PathBuf::from(""), }, + open_buffer: CircularPathBuffer::new(), move_buffer: CircularPathBuffer::new(), name_buffer: CircularPathBuffer::new(), entry: None, - op_success: false, - new_pointer: 0, - new_length: 0, + success: false, + pointer: 0, + length: 0, + enable_read: true, + enable_write: true, enable_create: true, enable_move: true, enable_delete: false, - enable_read: true, - enable_write: true, } } + /// Commit any pending writes to the currently-open file. pub fn flush_entry(&mut self) { if let Some((Entry::File(buffered_file), _)) = &mut self.entry { buffered_file.flush(); } } + /// Safely close the currently-open entry, cleaning up entry variables. pub fn close_entry(&mut self) { self.open_buffer.clear(); self.move_buffer.clear(); self.name_buffer.clear(); self.flush_entry(); self.entry = None; - self.new_pointer = 0; - self.new_length = 0; + self.pointer = 0; + self.length = 0; } + /// Process a byte received from the OPEN port. pub fn write_to_open_port(&mut self, byte: u8) { - if let Some(relative_path) = self.open_buffer.push_byte(byte) { + if let Some(buffer) = self.open_buffer.push_byte(byte) { self.close_entry(); - if !is_blank_path(&relative_path) { - if let Ok(path) = self.attach_base(&relative_path) { - let _ = self.open_entry(&path); - }; + if let Some(path) = BedrockFilePath::from_buffer(buffer, &self.base_path) { + self.success = self.open_entry(path).is_ok(); } } } - pub fn set_name_pointer(&mut self, byte: u8) { - self.name_buffer.set_pointer(byte); - } - - /// Open the entry at the given path. - pub fn open_entry(&mut self, path: &Path) -> Result<(), ()> { - macro_rules! raise_on_err { - ($res:expr) => {match $res {Ok(v)=>v, Err(_)=>return Err(())} } } - - if !path.starts_with(&self.base_path) { return Err(()); } - let metadata = raise_on_err!(metadata(&path)); + /// Opens the entry at the given path. + pub fn open_entry(&mut self, path: BedrockFilePath) -> Result<(), ()> { + macro_rules! unres { + ($result:expr) => { match $result {Ok(v)=>v,Err(_)=>return Err(())} }; + } + let absolute_path = path.as_path(); + let metadata = unres!(std::fs::metadata(&absolute_path)); if metadata.is_file() { - let open_result = OpenOptions::new() + let open_result = std::fs::OpenOptions::new() .read(self.enable_read) .write(self.enable_write) - .open(&path); + .open(&absolute_path); + // Keep the current entry open if we can't open the new path. if let Ok(file) = open_result { self.close_entry(); - let file_entry = Entry::File(BufferedFile::new(file)); - self.entry = Some((file_entry, path.to_owned())); - let relative = remove_base(&path, &self.base_path).unwrap(); - #[cfg(target_family = "unix")] - self.name_buffer.populate(relative.as_os_str().as_bytes()); - #[cfg(target_family = "windows")] - self.name_buffer.populate(relative.as_os_str().as_encoded_bytes()); + self.name_buffer.populate(path.as_buffer()); + self.entry = Some((Entry::File(BufferedFile::new(file)), path)); return Ok(()); }; } else if metadata.is_dir() { - if let Ok(listing) = DirectoryListing::from_path(&path, &self.base_path) { - let dir_entry = Entry::Directory(listing); - self.entry = Some((dir_entry, path.to_owned())); - let relative = remove_base(&path, &self.base_path).unwrap(); - #[cfg(target_family = "unix")] - self.name_buffer.populate(relative.as_os_str().as_bytes()); - #[cfg(target_family = "windows")] - self.name_buffer.populate(relative.as_os_str().as_encoded_bytes()); + // Keep the current entry open if we can't open the new path. + if let Some(listing) = path.directory_listing() { + self.close_entry(); + self.name_buffer.populate(path.as_buffer()); + self.entry = Some((Entry::Directory(listing), path)); return Ok(()); }; }; @@ -153,58 +130,60 @@ impl FileDevice { } pub fn write_to_move_port(&mut self, byte: u8) { - if let Some(dest) = self.move_buffer.push_byte(byte) { + if let Some(buffer) = self.move_buffer.push_byte(byte) { + let blank_destination = buffer[0] == 0x00; + let destination = BedrockFilePath::from_buffer(buffer, &self.base_path); + self.success = false; + if let Some((_, source)) = &self.entry { - if is_blank_path(&dest) { - match self.enable_delete { - true => self.op_success = delete_entry(&source), - false => self.op_success = false, + if blank_destination { + if self.enable_delete { + self.success = delete_entry(&source.as_path()); } - } else if let Ok(destination) = self.attach_base(&dest) { - match self.enable_move { - true => self.op_success = move_entry(&source, &destination), - false => self.op_success = false, + } else if let Some(dest) = destination { + if self.enable_move { + self.success = move_entry(&source.as_path(), &dest.as_path()); } } - } else { - if is_blank_path(&dest) { - self.op_success = false; - } else if let Ok(destination) = self.attach_base(&dest) { - match self.enable_create { - true => self.op_success = create_file(&destination), - false => self.op_success = false, - } + } else if let Some(dest) = destination { + if self.enable_create { + self.success = create_file(&dest.as_path()); } } + self.close_entry(); } } /// Attempt to open the parent directory of the current entry. pub fn ascend_to_parent(&mut self) { + self.success = false; if let Some((_, path)) = &self.entry { if let Some(parent_path) = path.parent() { - self.op_success = self.open_entry(&parent_path.to_owned()).is_ok(); - } else { - self.op_success = false; + self.success = self.open_entry(parent_path).is_ok(); } } else { - self.op_success = self.open_entry(&self.default_path.to_owned()).is_ok(); + if let Some(default) = BedrockFilePath::from_path(&self.default_path, &self.base_path) { + self.success = self.open_entry(default).is_ok(); + } } } - /// Attempt to open the currently-selected child. + /// Attempt to open the currently-selected child of the current directory. pub fn descend_to_child(&mut self) { + self.success = false; if let Some((Entry::Directory(listing), _)) = &self.entry { if let Some(child_path) = listing.child_path() { - if let Ok(child_path) = self.attach_base(&child_path) { - self.op_success = self.open_entry(&child_path).is_ok(); - } + self.success = self.open_entry(child_path).is_ok(); }; } } - /// Return true if the currently-open entry is a directory. + pub fn set_name_pointer(&mut self, value: u8) { + self.name_buffer.set_pointer(value); + } + + /// Returns true if the currently-open entry is a directory. pub fn entry_type(&self) -> bool { match self.entry { Some((Entry::Directory(_), _)) => true, @@ -212,7 +191,7 @@ impl FileDevice { } } - /// Return true if the currently-selected child is a directory. + /// Reads a byte from the name buffer of the currently-selected child. pub fn read_child_name(&mut self) -> u8 { if let Some((Entry::Directory(listing), _)) = &mut self.entry { listing.child_name().read_byte() @@ -227,7 +206,7 @@ impl FileDevice { } } - /// Return true if the currently-selected child is a directory. + /// Returns true if the currently-selected child is a directory. pub fn child_type(&self) -> bool { if let Some((Entry::Directory(listing), _)) = &self.entry { match listing.child_type() { @@ -240,6 +219,7 @@ impl FileDevice { } } + /// Reads a byte from the currently-open file. pub fn read_byte(&mut self) -> u8 { match &mut self.entry { Some((Entry::File(buffered_file), _)) => buffered_file.read_byte(), @@ -247,6 +227,7 @@ impl FileDevice { } } + /// Writes a byte to the currently-open file. pub fn write_byte(&mut self, byte: u8) { match &mut self.entry { Some((Entry::File(buffered_file), _)) => buffered_file.write_byte(byte), @@ -263,7 +244,7 @@ impl FileDevice { } pub fn commit_pointer(&mut self) { - let pointer = std::mem::take(&mut self.new_pointer); + let pointer = std::mem::take(&mut self.pointer); match &mut self.entry { Some((Entry::File(buffered_file), _)) => buffered_file.set_pointer(pointer), Some((Entry::Directory(listing), _)) => listing.set_selected(pointer), @@ -280,30 +261,12 @@ impl FileDevice { } pub fn commit_length(&mut self) { - let length = std::mem::take(&mut self.new_length); + let length = std::mem::take(&mut self.length); match &mut self.entry { Some((Entry::File(buffered_file), _)) => buffered_file.set_length(length), _ => (), } } - - fn attach_base(&self, relative_path: &Path) -> Result<PathBuf, ()> { - let mut full_path = self.base_path.clone(); - let mut has_root = false; - for component in relative_path.components() { - match component { - Component::Normal(s) => full_path.push(s), - Component::ParentDir => return Err(()), - Component::CurDir => continue, - Component::RootDir => has_root = true, - Component::Prefix(_) => continue, - } - }; - match has_root { - true => Ok(full_path), - false => Err(()) - } - } } impl Drop for FileDevice { |