From 4d01499903c63bb1354dd6d1ff8e9d99033212b9 Mon Sep 17 00:00:00 2001 From: schoettner Date: Sun, 5 Jan 2025 14:11:16 +0100 Subject: [PATCH] Basic naming service now working, including a tmpfs; new syscals: Open, Read, Write, Seek, Close, Touch, Readdir, Cwd, Cd --- os/kernel/Cargo.toml | 2 + os/kernel/src/naming/api.rs | 301 +++++++++++++++----- os/kernel/src/naming/directory.rs | 155 ---------- os/kernel/src/naming/file.rs | 139 --------- os/kernel/src/naming/lookup.rs | 72 +++++ os/kernel/src/naming/main.rs | 102 ------- os/kernel/src/naming/mod.rs | 9 +- os/kernel/src/naming/open_objects.rs | 241 +++++++++++++--- os/kernel/src/naming/stat.rs | 49 ++-- os/kernel/src/naming/tmpfs.rs | 206 ++++++++++++++ os/kernel/src/naming/traits.rs | 181 ++++++------ os/kernel/src/syscall/sys_naming.rs | 101 ++++++- os/kernel/src/syscall/syscall_dispatcher.rs | 15 +- os/library/naming/Cargo.toml | 1 + os/library/naming/src/lib.rs | 148 +++++++++- os/library/naming/src/shared_types.rs | 72 +++++ os/library/syscall/src/lib.rs | 15 +- os/library/syscall/src/return_vals.rs | 42 ++- 18 files changed, 1149 insertions(+), 702 deletions(-) delete mode 100644 os/kernel/src/naming/directory.rs delete mode 100644 os/kernel/src/naming/file.rs create mode 100644 os/kernel/src/naming/lookup.rs delete mode 100644 os/kernel/src/naming/main.rs create mode 100644 os/kernel/src/naming/tmpfs.rs create mode 100644 os/library/naming/src/shared_types.rs diff --git a/os/kernel/Cargo.toml b/os/kernel/Cargo.toml index 083250d..6a7daf8 100644 --- a/os/kernel/Cargo.toml +++ b/os/kernel/Cargo.toml @@ -14,6 +14,7 @@ path = "src/lib.rs" graphic = { path = "../library/graphic" } stream = { path = "../library/stream" } syscall = { path = "../library/syscall" } +naming = { path = "../library/naming" } # External depencies spin = "0.9.8" @@ -38,6 +39,7 @@ pci_types = "0.10.0" bitflags = "2.6.0" smoltcp = { version = "0.11.0", default-features = false, features = ["alloc", "log", "medium-ethernet", "proto-ipv4", "socket-udp"] } mbrs = { version = "0.3.1", default-features = false, features = ["no-std"] } +num_enum = { version = "0.7", default-features = false } [build-dependencies] built = { version = "0.7.5", features = ["chrono", "git2"] } diff --git a/os/kernel/src/naming/api.rs b/os/kernel/src/naming/api.rs index 1cffd60..afcf8dd 100644 --- a/os/kernel/src/naming/api.rs +++ b/os/kernel/src/naming/api.rs @@ -1,51 +1,54 @@ /* ╔═════════════════════════════════════════════════════════════════════════╗ ║ Module: api ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Descr.: Public interface of the name service (ns): ║ - ║ - mkdir: create a directory with all sub directories ║ + ║ Descr.: Public interface of the naming service: ║ + ║ - init: init ns, called once ║ ║ - open: open a named object ║ ║ - read: read bytes from an open object ║ ║ - write: write bytes into an open object ║ ║ - seek: set file pointer (for files) ║ - ║ - init: init ns, called once ║ - ║ - dump: print all entries on the screen (for debugging) ║ + ║ - mkdir: create a directory ║ + ║ - touch: create a file ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, Univ. Duesseldorf, 9.9.2024 ║ + ║ Author: Michael Schoettner, Univ. Duesseldorf, 30.12.2024 ║ ╚═════════════════════════════════════════════════════════════════════════╝ */ +use alloc::string::{String, ToString}; +use alloc::sync::Arc; +use alloc::vec::Vec; +use log::info; +use spin::{Mutex, Once}; -use alloc::string::String; -use syscall::return_vals::{OpenOptions, SeekOrigin, convert_syscall_result_to_ret_code}; +use super::traits::FileSystem; +use super::lookup; +use super::open_objects; +use super::stat::Mode; +use super::tmpfs; -use crate::naming::main; -use crate::naming::main::ns_get; -use crate::naming::open_objects::ns_get_oot; +use naming::shared_types::{OpenOptions, RawDirent, SeekOrigin}; +use syscall::return_vals::Errno; +// root of naming service +pub(super) static ROOT: Once> = Once::new(); -/// -/// Description: -/// Init function of NS. Must be called once before using it. -/// -pub fn init() { - main::init(); -} +// current working directory +static CWD: Mutex = Mutex::new(String::new()); /// -/// Description: Create a directory (including sub directories) for the given path +/// Description: Initilize the naming service (must be called once before using it) /// -/// Parameters: `path` The path -/// -/// Return: `SyscallResult` -/// -pub fn mkdir(path: &String) -> isize { - let result = ns_get().mkdir(path); - let sysret; - match result { - Ok(()) => sysret = Ok(0), // Convert the success case to a meaningful u64, e.g., 0 - Err(e) => sysret = Err(e), // Forward the error directly - } - return convert_syscall_result_to_ret_code(sysret); +pub fn init() { + // Initialize ROOT with TmpFs + ROOT.call_once(|| { + let tmpfs = tmpfs::TmpFs::new(); + Arc::new(tmpfs) + }); + open_objects::open_object_table_init(); + let mut cwd = CWD.lock(); + *cwd = "/".to_string(); + info!("naming service initialized"); + // test::running_tests(); } /// @@ -57,24 +60,15 @@ pub fn mkdir(path: &String) -> isize { /// /// Return: `SyscallResult` /// -pub fn open(path: &String, flags: OpenOptions) -> isize { - let res = ns_get().open(path, flags); - let sysret; - match res { - Ok(fptr) => { - sysret = ns_get_oot().lock().create_new_handle_for_filepointer(fptr); - }, - Err(e) => sysret = Err(e), - } - return convert_syscall_result_to_ret_code(sysret); - -/* match res { - Ok(fptr) => ns_get_oot().lock().create_new_handle_for_filepointer(fptr), - Err(e) => Err(e), - } - */ +pub fn open(path: &String, flags: OpenOptions) -> Result { + open_objects::open(path, flags).or_else(|e| { + if flags.contains(OpenOptions::CREATE) { + touch(path).and_then(|_| open_objects::open(path, flags)) + } else { + Err(e) + } + }) } - /// /// Description: \ /// Write bytes from the given buffer into the file (at the current position). \ @@ -86,13 +80,23 @@ pub fn open(path: &String, flags: OpenOptions) -> isize { /// /// Return: `Ok(#bytes written)` or `Err(Errno)` /// -pub fn write(fh: usize, buf: &[u8]) -> isize { - let sysret; - match ns_get_oot().lock().get(fh) { - Ok(fptr) => sysret = fptr.write(buf), - Err(e) => sysret = Err(e), - } - return convert_syscall_result_to_ret_code(sysret); +pub fn write(fh: usize, buf: &[u8]) -> Result { + open_objects::write(fh, &buf) +} + +/// +/// Description: \ +/// Read bytes from the file (from current position) into the given buffer. \ +/// The number of bytes to be read is determined by the buffer size +/// +/// Parameters: \ +/// `fh` file handle \ +/// `buf` buffer to copy file bytes into \ +/// +/// Return: `Ok(#bytes read)` or `Err(errno)` +/// +pub fn read(fh: usize, buf: &mut [u8]) -> Result { + open_objects::read(fh, buf) } /// @@ -103,43 +107,186 @@ pub fn write(fh: usize, buf: &[u8]) -> isize { /// `offset` offset in bytes \ /// `origin` point of origin /// -/// Return: `Ok(size in bytes)` or `Err(errno)` +/// Return: `Ok(0)` or `Err(errno)` +/// +pub fn seek(fh: usize, offset: usize, origin: SeekOrigin) -> Result { + open_objects::seek(fh, offset, origin) +} + +/// +/// Description: Close an open object \ +/// Return: `Ok(0)` or `Err(errno)` /// -pub fn seek(fh: usize, offset: usize, origin: SeekOrigin) -> isize { - let sysret; - match ns_get_oot().lock().get(fh) { - Ok(fptr) => sysret = fptr.seek(offset, origin), - Err(e) => sysret = Err(e), +pub fn close(fh: usize) -> Result { + open_objects::close(fh) +} + +/// +/// Description: Create a directory for the given path \ +/// Parameters: `path` absolute path \ +/// Return: `Ok(0)` or `Err(errno)` +/// +pub fn mkdir(path: &String) -> Result { + // Split the path into components + let mut components: Vec<&str> = path.split("/").collect(); + + // Remove the last component (the name of the new directory) + let new_dir_name = components.pop(); + + // We need parent directory to create the new directory + let parent_dir; + if components.len() == 1 { + parent_dir = "/".to_string(); + } else { + parent_dir = components.join("/"); // Joins the remaining components + } + + // Safely lookup the parent directory and create the new file + let result = lookup::lookup_dir(&parent_dir) + .and_then(|dir| { + new_dir_name + .ok_or(Errno::EINVAL) // Handle missing file name + .and_then(|name| dir.create_dir(name, Mode::new(0))) // Create the file + }) + .map(|_| 0); // Convert the success result to 0 + + match result { + Ok(_) => Ok(0), // Successfully created the file + Err(_) => { + // Handle the error here (e.g., logging or returning the error code) + Err(Errno::ENOTDIR) + } + } +} + +/// +/// Description: Create a new empty file \ +/// Parameters: `path` absolute path \ +/// Return: `0` if successful, otherwise an error code +/// +pub fn touch(path: &String) -> Result { + // Split the path into components + let mut components: Vec<&str> = path.split("/").collect(); + + // Remove the last component (the name of the new file) + let new_file_name = components.pop(); + + // We need parent directory to create the new file + let parent_dir; + if components.len() == 1 { + parent_dir = "/".to_string(); + } else { + parent_dir = components.join("/"); // Joins the remaining components + } + + // Safely lookup the parent directory and create the new file + let result = lookup::lookup_dir(&parent_dir) + .and_then(|dir| { + new_file_name + .ok_or(Errno::EINVAL) // Handle missing file name + .and_then(|name| dir.create_file(name, Mode::new(0))) // Create the file + }) + .map(|_| 0); // Convert the success result to 0 + + match result { + Ok(_) => Ok(0), // Successfully created the file + Err(_) => { + // Handle the error here (e.g., logging or returning the error code) + Err(Errno::ENOTDIR) + } } - return convert_syscall_result_to_ret_code(sysret); } /// /// Description: \ -/// Read bytes from the file (from current position) into the given buffer. \ -/// The number of bytes to be read is determined by the buffer size +/// Read next directory entry /// /// Parameters: \ -/// `fh` file handle \ -/// `buf` buffer to copy file bytes into \ +/// `fh` handle to an open directory \ +/// `dentry` memory for the next directory entry to be returned \ /// -/// Return: `Ok(#bytes read)` or `Err(errno)` +/// Return: \ +/// `Ok(1)` next directory entry in `dentry` \ +/// `Ok(0)` no more entries in the directory \ +/// `Err` error code /// -pub fn read(fh: usize, buf: &mut [u8]) -> isize { - let sysret; - match ns_get_oot().lock().get(fh) { - Ok(fptr) => sysret = fptr.read(buf), - Err(e) => sysret = Err(e), +pub fn readdir(fh: usize, dentry: *mut RawDirent) -> Result { + let res = open_objects::readdir(fh); + match res { + Ok(dir_entry) => { + match dir_entry { + Some(dir_entry_data) => { + // copy data + let mut de: RawDirent = RawDirent::new(); + de.d_type = dir_entry_data.file_type as usize; + let name_bytes: &[u8] = dir_entry_data.name.as_bytes(); + let len = name_bytes.len().min(255); // Avoid overflow + de.d_name[..len].copy_from_slice(&name_bytes[..len]); + + // Write the Dirent structure to the provided dentry pointer + unsafe { + if !dentry.is_null() { + *dentry = de; + return Ok(1); // Indicate success + } else { + return Err(Errno::EUNKN); // Handle null pointer case + } + } + } + None => Ok(0), + } + } + Err(e) => Err(e), } - return convert_syscall_result_to_ret_code(sysret); } /// -/// Description: Dump all named objects on the screen (for debugging) +/// Description: \ +/// Return the current working path. /// +/// Parameters: \ +/// `buf` buffer to copy current path into \ +/// +/// Return: `Ok(#len of string)` or `Err(errno)` +/// +pub fn cwd(buf: &mut [u8]) -> Result { + // Lock the CWD mutex to access its value + let cwd = CWD.lock(); + + // Get the string as bytes + let cwd_bytes = cwd.as_bytes(); + + // Calculate how much data can be copied (leave space for the null terminator) + let len_to_copy = (buf.len() - 1).min(cwd_bytes.len()); // Reserve space for the null terminator + + // Copy the data into the buffer + buf[..len_to_copy].copy_from_slice(&cwd_bytes[..len_to_copy]); + + // Add the null terminator if there is space + if buf.len() > len_to_copy { + buf[len_to_copy] = 0; + } + + // Return the total length including the null terminator, or just the copied length + Ok(len_to_copy + 1) +} + +/// +/// Description: Change working directory \ +/// Parameters: `path` absolute path \ /// Return: `Ok(0)` or `Err(errno)` /// -pub fn dump() -> i64 { - ns_get().dump(); - return 0; -} \ No newline at end of file +pub fn cd(path: &String) -> Result { + let result = lookup::lookup_dir(&path); + match result { + Ok(_) => { + let mut cwd = CWD.lock(); + *cwd = path.clone(); + Ok(0) + }, + Err(_) => { + // Handle the error here (e.g., logging or returning the error code) + Err(Errno::ENOTDIR) + } + } +} diff --git a/os/kernel/src/naming/directory.rs b/os/kernel/src/naming/directory.rs deleted file mode 100644 index c20cad3..0000000 --- a/os/kernel/src/naming/directory.rs +++ /dev/null @@ -1,155 +0,0 @@ -/* ╔═════════════════════════════════════════════════════════════════════════╗ - ║ Module: directory ║ - ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Descr.: Implementation of a directoy in the naming service. ║ - ║ Following structs are defined and implemented: ║ - ║ - NsDirectoryWrapper: wraps a directory with a RwLock ║ - ║ - NsDirectory: directory storing named objects ║ - ║ Following traits are implemented: ║ - ║ - NsNode ║ - ║ - NsNodeDirectory ║ - ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, Univ. Duesseldorf, 7.9.2024 ║ - ╚═════════════════════════════════════════════════════════════════════════╝ -*/ -use alloc::boxed::Box; -use alloc::string::String; -use alloc::vec::Vec; -use core::any::Any; -use spin::RwLock; - -use syscall::return_vals::{OpenOptions, Errno}; - -use crate::naming::traits::*; -use crate::naming::file::NsFile; - - -/// Wraps a directory with a RwLock -#[derive(Debug)] -pub(super) struct NsDirectoryWrapper(pub(super) RwLock); - -impl NsDirectoryWrapper { - pub(super) fn new() -> Self { - NsDirectoryWrapper(RwLock::new(NsDirectory { - children: Vec::new(), - })) - } -} - -impl NsNode for NsDirectoryWrapper { - /// Returns the node type - fn get_type(&self) -> NsNodeType { - NsNodeType::Directory - } -} - -impl NsNodeDirectory for NsDirectoryWrapper { - - /// Create directories (including all sub directories) in the given path - fn mkdir(&self, components: &mut Vec<&str>) -> Result<(),Errno> { - if let Some(component) = components.pop() { - let node_name = String::from(component); - - // follow path recurively for existing sub directories - for (name, node) in &self.0.read().children { - if name == &node_name { - let opt_sub_dir = node.downcast_ref::(); - if let Some(sub_dir) = opt_sub_dir { - return sub_dir.mkdir(components); - } - } - } - - // otherweise create them - let directory = Box::new(NsDirectoryWrapper::new()); - let result = directory.mkdir(components); - self.0.write().children.push((node_name, directory)); - result - } else { - Ok(()) - } - } - - fn dump(&self, mut tabs: String) { - tabs.push_str(" "); - for (name, node) in &self.0.read().children { - if let Some(directory) = node.downcast_ref::() { - println!("{}{} ({:?})", tabs, name, self.get_type()); - directory.dump(tabs.clone()); - } else if let Some(file) = node.downcast_ref::() { - println!("{}{} ({:?})", tabs, name, file.get_type()); - } else { - println!("{}{} (Unknown))", tabs, name); - } - } - } - - fn open( - &self, - path: &mut Vec<&str>, - flags: OpenOptions, - ) -> Result, Errno> { - if let Some(path_part) = path.pop() { - let node_name = String::from(path_part); - - if path.is_empty() == true { - // reach endpoint => reach file - for (name, node) in &self.0.read().children { - if name == &node_name { - let opt_file = node.downcast_ref::(); - if let Some(file) = opt_file { - return file.get_handle(flags); - } - } - } - } - - if path.is_empty() == true { - if flags.contains(OpenOptions::CREATE) { - // Create file on demand - let file = Box::new(NsFile::new()); - let result = file.get_handle(flags); - self.0.write().children.push((node_name, file)); - - result - } else { - Err(Errno::EINVAL) - } - } else { - // traverse to the directories to the endpoint - for (name, node) in &self.0.read().children { - if name == &node_name { - let opt_dir = node.downcast_ref::(); - if let Some(directory) = opt_dir { - return directory.open(path, flags); - } - } - } - Err(Errno::EINVAL) - } - } else { - Err(Errno::EINVAL) - } - } -} - - -#[derive(Debug)] -pub(super) struct NsDirectory { - children: Vec<( - String, - Box, - )>, -} - - -/// Helper function to check if the argument is an abolute path -pub fn check_absolute_path(path: &String) -> bool { - if let Some(pos) = path.find('/') { - if pos == 0 { - return true; - } - } - - false -} \ No newline at end of file diff --git a/os/kernel/src/naming/file.rs b/os/kernel/src/naming/file.rs deleted file mode 100644 index ff99ae3..0000000 --- a/os/kernel/src/naming/file.rs +++ /dev/null @@ -1,139 +0,0 @@ -/* ╔═════════════════════════════════════════════════════════════════════════╗ - ║ Module: ns_file ║ - ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Descr.: Implementation of files stored in the heap (). ║ - ║ Following structs are defined and implemented: ║ - ║ - NsFile ║ - ║ Following traits are implemented: ║ - ║ - NsNode ║ - ║ - NsNodeFile ║ - ║ - NsOpenFile ║ - ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, Univ. Duesseldorf, 15.9.2024 ║ - ╚═════════════════════════════════════════════════════════════════════════╝ -*/ -use alloc::boxed::Box; -use alloc::sync::Arc; -use alloc::vec::Vec; -use core::sync::atomic::AtomicUsize; -use core::sync::atomic::Ordering::SeqCst; -use spin::RwLock; - -use syscall::return_vals::{Errno, OpenOptions, SeekOrigin}; -use crate::naming::traits::*; - -/// NsFile is the actual file with its data -#[derive(Debug)] -pub(super) struct NsFile { - /// Position within the file - pos: AtomicUsize, - /// File content - data: Arc>>, -} - -impl Clone for NsFile { - fn clone(&self) -> Self { - NsFile { - pos: AtomicUsize::new(self.pos.load(core::sync::atomic::Ordering::SeqCst)), - data: self.data.clone(), - } - } -} - -impl NsFile { - pub fn new() -> Self { - NsFile { - pos: AtomicUsize::new(0), - data: Arc::new(RwLock::new(Vec::new())), - } - } -} - -/// Implement `NsNode` operations for `NsFile` -impl NsNode for NsFile { - fn get_type(&self) -> NsNodeType { - NsNodeType::File - } -} - -/// Implement `NsNodeFile` operations for `NsFile` -impl NsNodeFile for NsFile { - fn get_handle(&self, _opt: OpenOptions) -> Result, Errno> { - Ok(Box::new(NsFile { - pos: AtomicUsize::new(0), - data: self.data.clone(), - })) - } -} - -/// Implement `FilePointer` operations for `NsFile` -impl NsOpenFile for NsFile { - - fn read(&self, buf: &mut [u8]) -> Result { - let guard = self.data.read(); - let vec: &[u8] = guard.as_ref(); - let pos: usize = self.pos.load(SeqCst); - - if pos >= vec.len() { - return Ok(0); - } - - let len; - if vec.len() - pos < buf.len() { - len = vec.len() - pos - } else { - len = buf.len() - } - - buf[0..len].clone_from_slice(&vec[pos..pos + len]); - self.pos.fetch_add(len, SeqCst); - Ok(len) - } - - fn write(&self, buf: &[u8]) -> Result { - let mut guard = self.data.write(); - let vec: &mut Vec = guard.as_mut(); - let pos = self.pos.load(SeqCst); - - if pos + buf.len() > vec.len() { - vec.resize(pos + buf.len(), 0); - } - - vec[pos..pos + buf.len()].clone_from_slice(buf); - - self.pos.fetch_add( buf.len(), SeqCst); - - Ok(buf.len()) - } - - fn size(&self) -> usize { - let guard = self.data.read(); - let vec: &[u8] = guard.as_ref(); - vec.len() as usize - } - - fn seek(&self, offset: usize, origin: SeekOrigin) -> Result { - match origin { - SeekOrigin::Start => { - self.pos.store(offset, SeqCst); - Ok(offset) - } - SeekOrigin::End => { - let guard = self.data.read(); - let ref vec: &Vec = guard.as_ref(); - let data = vec.len() + offset; - self.pos.store(data, SeqCst); - Ok(data) - } - SeekOrigin::Current => { - let pos: i64 = self.pos.load(SeqCst) as i64 + offset as i64; - if pos >= 0 { - self.pos.store(pos as usize, SeqCst); - Ok(pos as usize) - } else { - Err(Errno::EINVAL) - } - } - } - } -} \ No newline at end of file diff --git a/os/kernel/src/naming/lookup.rs b/os/kernel/src/naming/lookup.rs new file mode 100644 index 0000000..691c4dd --- /dev/null +++ b/os/kernel/src/naming/lookup.rs @@ -0,0 +1,72 @@ +/* ╔═════════════════════════════════════════════════════════════════════════╗ + ║ Module: lookup ║ + ╟─────────────────────────────────────────────────────────────────────────╢ + ║ Descr.: Lookup functions. ║ + ╟─────────────────────────────────────────────────────────────────────────╢ + ║ Author: Michael Schoettner, Univ. Duesseldorf, 30.12.2024 ║ + ╚═════════════════════════════════════════════════════════════════════════╝ +*/ +use alloc::string::String; +use alloc::vec::Vec; +use alloc::sync::Arc; +use super::api::ROOT; +use super::traits; +use super::traits::{NamedObject, DirectoryObject}; +use syscall::return_vals::Errno; + +/// Resolves an absolute path into an `DirectoryLike` +pub(super) fn lookup_dir(path: &String) -> Result, Errno> { + match lookup_named_object(path)? { + NamedObject::DirectoryObject(dir) => Ok(dir), + NamedObject::FileObject(_) => Err(Errno::ENOTDIR), + } +} + +/// Resolves an absolute path into an `NamedObject` +pub(super) fn lookup_named_object(path: &String) -> Result { + let mut found_named_object; + + if check_absolute_path(path) { + if path == "/" { + found_named_object = traits::as_named_object(ROOT.get().unwrap().root_dir()); + return Ok(found_named_object); + } + let mut components: Vec<&str> = path.split("/").collect(); + components.remove(0); // remove empty string at position 0 + + // get root directory and open the desired file + let mut current_dir = ROOT.get().unwrap().root_dir(); + let mut len = components.len(); + let mut found; + for component in &components { + found = current_dir.lookup(component); + if found.is_err() { + return Err(Errno::ENOENT); + } + found_named_object = found.unwrap(); + + // if not last component, this must be a directory + if len > 1 { + if !found_named_object.is_dir() { + return Err(Errno::ENOENT); + } + current_dir = found_named_object.as_dir().unwrap().clone(); + // if this is the last component, this must be a file or directory (see flags) + } else { + return Ok(found_named_object.clone()); + } + len = len - 1; + } + } + Err(Errno::ENOENT) +} + +/// Helper function to check if `path` is an abolute path +fn check_absolute_path(path: &String) -> bool { + if let Some(pos) = path.find('/') { + if pos == 0 { + return true; + } + } + false +} diff --git a/os/kernel/src/naming/main.rs b/os/kernel/src/naming/main.rs deleted file mode 100644 index 6dfce2d..0000000 --- a/os/kernel/src/naming/main.rs +++ /dev/null @@ -1,102 +0,0 @@ -/* ╔═════════════════════════════════════════════════════════════════════════╗ - ║ Module: main ║ - ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Descr.: Main module of the implementation of the naming service (ns). ║ - ║ - NS: global entry point ║ - ║ Following traits are implemented: ║ - ║ - NsInterface: all operations provided by the naming service║ - ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, Univ. Duesseldorf, 15.9.2024 ║ - ╚═════════════════════════════════════════════════════════════════════════╝ -*/ -use alloc::boxed::Box; -use alloc::string::String; -use alloc::sync::Arc; -use alloc::vec::Vec; -use core::fmt::Debug; -use spin::Once; - -use syscall::return_vals::{Errno, OpenOptions}; - -use crate::naming::traits::*; -use crate::naming::directory::{NsDirectoryWrapper,check_absolute_path}; -use crate::naming::open_objects::ns_open_object_table_init; - - -/// Entrypoint of the naming service -pub(super) static NS: Once> = Once::new(); - -/// Init `NS` once -pub(super) fn init() { - NS.call_once(|| Arc::new(Ns::new())); - ns_open_object_table_init(); -} - -/// Helper function returning safe access to the naming service -pub(super) fn ns_get() -> Arc { - let ns = NS.get(); - return ns.unwrap().clone(); -} - -/// Entrypoint of the naming service -#[derive(Debug)] -pub(super) struct Ns { - /// root directory - root_dir: Arc, -} - -impl Ns { - pub fn new() -> Ns { - Ns { - root_dir: Arc::new(NsDirectoryWrapper::new()), - } - } - - /// return root directory of the naming service - pub fn root_dir(&self) -> &Arc { - &self.root_dir - } -} - -impl NsInterface for Ns { - - /// Create a directory (including all sub directories) - fn mkdir(&self, path: &String) -> Result<(), Errno> { - if check_absolute_path(path) { - let mut components: Vec<&str> = path.split("/").collect(); - - components.reverse(); - components.pop(); - - // get root directory and create directories as needed - self.root_dir().clone().mkdir(&mut components) - } else { - Err(Errno::ENOENT) - } - } - - /// Dump all nodes in the naming service (for debugging) - fn dump(&self) { - println!("/"); - - // get root directory and create directories as needed - self.root_dir().clone().dump(String::from("")); - } - - fn open(&self, path: &String, flags: OpenOptions)-> Result, Errno> { - if check_absolute_path(path) { - let mut components: Vec<&str> = path.split("/").collect(); - - components.reverse(); - components.pop(); - - // get root directory and open the desired file - self - .root_dir() - .clone() - .open(&mut components, flags) - } else { - Err(Errno::ENOENT) - } - } -} \ No newline at end of file diff --git a/os/kernel/src/naming/mod.rs b/os/kernel/src/naming/mod.rs index c052447..39ccef4 100644 --- a/os/kernel/src/naming/mod.rs +++ b/os/kernel/src/naming/mod.rs @@ -1,10 +1,7 @@ - - pub mod api; pub mod stat; -mod traits; -mod main; -mod file; -mod directory; mod open_objects; +mod tmpfs; +mod lookup; +mod traits; \ No newline at end of file diff --git a/os/kernel/src/naming/open_objects.rs b/os/kernel/src/naming/open_objects.rs index 4d131c7..99c6ecc 100644 --- a/os/kernel/src/naming/open_objects.rs +++ b/os/kernel/src/naming/open_objects.rs @@ -1,86 +1,241 @@ /* ╔═════════════════════════════════════════════════════════════════════════╗ ║ Module: open_objects ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Descr.: Managing all open objects in the open object table (oot). This ║ - ║ table stores tuples (usize, NsOpenFile). ║ - ║ The number of max. open objects is limited by MAX_OPEN_OBJECTS ║ + ║ Descr.: Managing opened objects in a global table (OPEN_OBJECTS). And ║ + ║ providing all major functions for the naming service. ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, Univ. Duesseldorf, 15.9.2024 ║ + ║ Author: Michael Schoettner, Univ. Duesseldorf, 30.12.2024 ║ ╚═════════════════════════════════════════════════════════════════════════╝ */ +use alloc::string::String; use alloc::sync::Arc; -use alloc::boxed::Box; -use spin::{Mutex, Once}; use alloc::vec::Vec; use core::result::Result; +use core::sync::atomic::{AtomicUsize, Ordering}; +use spin::{Mutex, Once}; -use syscall::return_vals::{SyscallResult, Errno}; - -use crate::naming::traits::NsOpenFile; +use super::traits::NamedObject; +use super::lookup; +use naming::shared_types::{DirEntry, OpenOptions, SeekOrigin}; +use syscall::return_vals::{Errno, SyscallResult}; /// Max. number of open objetcs const MAX_OPEN_OBJECTS: usize = 0x100; /// Helper function returning safe access to the open object table (oot) -pub(super) fn ns_get_oot() -> Arc> { - let oot = NS_OPEN_OBJECTS.get(); +fn get_open_object_table() -> Arc> { + let oot: Option<&Arc>> = OPEN_OBJECTS.get(); return oot.unwrap().clone(); } +static OPEN_OBJECTS: Once>> = Once::new(); + +struct OpenObjectTable { + open_handles: Vec<(usize, Option>)>, + free_handles: [usize; MAX_OPEN_OBJECTS], +} -static NS_OPEN_OBJECTS: Once>> = Once::new(); -pub fn ns_open_object_table_init() { - NS_OPEN_OBJECTS.call_once(|| Arc::new(Mutex::new(NsOpenObjectTable::new()))); +pub(super) fn open_object_table_init() { + OPEN_OBJECTS.call_once(|| Arc::new(Mutex::new(OpenObjectTable::new()))); } +pub(super) fn open(path: &String, flags: OpenOptions) -> Result { + // try to open the named object for the given path + let result = lookup::lookup_named_object(path); + if result.is_err() { + return Err(Errno::ENOENT); + } + let found_named_object: NamedObject = result.unwrap(); -pub(super) struct NsOpenObjectTable { - open_handles: Vec< (usize, Option>>) >, - free_handles: [usize; MAX_OPEN_OBJECTS], + // check if path is a directory and this was requested + if flags.contains(OpenOptions::DIRECTORY) { + if !found_named_object.is_dir() { + return Err(Errno::ENOTDIR); + } + } + + // try to allocate an new handle + let res = get_open_object_table() + .lock() + .allocate_handle(Arc::new(OpenedObject::new( + Arc::new(found_named_object), + AtomicUsize::new(0), + flags, + ))); + res +} + +pub(super) fn write(fh: usize, buf: &[u8]) -> Result { + get_open_object_table() + .lock() + .lookup_opened_object(fh) + .and_then(|opened_object| { + // Make `opened_object` mutable here + opened_object.named_object.as_file().and_then(|file| { + let pos = opened_object.pos.load(Ordering::SeqCst); + let bytes_written = file.write(buf, pos, opened_object.options)?; + opened_object + .pos + .store(pos + bytes_written, Ordering::SeqCst); + Ok(bytes_written) // Return the bytes written + }) + }) +} + +pub(super) fn read(fh: usize, buf: &mut [u8]) -> Result { + get_open_object_table() + .lock() + .lookup_opened_object(fh) + .and_then(|opened_object| { + // Make `opened_object` mutable here + opened_object.named_object.as_file().and_then(|file| { + let pos = opened_object.pos.load(Ordering::SeqCst); + let bytes_read = file.read(buf, pos, opened_object.options)?; + opened_object.pos.store(pos + bytes_read, Ordering::SeqCst); + Ok(bytes_read) // Return the bytes read + }) + }) +} + +pub fn seek(fh: usize, offset: usize, origin: SeekOrigin) -> Result { + get_open_object_table() + .lock() + .lookup_opened_object(fh) + .and_then(|opened_object| { + // Make `opened_object` mutable here + opened_object.named_object.as_file().and_then(|file| { + let new_pos = match origin { + SeekOrigin::Start => offset, + SeekOrigin::End => file.stat()?.size as usize + offset, + SeekOrigin::Current => opened_object.pos.load(Ordering::SeqCst) + offset, + }; + opened_object.pos.store(new_pos, Ordering::SeqCst); + Ok(0) // Success + }) + }) +} + +pub(super) fn readdir(fh: usize) -> Result, Errno> { + let res = get_open_object_table() + .lock() + .lookup_opened_object(fh) + .and_then(|opened_object| { + // Make `opened_object` mutable here + opened_object.named_object.as_dir().and_then(|dir| { + let pos = opened_object.pos.load(Ordering::SeqCst); + let dir_entry = dir.readdir(pos)?; + opened_object.pos.store(pos + 1, Ordering::SeqCst); + Ok(dir_entry) // Return the DirEntry + }) + }); + match res { + Ok(dir_entry) => Ok(dir_entry), + Err(e) => Err(e), + } } -impl NsOpenObjectTable { +pub(super) fn close(handle: usize) -> Result { + get_open_object_table().lock().free_handle(handle) +} + +/*pub(super) fn dump() { + get_open_object_table().lock().dump(); +}*/ + - fn new() -> NsOpenObjectTable { - NsOpenObjectTable { +impl OpenObjectTable { + /// Create a new OpenObjectTable + fn new() -> OpenObjectTable { + OpenObjectTable { open_handles: Vec::new(), free_handles: [0; MAX_OPEN_OBJECTS], } } - /// Get `NsOpenFile` for a given handle, if possible - pub fn get(&self, file_handle: usize) -> Result<&Arc>, Errno> { - for (fh,fptr) in &self.open_handles { - if *fh == file_handle { - match fptr { - Some(v) => return Ok(v), - _ => return Err(Errno::EINVALH), - } - } + /// Lookup an 'OpenedObject' for a given handle + fn lookup_opened_object( + &mut self, + opened_object_handle: usize, + ) -> Result<&mut Arc, Errno> { + self.open_handles + .iter_mut() + .find(|(h, _)| *h == opened_object_handle) + .ok_or(Errno::EINVALH) // Handle not found in the table + .and_then(|(_, named_obj)| named_obj.as_mut().ok_or(Errno::EINVALH)) + } + + /// Allocate a new handle for a given 'OpenObject' + fn allocate_handle(&mut self, opened_object: Arc) -> Result { + let res = self.find_free_handle(); + if res.is_none() { + return Err(Errno::ENOHANDLES); + } + let new_handle = res.unwrap(); + self.open_handles.push((new_handle, Some(opened_object))); + Ok(new_handle) + } + + /// Free handle + fn free_handle(&mut self, opened_object_handle: usize) -> SyscallResult { + // Find the position of the file handle in the `open_handles` vector + if let Some(index) = self + .open_handles + .iter() + .position(|(h, _)| *h == opened_object_handle) + { + // Remove the handle from `open_handles` + self.open_handles.swap_remove(index); + // set handle as free + self.free_handles[index] = 0; + Ok(0) + } else { + // Handle not found + Err(Errno::EINVALH) } - return Err(Errno::EINVALH); } + /// Helper function of 'allocate' to find a free handle fn find_free_handle(&mut self) -> Option { - for (index, &value) in self.free_handles.iter().enumerate() { - if value == 0 { + self.free_handles + .iter_mut() + .position(|value| *value == 0) + .map(|index| { self.free_handles[index] = 1; - return Some(index); + index + }) + } + +/* + fn dump(&self) { + info!("OpenObjectTable: dumping used handles"); + for (handle, opened_object) in &self.open_handles { + if let Some(opened_object) = opened_object { + info!( + " handle = {:?}, named object = {:?}", + handle, opened_object.named_object + ); } } - None } - - pub(super) fn create_new_handle_for_filepointer(&mut self, fp: Box) -> SyscallResult { - let opt_new_handle = self.find_free_handle(); - if opt_new_handle.is_none() { - return Err(Errno::ENOHANDLES); + */ +} + +// Opened object stored in the 'OpenObjectTable' +// (includes NamedObject, current position within object, and options) +pub struct OpenedObject { + named_object: Arc, + pos: AtomicUsize, // current position within file or number of next DirEntry + options: OpenOptions, +} + +impl OpenedObject { + pub fn new(named_object: Arc, pos: AtomicUsize, options: OpenOptions) -> OpenedObject { + OpenedObject { + named_object, + pos, + options, } - let new_handle = opt_new_handle.unwrap(); - self.open_handles.push( (new_handle, Some(Arc::new(fp)) )); - Ok(new_handle) } - } diff --git a/os/kernel/src/naming/stat.rs b/os/kernel/src/naming/stat.rs index 9821849..f84ef7d 100644 --- a/os/kernel/src/naming/stat.rs +++ b/os/kernel/src/naming/stat.rs @@ -1,47 +1,43 @@ /* ╔═════════════════════════════════════════════════════════════════════════╗ ║ Module: stat ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Descr.: Meta data for each name service entry. ║ + ║ Descr.: Meta data for each named object. ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, 23.7.2024, HHU ║ + ║ Author: Michael Schoettner, 30.12.2024, HHU ║ ╚═════════════════════════════════════════════════════════════════════════╝ */ -use alloc::string::{String, ToString}; +pub const MODE_FILE: u32 = 0x1; +pub const MODE_DIR: u32 = 0x2; +pub const MODE_LINK: u32 = 0x3; -pub const MODE_CONT: u32 = 0b001u32; -pub const MODE_DIR: u32 = 0b010u32; -pub const MODE_LINK: u32 = 0b011u32; -pub const MODE_DEV: u32 = 0b100u32; - - -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub struct Stat { - pub name: String, pub mode: Mode, pub size: usize, - pub ctime: u64, // creation time - pub dev_id: u64, // for device files + pub created_time: u64, + pub modified_time: u64, + pub accessed_time: u64, } impl Stat { - pub fn new(name: String, mode: Mode, size: usize) -> Stat { + pub fn new(mode: Mode, size: usize) -> Stat { Stat { - name, mode, - dev_id: 0, size, - ctime: 0, + created_time: 0, + modified_time: 0, + accessed_time: 0, } } pub fn zeroed() -> Stat { Stat { - name: "".to_string(), - mode: Mode::new(MODE_CONT), - dev_id: 0, + mode: Mode::new(MODE_FILE), size: 0, - ctime: 0, + created_time: 0, + modified_time: 0, + accessed_time: 0, } } } @@ -59,16 +55,11 @@ impl Mode { (self.0 & MODE_DIR) == MODE_DIR } - pub fn is_container(self) -> bool { - (self.0 & MODE_CONT) == MODE_CONT + pub fn is_file(self) -> bool { + (self.0 & MODE_FILE) == MODE_FILE } pub fn is_link(self) -> bool { (self.0 & MODE_LINK) == MODE_LINK } - - pub fn is_dev_entry(self) -> bool { - (self.0 & MODE_DEV) == MODE_DEV - } - -} \ No newline at end of file +} diff --git a/os/kernel/src/naming/tmpfs.rs b/os/kernel/src/naming/tmpfs.rs new file mode 100644 index 0000000..6d1958b --- /dev/null +++ b/os/kernel/src/naming/tmpfs.rs @@ -0,0 +1,206 @@ +/* ╔═════════════════════════════════════════════════════════════════════════╗ + ║ Module: tmpfs ║ + ╟─────────────────────────────────────────────────────────────────────────╢ + ║ Descr.: Temporary file system running storing data in main memory. ║ + ╟─────────────────────────────────────────────────────────────────────────╢ + ║ Author: Michael Schoettner, Univ. Duesseldorf, 30.12.2024 ║ + ╚═════════════════════════════════════════════════════════════════════════╝ +*/ +use alloc::string::{String, ToString}; +use alloc::sync::Arc; +use alloc::vec::Vec; +use spin::rwlock::RwLock; +use core::fmt; +use core::result::Result; + +use super::stat::Mode; +use super::stat::Stat; +use super::traits::{DirectoryObject, FileObject, FileSystem, NamedObject}; +use naming::shared_types::{DirEntry, FileType, OpenOptions}; +use syscall::return_vals::Errno; + +pub struct TmpFs { + root_dir: Arc, +} + +impl TmpFs { + pub fn new() -> TmpFs { + TmpFs { + root_dir: Arc::new(Dir::new()), + } + } +} + +impl FileSystem for TmpFs { + fn root_dir(&self) -> Arc { + self.root_dir.clone() + } +} + +enum TmpFsINode { + File(Arc), + Directory(Arc), +} + +struct DirInner { + files: Vec<(String, TmpFsINode)>, + stat: Stat, +} + +pub struct Dir(RwLock); + +impl Dir { + pub fn new() -> Dir { + Dir(RwLock::new(DirInner { + files: Vec::new(), + stat: Stat { + mode: Mode::new(0), + ..Stat::zeroed() + }, + })) + } +} + +impl DirectoryObject for Dir { + fn lookup(&self, name: &str) -> Result { + let guard = self.0.read(); // Lock the mutex to access the inner data + if let Some((_, tmpfs_inode)) = guard.files.iter().find(|(file_name, _)| file_name == name) + { + // Match on the TmpFsINode type + match tmpfs_inode { + TmpFsINode::File(file) => Ok(file.clone().into()), // Clone and convert to NamedObject + TmpFsINode::Directory(dir) => Ok((dir.clone() as Arc).into()), // Clone and cast directory + } + } else { + Err(Errno::ENOENT) // Return error if the file is not found + } + } + + fn create_file(&self, name: &str, _mode: Mode) -> Result { + let mut dir_lock = self.0.write(); + + // Check if the file already exists in the directory + if dir_lock + .files + .iter() + .any(|(file_name, _)| file_name == name) + { + return Err(Errno::EEXIST); // Return an error if the file exists + } + + // Create a new file and add it to the directory + let inode = Arc::new(File::new()); + dir_lock + .files + .push((name.to_string(), TmpFsINode::File(inode.clone()))); + + // Return the created file as a NamedObject + Ok((inode as Arc).into()) + } + + fn create_dir(&self, name: &str, _mode: Mode) -> Result { + let mut dir_lock = self.0.write(); + + // Check if a file or directory with the same name already exists + if dir_lock.files.iter().any(|(file_name, _)| file_name == name) { + return Err(Errno::EEXIST); // Return an error if the name exists + } + + // Create a new directory and add it to the directory's entries + let inode = Arc::new(Dir::new()); + dir_lock + .files + .push((name.to_string(), TmpFsINode::Directory(inode.clone()))); + + // Return the created directory as a NamedObject + Ok((inode as Arc).into()) + } + + fn stat(&self) -> Result { + Ok(self.0.read().stat) + } + + fn readdir(&self, index: usize) -> Result, Errno> { + let dir_lock = self.0.read(); + let (name, inode) = match dir_lock.files.iter().nth(index) { + Some(entry) => entry, + None => { + return Ok(None); + } + }; + + let entry = match inode { + TmpFsINode::Directory(_dir) => DirEntry { + file_type: FileType::Directory, + name: name.clone(), + }, + TmpFsINode::File(_file) => DirEntry { + file_type: FileType::Regular, + name: name.clone(), + }, + }; + Ok(Some(entry)) + } +} + +impl fmt::Debug for Dir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TmpFsDir").finish() + } +} + +struct File { + data: RwLock>, + stat: Stat, +} + +impl File { + pub fn new() -> File { + File { + data: RwLock::new(Vec::new()), + stat: Stat { + mode: Mode::new(0), + ..Stat::zeroed() + }, + } + } +} + +impl FileObject for File { + fn stat(&self) -> Result { + Ok(self.stat) + } + + fn read(&self, buf: &mut [u8], offset: usize, _options: OpenOptions) -> Result { + let data= self.data.write(); + if offset > data.len() { + return Ok(0); + } + + let len; + if data.len() - offset < buf.len() { + len = data.len() - offset; + } else { + len = buf.len(); + } + buf[0..len].clone_from_slice(&data[offset..offset + len]); + Ok(len) + } + + fn write(&self, buf: &[u8], offset: usize, _options: OpenOptions) -> Result { + let mut data = self.data.write(); + + if offset + buf.len() > data.len() { + data.resize(offset + buf.len(), 0); + } + + data[offset..offset + buf.len()].clone_from_slice(buf); + Ok(buf.len()) + } +} + +impl fmt::Debug for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TmpFsFile").finish() + } +} diff --git a/os/kernel/src/naming/traits.rs b/os/kernel/src/naming/traits.rs index 0f61640..e07a3ae 100644 --- a/os/kernel/src/naming/traits.rs +++ b/os/kernel/src/naming/traits.rs @@ -2,116 +2,111 @@ ║ Module: traits ║ ╟─────────────────────────────────────────────────────────────────────────╢ ║ Descr.: All internal traits used within the naming service (ns). ║ - ║ - NsInterface: specifies all operations provided by the ns ║ - ║ - NsNode: trait defining all operations on a named object ║ - ║ - NsNodeDirectory: specifies all operations on a directory ║ - ║ - NsNodeFile: trait defining all operations on a file ║ - ║ - NsOpenFile: specifies all operations on an open file ║ + ║ - FileSystem: type and operations for a file system ║ + ║ - NamedObject: generic type of an object ║ + ║ - DirectoryObject: specifies all operations on a directory obj. ║ + ║ - FileObject: specifies all operations on a file object ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, Univ. Duesseldorf, 12.9.2024 ║ + ║ Author: Michael Schoettner, Univ. Duesseldorf, 30.12.2024 ║ ╚═════════════════════════════════════════════════════════════════════════╝ */ -use alloc::vec::Vec; -use alloc::boxed::Box; -use alloc::string::String; -use core::fmt::Debug; + + +use alloc::sync::Arc; +use core::fmt::{self, Debug}; use core::result::Result; -use syscall::return_vals::{OpenOptions, Errno, SeekOrigin}; +use super::stat::{Mode, Stat}; +use naming::shared_types::{OpenOptions, DirEntry}; +use syscall::return_vals::Errno; + +/// FileSystem operations +pub trait FileSystem: Send + Sync { + fn root_dir(&self) -> Arc; +} + +/// File object operations +pub trait FileObject: Debug + Send + Sync { + fn stat(&self) -> Result { + Err(Errno::EBADF) + } + fn read(&self, _buf: &mut [u8], _offset: usize, _options: OpenOptions) -> Result { + Err(Errno::EBADF) + } -/// Types of a node stored in the naming service -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum NsNodeType { - File, - Directory, + fn write(&self, _buf: &[u8], _offset: usize, _options: OpenOptions) -> Result { + Err(Errno::EBADF) + } } -/// `NsInterface` specifies all operations provided by the naming service -pub(super) trait NsInterface: Debug + Send + Sync { - /// Create a directory (including all sub directories) - fn mkdir(&self, path: &String) -> Result<(),Errno>; - - /// Open the file given in `path` (must be absolute) - /// Options for opening files - /// Returns a file handle on success - fn open(&self, path: &String, flags: OpenOptions) -> Result, Errno>; - - /// Dump all nodes in the naming service (for debugging) - fn dump(&self); + +/// Directory object operations +pub trait DirectoryObject: Debug + Send + Sync { + fn lookup(&self, name: &str) -> Result; + fn create_file(&self, _name: &str, _mode: Mode) -> Result; + fn create_dir(&self, _name: &str, _mode: Mode) -> Result; + fn stat(&self) -> Result; + fn readdir(&self, index: usize) -> Result, Errno>; } -/// `NsNode` defines all operations for a node in the the ns -pub(super) trait NsNode: Debug + Send + Sync { - /// Determines the current node type - fn get_type(&self) -> NsNodeType; +/// A named object. +#[derive(Clone)] +pub enum NamedObject { + FileObject(Arc), + DirectoryObject(Arc), } -/// `NsNodeFile` represents a file node of the naming service -pub trait NsNodeFile: NsNode + Debug + Send + Sync { - /// Create a file handle to the current file - fn get_handle(&self, _opt: OpenOptions) -> Result, Errno>; +impl NamedObject { + /// Unwraps as a file. If it's not, returns `Errno::EBADF`. + pub fn as_file(&self) -> Result<&Arc, Errno> { + match self { + NamedObject::FileObject(file) => Ok(file), + _ => Err(Errno::EBADF), + } + } + + /// Unwraps as a directory. If it's not, returns `Errno::EBADF`. + pub fn as_dir(&self) -> Result<&Arc, Errno> { + match self { + NamedObject::DirectoryObject(dir) => Ok(dir), + _ => Err(Errno::EBADF), + } + } + + /// Returns `true` if it's a file. + pub fn is_file(&self) -> bool { + matches!(self, NamedObject::FileObject(_)) + } + + /// Returns `true` if it's a directory. + pub fn is_dir(&self) -> bool { + matches!(self, NamedObject::DirectoryObject(_)) + } } +impl fmt::Debug for NamedObject { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + NamedObject::FileObject(file) => fmt::Debug::fmt(file, f), + NamedObject::DirectoryObject(dir) => fmt::Debug::fmt(dir, f), + } + } +} -/// `NsNodeDirectory` specifies all operations on a directory -pub(super) trait NsNodeDirectory : NsNode + Debug + Send + Sync -{ - /// Helper function to create a new dirctory node - fn mkdir(&self, _components: &mut Vec<&str>) -> Result<(),Errno>; +impl From> for NamedObject { + fn from(file: Arc) -> Self { + NamedObject::FileObject(file) + } +} - /// Helper function to open a file - fn open( - &self, - path: &mut Vec<&str>, - _flags: OpenOptions, - ) -> Result, Errno>; +impl From> for NamedObject { + fn from(dir: Arc) -> Self { + NamedObject::DirectoryObject(dir) + } +} - /// Helper function to print the current state of the file system - fn dump(&self, _tabs: String); +pub fn as_named_object(dir: Arc) -> NamedObject { + NamedObject::DirectoryObject(dir) } -/// Description: This trait defines all functions that can be applied to an open file -#[allow(dead_code)] // size() is currently not used and produces a compiler warning. May be removed late, once all methods are in active use. -pub(super) trait NsOpenFile: Debug + Send + Sync { - - /// - /// Description: \ - /// Read bytes from the file (from current position) into the given buffer. \ - /// The number of bytes to be read is determined by the buffer size - /// - /// Parameters: `buf` buffer to copy file bytes into - /// - /// Return: `Ok(#bytes read)` or `Err(errno)` - /// - fn read(&self, buf: &mut [u8]) -> Result; - - /// - /// Description: \ - /// Write bytes from the given buffer into the file (at the current position). \ - /// The number of bytes to be written is determined by the buffer size - /// - /// Parameters: `buf` buffer from which bytes are copied into the file - /// - /// Return: `Ok(#bytes written)` or `Err(errno)` - /// - fn write(&self, buf: &[u8]) -> Result; - - /// - /// Description: Get file size. - /// - /// Return: `Ok(size in bytes)` or `Err(errno)` - /// - fn size(&self) -> usize; - - /// - /// Description: Set file pointer. - /// - /// Parameters: \ - /// `offset` offset in bytes \ - /// `origin` point of origin - /// - /// Return: `Ok( in bytes)` or `Err(errno)` - /// - fn seek(&self, offset: usize, origin: SeekOrigin) -> Result; -} \ No newline at end of file diff --git a/os/kernel/src/syscall/sys_naming.rs b/os/kernel/src/syscall/sys_naming.rs index c2aff89..827d007 100644 --- a/os/kernel/src/syscall/sys_naming.rs +++ b/os/kernel/src/syscall/sys_naming.rs @@ -3,22 +3,103 @@ ╟─────────────────────────────────────────────────────────────────────────╢ ║ Descr.: All system calls for the naming service. ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, 15.9.2024, HHU ║ + ║ Author: Michael Schoettner, 28.12.2024, HHU ║ ╚═════════════════════════════════════════════════════════════════════════╝ */ +use alloc::slice; +use alloc::string::{String, ToString}; use core::ptr::slice_from_raw_parts; use core::str::from_utf8; -use alloc::string::ToString; +use core::mem; +use naming::shared_types::{OpenOptions, SeekOrigin, RawDirent}; +use syscall::return_vals::{self, Errno}; +use num_enum::FromPrimitive; use crate::naming::api; -pub fn sys_mkdir(path_buff: *const u8, path_buff_len: usize) -> isize { - let path = from_utf8(unsafe { - slice_from_raw_parts(path_buff, path_buff_len) - .as_ref() - .unwrap() - }) - .unwrap(); +pub fn sys_open(path: *const u8, flags: OpenOptions) -> isize { + return_vals::convert_syscall_result_to_ret_code(api::open(&ptr_to_string(path).unwrap(), flags)) +} + +pub fn sys_read(fh: usize, buffer: *mut u8, buffer_length: usize) -> isize { + if buffer.is_null() || buffer_length == 0 { + return Errno::EINVAL as isize; + } + let buf: &mut[u8]; + unsafe { + buf = slice::from_raw_parts_mut(buffer, buffer_length); + } + return_vals::convert_syscall_result_to_ret_code(api::read(fh, buf)) +} + +pub fn sys_write(fh: usize, buffer: *const u8, buffer_length: usize) -> isize { + if buffer.is_null() || buffer_length == 0 { + return Errno::EINVAL as isize; + } + let buf: &[u8]; + unsafe { + buf = slice::from_raw_parts(buffer, buffer_length); + } + return_vals::convert_syscall_result_to_ret_code(api::write(fh, buf)) +} + +pub fn sys_seek(fh: usize, offset: usize, origin: usize) -> isize { + return_vals::convert_syscall_result_to_ret_code(api::seek(fh, offset, SeekOrigin::from_primitive(origin))) +} + +pub fn sys_close(fh: usize) -> isize { + return_vals::convert_syscall_result_to_ret_code(api::close(fh)) +} + +pub fn sys_mkdir(path: *const u8) -> isize { + return_vals::convert_syscall_result_to_ret_code(api::mkdir(&ptr_to_string(path).unwrap())) +} + +pub fn sys_touch(path: *const u8) -> isize { + return_vals::convert_syscall_result_to_ret_code(api::touch(&ptr_to_string(path).unwrap())) +} + +/// Convert a raw pointer resulting from a CString to a UTF-8 String +fn ptr_to_string(ptr: *const u8) -> Result { + if ptr.is_null() { + return Err(Errno::EBADSTR); + } + + let mut len = 0; + // Find the null terminator to determine the length + unsafe { + while *ptr.add(len) != 0 { + len += 1; + } + } + + let path = from_utf8(unsafe { slice_from_raw_parts(ptr, len).as_ref().unwrap() }); + match path { + Ok(path_str) => Ok(path_str.to_string()), + Err(_) => Err(Errno::EBADSTR), + } +} + +pub fn sys_readdir(fh: usize, buffer: *mut u8, buffer_length: usize) -> isize { + if buffer.is_null() || buffer_length == 0 || buffer_length < mem::size_of::() { + return Errno::EINVAL as isize; + } + let dentry = buffer as *mut RawDirent; + return_vals::convert_syscall_result_to_ret_code(api::readdir(fh, dentry)) +} + + +pub fn sys_cwd(buffer: *mut u8, buffer_length: usize) -> isize { + if buffer.is_null() || buffer_length == 0 { + return Errno::EINVAL as isize; + } + let buf: &mut[u8]; + unsafe { + buf = slice::from_raw_parts_mut(buffer, buffer_length); + } + return_vals::convert_syscall_result_to_ret_code(api::cwd(buf)) +} - api::mkdir(&path.to_string()) +pub fn sys_cd(path: *const u8) -> isize { + return_vals::convert_syscall_result_to_ret_code(api::cd(&ptr_to_string(path).unwrap())) } diff --git a/os/kernel/src/syscall/syscall_dispatcher.rs b/os/kernel/src/syscall/syscall_dispatcher.rs index c3bb2fc..d61a83e 100644 --- a/os/kernel/src/syscall/syscall_dispatcher.rs +++ b/os/kernel/src/syscall/syscall_dispatcher.rs @@ -3,7 +3,7 @@ ╟─────────────────────────────────────────────────────────────────────────╢ ║ Descr.: Low-level dispatcher for system calls. ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Fabian Ruhland, 15.9.2024, HHU ║ + ║ Author: Fabian Ruhland, 27.12.2024, HHU ║ ╚═════════════════════════════════════════════════════════════════════════╝ */ use core::arch::{asm, naked_asm}; @@ -18,9 +18,9 @@ use x86_64::{PrivilegeLevel, VirtAddr}; use crate::syscall::sys_vmem::sys_map_user_heap; use crate::syscall::sys_time::{sys_get_date, sys_get_system_time, sys_set_date, }; use crate::syscall::sys_concurrent::{sys_process_execute_binary, sys_process_exit, sys_process_id, sys_thread_create, sys_thread_exit, - sys_thread_id, sys_thread_join, sys_thread_sleep, sys_thread_switch,}; + sys_thread_id, sys_thread_join, sys_thread_sleep, sys_thread_switch}; use crate::syscall::sys_terminal::{sys_terminal_read, sys_terminal_write}; -use crate::syscall::sys_naming::sys_mkdir; +use crate::syscall::sys_naming::*; use crate::{core_local_storage, tss}; @@ -98,7 +98,16 @@ impl SyscallTable { sys_get_system_time as *const _, sys_get_date as *const _, sys_set_date as *const _, + sys_open as *const _, + sys_read as *const _, + sys_write as *const _, + sys_seek as *const _, + sys_close as *const _, sys_mkdir as *const _, + sys_touch as *const _, + sys_readdir as *const _, + sys_cwd as *const _, + sys_cd as *const _, ], } } diff --git a/os/library/naming/Cargo.toml b/os/library/naming/Cargo.toml index 2c60530..20b6350 100644 --- a/os/library/naming/Cargo.toml +++ b/os/library/naming/Cargo.toml @@ -11,3 +11,4 @@ terminal = { path = "../terminal" } # External depencies num_enum = { version = "0.7", default-features = false } +bitflags = "1.0" diff --git a/os/library/naming/src/lib.rs b/os/library/naming/src/lib.rs index f556dce..a4bc36a 100644 --- a/os/library/naming/src/lib.rs +++ b/os/library/naming/src/lib.rs @@ -3,25 +3,145 @@ ╟─────────────────────────────────────────────────────────────────────────╢ ║ Descr.: Syscalls for the naming service. ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, 15.9.2024, HHU ║ + ║ Author: Michael Schoettner, 28.12.2024, HHU ║ ╚═════════════════════════════════════════════════════════════════════════╝ */ #![no_std] -use syscall::{return_vals::Errno, syscall, SystemCall}; +extern crate alloc; + +#[macro_use] +extern crate bitflags; + +pub mod shared_types; + +use alloc::string::String; +use alloc::ffi::CString; +use core::mem; + +use shared_types::{DirEntry, FileType, OpenOptions, RawDirent, SeekOrigin}; +use syscall::{SystemCall, return_vals::Errno, syscall}; + + + +pub fn open(path: &str, flags: OpenOptions) -> Result { + match CString::new(path) { + Ok(c_path) => { + return syscall(SystemCall::Open, &[ + c_path.as_bytes().as_ptr() as usize, + flags.bits(), + ]); + } + Err(_) => Err(Errno::EBADSTR), + } +} + +pub fn write(fh: usize, buf: &[u8]) -> Result { + return syscall(SystemCall::Write, &[fh, buf.as_ptr() as usize, buf.len()]); +} + +pub fn read(fh: usize, buf: &mut [u8]) -> Result { + return syscall(SystemCall::Read, &[ + fh, + buf.as_mut_ptr() as usize, + buf.len(), + ]); +} + +pub fn seek(fh: usize, offset: usize, origin: SeekOrigin) -> Result { + return syscall(SystemCall::Seek, &[fh, offset, origin.into()]); +} + +pub fn close(fh: usize) -> Result { + return syscall(SystemCall::Close, &[fh]); +} pub fn mkdir(path: &str) -> Result { - // Check if params are valid - if path.is_empty() { - Err(Errno::EINVAL) // Abort, if not - } else { - // params OK, do the syscall - syscall( - SystemCall::MkDir, - &[ - path.as_bytes().as_ptr() as usize, - path.len(), - ], - ) + match CString::new(path) { + Ok(c_path) => { + return syscall(SystemCall::MkDir, &[c_path.as_bytes().as_ptr() as usize]); + } + Err(_) => Err(Errno::EBADSTR), } } + +pub fn touch(path: &str) -> Result { + match CString::new(path) { + Ok(c_path) => { + return syscall(SystemCall::Touch, &[c_path.as_bytes().as_ptr() as usize]); + } + Err(_) => Err(Errno::EBADSTR), + } +} + +pub fn readdir(fh: usize) -> Result, Errno> { + let mut raw_dirent = RawDirent::new(); + let ret = syscall(SystemCall::Readdir, &[ + fh, + raw_dirent.as_mut_ptr() as usize, + mem::size_of::(), + ]); + match ret { + Ok(rescode) => { + if rescode == 0 { + return Ok(None); + } else { + return Ok(DirEntry::from_dirent(&raw_dirent).clone()); + } + } + Err(e) => Err(e), + } +} + +impl DirEntry { + pub fn from_dirent(dirent: &RawDirent) -> Option { + // Convert d_type to a FileType enum + let file_type = match dirent.d_type { + 4 => FileType::Directory, + 8 => FileType::Regular, + 10 => FileType::Link, + _ => return None, // Return None for unsupported file types + }; + + // Convert d_name (null-terminated) to a Rust String + let name = dirent + .d_name + .iter() + .take_while(|&&c| c != 0) // Stop at the null terminator + .map(|&c| c as char) + .collect::(); + + // If the name is empty, return None + if name.is_empty() { + return None; + } + + Some(DirEntry { file_type, name }) + } +} + +pub fn cwd() -> Result { + let buf: [u8; 512] = [0; 512]; // buffer for the path + let result = syscall(SystemCall::Cwd, &[ buf.as_ptr() as usize, buf.len(), ]); + match result { + Ok(_) => { + // Convert d_name (null-terminated) to a Rust String + let name = buf + .iter() + .take_while(|&&c| c != 0) // Stop at the null terminator + .map(|&c| c as char) + .collect::(); + return Ok(name); + }, + Err(e) => Err(e), + } +} + +pub fn cd(path: &str) -> Result { + match CString::new(path) { + Ok(c_path) => { + return syscall(SystemCall::Cd, &[c_path.as_bytes().as_ptr() as usize]); + } + Err(_) => Err(Errno::EBADSTR), + } +} \ No newline at end of file diff --git a/os/library/naming/src/shared_types.rs b/os/library/naming/src/shared_types.rs new file mode 100644 index 0000000..b05c26b --- /dev/null +++ b/os/library/naming/src/shared_types.rs @@ -0,0 +1,72 @@ +/* ╔═════════════════════════════════════════════════════════════════════════╗ + ║ Module: shared_types ║ + ╟─────────────────────────────────────────────────────────────────────────╢ + ║ Descr.: Types used by the naming service both in user und kernel mode. ║ + ╟─────────────────────────────────────────────────────────────────────────╢ + ║ Author: Michael Schoettner, 28.12.2024, HHU ║ + ╚═════════════════════════════════════════════════════════════════════════╝ +*/ +use alloc::string::String; +use num_enum::{FromPrimitive, IntoPrimitive}; + +bitflags! { + /// Description: Option flags for opening objects + pub struct OpenOptions: usize { + const READONLY = 1; + const READWRITE = 2; + const CREATE = 3; + const EXCLUSIVE = 4; + const DIRECTORY = 5; + } +} + +/// Description: origin for `seek` +#[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, FromPrimitive)] +#[repr(usize)] +pub enum SeekOrigin { + #[num_enum(default)] + Start = 1, + End = 2, + Current = 3, +} + +/// File types +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[repr(u8)] +#[non_exhaustive] +pub enum FileType { + Directory = 4, + Regular = 8, + Link = 10, +} + +/// A directory entry +#[derive(Debug, Clone)] +pub struct DirEntry { + pub file_type: FileType, + pub name: String, +} + + +/// Description: internally used for `readdir` syscall for passing data between kernel and user space +// 256 name length limit, see: https://man7.org/linux/man-pages/man3/readdir.3.html +#[derive(Debug)] +#[repr(C)] +pub struct RawDirent { + pub d_type: usize, // type of file + pub d_name: [u8; 256], // null terminated entry name +} + +impl RawDirent { + pub fn new() -> Self { + RawDirent { + d_type: 0, + d_name: [0; 256], + } + } + + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self as *mut RawDirent as *mut u8 + } +} + diff --git a/os/library/syscall/src/lib.rs b/os/library/syscall/src/lib.rs index 900b879..707a057 100644 --- a/os/library/syscall/src/lib.rs +++ b/os/library/syscall/src/lib.rs @@ -3,14 +3,11 @@ ╟─────────────────────────────────────────────────────────────────────────╢ ║ Descr.: Syscall interface in user mode. ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Fabian Ruhland, Michael Schoettner, 10.09.2024, HHU ║ + ║ Author: Fabian Ruhland, Michael Schoettner, 30.12.2024, HHU ║ ╚═════════════════════════════════════════════════════════════════════════╝ */ #![no_std] -#[macro_use] -extern crate bitflags; - pub mod return_vals; use core::arch::asm; @@ -35,8 +32,16 @@ pub enum SystemCall { GetSystemTime, GetDate, SetDate, + Open, + Read, + Write, + Seek, + Close, MkDir, - + Touch, + Readdir, + Cwd, + Cd, // no syscall, just marking last number, see NUM_SYSCALLS // insert any new system calls before this marker LastEntryMarker, diff --git a/os/library/syscall/src/return_vals.rs b/os/library/syscall/src/return_vals.rs index b07a251..48da4f3 100644 --- a/os/library/syscall/src/return_vals.rs +++ b/os/library/syscall/src/return_vals.rs @@ -3,46 +3,35 @@ ╟─────────────────────────────────────────────────────────────────────────╢ ║ Descr.: Consts and types for syscall return values. ║ ╟─────────────────────────────────────────────────────────────────────────╢ - ║ Author: Michael Schoettner, 15.09.2024, HHU ║ + ║ Author: Michael Schoettner, 27.12.2024, HHU ║ ╚═════════════════════════════════════════════════════════════════════════╝ */ use num_enum::{FromPrimitive, IntoPrimitive}; +/// Description: error codes for syscalls #[derive(Debug, Copy, Clone, Eq, PartialEq, IntoPrimitive, FromPrimitive)] #[repr(isize)] pub enum Errno { #[num_enum(default)] - EUNKN = -1, // Unknown error - ENOENT = -2, // No such file or directory - ENOHANDLES = -3, // No more free handles - EACCES = -13, // Permission denied - EEXIST = -17, // File/directory exists - ENOTDIR = -20, // Not a directory - EINVAL = -22, // Invalid argument - EINVALH = -23, // Invalid handle - ENOTEMPTY = -90, // Directory not empty + EUNKN = -1, // Unknown error + ENOENT = -2, // No such file or directory + ENOHANDLES = -3, // No more free handles + EBADF = -4, // Bad file descriptor for an operation + EACCES = -5, // Permission denied + EEXIST = -6, // File/directory exists + ENOTDIR = -7, // Not a directory + EINVAL = -8, // Invalid argument + EINVALH = -9, // Invalid handle + ENOTEMPTY = -10, // Directory not empty + EBADSTR = -11, // Bad string } -bitflags! { - /// Description: Option flags for opening objects - pub struct OpenOptions: u32 { - const READONLY = 0x01; - const READWRITE = 0x02; - const CREATE = 0x04; - } -} - -/// Enumeration of possible methods to seek within an I/O object. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum SeekOrigin { - Start, - End, - Current, -} +/// Description: Result type for syscalls pub type SyscallResult = Result; +/// Description: convert a return code to a syscall result pub fn convert_ret_code_to_syscall_result(ret_code: isize) -> SyscallResult { if ret_code < 0 { Err(Errno::from(ret_code)) @@ -51,6 +40,7 @@ pub fn convert_ret_code_to_syscall_result(ret_code: isize) -> SyscallResult { } } +/// Description: convert a syscall result to a return code pub fn convert_syscall_result_to_ret_code(syscall_result: SyscallResult) -> isize { let ret_val: isize; match syscall_result {