vfs: symlink creation

This commit is contained in:
Mark Poliakov 2025-01-02 20:05:22 +02:00
parent f13f756c20
commit df0a48ca42
18 changed files with 189 additions and 64 deletions

View File

@ -28,8 +28,11 @@ impl Ext2Fs {
Ok(())
})
.await?;
self.with_superblock_mut(|sb| Ok(superblock_mapper(sb)))
.await?;
self.with_superblock_mut(|sb| {
superblock_mapper(sb);
Ok(())
})
.await?;
Ok(())
}

View File

@ -282,7 +282,7 @@ impl InodeMode {
match ty {
FileType::File => Self(0o644 | 0x8000),
FileType::Directory => Self(0o755 | 0x4000),
FileType::Symlink => todo!(),
FileType::Symlink => Self(0o755 | 0xA000),
_ => todo!(),
}
}

View File

@ -11,14 +11,14 @@ use libk::{
block,
error::Error,
vfs::{
CommonImpl, DirectoryImpl, DirectoryOpenPosition, Filename, Metadata, Node, NodeFlags,
NodeRef,
CommonImpl, CreateFileType, CreateInfo, DirectoryImpl, DirectoryOpenPosition, Filename,
Metadata, Node, NodeFlags, NodeRef,
},
};
use walk::{DirentIter, DirentIterMut};
use yggdrasil_abi::io::{DirectoryEntry, FileMode, FileType};
use yggdrasil_abi::io::{DirectoryEntry, FileType};
use crate::{file::RegularNode, inode::InodeAccess, Dirent, Ext2Fs, Inode};
use crate::{file::RegularNode, inode::InodeAccess, symlink::SymlinkNode, Dirent, Ext2Fs, Inode};
mod walk;
@ -245,6 +245,9 @@ impl DirectoryNode {
data if let Some(file) = data.downcast_ref::<RegularNode>() => {
(file.inode.ino(), false)
}
data if let Some(link) = data.downcast_ref::<SymlinkNode>() => {
(link.inode.ino(), false)
}
_ => return Err(Error::InvalidOperation),
};
@ -379,15 +382,20 @@ impl DirectoryImpl for DirectoryNode {
block!(self.lookup_entry(name).await)?
}
fn create_node(&self, _parent: &NodeRef, ty: FileType) -> Result<NodeRef, Error> {
fn create_node(&self, _parent: &NodeRef, info: &CreateInfo) -> Result<NodeRef, Error> {
if self.fs.force_readonly {
return Err(Error::ReadOnly);
}
let mode = match ty {
FileType::Directory | FileType::Symlink => FileMode::default_dir(),
_ => FileMode::default_file(),
let (ty, symlink) = match info.ty {
CreateFileType::File => (FileType::File, None),
CreateFileType::Directory => (FileType::Directory, None),
CreateFileType::Symlink(symlink) => (FileType::Symlink, Some(symlink)),
};
block!(InodeAccess::allocate(&self.fs, ty, mode, Some(self.inode.ino())).await)?
block!(
InodeAccess::allocate(&self.fs, ty, info.mode, Some(self.inode.ino()), symlink).await
)?
}
fn attach_node(
@ -404,6 +412,7 @@ impl DirectoryImpl for DirectoryNode {
let child_ino = match child.data_as_any() {
data if let Some(dir) = data.downcast_ref::<DirectoryNode>() => dir.inode.ino(),
data if let Some(file) = data.downcast_ref::<RegularNode>() => file.inode.ino(),
data if let Some(link) = data.downcast_ref::<SymlinkNode>() => link.inode.ino(),
_ => return Err(Error::InvalidOperation),
};
block!(self.create_entry(name, child_ino).await)??;

View File

@ -9,9 +9,14 @@ use libk::{
vfs::{Metadata, NodeRef},
};
use libk_util::{lru_hash_table::LruCache, sync::spin_rwlock::IrqSafeRwLock};
use yggdrasil_abi::io::{FileMode, FileType};
use yggdrasil_abi::{
io::{FileMode, FileType},
path::Path,
};
use crate::{data::InodeMode, dir::DirectoryNode, file::RegularNode, Ext2Fs, Inode};
use crate::{
data::InodeMode, dir::DirectoryNode, file::RegularNode, symlink::SymlinkNode, Ext2Fs, Inode,
};
pub struct InodeHolder {
inode: Inode,
@ -189,6 +194,7 @@ impl InodeAccess {
mode: FileMode,
parent_ino: Option<u32>,
ino: u32,
symlink_target: Option<&Path>,
) -> Result<NodeRef, Error> {
log::info!("ext2: allocated inode #{ino}");
@ -198,24 +204,39 @@ impl InodeAccess {
let mut imode = InodeMode::default_for_type(ty);
imode.update_permissions(mode);
fs.write_inode(
ino,
&Inode {
ctime: now,
mtime: now,
atime: now,
mode: imode,
..Inode::zeroed()
},
)
.await?;
let mut inode = Inode {
ctime: now,
mtime: now,
atime: now,
mode: imode,
..Inode::zeroed()
};
if let Some(symlink_target) = symlink_target {
log::info!("Write inode path: {symlink_target:?}");
let symlink_target = symlink_target.as_str().as_bytes();
if symlink_target.len() < 60 {
// Place into block pointers
unsafe { SymlinkNode::write_link_to_inode_blocks(&mut inode, symlink_target) }
} else {
// Or allocate a block
inode.resize(fs, symlink_target.len() as u64).await?;
fs.with_inode_block_mut(&inode, 0, symlink_target.len(), |block| {
block[..symlink_target.len()].copy_from_slice(symlink_target);
Ok(())
})
.await?;
}
}
fs.write_inode(ino, &inode).await?;
let this = InodeAccess::new(cache, ino);
let fs = fs.clone();
let node = match ty {
FileType::Directory => DirectoryNode::create(fs, this, parent_ino).await?,
FileType::File => RegularNode::new(fs, this),
FileType::Symlink => todo!(),
FileType::Symlink => SymlinkNode::new(fs, this),
_ => return Err(Error::NotImplemented),
};
@ -236,7 +257,15 @@ impl InodeAccess {
ty: FileType,
mode: FileMode,
parent_ino: Option<u32>,
symlink_target: Option<&Path>,
) -> Result<NodeRef, Error> {
if symlink_target
.map(|link| link.len() > fs.block_size)
.unwrap_or(false)
{
log::warn!("ext2: symlink target too long");
return Err(Error::InvalidOperation);
}
if parent_ino.is_none() && ty != FileType::Directory {
log::warn!("ext2: cannot allocate non-directory inode without a parent");
return Err(Error::InvalidOperation);
@ -248,7 +277,7 @@ impl InodeAccess {
let ino = fs.allocate_inode(ty == FileType::Directory).await?;
match Self::populate_inode(fs, ty, mode, parent_ino, ino).await {
match Self::populate_inode(fs, ty, mode, parent_ino, ino, symlink_target).await {
Ok(node) => Ok(node),
Err(error) => {
log::warn!("ext2: couldn't set up inode #{ino}: {error:?}");

View File

@ -12,7 +12,7 @@ use crate::{inode::InodeAccess, Ext2Fs, Inode};
pub struct SymlinkNode {
fs: Arc<Ext2Fs>,
inode: InodeAccess,
pub(crate) inode: InodeAccess,
cache: IrqSafeRwLock<Vec<u8>>,
}
@ -70,6 +70,12 @@ impl SymlinkNode {
debug_assert!(len < 60);
&bytemuck::bytes_of(&inode.blocks)[..len]
}
pub(crate) unsafe fn write_link_to_inode_blocks(inode: &mut Inode, link: &[u8]) {
debug_assert!(link.len() < 60);
bytemuck::bytes_of_mut(&mut inode.blocks)[..link.len()].copy_from_slice(link);
inode.size_lower = link.len() as _;
}
}
impl CommonImpl for SymlinkNode {

View File

@ -1,12 +1,10 @@
use alloc::sync::Arc;
use libk::vfs::{
CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef,
};
use yggdrasil_abi::{
error::Error,
io::{FileMode, FileType},
impls::fixed_path_symlink_ext, CommonImpl, CreateFileType, CreateInfo, DirectoryImpl,
DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef,
};
use yggdrasil_abi::error::Error;
use crate::{block::BlockAllocator, file::FileNode, MemoryFilesystem};
@ -32,17 +30,14 @@ impl<A: BlockAllocator> DirectoryImpl for DirectoryNode<A> {
Ok(DirectoryOpenPosition::FromCache)
}
fn create_node(&self, _parent: &NodeRef, ty: FileType) -> Result<NodeRef, Error> {
match ty {
FileType::File => Ok(FileNode::<A>::new(
self.fs.clone(),
Metadata::now_root(FileMode::new(0o644)),
)),
FileType::Directory => Ok(DirectoryNode::<A>::new(
self.fs.clone(),
Metadata::now_root(FileMode::new(0o755)),
)),
_ => todo!(),
fn create_node(&self, _parent: &NodeRef, info: &CreateInfo) -> Result<NodeRef, Error> {
let metadata = Metadata::now(info.uid, info.gid, info.mode);
match info.ty {
CreateFileType::File => Ok(FileNode::<A>::new(self.fs.clone(), metadata)),
CreateFileType::Directory => Ok(DirectoryNode::<A>::new(self.fs.clone(), metadata)),
CreateFileType::Symlink(target) => {
Ok(fixed_path_symlink_ext(target.to_string(), metadata))
}
}
}
}

View File

@ -1,6 +1,6 @@
use yggdrasil_abi::{
error::Error,
io::{AccessMode, FileMode, FileType, GroupId, OpenOptions, UserId},
io::{AccessMode, FileMode, GroupId, OpenOptions, UserId},
path::{Path, PathBuf},
};
@ -9,7 +9,7 @@ use crate::vfs::{
FileRef, Filename, NodeRef,
};
use super::{path::OwnedFilename, Node};
use super::{node::CreateFileType, Node};
/// Describes a general filesystem access
pub enum Action {
@ -189,13 +189,13 @@ impl IoContext {
// let create_mode = mode & !self.umask;
let (parent, name) = path.split_right();
let parent = self.find(at, parent, true)?;
let filename = OwnedFilename::new(name)?;
let filename = Filename::new(name)?;
let create_info = CreateInfo {
name: filename,
mode: mode & !self.umask,
uid: self.uid,
gid: self.gid,
ty: FileType::File,
ty: CreateFileType::File,
};
let access = self.check_access(&parent, AccessMode::WRITE)?;
parent.create(create_info, access)?
@ -233,10 +233,34 @@ impl IoContext {
let (parent, name) = path.split_right();
let parent = self.find(at, parent, true)?;
let access = self.check_access(&parent, AccessMode::WRITE)?;
let filename = OwnedFilename::new(name)?;
let filename = Filename::new(name)?;
let create_info = CreateInfo {
ty: CreateFileType::Directory,
name: filename,
uid: self.uid,
gid: self.gid,
mode: mode & !self.umask,
};
parent.create(create_info, access)
}
pub fn create_symlink<P: AsRef<Path>, Q: AsRef<Path>>(
&mut self,
at: Option<NodeRef>,
target: P,
path: Q,
mode: FileMode,
) -> Result<NodeRef, Error> {
let target = target.as_ref();
let path = path.as_ref();
let (parent, name) = path.split_right();
let parent = self.find(at, parent, true)?;
let access = self.check_access(&parent, AccessMode::WRITE)?;
let filename = Filename::new(name)?;
let create_info = CreateInfo {
ty: CreateFileType::Symlink(target),
name: filename,
ty: FileType::Directory,
uid: self.uid,
gid: self.gid,
mode: mode & !self.umask,

View File

@ -28,8 +28,8 @@ pub use filesystem::{
};
pub use ioctx::{Action, IoContext};
pub use node::{
impls, AccessToken, CommonImpl, CreateInfo, DirectoryImpl, Metadata, Node, NodeFlags, NodeRef,
RegularImpl, SymlinkImpl,
impls, AccessToken, CommonImpl, CreateFileType, CreateInfo, DirectoryImpl, Metadata, Node,
NodeFlags, NodeRef, RegularImpl, SymlinkImpl,
};
pub use path::{Filename, OwnedFilename};
pub use poll::FdPoll;

View File

@ -480,17 +480,21 @@ where
dir
}
pub fn fixed_path_symlink(target: impl Into<String>) -> NodeRef {
pub fn fixed_path_symlink_ext(target: impl Into<String>, metadata: Metadata) -> NodeRef {
Node::symlink(
FixedPathSymlink {
target: target.into(),
},
NodeFlags::IN_MEMORY_PROPS,
Some(Metadata::now_root(FileMode::new(0o555))),
Some(metadata),
None,
)
}
pub fn fixed_path_symlink(target: impl Into<String>) -> NodeRef {
fixed_path_symlink_ext(target, Metadata::now_root(FileMode::new(0o555)))
}
pub fn fixed_hardlink(target: NodeRef) -> Result<NodeRef, Error> {
if target.is_hard_link() || target.ty() != FileType::File {
return Err(Error::InvalidOperation);

View File

@ -15,6 +15,7 @@ use yggdrasil_abi::{
bitflags,
error::Error,
io::{FileMode, FileType, GroupId, UserId},
path::Path,
};
mod access;
@ -39,7 +40,7 @@ use crate::{
use super::{
path::OwnedFilename,
pty::{PseudoTerminalMaster, PseudoTerminalSlave},
Filesystem,
Filename, Filesystem,
};
/// Wrapper type for a [Node] shared reference
@ -57,19 +58,26 @@ bitflags! {
}
}
#[derive(Debug, Clone)]
pub enum CreateFileType<'a> {
Symlink(&'a Path),
Directory,
File,
}
/// Information used when creating an entry within a directory
#[derive(Debug, Clone)]
pub struct CreateInfo {
pub struct CreateInfo<'a> {
/// New entry name
pub name: OwnedFilename,
pub name: &'a Filename,
/// User ID of the entry
pub uid: UserId,
/// Group ID of the entry
pub gid: GroupId,
/// Access mode of the entry
pub mode: FileMode,
/// Entry type
pub ty: FileType,
/// Node type to be created
pub ty: CreateFileType<'a>,
}
pub(crate) struct DirectoryData {

View File

@ -105,7 +105,7 @@ impl Node {
/// Creates an entry within a directory with given [CreateInfo].
pub fn create(self: &NodeRef, info: CreateInfo, check: AccessToken) -> Result<NodeRef, Error> {
let directory = self.as_directory()?;
let node = directory.imp.create_node(self, info.ty)?;
let node = directory.imp.create_node(self, &info)?;
self.create_node(node.clone(), info.name, check.clone())?;

View File

@ -3,7 +3,7 @@ use core::{any::Any, mem::MaybeUninit};
use yggdrasil_abi::{
error::Error,
io::{DirectoryEntry, FileType, OpenOptions},
io::{DirectoryEntry, OpenOptions},
};
use crate::vfs::{
@ -11,7 +11,7 @@ use crate::vfs::{
Filename,
};
use super::{Metadata, NodeRef};
use super::{CreateInfo, Metadata, NodeRef};
/// Common interface shared by all filesystem nodes
pub trait CommonImpl: Send + Sync {
@ -120,9 +120,9 @@ pub trait DirectoryImpl: CommonImpl {
}
/// Creates a child node, but does not associate it with the directory yet
fn create_node(&self, parent: &NodeRef, ty: FileType) -> Result<NodeRef, Error> {
fn create_node(&self, parent: &NodeRef, info: &CreateInfo) -> Result<NodeRef, Error> {
let _ = parent;
let _ = ty;
let _ = info;
Err(Error::ReadOnly)
}

View File

@ -153,6 +153,17 @@ pub(crate) fn create_directory(at: Option<RawFd>, path: &str, mode: FileMode) ->
})
}
pub(crate) fn create_symlink(at: Option<RawFd>, target: &str, path: &str) -> Result<(), Error> {
let thread = Thread::current();
let process = thread.process();
run_with_io_at(&process, at, |at, mut io| {
io.ioctx_mut()
.create_symlink(Some(at), target, path, FileMode::default_dir())?;
Ok(())
})
}
pub(crate) fn remove_directory(_at: Option<RawFd>, _path: &str) -> Result<(), Error> {
todo!()
}

View File

@ -124,6 +124,7 @@ syscall open_directory(at: Option<RawFd>, path: &str) -> Result<RawFd>;
syscall read_directory_entries(fd: RawFd, entries: &mut [MaybeUninit<DirectoryEntry>]) -> Result<usize>;
syscall create_directory(at: Option<RawFd>, path: &str, mode: FileMode) -> Result<()>;
syscall remove_directory(at: Option<RawFd>, path: &str) -> Result<()>;
syscall create_symlink(at: Option<RawFd>, target: &str, path: &str) -> Result<()>;
syscall read_link(at: Option<RawFd>, path: &str, buf: &mut [u8]) -> Result<usize>;
syscall remove(at: Option<RawFd>, path: &str) -> Result<()>;

View File

@ -1,5 +1,7 @@
use core::ffi::CStr;
use alloc::string::String;
use crate::error::Error;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -106,6 +108,12 @@ impl Path {
pub fn as_str(&self) -> &str {
&self.0
}
#[allow(clippy::inherent_to_string)]
#[inline]
pub fn to_string(&self) -> String {
self.0.into()
}
}
impl AsRef<str> for Path {

View File

@ -56,6 +56,10 @@ path = "src/ls.rs"
name = "mv"
path = "src/mv.rs"
[[bin]]
name = "ln"
path = "src/ln.rs"
[[bin]]
name = "mkdir"
path = "src/mkdir.rs"

View File

@ -0,0 +1,22 @@
#![feature(yggdrasil_os)]
use std::{path::PathBuf, process::ExitCode, os::yggdrasil::fs};
use clap::Parser;
#[derive(Debug, Parser)]
struct Args {
target: PathBuf,
path: PathBuf,
}
fn main() -> ExitCode {
let args = Args::parse();
match fs::symlink(&args.target, &args.path) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("{}: {error}", args.path.display());
ExitCode::FAILURE
}
}
}

View File

@ -30,6 +30,7 @@ const PROGRAMS: &[(&str, &str)] = &[
("login", "sbin/login"),
("ls", "bin/ls"),
("mv", "bin/mv"),
("ln", "bin/ln"),
("mkdir", "bin/mkdir"),
("touch", "bin/touch"),
("rm", "bin/rm"),