Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 272 additions & 33 deletions Cargo.lock

Large diffs are not rendered by default.

24 changes: 16 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
[workspace]
members = ["libkernel", "moss-macros", "usertest"]

[workspace.dependencies]
async-trait = "0.1.89"
bitflags = "2.11"
log = "0.4"
paste = "1.0.15"
rand = { version = "0.10", default-features = false }

[workspace.lints.clippy]
semicolon_if_nothing_returned = "warn"
uninlined_format_args = "warn"
Expand All @@ -18,20 +25,21 @@ bench = false
[dependencies]
libkernel = { path = "libkernel" }
moss-macros = { path = "moss-macros" }

aarch64-cpu = "11.1.0"
arm-pl011-uart = { version = "0.4.0", default-features = false }
tock-registers = "0.10.1"
log = "0.4.27"
async-trait = { workspace = true }
bitflags = { workspace = true }
fdt-parser = "0.4.16"
paste = "1.0.15"
object = { version = "0.38.0", default-features = false, features = ["core", "elf", "read_core"] }
async-trait = "0.1.88"
futures = { version = "0.3.31", default-features = false, features = ["alloc", "async-await"] }
getargs = { version = "0.5.0", default-features = false }
log = { workspace = true }
object = { version = "0.38.0", default-features = false, features = ["core", "elf", "read_core"] }
paste = { workspace = true }
ringbuf = { version = "0.4.8", default-features = false, features = ["alloc"] }
bitflags = "2.9.1"
futures = { version = "0.3.31", default-features = false, features = ["alloc", "async-await"] }
rand = { version = "0.9.2", default-features = false, features = ["small_rng"] }
rand = { workspace = true }
rustc-hash = { version = "2.1", default-features = false }
tock-registers = "0.10.1"

[build-dependencies]
time = { version = "0.3.47", features = ["formatting", "macros"] } # For build timestamping via build.rs
Expand Down
16 changes: 8 additions & 8 deletions libkernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ version = "0.0.0"
edition = "2024"

[dependencies]
async-trait = { workspace = true }
bitflags = { workspace = true }
ext4-view = { git = "https://github.com/arihant2math/ext4-view-rs.git", branch = "main" }
paste = "1.0.15"
thiserror = { version = "2.0.12", default-features = false }
tock-registers = "0.10.1"
log = "0.4.27"
async-trait = "0.1.88"
intrusive-collections = { version = "0.10.0", default-features = false }
log = { workspace = true }
object = { version = "0.38.0", default-features = false, features = ["core", "elf", "read_core"] }
bitflags = "2.9.1"
paste = { workspace = true }
ringbuf = { version = "0.4.8", default-features = false, features = ["alloc"] }
intrusive-collections = { version = "0.9.7", default-features = false }
thiserror = { version = "2.0.12", default-features = false }
tock-registers = "0.10.1"

[dev-dependencies]
rand = "0.9.1"
rand = { workspace = true, features = ["default"] }
tokio = { version = "1.47.1", features = ["full"] }

[lints]
Expand Down
232 changes: 205 additions & 27 deletions libkernel/src/fs/filesystems/ext4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#![allow(unused_imports)]

use crate::error::FsError;
use crate::fs::path::Path;
use crate::fs::pathbuf::PathBuf;
use crate::fs::{DirStream, Dirent};
use crate::proc::ids::{Gid, Uid};
Expand All @@ -28,9 +29,11 @@ use async_trait::async_trait;
use core::error::Error;
use core::marker::PhantomData;
use core::num::NonZeroU32;
use core::time::Duration;
use ext4_view::{
AsyncIterator, AsyncSkip, Ext4, Ext4Read, Ext4Write, File, FollowSymlinks, Metadata, ReadDir,
get_dir_entry_inode_by_name,
AsyncIterator, AsyncSkip, Ext4, Ext4Read, Ext4Write, File, FollowSymlinks,
InodeCreationOptions, InodeFlags, InodeMode, Metadata, ReadDir, get_dir_entry_inode_by_name,
write_at,
};
use log::error;

Expand Down Expand Up @@ -61,7 +64,11 @@ impl From<ext4_view::Ext4Error> for KernelError {
match err {
ext4_view::Ext4Error::NotFound => KernelError::Fs(FsError::NotFound),
ext4_view::Ext4Error::NotADirectory => KernelError::Fs(FsError::NotADirectory),
ext4_view::Ext4Error::Corrupt(_) => KernelError::Fs(FsError::InvalidFs),
ext4_view::Ext4Error::AlreadyExists => KernelError::Fs(FsError::AlreadyExists),
ext4_view::Ext4Error::Corrupt(c) => {
error!("Corrupt EXT4 filesystem: {c}, likely a bug");
KernelError::Fs(FsError::InvalidFs)
}
e => {
error!("Unmapped EXT4 error: {e:?}");
KernelError::Other("EXT4 error")
Expand Down Expand Up @@ -199,24 +206,7 @@ where
}

let fs = self.fs_ref.upgrade().unwrap();
let mut file = File::open_inode(&fs.inner, inner.clone())?;

file.seek_to(offset).await?;

// `ext4_view::File::write_bytes` may write fewer bytes than requested
// if the write crosses a block boundary. Loop until we've written
// all bytes.
let mut total_written = 0;
while total_written < buf.len() {
let bytes_written = file.write_bytes(&buf[total_written..]).await?;
if bytes_written == 0 {
break; // Should not happen unless disk is full
}
total_written += bytes_written;
}

// Update inode metadata in case size changed.
*inner = file.into_inode();
let total_written = write_at(&fs.inner, &mut inner, buf, offset).await?;

Ok(total_written)
}
Expand Down Expand Up @@ -276,15 +266,94 @@ where

async fn create(
&self,
_name: &str,
_file_type: FileType,
_permissions: FilePermissions,
name: &str,
file_type: FileType,
permissions: FilePermissions,
) -> Result<Arc<dyn Inode>> {
Err(KernelError::NotSupported)
let fs = self.fs_ref.upgrade().unwrap();
let mut inner = self.inner.lock().await;
let mut new_inode = if matches!(file_type, FileType::File) {
fs.inner
.create_inode(InodeCreationOptions {
file_type: ext4_view::FileType::Regular,
mode: InodeMode::S_IFREG | InodeMode::from_bits(permissions.bits()).unwrap(),
uid: 0,
gid: 0,
time: Default::default(),
flags: InodeFlags::empty(),
})
.await?
} else if matches!(file_type, FileType::Directory) {
let old_links_count = inner.links_count();
inner.set_links_count(old_links_count + 1);
let mut inode = fs
.inner
.create_inode(InodeCreationOptions {
file_type: ext4_view::FileType::Directory,
mode: InodeMode::S_IFDIR | InodeMode::from_bits(permissions.bits()).unwrap(),
uid: 0,
gid: 0,
time: Default::default(),
flags: InodeFlags::empty(),
})
.await?;
ext4_view::init_directory(&fs.inner, &mut inode, inner.index).await?;
inode
} else {
return Err(KernelError::NotSupported);
};
fs.inner
.link(&inner, name.to_string(), &mut new_inode)
.await?;
let new_path = self.path.join(name);
Ok(Arc::new(Ext4Inode::<CPU> {
fs_ref: self.fs_ref.clone(),
id: new_inode.index,
inner: Mutex::new(new_inode),
path: new_path,
}))
}

async fn link(&self, name: &str, inode: Arc<dyn Inode>) -> Result<()> {
let inner = self.inner.lock().await;
if inner.file_type() != ext4_view::FileType::Directory {
return Err(KernelError::NotSupported);
}
let fs = self.fs_ref.upgrade().unwrap();
// TODO: This forces the other inode out of sync
// Check fs ids match
if inode.id().fs_id() != fs.id() {
return Err(KernelError::Fs(FsError::CrossDevice));
}
let mut other_inode = ext4_view::Inode::read(
&fs.inner,
(inode.id().inode_id() as u32).try_into().unwrap(),
)
.await?;
let file_type = other_inode.file_type();
fs.inner
.link(&inner, name.to_string(), &mut other_inode)
.await?;
Ok(())
}

async fn unlink(&self, _name: &str) -> Result<()> {
Err(KernelError::NotSupported)
async fn unlink(&self, name: &str) -> Result<()> {
let inner = self.inner.lock().await;
if inner.file_type() != ext4_view::FileType::Directory {
return Err(KernelError::NotSupported);
}
let fs = self.fs_ref.upgrade().unwrap();
let child_inode = get_dir_entry_inode_by_name(
&fs.inner,
&inner,
ext4_view::DirEntryName::try_from(name)
.map_err(|_| KernelError::Fs(FsError::InvalidInput))?,
)
.await?;
fs.inner
.unlink(&inner, name.to_string(), child_inode)
.await?;
Ok(())
}

async fn readdir(&self, start_offset: u64) -> Result<Box<dyn DirStream>> {
Expand Down Expand Up @@ -313,6 +382,115 @@ where
.map(|p| PathBuf::from(p.to_str().unwrap()))?)
}

async fn rename_from(
&self,
old_parent: Arc<dyn Inode>,
old_name: &str,
new_name: &str,
no_replace: bool,
) -> Result<()> {
if old_name == new_name && old_parent.id().inode_id() == self.id().inode_id() {
return Ok(());
}

let old_parent_id = old_parent.id();
let fs = self.fs_ref.upgrade().unwrap();

let old_parent_inode = ext4_view::Inode::read(
&fs.inner,
(old_parent_id.inode_id() as u32).try_into().unwrap(),
)
.await?;

let inner = self.inner.lock().await;

// inode being moved (source)
let mut child_inode = get_dir_entry_inode_by_name(
&fs.inner,
&old_parent_inode,
ext4_view::DirEntryName::try_from(old_name)
.map_err(|_| KernelError::Fs(FsError::InvalidInput))?,
)
.await?;

// Check if destination exists and handle overwrite constraints.
let dst_lookup = get_dir_entry_inode_by_name(
&fs.inner,
&inner,
ext4_view::DirEntryName::try_from(new_name)
.map_err(|_| KernelError::Fs(FsError::InvalidInput))?,
)
.await;

if no_replace && dst_lookup.is_ok() {
return Err(KernelError::Fs(FsError::AlreadyExists));
}

if let Ok(target_inode) = dst_lookup {
let target_kind = target_inode.file_type();
let source_kind = child_inode.file_type();

if target_kind == ext4_view::FileType::Directory {
let target_is_empty = fs
.inner
.read_dir(&self.path.join(new_name))
.await?
.all(|e| {
let Ok(entry) = e else {
// If we fail to read the directory, be conservative and treat it as non-empty.
return false;
};
let name = entry.file_name().as_str().unwrap();
name == "." || name == ".."
})
.await;

if !target_is_empty {
return Err(KernelError::Fs(FsError::DirectoryNotEmpty));
}
if source_kind != ext4_view::FileType::Directory {
return Err(KernelError::Fs(FsError::IsADirectory));
}
} else if source_kind == ext4_view::FileType::Directory {
// Can't replace non-directory with a directory.
return Err(KernelError::Fs(FsError::NotADirectory));
}

// Overwrite: remove destination entry first.
fs.inner
.unlink(&inner, new_name.to_string(), target_inode)
.await?;
}

// Link into destination parent, then unlink from source parent.
fs.inner
.link(&inner, new_name.to_string(), &mut child_inode)
.await?;
fs.inner
.unlink(&old_parent_inode, old_name.to_string(), child_inode)
.await?;
Ok(())
}

async fn symlink(&self, name: &str, target: &Path) -> Result<()> {
let inner = self.inner.lock().await;
if inner.file_type() != ext4_view::FileType::Directory {
return Err(KernelError::NotSupported);
}
let fs = self.fs_ref.upgrade().unwrap();
fs.inner
.symlink(
&inner,
name.to_string(),
ext4_view::PathBuf::new(target.as_str().as_bytes()),
0,
0,
Duration::from_secs(0),
)
.await?;
Ok(())
}

async fn sync(&self) -> Result<()> {
let mut inner = self.inner.lock().await;
let fs = self.fs_ref.upgrade().ok_or(FsError::InvalidFs)?;
Expand Down
2 changes: 1 addition & 1 deletion libkernel/src/memory/allocators/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub struct Frame {
pub pfn: PageFrame,
}

intrusive_adapter!(pub FrameAdapter = UnsafeRef<Frame>: Frame { link: LinkedListLink });
intrusive_adapter!(pub FrameAdapter = UnsafeRef<Frame>: Frame { link => LinkedListLink });

impl Frame {
pub fn new(pfn: PageFrame) -> Self {
Expand Down
2 changes: 1 addition & 1 deletion libkernel/src/memory/allocators/slab/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ mod tests {
},
test::MockCpuOps,
};
use rand::{Rng, rng};
use rand::{RngExt, rng};
use std::{
cell::RefCell,
ops::{Deref, DerefMut},
Expand Down
2 changes: 1 addition & 1 deletion libkernel/src/memory/allocators/smalloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,7 @@ mod tests {
ops::{Deref, DerefMut},
};

use rand::{Rng, SeedableRng};
use rand::{RngExt, SeedableRng};

use crate::{
error::KernelError,
Expand Down
2 changes: 1 addition & 1 deletion moss-macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ proc-macro = true

[dependencies]
proc-macro2 = "1.0"
syn = "2.0"
quote = "1.0"
syn = "2.0"

[lints]
workspace = true
1 change: 1 addition & 0 deletions src/arch/arm64/exceptions/syscall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ pub async fn handle_syscall() {
0x18 => sys_dup3(arg1.into(), arg2.into(), arg3 as _),
0x19 => sys_fcntl(arg1.into(), arg2 as _, arg3 as _).await,
0x1d => sys_ioctl(arg1.into(), arg2 as _, arg3 as _).await,
0x20 => Ok(0), // sys_flock is a noop
0x22 => sys_mkdirat(arg1.into(), TUA::from_value(arg2 as _), arg3 as _).await,
0x23 => sys_unlinkat(arg1.into(), TUA::from_value(arg2 as _), arg3 as _).await,
0x24 => {
Expand Down
Loading
Loading