fs/ext2: add symlinks

This commit is contained in:
Mark Poliakov 2024-08-02 17:04:47 +03:00
parent dfd45f0ab9
commit 128b699352
10 changed files with 198 additions and 22 deletions

View File

@ -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),
}
}

View File

@ -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<u32, Error> {
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?;

View File

@ -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<Ext2Fs>,
inode: Inode,
#[allow(unused)]
ino: u32,
cache: IrqSafeRwLock<Vec<u8>>,
}
impl SymlinkNode {
pub fn new(fs: Arc<Ext2Fs>, 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<usize, Error> {
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<u64, Error> {
Ok(self.inode.size_lower as _)
}
fn as_any(&self) -> &dyn Any {
self
}
fn metadata(&self, node: &NodeRef) -> Result<Metadata, Error> {
Ok(self.inode.metadata())
}
}
impl SymlinkImpl for SymlinkNode {
fn read_link(&self, buf: &mut [u8]) -> Result<usize, Error> {
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)?
}
}

View File

@ -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!()
}

View File

@ -299,7 +299,7 @@ impl IoContext {
/// Locates a [crate::Node] pointed to by given [Path]
pub fn find<P: AsRef<Path>>(
&mut self,
&self,
at: Option<NodeRef>,
path: P,
follow_links: bool,
@ -321,10 +321,15 @@ impl IoContext {
fn _resolve_link(&self, at: &NodeRef) -> Result<NodeRef, Error> {
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),
}
}

View File

@ -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<String, Error> {
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),

View File

@ -70,17 +70,23 @@ impl Node {
_check: AccessToken,
) -> Result<NodeRef, Error> {
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<usize, Error> {
let symlink = self.as_symlink()?;
symlink.imp.read_link(buffer)
}
}

View File

@ -192,6 +192,21 @@ pub(crate) fn get_metadata(
})
}
pub(crate) fn read_link(at: Option<RawFd>, path: &str, buffer: &mut [u8]) -> Result<usize, Error> {
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();

View File

@ -90,6 +90,7 @@ syscall read_directory_entries(fd: RawFd, entries: &mut [MaybeUninit<DirectoryEn
syscall create_directory(at: Option<RawFd>, path: &str, mode: FileMode) -> Result<()>;
syscall remove_directory(at: Option<RawFd>, path: &str) -> Result<()>;
syscall read_link(at: Option<RawFd>, path: &str, buf: &mut [u8]) -> Result<usize>;
syscall remove(at: Option<RawFd>, path: &str) -> Result<()>;
syscall clone_fd(source: RawFd, target: Option<RawFd>) -> Result<RawFd>;
syscall update_metadata(at: Option<RawFd>, path: &str, update: &FileMetadataUpdate) -> Result<()>;

View File

@ -1,4 +1,5 @@
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os))]
#![feature(let_chains)]
use std::{
fmt,
@ -79,6 +80,7 @@ impl<T: DisplaySizeBit + Copy> fmt::Display for DisplaySizeWith<'_, T> {
struct Entry {
name: String,
target: Option<String>,
ty: Option<FileType>,
attrs: Option<Metadata>,
}
@ -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<Vec<Entry>> {
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,
});