fs: implement rename()

This commit is contained in:
Mark Poliakov 2024-12-29 15:34:59 +02:00
parent 1d58b77241
commit 8c96a009ad
25 changed files with 357 additions and 72 deletions

View File

@ -4,10 +4,7 @@ use alloc::sync::Arc;
use libk::{
block,
error::Error,
vfs::{
CommonImpl, DirectoryImpl, DirectoryOpenPosition, Filesystem, Metadata, Node, NodeFlags,
NodeRef,
},
vfs::{CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef},
};
use yggdrasil_abi::{
io::{DirectoryEntry, FileType},
@ -148,7 +145,15 @@ impl<'a> Iterator for DirentIter<'a> {
impl DirectoryNode {
pub fn new(fs: Arc<Ext2Fs>, inode: InodeAccess) -> NodeRef {
Node::directory(Self { fs, inode }, NodeFlags::empty(), None)
Node::directory(
Self {
fs: fs.clone(),
inode,
},
NodeFlags::empty(),
None,
Some(fs),
)
}
pub async fn create(
@ -157,7 +162,10 @@ impl DirectoryNode {
parent_ino: Option<u32>,
) -> Result<NodeRef, Error> {
let ino = inode.ino();
let this = Self { fs, inode };
let this = Self {
fs: fs.clone(),
inode,
};
// fsck wants . as first entry, .. as second
this.create_entry(".", ino).await?;
@ -165,7 +173,7 @@ impl DirectoryNode {
this.create_entry("..", parent).await?;
}
Ok(Node::directory(this, NodeFlags::empty(), None))
Ok(Node::directory(this, NodeFlags::empty(), None, Some(fs)))
}
async fn create_entry(&self, name: &str, ino: u32) -> Result<(), Error> {
@ -360,10 +368,6 @@ impl CommonImpl for DirectoryNode {
self
}
fn filesystem(&self) -> Option<&dyn Filesystem> {
Some(self.fs.as_ref())
}
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
let inode = block!(self.inode.get().await)??;
let inode = inode.read();

View File

@ -17,7 +17,15 @@ pub struct RegularNode {
impl RegularNode {
pub fn new(fs: Arc<Ext2Fs>, inode: InodeAccess) -> NodeRef {
Node::regular(Self { fs, inode }, NodeFlags::empty(), None)
Node::regular(
Self {
fs: fs.clone(),
inode,
},
NodeFlags::empty(),
None,
Some(fs),
)
}
async fn resize(&self, new_size: u64) -> Result<(), Error> {

View File

@ -20,12 +20,13 @@ impl SymlinkNode {
pub fn new(fs: Arc<Ext2Fs>, inode: InodeAccess) -> NodeRef {
Node::symlink(
Self {
fs,
fs: fs.clone(),
inode,
cache: IrqSafeRwLock::new(Vec::new()),
},
NodeFlags::empty(),
None,
Some(fs),
)
}

View File

@ -1,4 +1,4 @@
use core::marker::PhantomData;
use alloc::sync::Arc;
use libk::vfs::{
CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef,
@ -8,18 +8,19 @@ use yggdrasil_abi::{
io::{FileMode, FileType},
};
use crate::{block::BlockAllocator, file::FileNode};
use crate::{block::BlockAllocator, file::FileNode, MemoryFilesystem};
pub(crate) struct DirectoryNode<A: BlockAllocator> {
_pd: PhantomData<A>,
fs: Arc<MemoryFilesystem<A>>,
}
impl<A: BlockAllocator> DirectoryNode<A> {
pub fn new(metadata: Metadata) -> NodeRef {
pub fn new(fs: Arc<MemoryFilesystem<A>>, metadata: Metadata) -> NodeRef {
Node::directory(
Self { _pd: PhantomData },
Self { fs: fs.clone() },
NodeFlags::IN_MEMORY_SIZE | NodeFlags::IN_MEMORY_PROPS,
Some(metadata),
Some(fs),
)
}
}
@ -33,10 +34,14 @@ impl<A: BlockAllocator> DirectoryImpl for DirectoryNode<A> {
fn create_node(&self, _parent: &NodeRef, ty: FileType) -> Result<NodeRef, Error> {
match ty {
FileType::File => Ok(FileNode::<A>::new(Metadata::now_root(FileMode::new(0o644)))),
FileType::Directory => Ok(DirectoryNode::<A>::new(Metadata::now_root(FileMode::new(
0o755,
)))),
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!(),
}
}

View File

@ -1,23 +1,25 @@
use alloc::sync::Arc;
use core::any::Any;
use libk::vfs::{CommonImpl, InstanceData, Metadata, Node, NodeFlags, NodeRef, RegularImpl};
use libk_util::sync::IrqSafeSpinlock;
use yggdrasil_abi::{error::Error, io::OpenOptions};
use crate::{block::BlockAllocator, bvec::BVec};
use crate::{block::BlockAllocator, bvec::BVec, MemoryFilesystem};
pub(crate) struct FileNode<A: BlockAllocator> {
pub(crate) data: IrqSafeSpinlock<BVec<'static, A>>,
}
impl<A: BlockAllocator> FileNode<A> {
pub fn new(metadata: Metadata) -> NodeRef {
pub fn new(fs: Arc<MemoryFilesystem<A>>, metadata: Metadata) -> NodeRef {
Node::regular(
Self {
data: IrqSafeSpinlock::new(BVec::new()),
},
NodeFlags::IN_MEMORY_PROPS,
Some(metadata),
Some(fs),
)
}
}

View File

@ -4,13 +4,14 @@
#![allow(clippy::new_without_default, clippy::new_ret_no_self)]
#![feature(maybe_uninit_uninit_array, maybe_uninit_array_assume_init)]
use core::{cell::RefCell, marker::PhantomData};
use core::marker::PhantomData;
use alloc::rc::Rc;
use alloc::sync::Arc;
use block::BlockAllocator;
use dir::DirectoryNode;
use file::FileNode;
use libk::vfs::{impls::fixed_path_symlink, AccessToken, Metadata, NodeRef};
use libk::vfs::{impls::fixed_path_symlink, AccessToken, Filesystem, Metadata, NodeRef};
use libk_util::sync::IrqSafeSpinlock;
use tar::TarEntry;
use yggdrasil_abi::{
error::Error,
@ -58,13 +59,13 @@ mod tar;
/// In-memory read/write filesystem
pub struct MemoryFilesystem<A: BlockAllocator> {
root: RefCell<Option<NodeRef>>,
root: IrqSafeSpinlock<Option<NodeRef>>,
_pd: PhantomData<A>,
}
impl<A: BlockAllocator> MemoryFilesystem<A> {
fn make_path(
// self: &Rc<Self>,
self: &Arc<Self>,
at: &NodeRef,
path: &Path,
create: bool,
@ -88,7 +89,7 @@ impl<A: BlockAllocator> MemoryFilesystem<A> {
return Err(Error::DoesNotExist);
}
let node = DirectoryNode::<A>::new(Metadata::now_root(mode));
let node = DirectoryNode::<A>::new(self.clone(), Metadata::now_root(mode));
at.add_child(element, node.clone())?;
node
@ -103,17 +104,20 @@ impl<A: BlockAllocator> MemoryFilesystem<A> {
Ok(node)
} else {
assert!(node.is_directory());
Self::make_path(&node, rest, create, mode)
self.make_path(&node, rest, create, mode)
}
}
fn create_node_initial(self: &Rc<Self>, hdr: &TarEntry) -> Result<NodeRef, Error> {
fn create_node_initial(self: &Arc<Self>, hdr: &TarEntry) -> Result<NodeRef, Error> {
let kind = hdr.node_kind();
let mode = usize::from(&hdr.mode);
let mode = FileMode::new(0o777 & (mode as u32));
match kind {
FileType::File => Ok(FileNode::<A>::new(Metadata::now_root(mode))),
FileType::Directory => Ok(DirectoryNode::<A>::new(Metadata::now_root(mode))),
FileType::File => Ok(FileNode::<A>::new(self.clone(), Metadata::now_root(mode))),
FileType::Directory => Ok(DirectoryNode::<A>::new(
self.clone(),
Metadata::now_root(mode),
)),
FileType::Symlink => {
let target = hdr.symlink_target()?;
Ok(fixed_path_symlink(target))
@ -122,8 +126,8 @@ impl<A: BlockAllocator> MemoryFilesystem<A> {
}
}
fn from_slice_internal(self: &Rc<Self>, tar_data: &'static [u8]) -> Result<NodeRef, Error> {
let root = DirectoryNode::<A>::new(Metadata::now_root(FileMode::new(0o755)));
fn from_slice_internal(self: &Arc<Self>, tar_data: &'static [u8]) -> Result<NodeRef, Error> {
let root = DirectoryNode::<A>::new(self.clone(), Metadata::now_root(FileMode::new(0o755)));
// 1. Create paths in tar
for item in TarIterator::new(tar_data) {
@ -134,7 +138,7 @@ impl<A: BlockAllocator> MemoryFilesystem<A> {
let path = Path::from_str(hdr.name.as_str()?.trim_matches('/'));
let (dirname, filename) = path.split_right();
let parent = Self::make_path(&root, dirname, true, FileMode::new(0o755))?;
let parent = self.make_path(&root, dirname, true, FileMode::new(0o755))?;
let node = self.create_node_initial(hdr)?;
parent.add_child(filename, node)?;
@ -147,7 +151,7 @@ impl<A: BlockAllocator> MemoryFilesystem<A> {
};
let path = Path::from_str(hdr.name.as_str()?.trim_matches('/'));
let node = Self::make_path(&root, path, false, FileMode::empty())?;
let node = self.make_path(&root, path, false, FileMode::empty())?;
assert_eq!(node.ty(), hdr.node_kind());
let uid = unsafe { UserId::from_raw(usize::from(&hdr.uid) as u32) };
@ -171,9 +175,9 @@ impl<A: BlockAllocator> MemoryFilesystem<A> {
}
/// Constructs a filesystem tree from a tar image in memory
pub fn from_slice(tar_data: &'static [u8]) -> Result<Rc<Self>, Error> {
let fs = Rc::new(Self {
root: RefCell::new(None),
pub fn from_slice(tar_data: &'static [u8]) -> Result<Arc<Self>, Error> {
let fs = Arc::new(Self {
root: IrqSafeSpinlock::new(None),
_pd: PhantomData,
});
let root = fs.from_slice_internal(tar_data)?;
@ -185,7 +189,13 @@ impl<A: BlockAllocator> MemoryFilesystem<A> {
// TODO Filesystem trait?
/// Returns the root node of the memory filesystem
pub fn root(&self) -> Result<NodeRef, Error> {
Ok(self.root.borrow().clone().unwrap())
Ok(self.root.lock().clone().unwrap())
}
}
impl<A: BlockAllocator> Filesystem for MemoryFilesystem<A> {
fn display_name(&self) -> &'static str {
"memfs"
}
}

View File

@ -46,7 +46,12 @@ pub fn add_named_char_device(
) -> Result<(), Error> {
log::info!("Add char device: {}", name);
let node = Node::char(dev, NodeFlags::IN_MEMORY_PROPS, Metadata::now_root(mode));
let node = Node::char(
dev,
NodeFlags::IN_MEMORY_PROPS,
Metadata::now_root(mode),
None,
);
DEVFS_ROOT.get().add_child(name, node)
}
@ -60,7 +65,12 @@ pub fn add_named_block_device<S: Into<String>>(
let name = name.into();
log::info!("Add block device: {}", name);
let node = Node::block(dev, NodeFlags::IN_MEMORY_PROPS, Metadata::now_root(mode));
let node = Node::block(
dev,
NodeFlags::IN_MEMORY_PROPS,
Metadata::now_root(mode),
None,
);
DEVFS_ROOT.get().add_child(name, node.clone())?;

View File

@ -119,6 +119,7 @@ impl<V: BytesAttributeOps> Attribute<V::Data> for BytesAttribute<V> {
},
NodeFlags::IN_MEMORY_PROPS,
Some(Metadata::now_root(mode)),
None,
))
}

View File

@ -187,6 +187,7 @@ impl<V: StringAttributeOps> Attribute<V::Data> for StringAttribute<V> {
},
NodeFlags::IN_MEMORY_PROPS,
Some(Metadata::now_root(mode)),
None,
))
}

View File

@ -17,6 +17,7 @@ impl<D> KObject<D> {
MemoryDirectory,
NodeFlags::IN_MEMORY_SIZE | NodeFlags::IN_MEMORY_PROPS,
Some(Metadata::now_root(FileMode::new(0o555))),
None,
);
Arc::new(Self { data, node })
}

View File

@ -6,7 +6,7 @@ use yggdrasil_abi::error::Error;
use super::NodeRef;
#[async_trait]
pub trait Filesystem: Sync {
pub trait Filesystem: Sync + Send {
async fn flush(&self) -> Result<(), Error> {
Ok(())
}

View File

@ -9,6 +9,8 @@ use crate::vfs::{
FileRef, NodeRef,
};
use super::Node;
/// Describes a general filesystem access
pub enum Action {
/// Access involves reading data without modification
@ -283,6 +285,59 @@ impl IoContext {
}
}
pub fn rename(
&mut self,
src_at: Option<NodeRef>,
src: impl AsRef<Path>,
dst_at: Option<NodeRef>,
dst: impl AsRef<Path>,
) -> Result<(), Error> {
let src = src.as_ref();
let dst = dst.as_ref();
let (dst_parent, dst_name) = dst.split_dirname();
let (_, src_name) = src.split_dirname();
if src_name.is_empty() {
log::warn!("Unhandled src move path: {src:?}");
return Err(Error::InvalidArgument);
}
let src_node = self.find(src_at, src, false)?;
if src_node.is_root() {
log::warn!("Cannot move root path");
return Err(Error::InvalidArgument);
}
let src_parent = src_node.parent();
// Try to find destination node first
let (dst_node, dst_name) = if let Ok(node) = self.find(dst_at.clone(), dst, false) {
// There is a node with the destination name, but it's not a directory
if !node.is_directory() {
return Err(Error::AlreadyExists);
}
// mv /a/b /c if /c exists -> move /a/b into /c with filename b
(node, src_name)
} else {
// Otherwise try looking up a parent
let node = self.find(dst_at, dst_parent, true)?;
if !node.is_directory() {
return Err(Error::NotADirectory);
}
// mv /a/b /c/d if /c/d doesn't exist -> move /a/b into /c with filename d
(node, dst_name)
};
let access = self.check_access(&src_node, AccessMode::READ | AccessMode::WRITE)?
+ self.check_access(&dst_node, AccessMode::WRITE)?;
Node::move_node(&src_parent, &dst_node, src_name, dst_name, access)?;
Ok(())
}
/// Removes a device or regular file node at given path
pub fn remove_file<P: AsRef<Path>>(
&mut self,

View File

@ -130,6 +130,7 @@ where
Self::new(read),
NodeFlags::IN_MEMORY_PROPS,
Some(Metadata::now_root(FileMode::new(0o444))),
None,
)
}
}
@ -233,6 +234,7 @@ where
Self::new(read, write),
NodeFlags::IN_MEMORY_PROPS,
Some(Metadata::now_root(FileMode::new(0o644))),
None,
)
}
}
@ -373,6 +375,7 @@ impl MemoryDirectory {
MemoryDirectory,
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
Some(Metadata::now_root(mode)),
None,
)
}
}
@ -456,6 +459,7 @@ pub fn read_fn_node<R: ReadFn + 'static>(read: R) -> NodeRef {
ReadOnlyFnNode::new(read),
NodeFlags::IN_MEMORY_PROPS,
Some(Metadata::now_root(FileMode::new(0o444))),
None,
)
}
@ -465,6 +469,7 @@ pub fn mdir<S: Into<String>, I: IntoIterator<Item = (S, NodeRef)>>(it: I) -> Nod
MemoryDirectory,
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
Some(Metadata::now_root(FileMode::new(0o555))),
None,
);
for (name, node) in it {
dir.add_child(name, node).unwrap();
@ -479,6 +484,7 @@ pub fn fixed_path_symlink(target: impl Into<String>) -> NodeRef {
},
NodeFlags::IN_MEMORY_PROPS,
Some(Metadata::now_root(FileMode::new(0o555))),
None,
)
}

View File

@ -132,6 +132,7 @@ struct PropertyCache {
pub struct Node {
data: NodeImpl,
flags: NodeFlags,
filesystem: Option<Arc<dyn Filesystem>>,
props: IrqSafeSpinlock<PropertyCache>,
parent: IrqSafeSpinlock<Option<NodeRef>>,
}
@ -157,7 +158,12 @@ impl Metadata {
}
impl Node {
fn new(data: NodeImpl, flags: NodeFlags, metadata: Option<Metadata>) -> NodeRef {
fn new(
data: NodeImpl,
flags: NodeFlags,
metadata: Option<Metadata>,
filesystem: Option<Arc<dyn Filesystem>>,
) -> NodeRef {
Arc::new(Self {
data,
flags,
@ -165,6 +171,7 @@ impl Node {
metadata,
size: None,
}),
filesystem,
parent: IrqSafeSpinlock::new(None),
})
}
@ -178,11 +185,13 @@ impl Node {
NodeImpl::PseudoTerminalMaster(Arc::downgrade(master)),
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
Some(metadata),
None,
);
let slave = Self::new(
NodeImpl::PseudoTerminalSlave(Arc::downgrade(slave)),
NodeFlags::IN_MEMORY_PROPS | NodeFlags::IN_MEMORY_SIZE,
Some(metadata),
None,
);
master.props.lock().size = Some(0);
@ -196,13 +205,14 @@ impl Node {
data: T,
flags: NodeFlags,
metadata: Option<Metadata>,
filesystem: Option<Arc<dyn Filesystem>>,
) -> NodeRef {
let data = NodeImpl::Directory(DirectoryData {
imp: Box::new(data),
mountpoint: IrqSafeSpinlock::new(None),
children: IrqSafeSpinlock::new(Vec::new()),
});
Self::new(data, flags, metadata)
Self::new(data, flags, metadata, filesystem)
}
// /// Creates a new directory node with given [DirectoryImpl]
@ -215,8 +225,14 @@ impl Node {
data: T,
flags: NodeFlags,
metadata: Option<Metadata>,
filesystem: Option<Arc<dyn Filesystem>>,
) -> NodeRef {
Self::new(NodeImpl::Regular(Box::new(data)), flags, metadata)
Self::new(
NodeImpl::Regular(Box::new(data)),
flags,
metadata,
filesystem,
)
}
// /// Creates a new file node with given [RegularImpl]
@ -225,20 +241,32 @@ impl Node {
// }
/// Creates a new block device node with given [BlockDevice]
pub fn block(device: Arc<dyn BlockDevice>, flags: NodeFlags, metadata: Metadata) -> NodeRef {
pub fn block(
device: Arc<dyn BlockDevice>,
flags: NodeFlags,
metadata: Metadata,
filesystem: Option<Arc<dyn Filesystem>>,
) -> NodeRef {
Self::new(
NodeImpl::Block(BlockDeviceFile(device)),
flags,
Some(metadata),
filesystem,
)
}
/// Creates a new character device node with given [CharDevice]
pub fn char(device: Arc<dyn CharDevice>, flags: NodeFlags, metadata: Metadata) -> NodeRef {
pub fn char(
device: Arc<dyn CharDevice>,
flags: NodeFlags,
metadata: Metadata,
filesystem: Option<Arc<dyn Filesystem>>,
) -> NodeRef {
Self::new(
NodeImpl::Char(CharDeviceFile(device)),
flags,
Some(metadata),
filesystem,
)
}
@ -247,6 +275,7 @@ impl Node {
data: T,
flags: NodeFlags,
metadata: Option<Metadata>,
filesystem: Option<Arc<dyn Filesystem>>,
) -> NodeRef {
Self::new(
NodeImpl::Symlink(SymlinkData {
@ -255,6 +284,7 @@ impl Node {
}),
flags,
metadata,
filesystem,
)
}
@ -266,6 +296,7 @@ impl Node {
}),
NodeFlags::IN_MEMORY_SIZE,
None,
None,
)
}
@ -286,8 +317,8 @@ impl Node {
}
/// Returns a [Filesystem] this node belongs to.
pub fn filesystem(&self) -> Option<&dyn Filesystem> {
self.data_as_common().filesystem()
pub fn filesystem(&self) -> Option<Arc<dyn Filesystem>> {
self.filesystem.clone()
}
/// Returns the impl data of the node as `dyn Any`
@ -355,6 +386,14 @@ impl Node {
}
}
pub(crate) fn is_same_filesystem(a: &Self, b: &Self) -> bool {
if let (Some(fs_a), Some(fs_b)) = (a.filesystem.as_ref(), b.filesystem.as_ref()) {
Arc::ptr_eq(fs_a, fs_b)
} else {
false
}
}
pub(crate) fn mountpoint_target(&self) -> Option<NodeRef> {
match &self.data {
NodeImpl::Directory(dir) => dir.mountpoint.lock().clone(),
@ -476,16 +515,16 @@ mod tests {
fn node_sync_send() {
fn node_send<T: Send>(_n: &T) {}
let node = Node::regular(DummyFile, NodeFlags::empty());
let node = Node::regular(DummyFile, NodeFlags::empty(), None, None);
node_send(&node);
}
#[test]
fn dir_cache_add() {
let d1 = Node::directory(DummyDirectory, NodeFlags::empty(), None);
let d2 = Node::directory(DummyDirectory, NodeFlags::empty(), None);
let f1 = Node::regular(DummyFile, NodeFlags::empty());
let d1 = Node::directory(DummyDirectory, NodeFlags::empty(), None, None);
let d2 = Node::directory(DummyDirectory, NodeFlags::empty(), None, None);
let f1 = Node::regular(DummyFile, NodeFlags::empty(), None, None);
assert!(Arc::ptr_eq(&f1.parent(), &f1));
assert_eq!(d1.children_len().unwrap(), 0);
@ -504,11 +543,11 @@ mod tests {
#[test]
fn in_mem_dir_size_coherence() {
let d = Node::directory(DummyDirectory, NodeFlags::IN_MEMORY_SIZE, None);
let d = Node::directory(DummyDirectory, NodeFlags::IN_MEMORY_SIZE, None, None);
for i in 0..10 {
let name = format!("f{}", i);
let node = Node::regular(DummyFile, NodeFlags::empty());
let node = Node::regular(DummyFile, NodeFlags::empty(), None, None);
d.add_child(name, node).unwrap();
assert_eq!(d.size().unwrap(), d.children_len().unwrap() as u64);
@ -528,7 +567,7 @@ mod tests {
}
impl DirectoryImpl for AnyData {}
let d = Node::directory(AnyData { value: 1234 }, NodeFlags::empty(), None);
let d = Node::directory(AnyData { value: 1234 }, NodeFlags::empty(), None, None);
let r = d.data_as_ref::<AnyData>();
assert_eq!(r.value, 1234);

View File

@ -8,7 +8,7 @@ use yggdrasil_abi::{
use crate::vfs::file::{File, FileRef};
use super::{AccessToken, CreateInfo, Metadata, Node, NodeFlags, NodeImpl, NodeRef};
use super::{AccessToken, CreateInfo, DirectoryData, Metadata, Node, NodeFlags, NodeImpl, NodeRef};
impl Node {
// Devices
@ -159,6 +159,50 @@ impl Node {
Ok(())
}
pub fn move_node(
source: &NodeRef,
destination: &NodeRef,
source_name: &str,
destination_name: &str,
access: AccessToken,
) -> Result<(), Error> {
// 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);
}
let source_dir = source.as_directory()?;
let destination_dir = destination.as_directory()?;
let node = source.lookup_or_load(source_name, access)?;
match source_dir.imp.unlink_node(source, source_name) {
Ok(()) | Err(Error::NotImplemented) => (),
Err(error) => {
log::warn!("Rename: unlink {source_name}: {error:?}");
return Err(error);
}
}
match destination_dir
.imp
.attach_node(destination, &node, destination_name)
{
Ok(()) | Err(Error::NotImplemented) => (),
Err(error) => {
log::warn!("Rename: attach {destination_name}: {error:?}");
return Err(error);
}
}
source_dir.remove_child(source_name);
destination.add_child(destination_name, node)?;
Ok(())
}
// Common
/// Changes user/group ID or access mode of the node
@ -245,3 +289,18 @@ impl Node {
symlink.imp.read_link(buffer)
}
}
impl DirectoryData {
fn remove_child(&self, name: &str) {
let mut children = self.children.lock();
children.retain(|(t, child)| {
if t == name {
*child.parent.lock() = None;
false
} else {
true
}
});
}
}

View File

@ -6,10 +6,7 @@ use yggdrasil_abi::{
io::{DirectoryEntry, FileType, OpenOptions},
};
use crate::vfs::{
file::{DirectoryOpenPosition, InstanceData},
Filesystem,
};
use crate::vfs::file::{DirectoryOpenPosition, InstanceData};
use super::{Metadata, NodeRef};
@ -21,11 +18,6 @@ pub trait CommonImpl: Send + Sync {
unimplemented!();
}
/// Returns a [Filesystem] this node belongs to.
fn filesystem(&self) -> Option<&dyn Filesystem> {
None
}
/// Fetches the metadata of the file from underlying storage
fn metadata(&self, node: &NodeRef) -> Result<Metadata, Error> {
unreachable!("Kernel bug: .metadata() not implemented and no IN_MEMORY_PROPS set")

View File

@ -5,7 +5,7 @@ use abi::{
io::{
AccessMode, ChannelPublisherId, DeviceRequest, DirectoryEntry, FileAttr, FileControl,
FileMetadataUpdate, FileMode, FilesystemControl, MessageDestination, OpenOptions,
PipeOptions, PollControl, RawFd, ReceivedMessageMetadata, SeekFrom, SentMessage,
PipeOptions, PollControl, RawFd, ReceivedMessageMetadata, Rename, SeekFrom, SentMessage,
TerminalOptions, TerminalSize, TimerOptions,
},
process::{ProcessWait, WaitFlags},
@ -18,7 +18,7 @@ use libk::{
};
use libk_util::io::{ReadAt, WriteAt};
use crate::syscall::{run_with_io, run_with_io_at};
use crate::syscall::{at_fd, run_with_io, run_with_io_at};
// I/O
pub(crate) fn open(
@ -167,6 +167,18 @@ pub(crate) fn remove(at: Option<RawFd>, path: &str) -> Result<(), Error> {
})
}
pub(crate) fn rename(rename: &Rename) -> Result<(), Error> {
let thread = Thread::current();
let process = thread.process();
run_with_io(&process, |mut io| {
let src_at = at_fd(&io, rename.source_at)?;
let dst_at = at_fd(&io, rename.destination_at)?;
io.ioctx_mut()
.rename(src_at, rename.source, dst_at, rename.destination)
})
}
pub(crate) fn clone_fd(source_fd: RawFd, target_fd: Option<RawFd>) -> Result<RawFd, Error> {
let thread = Thread::current();
let process = thread.process();

View File

@ -16,6 +16,15 @@ fn run_with_io<T, F: FnOnce(IrqSafeSpinlockGuard<ProcessIo>) -> T>(proc: &Proces
f(io)
}
fn at_fd(io: &ProcessIo, fd: Option<RawFd>) -> Result<Option<NodeRef>, Error> {
if let Some(fd) = fd {
let file = io.files.file(fd)?;
file.node().ok_or(Error::InvalidFile).cloned().map(Some)
} else {
Ok(None)
}
}
fn run_with_io_at<T, F: FnOnce(NodeRef, IrqSafeSpinlockGuard<ProcessIo>) -> Result<T, Error>>(
proc: &Process,
at: Option<RawFd>,

View File

@ -31,6 +31,7 @@ extern {
type FileMetadataUpdate = yggdrasil_abi::io::FileMetadataUpdate;
type FileControl = yggdrasil_abi::io::FileControl;
type FilesystemControl = yggdrasil_abi::io::FilesystemControl;
type Rename = yggdrasil_abi::io::Rename;
type DebugOperation = yggdrasil_abi::debug::DebugOperation;
type DebugFrame = yggdrasil_abi::debug::DebugFrame;
@ -126,6 +127,7 @@ 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 rename(rename: &Rename<'_>) -> Result<()>;
syscall clone_fd(source: RawFd, target: Option<RawFd>) -> Result<RawFd>;
syscall update_metadata(at: Option<RawFd>, path: &str, update: &FileMetadataUpdate) -> Result<()>;
syscall get_metadata(at: Option<RawFd>, path: &str, metadata: &mut MaybeUninit<FileAttr>, follow: bool) -> Result<()>;

View File

@ -19,6 +19,14 @@ pub use terminal::{
TerminalOutputOptions, TerminalSize,
};
#[derive(Clone, Debug)]
pub struct Rename<'a> {
pub source_at: Option<RawFd>,
pub source: &'a str,
pub destination_at: Option<RawFd>,
pub destination: &'a str,
}
impl UserId {
pub const fn root() -> Self {
Self(0)

View File

@ -67,6 +67,21 @@ impl Path {
}
}
pub fn split_dirname(&self) -> (&Path, &str) {
if let Some((left, right)) = self.0.rsplit_once(Self::SEPARATOR) {
if left.is_empty() {
// Cases like "/a" or "a"
let left = if self.is_absolute() { "/" } else { "" }.as_ref();
(left, right)
} else {
(left.as_ref(), right)
}
} else {
("".as_ref(), self.as_str())
}
}
#[inline]
pub fn is_absolute(&self) -> bool {
self.0.starts_with(Self::SEPARATOR)
@ -117,3 +132,28 @@ impl AsRef<Path> for alloc::string::String {
self.as_str().as_ref()
}
}
#[cfg(test)]
mod tests {
use super::Path;
#[test]
fn test_path_split_dirname() {
let cases = [
("/a/b", "/a", "b"),
("/a/b/", "/a/b", ""),
("/", "/", ""),
("", "", ""),
("a/b", "a", "b"),
("a", "", "a"),
("a/b/", "a/b", ""),
];
for (input, out_path, out_name) in cases {
let path = Path::from_str(input);
let (left, right) = path.split_dirname();
assert_eq!(left.as_str(), out_path);
assert_eq!(right, out_name);
}
}
}

View File

@ -25,7 +25,7 @@ use core::str::FromStr;
pub use abi::io::{
AccessMode, DirectoryEntry, FileAttr, FileMetadataUpdate, FileMetadataUpdateMode, FileMode,
FileType, OpenOptions, PipeOptions, RawFd, SeekFrom, TimerOptions,
FileType, OpenOptions, PipeOptions, RawFd, Rename, SeekFrom, TimerOptions,
};
use abi::{error::Error, io::DeviceRequest, process::ProcessOption, util::FixedString};

View File

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

View File

@ -0,0 +1,15 @@
use std::path::PathBuf;
use clap::Parser;
#[derive(Debug, Parser)]
struct Args {
src: PathBuf,
dst: PathBuf
}
fn main() {
let args = Args::parse();
std::fs::rename(args.src, args.dst).unwrap();
}

View File

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