diff --git a/Cargo.toml b/Cargo.toml index 38a79f29..b78ae0fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,5 +88,14 @@ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] } [workspace.lints.clippy] derivable_impls = { level = "allow" } +[profile.dev] +opt-level = 1 +split-debuginfo = "packed" +lto = "thin" +panic = "abort" + +[profile.dev.package."*"] +opt-level = 3 + # [profile.dev] # opt-level = "s" diff --git a/kernel/driver/fs/ext2/src/data.rs b/kernel/driver/fs/ext2/src/data.rs index 7aa8fc46..f007371a 100644 --- a/kernel/driver/fs/ext2/src/data.rs +++ b/kernel/driver/fs/ext2/src/data.rs @@ -274,6 +274,11 @@ impl InodeMode { } } + pub fn update_permissions(&mut self, mode: FileMode) { + self.0 &= !0o777; + self.0 |= (mode.bits() & 0o777) as u16; + } + pub fn permissions(&self) -> FileMode { unsafe { FileMode::from_raw(self.0 as u32 & 0o777) } } diff --git a/kernel/driver/fs/ext2/src/dir.rs b/kernel/driver/fs/ext2/src/dir.rs index eb4e6cf9..a59282ec 100644 --- a/kernel/driver/fs/ext2/src/dir.rs +++ b/kernel/driver/fs/ext2/src/dir.rs @@ -414,6 +414,8 @@ impl DirectoryNode { // Decrement the refcount on the inode let mut holder = self.inode.cache().get_mut(ino).await?; { + // TODO put the inode into the "free list" to be freed on cache flush/when no one is + // holding a ref to it any longer let mut inode = holder.write(); inode.hard_links = inode.hard_links.saturating_sub(1); inode.dtime = real_time().seconds as _; @@ -539,6 +541,10 @@ impl CommonImpl for DirectoryNode { let inode = inode.read(); Ok(inode.metadata(&self.fs, self.inode.ino())) } + + fn set_metadata(&self, node: &NodeRef, metadata: &Metadata) -> Result<(), Error> { + block!(self.inode.update_metadata(metadata).await)? + } } impl DirectoryImpl for DirectoryNode { diff --git a/kernel/driver/fs/ext2/src/file.rs b/kernel/driver/fs/ext2/src/file.rs index 9ec009cd..cff63410 100644 --- a/kernel/driver/fs/ext2/src/file.rs +++ b/kernel/driver/fs/ext2/src/file.rs @@ -86,6 +86,10 @@ impl CommonImpl for RegularNode { Ok(inode.metadata(&self.fs, self.inode.ino())) } + fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> { + block!(self.inode.update_metadata(metadata).await)? + } + fn as_any(&self) -> &dyn Any { self } diff --git a/kernel/driver/fs/ext2/src/inode.rs b/kernel/driver/fs/ext2/src/inode.rs index 39e434fe..6755508a 100644 --- a/kernel/driver/fs/ext2/src/inode.rs +++ b/kernel/driver/fs/ext2/src/inode.rs @@ -4,14 +4,15 @@ use core::{ }; use alloc::sync::Arc; -use libk::{block, error::Error, task::sync::AsyncMutex}; +use libk::{block, error::Error, task::sync::AsyncMutex, vfs::Metadata}; use libk_util::{ lru_hash_table::LruCache, sync::spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard, IrqSafeRwLockWriteGuard}, }; +use yggdrasil_abi::io::FileType; use crate::{ - data::{FsReadonlyFeatures, DIRECT_BLOCK_COUNT}, + data::{FsReadonlyFeatures, InodeMode, DIRECT_BLOCK_COUNT}, Ext2Fs, Inode, }; @@ -79,6 +80,33 @@ impl InodeAccess { pub async fn get_mut(&self) -> Result { self.inode_cache.get_mut(self.ino).await } + + pub async fn update_metadata(&self, metadata: &Metadata) -> Result<(), Error> { + let uid = metadata + .uid + .bits() + .try_into() + .map_err(|_| Error::InvalidArgument)?; + let gid = metadata + .gid + .bits() + .try_into() + .map_err(|_| Error::InvalidArgument)?; + + let mut holder = self.get_mut().await?; + { + let mut inode = holder.write(); + + inode.mtime = metadata.mtime as _; + inode.atime = metadata.mtime as _; + inode.ctime = metadata.ctime as _; + + inode.mode.update_permissions(metadata.mode); + inode.uid = uid; + inode.gid = gid; + } + holder.put().await + } } impl InodeCache { diff --git a/kernel/driver/fs/ext2/src/lib.rs b/kernel/driver/fs/ext2/src/lib.rs index 6d1d4349..77876d1a 100644 --- a/kernel/driver/fs/ext2/src/lib.rs +++ b/kernel/driver/fs/ext2/src/lib.rs @@ -14,6 +14,7 @@ use inode::{InodeAccess, InodeCache}; use libk::{ device::block::{cache::DeviceMapper, BlockDevice}, error::Error, + time::real_time, vfs::{Filesystem, FilesystemMountOption, NodeRef}, }; use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit}; @@ -608,6 +609,7 @@ impl Ext2Fs { log::info!("Allocated inode #{ino}"); let access = InodeAccess::new(inode_cache.clone(), ino); + let now = real_time().seconds as u32; { // Write initial inode @@ -619,6 +621,9 @@ impl Ext2Fs { // Create new inode struct let mut value = Inode::zeroed(); value.mode = InodeMode::default_for_type(ty); + value.ctime = now; + value.mtime = now; + value.atime = now; **data = value; } diff --git a/kernel/driver/fs/ext2/src/symlink.rs b/kernel/driver/fs/ext2/src/symlink.rs index e237281e..3e863e5c 100644 --- a/kernel/driver/fs/ext2/src/symlink.rs +++ b/kernel/driver/fs/ext2/src/symlink.rs @@ -87,6 +87,10 @@ impl CommonImpl for SymlinkNode { let inode = inode.read(); Ok(inode.metadata(&self.fs, self.inode.ino())) } + + fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> { + block!(self.inode.update_metadata(metadata).await)? + } } impl SymlinkImpl for SymlinkNode { diff --git a/kernel/libk/src/vfs/node/ops.rs b/kernel/libk/src/vfs/node/ops.rs index 5e9eb6cc..0f2083fc 100644 --- a/kernel/libk/src/vfs/node/ops.rs +++ b/kernel/libk/src/vfs/node/ops.rs @@ -169,8 +169,7 @@ impl Node { // TODO sanity checks: source and destination is not the same // filenames are valid if !Self::is_same_filesystem(source, destination) { - // TODO an error for this - return Err(Error::InvalidOperation); + return Err(Error::CrossDeviceLink); } let source_dir = source.as_directory()?; @@ -218,10 +217,11 @@ impl Node { } let mut cache = self.props.lock(); + let common = self.data_as_common(); let metadata = cache .metadata - .get_or_try_insert_with(|| self.data_as_common().metadata(self))?; + .get_or_try_insert_with(|| common.metadata(self))?; // let mut metadata = self.metadata()?; @@ -237,8 +237,7 @@ impl Node { if !self.flags.contains(NodeFlags::IN_MEMORY_PROPS) { // Update permissions in the real node - // todo!(); - log::error!("TODO: update real node metadata"); + common.set_metadata(self, &metadata)?; } Ok(()) @@ -246,12 +245,13 @@ impl Node { /// Returns the "metadata" of the file: uid, gid, access mode pub fn metadata(self: &NodeRef) -> Result { - if self.flags.contains(NodeFlags::IN_MEMORY_PROPS) { - let props = self.props.lock(); - return Ok(props.metadata.unwrap()); - } + let mut cache = self.props.lock(); - self.data_as_common().metadata(self) + let metadata = cache + .metadata + .get_or_try_insert_with(|| self.data_as_common().metadata(self))?; + + Ok(metadata.clone()) } // TODO clarify directory size diff --git a/kernel/libk/src/vfs/node/traits.rs b/kernel/libk/src/vfs/node/traits.rs index a116e5cc..33e31dc4 100644 --- a/kernel/libk/src/vfs/node/traits.rs +++ b/kernel/libk/src/vfs/node/traits.rs @@ -11,7 +11,6 @@ use crate::vfs::file::{DirectoryOpenPosition, InstanceData}; use super::{Metadata, NodeRef}; /// Common interface shared by all filesystem nodes -#[allow(unused)] pub trait CommonImpl: Send + Sync { /// Returns `&self` as a reference to `dyn Any` fn as_any(&self) -> &dyn Any { @@ -20,11 +19,19 @@ pub trait CommonImpl: Send + Sync { /// Fetches the metadata of the file from underlying storage fn metadata(&self, node: &NodeRef) -> Result { + let _ = node; unreachable!("Kernel bug: .metadata() not implemented and no IN_MEMORY_PROPS set") } + fn set_metadata(&self, node: &NodeRef, metadata: &Metadata) -> Result<(), Error> { + let _ = node; + let _ = metadata; + unreachable!("Kernel bug: .set_metadata() not implemented and no IN_MEMORY_PROPS set") + } + /// Fetches the size of the file from underlying storage fn size(&self, node: &NodeRef) -> Result { + let _ = node; Err(Error::NotImplemented) } } diff --git a/lib/abi/def/error.abi b/lib/abi/def/error.abi index 22faa789..d6c5a75a 100644 --- a/lib/abi/def/error.abi +++ b/lib/abi/def/error.abi @@ -29,4 +29,5 @@ enum Error(u32) { DirectoryNotEmpty = 26, NotConnected = 27, ProcessNotFound = 28, + CrossDeviceLink = 29, } diff --git a/userspace/sysutils/src/mv.rs b/userspace/sysutils/src/mv.rs index d299a5c2..201f4dc4 100644 --- a/userspace/sysutils/src/mv.rs +++ b/userspace/sysutils/src/mv.rs @@ -1,15 +1,77 @@ -use std::path::PathBuf; +#![feature(io_error_more)] +use std::{ + fs, io, + path::{Path, PathBuf}, + process::ExitCode, +}; use clap::Parser; #[derive(Debug, Parser)] struct Args { + #[clap(short, long)] + verbose: bool, + #[clap(short, long)] + no_copy: bool, src: PathBuf, - dst: PathBuf + dst: PathBuf, } -fn main() { +fn run(args: &Args) -> Result<(), io::Error> { + let src: &Path = args.src.as_ref(); + let dst: &Path = args.dst.as_ref(); + + if args.verbose { + eprintln!("mv: rename {} -> {}", src.display(), dst.display()); + } + + match fs::rename(src, dst) { + Ok(()) => return Ok(()), + Err(e) if e.kind() == io::ErrorKind::CrossesDevices => { + if args.no_copy { + if args.verbose { + eprintln!("mv: --no-copy is specified and a move is a cross-device operation"); + } + return Err(e); + } + } + Err(e) => return Err(e), + } + + if src.is_file() { + let filename = src.file_name().unwrap(); + + let dst = if dst.is_dir() { + dst.join(filename) + } else { + dst.into() + }; + + if args.verbose { + eprintln!("mv: copy {} -> {}", src.display(), dst.display()); + } + fs::copy(src, dst)?; + + if args.verbose { + eprintln!("mv: rm {}", src.display()); + } + fs::remove_file(src)?; + } else { + // TODO mv symlink, mv dir + todo!() + } + + Ok(()) +} + +fn main() -> ExitCode { let args = Args::parse(); - std::fs::rename(args.src, args.dst).unwrap(); + match run(&args) { + Ok(()) => ExitCode::SUCCESS, + Err(error) => { + eprintln!("{}: {error}", args.src.display()); + ExitCode::FAILURE + } + } }