diff --git a/kernel/driver/fs/ext2/src/data.rs b/kernel/driver/fs/ext2/src/data.rs index e5c41690..1e76d181 100644 --- a/kernel/driver/fs/ext2/src/data.rs +++ b/kernel/driver/fs/ext2/src/data.rs @@ -103,10 +103,7 @@ pub struct Inode { pub sector_count: u32, pub flags: u32, pub os_val1: u32, - pub direct_blocks: [u32; DIRECT_BLOCK_COUNT], - pub indirect_block_l1: u32, - pub indirect_block_l2: u32, - pub indirect_block_l3: u32, + pub blocks: InodeBlockRefs, pub generation: u32, pub facl: u32, pub size_upper: u32, @@ -114,6 +111,15 @@ pub struct Inode { pub os_val2: u32, } +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[repr(C)] +pub struct InodeBlockRefs { + pub direct_blocks: [u32; DIRECT_BLOCK_COUNT], + pub indirect_block_l1: u32, + pub indirect_block_l2: u32, + pub indirect_block_l3: u32, +} + #[derive(Debug, Clone, Copy, Pod, Zeroable)] #[repr(C)] pub struct Dirent { @@ -202,7 +208,8 @@ impl InodeMode { match self.0 & 0xF000 { 0x4000 => Some(FileType::Directory), 0x8000 => Some(FileType::File), - _ => todo!(), + 0xA000 => Some(FileType::Symlink), + val => todo!("Unhandled ext2 node type: {:#x}", val), } } diff --git a/kernel/driver/fs/ext2/src/lib.rs b/kernel/driver/fs/ext2/src/lib.rs index 6710f822..8906a479 100644 --- a/kernel/driver/fs/ext2/src/lib.rs +++ b/kernel/driver/fs/ext2/src/lib.rs @@ -21,10 +21,12 @@ use libk_util::OneTimeInit; mod data; pub mod dir; pub mod file; +pub mod symlink; pub use data::{ BlockGroupDescriptor, BlockGroupDescriptorTable, Dirent, ExtendedSuperblock, Inode, Superblock, }; +use symlink::SymlinkNode; use yggdrasil_abi::io::FileType; pub struct Ext2Fs { @@ -128,6 +130,7 @@ impl Ext2Fs { match inode.mode.node_type() { Some(FileType::Directory) => Ok(DirectoryNode::new(self.clone(), inode, ino)), Some(FileType::File) => Ok(RegularNode::new(self.clone(), inode, ino)), + Some(FileType::Symlink) => Ok(SymlinkNode::new(self.clone(), inode, ino)), e => todo!("Unhandled inode type: {:?} ({:#x?})", e, inode.mode), } } @@ -180,21 +183,24 @@ impl Ext2Fs { async fn inode_block_index(&self, inode: &Inode, index: u32) -> Result { let mut index = index as usize; // L0 - if index < inode.direct_blocks.len() { - return Ok(inode.direct_blocks[index]); + if index < data::DIRECT_BLOCK_COUNT { + return Ok(inode.blocks.direct_blocks[index]); } // L1 - index -= inode.direct_blocks.len(); + index -= data::DIRECT_BLOCK_COUNT; if index < self.pointers_per_block { - return self.read_index(inode.indirect_block_l1, index).await; + return self.read_index(inode.blocks.indirect_block_l1, index).await; } // L2 index -= self.pointers_per_block; if index < self.pointers_per_block * self.pointers_per_block { let l1_index = self - .read_index(inode.indirect_block_l2, index / self.pointers_per_block) + .read_index( + inode.blocks.indirect_block_l2, + index / self.pointers_per_block, + ) .await?; return self .read_index(l1_index, index % self.pointers_per_block) @@ -207,7 +213,7 @@ impl Ext2Fs { let l2_pointer_index = index / self.pointers_per_block; let l2_index = self .read_index( - inode.indirect_block_l3, + inode.blocks.indirect_block_l3, l2_pointer_index / self.pointers_per_block, ) .await?; diff --git a/kernel/driver/fs/ext2/src/symlink.rs b/kernel/driver/fs/ext2/src/symlink.rs new file mode 100644 index 00000000..3dbd7e0c --- /dev/null +++ b/kernel/driver/fs/ext2/src/symlink.rs @@ -0,0 +1,95 @@ +use core::any::Any; + +use alloc::{string::String, sync::Arc, vec::Vec}; +use libk::{ + block, + error::Error, + vfs::{CommonImpl, Metadata, Node, NodeFlags, NodeRef, SymlinkImpl}, +}; +use libk_util::sync::spin_rwlock::IrqSafeRwLock; + +use crate::{Ext2Fs, Inode}; + +pub struct SymlinkNode { + fs: Arc, + inode: Inode, + #[allow(unused)] + ino: u32, + + cache: IrqSafeRwLock>, +} + +impl SymlinkNode { + pub fn new(fs: Arc, inode: Inode, ino: u32) -> NodeRef { + Node::symlink( + Self { + fs, + inode, + ino, + cache: IrqSafeRwLock::new(Vec::new()), + }, + NodeFlags::empty(), + ) + } + + async fn read(&self, buf: &mut [u8]) -> Result { + let len = self.inode.size_lower as usize; + if len >= self.fs.block_size { + todo!() + } + + let mut write = self.cache.write(); + write.clear(); + + // If length of symlink is lower than 60, data is stored directly in "block address" + // section of the inode + if len < 60 { + let bytes = unsafe { self.link_from_inode_blocks(len) }; + write.extend_from_slice(bytes); + buf[..len].copy_from_slice(bytes); + } else { + let block = self.fs.inode_block(&self.inode, 0).await?; + write.extend_from_slice(&block[..len]); + buf[..len].copy_from_slice(&block[..len]); + } + + Ok(len) + } + + unsafe fn link_from_inode_blocks(&self, len: usize) -> &[u8] { + debug_assert!(len < 60); + &bytemuck::bytes_of(&self.inode.blocks)[..len] + } +} + +impl CommonImpl for SymlinkNode { + fn size(&self, node: &NodeRef) -> Result { + Ok(self.inode.size_lower as _) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn metadata(&self, node: &NodeRef) -> Result { + Ok(self.inode.metadata()) + } +} + +impl SymlinkImpl for SymlinkNode { + fn read_link(&self, buf: &mut [u8]) -> Result { + if buf.len() < self.inode.size_lower as usize { + todo!() + } + + { + let read = self.cache.read(); + if !read.is_empty() { + buf[..read.len()].copy_from_slice(&read[..]); + return Ok(read.len()); + } + } + + block!(self.read(buf).await)? + } +} diff --git a/kernel/libk/src/vfs/block/partition.rs b/kernel/libk/src/vfs/block/partition.rs index e49e4add..2880740a 100644 --- a/kernel/libk/src/vfs/block/partition.rs +++ b/kernel/libk/src/vfs/block/partition.rs @@ -90,6 +90,14 @@ impl<'a, D: NgBlockDevice + 'a> BlockDevice for Partition<'a, D> { || buf.len() % self.device.block_size != 0 { // TODO fallback to unaligned read + log::info!( + "Unaligned read: block_size={}, off={:#x}, pos={:#x}, len={}", + self.device.block_size, + pos, + start, + buf.len() + ); + loop {} todo!() } diff --git a/kernel/libk/src/vfs/ioctx.rs b/kernel/libk/src/vfs/ioctx.rs index 0a718eeb..a6d2d54b 100644 --- a/kernel/libk/src/vfs/ioctx.rs +++ b/kernel/libk/src/vfs/ioctx.rs @@ -299,7 +299,7 @@ impl IoContext { /// Locates a [crate::Node] pointed to by given [Path] pub fn find>( - &mut self, + &self, at: Option, path: P, follow_links: bool, @@ -321,10 +321,15 @@ impl IoContext { fn _resolve_link(&self, at: &NodeRef) -> Result { let token = self.check_access(Action::Read, at)?; // let _path = link.imp.read_to_string()?; - match at.read_symlink_node(token) { + match at.read_symlink_node(token.clone()) { Ok(node) => Ok(node), // Need to read the link data and resolve it manually - Err(Error::NotImplemented) => todo!(), + Err(Error::NotImplemented) => { + let path = at.read_symlink_path(token)?; + let target = self._find(at.parent(), path.as_ref(), true, true)?; + // TODO update node cache + Ok(target) + } Err(e) => Err(e), } } diff --git a/kernel/libk/src/vfs/node/mod.rs b/kernel/libk/src/vfs/node/mod.rs index a87e652c..6ab7c772 100644 --- a/kernel/libk/src/vfs/node/mod.rs +++ b/kernel/libk/src/vfs/node/mod.rs @@ -327,6 +327,13 @@ impl Node { Ok(cache.insert((String::new(), target)).1.clone()) } + pub(crate) fn read_symlink_path(self: &NodeRef, _token: AccessToken) -> Result { + let symlink = self.as_symlink()?; + + // TODO cache path + symlink.imp.read_to_string() + } + pub fn as_block_device(&self) -> Result<&'static dyn BlockDevice, Error> { match &self.data { NodeImpl::Block(dev) => Ok(dev.0), diff --git a/kernel/libk/src/vfs/node/ops.rs b/kernel/libk/src/vfs/node/ops.rs index ffae0808..dc4c663a 100644 --- a/kernel/libk/src/vfs/node/ops.rs +++ b/kernel/libk/src/vfs/node/ops.rs @@ -70,17 +70,23 @@ impl Node { _check: AccessToken, ) -> Result { let dir = self.as_directory()?; - let children = dir.children.lock(); - if let Some((_, node)) = children.iter().find(|(name_, _)| name_ == name) { - return Ok(node.clone()); + { + let children = dir.children.lock(); + + if let Some((_, node)) = children.iter().find(|(name_, _)| name_ == name) { + return Ok(node.clone()); + } } - // TODO lookup in real FS - match dir.imp.lookup(self, name) { + let node = match dir.imp.lookup(self, name) { Err(Error::NotImplemented) => Err(Error::DoesNotExist), res => res, - } + }?; + + self.add_child(name, node.clone())?; + + Ok(node) } /// Creates an entry within a directory with given [CreateInfo]. @@ -215,4 +221,9 @@ impl Node { Ok(size) } } + + pub fn read_link(self: &NodeRef, buffer: &mut [u8]) -> Result { + let symlink = self.as_symlink()?; + symlink.imp.read_link(buffer) + } } diff --git a/kernel/src/syscall/imp/sys_io.rs b/kernel/src/syscall/imp/sys_io.rs index 8cef3635..05484789 100644 --- a/kernel/src/syscall/imp/sys_io.rs +++ b/kernel/src/syscall/imp/sys_io.rs @@ -192,6 +192,21 @@ pub(crate) fn get_metadata( }) } +pub(crate) fn read_link(at: Option, path: &str, buffer: &mut [u8]) -> Result { + let thread = Thread::current(); + let process = thread.process(); + + run_with_io_at(&process, at, |at, mut io| { + let node = if path.is_empty() { + at + } else { + io.ioctx_mut().find(Some(at), path, false, true)? + }; + + node.read_link(buffer) + }) +} + pub(crate) fn device_request(fd: RawFd, req: &mut DeviceRequest) -> Result<(), Error> { let thread = Thread::current(); let process = thread.process(); diff --git a/lib/abi/def/yggdrasil.abi b/lib/abi/def/yggdrasil.abi index b09668eb..5b13edb7 100644 --- a/lib/abi/def/yggdrasil.abi +++ b/lib/abi/def/yggdrasil.abi @@ -90,6 +90,7 @@ syscall read_directory_entries(fd: RawFd, entries: &mut [MaybeUninit, path: &str, mode: FileMode) -> Result<()>; syscall remove_directory(at: Option, path: &str) -> Result<()>; +syscall read_link(at: Option, path: &str, buf: &mut [u8]) -> Result; syscall remove(at: Option, path: &str) -> Result<()>; syscall clone_fd(source: RawFd, target: Option) -> Result; syscall update_metadata(at: Option, path: &str, update: &FileMetadataUpdate) -> Result<()>; diff --git a/userspace/sysutils/src/ls.rs b/userspace/sysutils/src/ls.rs index 98c8a1bb..53304452 100644 --- a/userspace/sysutils/src/ls.rs +++ b/userspace/sysutils/src/ls.rs @@ -1,4 +1,5 @@ #![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))] +#![feature(let_chains)] use std::{ fmt, @@ -79,6 +80,7 @@ impl fmt::Display for DisplaySizeWith<'_, T> { struct Entry { name: String, + target: Option, ty: Option, attrs: Option, } @@ -134,7 +136,13 @@ impl DisplayBit for Entry { self.ty.display_with(opts), self.attrs.display_with(opts), self.name - ) + )?; + + if let Some(target) = self.target.as_ref() { + write!(f, " -> {}", target)?; + } + + Ok(()) } else { f.write_str(&self.name) } @@ -145,6 +153,7 @@ impl Entry { fn invalid() -> Self { Self { name: "???".to_owned(), + target: None, ty: None, attrs: None, } @@ -161,10 +170,22 @@ fn list_directory(path: &Path) -> io::Result> { let os_filename = entry.file_name(); let ty = entry.file_type().ok(); - let attrs = entry.path().metadata().ok(); + let attrs = entry.path().symlink_metadata().ok(); + + let target = if let Some(attrs) = attrs.as_ref() + && attrs.is_symlink() + { + Some(match entry.path().read_link() { + Ok(res) => res.to_string_lossy().to_string(), + Err(_) => "???".into(), + }) + } else { + None + }; entries.push(Entry { name: os_filename.to_string_lossy().to_string(), + target, ty, attrs, });