diff --git a/ableos/Cargo.toml b/ableos/Cargo.toml index 665889b5..a50c735c 100644 --- a/ableos/Cargo.toml +++ b/ableos/Cargo.toml @@ -58,7 +58,7 @@ test-args = [ [dependencies] lazy_static = { version = "1.4.0", features = ["spin_no_std"] } qrcode = { path = "../qrcode-rust" } -bitflags = "1.2.1" +bitflags = "1.3" lliw = "0.2.0" spin = "0.9" pretty-hex = "0.2.1" diff --git a/ableos/src/filesystem/errors.rs b/ableos/src/filesystem/errors.rs new file mode 100644 index 00000000..3f2fb406 --- /dev/null +++ b/ableos/src/filesystem/errors.rs @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022, Umut İnan Erdoğan + * + * SPDX-License-Identifier: MPL-2.0 + */ + +#[derive(Copy, Clone, Debug)] +pub enum FsError { + EndOfFile, + InodeNotFound, + InvalidDevice, + InvalidPath, + IsDirectory, + NotAbsolute, + NotADirectory, + NotFound, + Recursion, + UnsupportedOperation, +} + +impl Into for ext2::error::Error { + fn into(self) -> FsError { + match self { + ext2::error::Error::Other(_) => todo!(), + ext2::error::Error::BadMagic { magic: _ } => todo!(), + ext2::error::Error::OutOfBounds { index: _ } => todo!(), + ext2::error::Error::AddressOutOfBounds { + sector: _, + offset: _, + size: _, + } => todo!(), + ext2::error::Error::BadBlockGroupCount { + by_blocks: _, + by_inodes: _, + } => todo!(), + ext2::error::Error::InodeNotFound { inode: _ } => FsError::InodeNotFound, + ext2::error::Error::NotADirectory { inode: _, name: _ } => FsError::NotADirectory, + ext2::error::Error::NotAbsolute { name: _ } => todo!(), + ext2::error::Error::NotFound { name: _ } => FsError::NotFound, + } + } +} diff --git a/ableos/src/filesystem/ext2.rs b/ableos/src/filesystem/ext2.rs new file mode 100644 index 00000000..76dc14f2 --- /dev/null +++ b/ableos/src/filesystem/ext2.rs @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2022, Umut İnan Erdoğan + * + * SPDX-License-Identifier: MPL-2.0 + */ + +use alloc::sync::{Arc, Weak}; +use ext2::fs::{sync::Synced, Ext2}; +use ext2::sector::SectorSize; +use ext2::sys::inode::TypePerm; +use ext2::volume::Volume; + +use crate::handle::{Handle, HandleResource}; + +use super::errors::FsError; +use super::vfs::{DirectoryEntry, FsFlags, VirtualFileSystem, VFS}; +use super::{FsNode, FsResult as Result, StorageDevice}; + +pub struct Ext2StorageDevice +where + S: SectorSize, + V: Volume, +{ + fs: Synced>, + device_handle: Handle, + root_handle: Handle, +} + +impl Ext2StorageDevice +where + S: SectorSize, + V: Volume, +{ + pub fn new(volume: V) -> Result { + let fs = Synced::new(volume).map_err(|e| e.into())?; + let root_inode = fs.root_inode(); + let device_handle = Handle::new(HandleResource::StorageDevice); + let root = Arc::new(FsNode::new( + FsFlags::DIRECTORY, + root_inode.size(), + 2, + device_handle, + Weak::new(), + )); + + let mut vfs = VFS.lock(); + let root_handle = vfs.add_fs_node(root.clone()); + + Ok(Self { + fs, + device_handle, + root_handle, + }) + } +} + +impl StorageDevice for Ext2StorageDevice +where + S: SectorSize + Send, + V: Volume + Send, +{ + fn read(&self, node: &FsNode, offset: usize, size: usize, buffer: &mut Vec) -> Result<()> { + let inode = self + .fs + .inode_nth(node.inode() as usize) + .ok_or_else(|| FsError::InodeNotFound)?; + + // FIXME: I don't really know how Ext2 works, so for now we don't + // support non-zero offsets and buffer sizes that don't match + // the file length. We always read the whole file. + if offset > 0 || size != inode.size() { + Err(FsError::UnsupportedOperation)?; + } + + inode.read_to_end(buffer).map_err(|e| e.into())?; + Ok(()) + } + + fn write(&self, _node: &FsNode, _offset: usize, _buffer: &[u8]) -> Result<()> { + todo!() + } + + fn read_dir(&self, node: &FsNode, index: usize) -> Result { + let inode = self + .fs + .inode_nth(node.inode() as usize) + .ok_or_else(|| FsError::InodeNotFound)?; + let mut dir = inode.directory().ok_or_else(|| FsError::NotADirectory)?; + let entry = dir + .nth(index) + .ok_or_else(|| FsError::InodeNotFound)? + .map_err(|e| e.into())?; + let entry_inode = self + .fs + .inode_nth(entry.inode) + .ok_or_else(|| FsError::InodeNotFound)?; + let mut vfs = VFS.lock(); + let entry_node_handle = vfs + .find_fs_node(entry.inode, self.device_handle) + .unwrap_or_else(|| { + vfs.add_fs_node(Arc::new(FsNode::new( + ext2_type_to_fs_flags(entry_inode.type_perm()), + entry_inode.size(), + entry.inode, + self.device_handle, + Weak::new(), + ))) + }); + + Ok(DirectoryEntry::new( + entry.file_name_string(), + entry_node_handle, + )) + } + + fn find_dir(&self, vfs: &mut VirtualFileSystem, node: &FsNode, name: &str) -> Result { + let inode = self + .fs + .inode_nth(node.inode() as usize) + .ok_or_else(|| FsError::InodeNotFound)?; + let dir = inode.directory().ok_or_else(|| FsError::NotADirectory)?; + let mut found_node = Err(FsError::NotFound); + for entry in dir { + if entry.is_err() { + continue; + } + + let entry = entry.unwrap(); + let entry_inode = self + .fs + .inode_nth(entry.inode as usize) + .ok_or_else(|| FsError::InodeNotFound)?; + if entry.file_name_string() == name { + found_node = Ok(vfs + .find_fs_node(entry.inode, self.device_handle) + .unwrap_or_else(|| { + vfs.add_fs_node(Arc::new(FsNode::new( + ext2_type_to_fs_flags(entry_inode.type_perm()), + entry_inode.size(), + entry.inode, + self.device_handle, + Weak::new(), + ))) + })); + break; + } + } + + found_node + } + + fn root(&self) -> Handle { + self.root_handle + } + + fn device_handle(&self) -> Handle { + self.device_handle + } +} + +fn ext2_type_to_fs_flags(type_perm: TypePerm) -> FsFlags { + trace!("{type_perm:?}"); + let is_directory = type_perm & TypePerm::DIRECTORY == TypePerm::DIRECTORY; + let is_symlink = type_perm & TypePerm::SYMLINK == TypePerm::SYMLINK; + let t = if is_directory { + FsFlags::DIRECTORY + } else { + FsFlags::FILE + }; + let s = if is_symlink { + FsFlags::SYMBOLIC_LINK + } else { + FsFlags::empty() + }; + + t | s +} diff --git a/ableos/src/filesystem/mod.rs b/ableos/src/filesystem/mod.rs index b712e683..c7350646 100644 --- a/ableos/src/filesystem/mod.rs +++ b/ableos/src/filesystem/mod.rs @@ -1,42 +1,90 @@ -use ext2::{ - fs::{ - sync::{Inode, Synced}, - Ext2, - }, - sector::{SectorSize, Size1024}, - volume::Volume, +/* + * Copyright (c) 2022, Umut İnan Erdoğan + * + * SPDX-License-Identifier: MPL-2.0 + */ + +pub mod errors; +pub mod ext2; +pub mod vfs; + +use ::ext2::sector::Size1024; +use alloc::sync::Arc; + +use crate::{filesystem::vfs::VFS, handle::Handle, KERNEL_STATE}; + +use self::{ + errors::FsError, + ext2::Ext2StorageDevice, + vfs::{DirectoryEntry, FsNode, VirtualFileSystem}, }; -use spin::Lazy; +use FsResult as Result; -pub static FILE_SYSTEM: Lazy>>>> = - Lazy::new(|| spin::Mutex::new(load_fs())); +pub type FsResult = core::result::Result; -pub fn walk>( - fs: &Synced>, - inode: Inode, - name: String, -) { - if let Some(dir) = inode.directory() { +/// The methods on this trait are to be used internally. +pub trait StorageDevice +where + Self: Send, +{ + fn read(&self, node: &FsNode, offset: usize, size: usize, buffer: &mut Vec) -> Result<()>; + fn write(&self, node: &FsNode, offset: usize, buffer: &[u8]) -> Result<()>; + fn read_dir(&self, node: &FsNode, index: usize) -> Result; + fn find_dir(&self, vfs: &mut VirtualFileSystem, node: &FsNode, name: &str) -> Result; + // TODO: flush to disk + + fn root(&self) -> Handle; + fn device_handle(&self) -> Handle; +} + +pub fn init() -> Result<()> { + let mut state = KERNEL_STATE.lock(); + let fs = load_fs()?; + let mut vfs = VFS.lock(); + vfs.set_root(fs.root())?; + state.add_storage_device(fs); + Ok(()) +} + +fn load_fs() -> Result>> { + let mut volume = Vec::new(); + volume.extend_from_slice(include_bytes!("../../../userland/root_fs/ext2.img")); + + Ext2StorageDevice::new(volume) +} + +pub fn tree(path: &str) -> Result<()> { + let dir = { + let mut vfs = VFS.lock(); + let handle = vfs.resolve(path)?; + vfs.fs_node(handle).ok_or_else(|| FsError::NotFound)? + }; + + tree_inner( + dir, + if path.starts_with('/') { + &path[1..] + } else { + path + }, + ); + Ok(()) +} + +fn tree_inner>(dir: Arc, path: S) { + let path = path.into(); + if let Some(dir) = dir.directory() { for entry in dir { - assert!(entry.is_ok()); - let entry = entry.unwrap(); - let entry_name = String::from_utf8_lossy(&entry.name); + let fs_node = { + let vfs = VFS.lock(); + vfs.fs_node(entry.node()).unwrap() + }; - println!("{}/{} => {}", name, entry_name, entry.inode,); - if entry_name != "." && entry_name != ".." { - walk( - fs, - fs.inode_nth(entry.inode).unwrap(), - format!("{}/{}", name, entry_name), - ); + println!("{}/{} => {}", path, entry.name(), fs_node.inode()); + trace!("{entry:#?}"); + if entry.name() != "." && entry.name() != ".." { + tree_inner(fs_node, format!("{}/{}", path, entry.name())); } } } } - -fn load_fs() -> Synced>> { - let mut volume = Vec::new(); - volume.extend_from_slice(include_bytes!("../../../userland/root_fs/ext2.img")); - - Synced::>::new(volume).unwrap() -} diff --git a/ableos/src/filesystem/vfs.rs b/ableos/src/filesystem/vfs.rs new file mode 100644 index 00000000..5a8fdeb7 --- /dev/null +++ b/ableos/src/filesystem/vfs.rs @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2022, Umut İnan Erdoğan + * + * SPDX-License-Identifier: MPL-2.0 + */ + +use core::cmp; + +use alloc::sync::{Arc, Weak}; +use hashbrown::HashMap; +use lazy_static::lazy_static; +use spin::Mutex; + +use super::{errors::FsError, FsResult as Result}; +use crate::{ + handle::{Handle, HandleResource}, + KERNEL_STATE, +}; + +/// The limit for symlink recursion. In POSIX, this is at least 8. In Linux, this is 40. +const SYMLINK_RECURSION_LIMIT: u8 = 8; + +lazy_static! { + pub static ref VFS: Mutex = Default::default(); +} + +pub struct VirtualFileSystem { + fs_nodes: HashMap>, + root_node: Weak, + root_handle: Option, +} + +impl VirtualFileSystem { + /// Sets the VFS root to the given VFS node handle. + pub fn set_root(&mut self, handle: Handle) -> Result<()> { + let root_node = self.fs_node(handle).ok_or_else(|| FsError::NotFound)?; + self.root_node = Arc::downgrade(&root_node); + self.root_handle = Some(handle); + Ok(()) + } + + /// Resolves the path to a handle. If the resulting node is a symlink, + /// the symlink itself is returned. All symlinks but the resulting node + /// are traversed. + /// + /// Requires a mutable reference because internally it might open new VFS + /// nodes while resolving the path. + pub fn resolve>(&mut self, path: S) -> Result { + // TODO: caching + let path = path.as_ref(); + + if !path.starts_with('/') { + // FIXME: use current process working directory for relative paths? + Err(FsError::NotAbsolute)?; + } + + let mut components = path.split_terminator('/'); + components.next(); // throw the empty string caused by the root / + // will be initialised beforehand so okay to unwrap + let mut resolved_node = self.root_handle.unwrap(); + // let mut symlink_recursion_level = 0; + for component in components { + // if symlink_recursion_level >= SYMLINK_RECURSION_LIMIT { + // Err(FsError::Recursion)?; + // } + + if component == "" { + Err(FsError::InvalidPath)?; + } + + // checked by previous iteration so okay to unwrap + let parent = self.fs_node(resolved_node).unwrap(); + + // TODO: permission checks + + // FIXME: find_dir checks that this is a directory already but + // that's just more boilerplate in StorageDevice impls + // we should probably check that here instead to reduce + // required boilerplate + // if !parent.is_dir() { + // Err(FsError::NotADirectory)?; + // } + + // FIXME: handle mount points + // FIXME: handle symlinks + + resolved_node = parent.find_dir(self, component)?; + } + + Ok(resolved_node) + } + + pub fn add_fs_node(&mut self, fs_node: Arc) -> Handle { + let handle = Handle::new(HandleResource::FsNode); + self.fs_nodes.insert(handle, fs_node); + handle + } + + pub fn find_fs_node(&mut self, inode: usize, device_handle: Handle) -> Option { + self.fs_nodes.iter().find_map(|(handle, fs_node)| { + if fs_node.inode == inode && fs_node.device_handle == device_handle { + Some(*handle) + } else { + None + } + }) + } + + pub fn fs_node(&self, handle: Handle) -> Option> { + self.fs_nodes.get(&handle).cloned() + } + + pub fn root_node(&self) -> Arc { + // okay to unwrap since this should never be called before init + self.root_node.upgrade().unwrap() + } +} + +impl Default for VirtualFileSystem { + fn default() -> Self { + Self { + fs_nodes: HashMap::new(), + root_node: Weak::new(), + root_handle: None, + } + } +} + +/// A VFS node, that can either be a file or a directory. +/// +/// VFS nodes are created whenever a file that doesn't have an open VFS node is +/// opened. When there are no open file descriptors to a file, the associated +/// VFS node is dropped. +#[derive(Debug)] +pub struct FsNode { + flags: FsFlags, + size: usize, // in bytes + inode: usize, // implementation specific identifier for the node + device_handle: Handle, // uniquely assigned device handle + ptr: Weak, // used by mountpoints and symlinks + // TODO: permissions mask + // TODO: owning user/group +} + +impl FsNode { + pub fn new( + flags: FsFlags, + size: usize, + inode: usize, + device_handle: Handle, + ptr: Weak, + ) -> Self { + Self { + flags, + size, + inode, + device_handle, + ptr, + } + } + + /// This method opens a new file descriptor for this VFS node. + // TODO: make this take flags + pub fn open(self: Arc) -> Result { + let mut state = KERNEL_STATE.lock(); + let handle = state.open_file_descriptor(self); + + Ok(handle) + } + + /// This method reads from the VFS node without opening a new file + /// descriptor. This is intended to be used internally, if you're trying to + /// read a file then you probably want to read from a file descriptor. + pub fn read(&self, offset: usize, size: usize, buffer: &mut Vec) -> Result { + let state = KERNEL_STATE.lock(); + let device = state + .storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + if self.is_dir() { + Err(FsError::IsDirectory)?; + } + + if offset > self.size { + Err(FsError::EndOfFile)?; + } + + let readable_size = cmp::min(size, self.size - offset); + device.read(self, offset, readable_size, buffer)?; + Ok(readable_size) + } + + /// This method writes to the VFS node without opening a new file + /// descriptor. This is intended to be used internally, if you're trying to + /// write to a file then you probably want to write to a file descriptor. + pub fn write(&self, offset: usize, buffer: &[u8]) -> Result<()> { + let state = KERNEL_STATE.lock(); + let device = state + .storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + device.write(self, offset, buffer) + } + + pub fn read_dir(&self, index: usize) -> Result { + let state = KERNEL_STATE.lock(); + let device = state + .storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + device.read_dir(self, index) + } + + pub fn find_dir(&self, vfs: &mut VirtualFileSystem, name: &str) -> Result { + let state = KERNEL_STATE.lock(); + let device = state + .storage_device(self.device_handle) + .ok_or_else(|| FsError::InvalidDevice)?; + + device.find_dir(vfs, self, name) + } + + pub fn directory(self: Arc) -> Option { + if self.is_dir() { + Some(Directory::new(self)) + } else { + None + } + } + + pub fn is_dir(&self) -> bool { + (self.flags & FsFlags::DIRECTORY) == FsFlags::DIRECTORY + } + + pub fn inode(&self) -> usize { + self.inode + } + + pub fn size(&self) -> usize { + self.size + } +} + +impl Drop for FsNode { + fn drop(&mut self) { + trace!("dropping VFS node: {self:#?}"); + // TODO: flush to disk here + } +} + +bitflags! { + /// Flags associated with VFS nodes and file descriptors + /// + /// 0x00000MST \ + /// T is set to 0 for files, 1 for directories \ + /// S is set when the file is a symbolic link \ + /// M is set if the file is an active mount point + pub struct FsFlags: u8 { + const FILE = 0b00000000; + const DIRECTORY = 0b00000001; + const SYMBOLIC_LINK = 0b00000010; + const MOUNT_POINT = 0b00000100; + } +} + +pub struct Directory { + node: Arc, + index: usize, +} + +impl Directory { + fn new(node: Arc) -> Self { + Self { node, index: 0 } + } +} + +impl Iterator for Directory { + type Item = DirectoryEntry; + + fn next(&mut self) -> Option { + let value = self.node.read_dir(self.index).ok(); + self.index += 1; + value + } +} + +#[derive(Clone, Debug)] +pub struct DirectoryEntry { + name: String, + node: Handle, +} + +impl DirectoryEntry { + pub(super) fn new(name: String, node: Handle) -> Self { + Self { name, node } + } + + pub fn name(&self) -> String { + self.name.clone() + } + + pub fn node(&self) -> Handle { + self.node + } +} diff --git a/ableos/src/handle.rs b/ableos/src/handle.rs index dda62396..4c30c730 100644 --- a/ableos/src/handle.rs +++ b/ableos/src/handle.rs @@ -1,3 +1,9 @@ +/* + * Copyright (c) 2022, Umut İnan Erdoğan + * + * SPDX-License-Identifier: MPL-2.0 + */ + //! A handle is a u128 with a set of permissions //! and a resource connected to it @@ -14,8 +20,10 @@ pub struct BinaryData { pub enum HandleResource { Channel, Socket, - BinaryData, + StorageDevice, + FileDescriptor, + FsNode, } #[derive(Debug, Eq, Hash, PartialEq, Clone, Copy)] @@ -30,16 +38,19 @@ impl Display for Handle { match &self.res { HandleResource::Channel => write!(f, "-Channel")?, HandleResource::BinaryData => write!(f, "-Binary")?, - Socket => write!(f, "-Socket")?, + HandleResource::Socket => write!(f, "-Socket")?, + HandleResource::StorageDevice => write!(f, "-StorageDevice")?, + HandleResource::FileDescriptor => write!(f, "-FileDescriptor")?, + HandleResource::FsNode => write!(f, "-FsNode")?, } Ok(()) } } -use crate::handle::HandleResource::*; impl Handle { pub fn new(htype: HandleResource) -> Self { + // FIXME: check if inner collides Self { inner: generate_process_pass(), res: htype, diff --git a/ableos/src/kernel_state.rs b/ableos/src/kernel_state.rs index 52ec3712..9b40ece0 100644 --- a/ableos/src/kernel_state.rs +++ b/ableos/src/kernel_state.rs @@ -1,10 +1,26 @@ +/* + * Copyright (c) 2022, Umut İnan Erdoğan + * + * SPDX-License-Identifier: MPL-2.0 + */ + +use alloc::sync::Arc; +use hashbrown::HashMap; use spin::Lazy; +use crate::{ + filesystem::{vfs::FsNode, StorageDevice}, + handle::{Handle, HandleResource}, +}; + pub static KERNEL_STATE: Lazy> = Lazy::new(|| spin::Mutex::new(KernelInternalState::new())); pub struct KernelInternalState { pub hostname: String, + pub storage_devices: HashMap>, + // FIXME: should this be per-process? + file_table: HashMap, should_shutdown: bool, } @@ -12,6 +28,8 @@ impl KernelInternalState { pub fn new() -> Self { Self { should_shutdown: false, + storage_devices: HashMap::new(), + file_table: HashMap::new(), hostname: "".to_string(), } } @@ -19,9 +37,35 @@ impl KernelInternalState { pub fn set_hostname(&mut self, hostname: String) { self.hostname = hostname; } + + pub fn add_storage_device(&mut self, device: impl StorageDevice + Send + 'static) { + let device = Box::new(device); + self.storage_devices.insert(device.device_handle(), device); + } + + pub fn storage_device(&self, handle: Handle) -> Option<&dyn StorageDevice> { + self.storage_devices.get(&handle).map(|d| &**d) + } + + // TODO: implement flags here + pub fn open_file_descriptor(&mut self, fs_node: Arc) -> Handle { + let handle = Handle::new(HandleResource::FileDescriptor); + self.file_table.insert(handle, FileTableEntry::new(fs_node)); + handle + } + + pub fn file_descriptor(&self, handle: Handle) -> Option<&FileTableEntry> { + self.file_table.get(&handle) + } + + pub fn close_file_descriptor(&mut self, handle: Handle) { + self.file_table.remove(&handle); + } + pub fn shutdown(&mut self) { self.should_shutdown = true; } + pub fn update_state(&mut self) { if self.should_shutdown { crate::arch::shutdown(); @@ -34,3 +78,18 @@ impl Default for KernelInternalState { Self::new() } } + +pub struct FileTableEntry { + fs_node: Arc, + // TODO: permissions, flags, owner, maybe cache stuff here? +} + +impl FileTableEntry { + fn new(fs_node: Arc) -> Self { + Self { fs_node } + } + + pub fn fs_node(&self) -> Arc { + self.fs_node.clone() + } +} diff --git a/ableos/src/kmain.rs b/ableos/src/kmain.rs index 9a4cc1d2..0115208a 100644 --- a/ableos/src/kmain.rs +++ b/ableos/src/kmain.rs @@ -1,13 +1,19 @@ +/* + * Copyright (c) 2022, Umut İnan Erdoğan + * + * SPDX-License-Identifier: MPL-2.0 + */ + #![allow(clippy::empty_loop)] use core::sync::atomic::AtomicU64; use crate::arch::{drivers::sysinfo::master, init, sloop}; -use crate::hardware; use crate::relib::network::socket::{SimpleSock, Socket}; use crate::{ boot_conf::KernelConfig, scratchpad, systeminfo::RELEASE_TYPE, TERM, }; +use crate::{filesystem, hardware}; use kernel::KERNEL_VERSION; use spin::Lazy; @@ -36,6 +42,9 @@ pub fn kernel_main() -> ! { x86_64::instructions::interrupts::without_interrupts(|| { hardware::init_mouse(); }); + + filesystem::init().unwrap(); + /* // println!("abc"); diff --git a/ableos/src/lib.rs b/ableos/src/lib.rs index 2ab6915a..201ece87 100644 --- a/ableos/src/lib.rs +++ b/ableos/src/lib.rs @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, Able + * Copyright (c) 2022, Umut İnan Erdoğan * * SPDX-License-Identifier: MPL-2.0 */ @@ -25,6 +26,9 @@ #[macro_use] pub extern crate log; +#[macro_use] +pub extern crate bitflags; + pub extern crate alloc; pub extern crate externc_libm as libm; diff --git a/ableos/src/relib/time/kilotime.rs b/ableos/src/relib/time/kilotime.rs index bb39533e..06cae00d 100644 --- a/ableos/src/relib/time/kilotime.rs +++ b/ableos/src/relib/time/kilotime.rs @@ -5,57 +5,57 @@ use core::fmt::{Display, Error, Formatter}; #[repr(transparent)] pub struct Kilosecond(u32); impl Kilosecond { - pub fn from_ms(ms: u32) -> Self { - Self(ms) - } + pub fn from_ms(ms: u32) -> Self { + Self(ms) + } - pub fn from_sec(sec: u32) -> Self { - Self(sec * 1000) - } + pub fn from_sec(sec: u32) -> Self { + Self(sec * 1000) + } - pub fn from_minutes(min: u32) -> Self { - Self(min * 60 * 1000) - } + pub fn from_minutes(min: u32) -> Self { + Self(min * 60 * 1000) + } - pub fn from_hours(hrs: u32) -> Self { - Self(hrs * 60 * 60 * 1000) - } + pub fn from_hours(hrs: u32) -> Self { + Self(hrs * 60 * 60 * 1000) + } - pub fn from_days(days: u32) -> Self { - Self(days * 24 * 60 * 60 * 1000) - } + pub fn from_days(days: u32) -> Self { + Self(days * 24 * 60 * 60 * 1000) + } - pub fn s(&self) -> u32 { - (self.0 % 1000) - } + pub fn s(&self) -> u32 { + (self.0 % 1000) + } - pub fn k(&self) -> u32 { - self.0 / 1000 - } + pub fn k(&self) -> u32 { + self.0 / 1000 + } } impl Display for Kilosecond { - fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { - write![f, "{}K {}S", self.k(), self.s()] - } + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write![f, "{}K {}S", self.k(), self.s()] + } } impl core::ops::Add for Kilosecond { - type Output = Self; - fn add(self, rhs: Self) -> Self { - Self(self.0 + rhs.0) - } + type Output = Self; + fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0) + } } impl core::ops::Sub for Kilosecond { - type Output = Self; - fn sub(self, rhs: Self) -> Self { - Self(self.0 - rhs.0) - } + type Output = Self; + fn sub(self, rhs: Self) -> Self { + Self(self.0 - rhs.0) + } } impl From