From b28d2baa7f06621d1f125111aec49cd541673abd Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Sun, 22 Feb 2026 22:25:20 -0800 Subject: [PATCH 01/10] bump dependencies and use workspace dependencies when possible --- Cargo.lock | 297 +++++++++++++++++-- Cargo.toml | 24 +- libkernel/Cargo.toml | 16 +- libkernel/src/memory/allocators/frame.rs | 2 +- libkernel/src/memory/allocators/slab/heap.rs | 2 +- libkernel/src/memory/allocators/smalloc.rs | 2 +- moss-macros/Cargo.toml | 2 +- src/kernel/rand.rs | 2 +- 8 files changed, 297 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5268bc42..a194a035 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "tock-registers", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "arm-pl011-uart" version = "0.4.0" @@ -58,6 +64,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + [[package]] name = "colored" version = "3.1.1" @@ -67,6 +84,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -90,13 +116,19 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -110,7 +142,7 @@ dependencies = [ [[package]] name = "ext4-view" version = "0.9.3" -source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#86785325204a60c02a74d2975db19af570878965" +source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#4ca848f90b8eae11c38820f7626b0cca1ec24b70" dependencies = [ "async-trait", "bitflags", @@ -123,6 +155,12 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f95f0bda5ff920492f6573294d8e3a99b75ee2e5ef93ab313fc6d517fa46785" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "futures" version = "0.3.32" @@ -204,21 +242,62 @@ checksum = "084c6b182b01dec54ff12986b9cc8859a9b0d92b074f878c382a4481a070e66e" [[package]] name = "getrandom" -version = "0.3.4" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" dependencies = [ "cfg-if", "libc", "r-efi", + "rand_core", "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] name = "intrusive-collections" -version = "0.9.7" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" +checksum = "ec0375b6b871e424e9e052e1107d57dc6952f77ff882bd4bf74333a833779bab" dependencies = [ "memoffset", ] @@ -238,6 +317,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.182" @@ -408,12 +493,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "zerocopy", + "proc-macro2", + "syn", ] [[package]] @@ -442,32 +528,20 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" dependencies = [ - "ppv-lite86", + "chacha20", + "getrandom", "rand_core", ] [[package]] name = "rand_core" -version = "0.9.5" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom", -] +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" [[package]] name = "redox_syscall" @@ -521,6 +595,21 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -541,6 +630,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -675,6 +777,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "usertest" version = "0.1.0" @@ -700,6 +808,49 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "windows-link" version = "0.2.1" @@ -794,6 +945,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "zerocopy" @@ -814,3 +1047,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 98527b19..3be11f8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" @@ -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 diff --git a/libkernel/Cargo.toml b/libkernel/Cargo.toml index f4d6425b..3e0befaa 100644 --- a/libkernel/Cargo.toml +++ b/libkernel/Cargo.toml @@ -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] diff --git a/libkernel/src/memory/allocators/frame.rs b/libkernel/src/memory/allocators/frame.rs index 3a7e5dae..87ebcd55 100644 --- a/libkernel/src/memory/allocators/frame.rs +++ b/libkernel/src/memory/allocators/frame.rs @@ -42,7 +42,7 @@ pub struct Frame { pub pfn: PageFrame, } -intrusive_adapter!(pub FrameAdapter = UnsafeRef: Frame { link: LinkedListLink }); +intrusive_adapter!(pub FrameAdapter = UnsafeRef: Frame { link => LinkedListLink }); impl Frame { pub fn new(pfn: PageFrame) -> Self { diff --git a/libkernel/src/memory/allocators/slab/heap.rs b/libkernel/src/memory/allocators/slab/heap.rs index b6dbdfc8..3cb03328 100644 --- a/libkernel/src/memory/allocators/slab/heap.rs +++ b/libkernel/src/memory/allocators/slab/heap.rs @@ -179,7 +179,7 @@ mod tests { }, test::MockCpuOps, }; - use rand::{Rng, rng}; + use rand::{RngExt, rng}; use std::{ cell::RefCell, ops::{Deref, DerefMut}, diff --git a/libkernel/src/memory/allocators/smalloc.rs b/libkernel/src/memory/allocators/smalloc.rs index 27a645c0..4c1b6a0c 100644 --- a/libkernel/src/memory/allocators/smalloc.rs +++ b/libkernel/src/memory/allocators/smalloc.rs @@ -507,7 +507,7 @@ mod tests { ops::{Deref, DerefMut}, }; - use rand::{Rng, SeedableRng}; + use rand::{RngExt, SeedableRng}; use crate::{ error::KernelError, diff --git a/moss-macros/Cargo.toml b/moss-macros/Cargo.toml index d26745d9..036eaf44 100644 --- a/moss-macros/Cargo.toml +++ b/moss-macros/Cargo.toml @@ -8,8 +8,8 @@ proc-macro = true [dependencies] proc-macro2 = "1.0" -syn = "2.0" quote = "1.0" +syn = "2.0" [lints] workspace = true diff --git a/src/kernel/rand.rs b/src/kernel/rand.rs index 0588ac40..fd471581 100644 --- a/src/kernel/rand.rs +++ b/src/kernel/rand.rs @@ -8,7 +8,7 @@ use crate::{ use alloc::vec::Vec; use libkernel::error::Result; use libkernel::memory::address::TUA; -use rand::{RngCore, SeedableRng, rngs::SmallRng}; +use rand::{Rng, SeedableRng, rngs::SmallRng}; pub async fn sys_getrandom(ubuf: TUA, size: isize, _flags: u32) -> Result { let buf = { From 28e3f4ab023896a15c674636a3af859c65de7f49 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Tue, 24 Feb 2026 17:39:38 -0800 Subject: [PATCH 02/10] support linking and unlinking --- Cargo.lock | 2 +- libkernel/src/fs/filesystems/ext4/mod.rs | 47 ++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a194a035..c07bd7e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ dependencies = [ [[package]] name = "ext4-view" version = "0.9.3" -source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#4ca848f90b8eae11c38820f7626b0cca1ec24b70" +source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#20ecd9c422a42d9c855b9d202c8b59cd45c0e69b" dependencies = [ "async-trait", "bitflags", diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs index a94e8b24..905f3bdf 100644 --- a/libkernel/src/fs/filesystems/ext4/mod.rs +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -61,7 +61,10 @@ impl From 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::Corrupt(c) => { + error!("Corrupt EXT4 filesystem: {c}, likely a bug"); + KernelError::Fs(FsError::InvalidFs) + } e => { error!("Unmapped EXT4 error: {e:?}"); KernelError::Other("EXT4 error") @@ -283,8 +286,46 @@ where Err(KernelError::NotSupported) } - async fn unlink(&self, _name: &str) -> Result<()> { - Err(KernelError::NotSupported) + async fn link(&self, name: &str, inode: Arc) -> 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<()> { + 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> { From d21c449b5c7e66bee4d90516719e59448acbf673 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 26 Feb 2026 10:06:42 -0800 Subject: [PATCH 03/10] support file creation --- Cargo.lock | 2 +- libkernel/src/fs/filesystems/ext4/mod.rs | 37 ++++++++++++++++++++---- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c07bd7e9..ac1f313d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ dependencies = [ [[package]] name = "ext4-view" version = "0.9.3" -source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#20ecd9c422a42d9c855b9d202c8b59cd45c0e69b" +source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#87905cdb24c2189465e34d5c4378bd4e82607272" dependencies = [ "async-trait", "bitflags", diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs index 905f3bdf..39491622 100644 --- a/libkernel/src/fs/filesystems/ext4/mod.rs +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -29,8 +29,8 @@ use core::error::Error; use core::marker::PhantomData; use core::num::NonZeroU32; 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, }; use log::error; @@ -279,11 +279,36 @@ where async fn create( &self, - _name: &str, - _file_type: FileType, - _permissions: FilePermissions, + name: &str, + file_type: FileType, + permissions: FilePermissions, ) -> Result> { - Err(KernelError::NotSupported) + let fs = self.fs_ref.upgrade().unwrap(); + if !matches!(file_type, FileType::File) { + return Err(KernelError::NotSupported); + } + let new_inode = 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?; + let inner = self.inner.lock().await; + fs.inner + .link(&inner, name.to_string(), &mut new_inode.clone()) + .await?; + let child_path = self.path.join(name); + Ok(Arc::new(Ext4Inode:: { + fs_ref: self.fs_ref.clone(), + id: new_inode.index, + inner: Mutex::new(new_inode), + path: child_path, + })) } async fn link(&self, name: &str, inode: Arc) -> Result<()> { From c77ea09264b0fd9202b8684ccc61d8c397ddfb16 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 26 Feb 2026 13:18:38 -0800 Subject: [PATCH 04/10] support file writing beyond block boundaries --- Cargo.lock | 2 +- libkernel/src/fs/filesystems/ext4/mod.rs | 24 ++---------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac1f313d..d993a432 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ dependencies = [ [[package]] name = "ext4-view" version = "0.9.3" -source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#87905cdb24c2189465e34d5c4378bd4e82607272" +source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#7bb52e83879b3c630a464919d8316fefb91636af" dependencies = [ "async-trait", "bitflags", diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs index 39491622..1cb5acf2 100644 --- a/libkernel/src/fs/filesystems/ext4/mod.rs +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -28,10 +28,7 @@ use async_trait::async_trait; use core::error::Error; use core::marker::PhantomData; use core::num::NonZeroU32; -use ext4_view::{ - AsyncIterator, AsyncSkip, Ext4, Ext4Read, Ext4Write, File, FollowSymlinks, - InodeCreationOptions, InodeFlags, InodeMode, Metadata, ReadDir, get_dir_entry_inode_by_name, -}; +use ext4_view::{AsyncIterator, AsyncSkip, Ext4, Ext4Read, Ext4Write, File, FollowSymlinks, InodeCreationOptions, InodeFlags, InodeMode, Metadata, ReadDir, get_dir_entry_inode_by_name, write_at}; use log::error; #[async_trait] @@ -202,24 +199,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) } From 357707fdb9c8743c296282fe978e6833926bd691 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 26 Feb 2026 15:55:53 -0800 Subject: [PATCH 05/10] flock noop --- src/arch/arm64/exceptions/syscall.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/arch/arm64/exceptions/syscall.rs b/src/arch/arm64/exceptions/syscall.rs index 2a7fd08a..ae84d00b 100644 --- a/src/arch/arm64/exceptions/syscall.rs +++ b/src/arch/arm64/exceptions/syscall.rs @@ -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 => { From 1d5fb030b453c89382f41de919ab83df6be9e0f6 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 26 Feb 2026 15:57:30 -0800 Subject: [PATCH 06/10] support directory creation --- Cargo.lock | 10 ++--- libkernel/src/fs/filesystems/ext4/mod.rs | 53 ++++++++++++++++-------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d993a432..6c8c2dd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ dependencies = [ [[package]] name = "ext4-view" version = "0.9.3" -source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#7bb52e83879b3c630a464919d8316fefb91636af" +source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#1dca998db9d4b0b7dda31856638b19a26ae4aca0" dependencies = [ "async-trait", "bitflags", @@ -1030,18 +1030,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs index 1cb5acf2..7d65d75b 100644 --- a/libkernel/src/fs/filesystems/ext4/mod.rs +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -28,7 +28,11 @@ use async_trait::async_trait; use core::error::Error; use core::marker::PhantomData; use core::num::NonZeroU32; -use ext4_view::{AsyncIterator, AsyncSkip, Ext4, Ext4Read, Ext4Write, File, FollowSymlinks, InodeCreationOptions, InodeFlags, InodeMode, Metadata, ReadDir, get_dir_entry_inode_by_name, write_at}; +use ext4_view::{ + AsyncIterator, AsyncSkip, Ext4, Ext4Read, Ext4Write, File, FollowSymlinks, + InodeCreationOptions, InodeFlags, InodeMode, Metadata, ReadDir, get_dir_entry_inode_by_name, + write_at, +}; use log::error; #[async_trait] @@ -264,30 +268,45 @@ where permissions: FilePermissions, ) -> Result> { let fs = self.fs_ref.upgrade().unwrap(); - if !matches!(file_type, FileType::File) { + let mut inner = self.inner.lock().await; + let 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); - } - let new_inode = 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?; - let inner = self.inner.lock().await; + }; fs.inner .link(&inner, name.to_string(), &mut new_inode.clone()) .await?; - let child_path = self.path.join(name); + let new_path = self.path.join(name); Ok(Arc::new(Ext4Inode:: { fs_ref: self.fs_ref.clone(), id: new_inode.index, inner: Mutex::new(new_inode), - path: child_path, + path: new_path, })) } From f354fa7eaf92f7e88e106473963ca7d6d8a23279 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 26 Feb 2026 16:03:03 -0800 Subject: [PATCH 07/10] implement rename_from --- libkernel/src/fs/filesystems/ext4/mod.rs | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs index 7d65d75b..e9377b27 100644 --- a/libkernel/src/fs/filesystems/ext4/mod.rs +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -62,6 +62,7 @@ impl From for KernelError { match err { ext4_view::Ext4Error::NotFound => KernelError::Fs(FsError::NotFound), ext4_view::Ext4Error::NotADirectory => KernelError::Fs(FsError::NotADirectory), + 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) @@ -378,6 +379,36 @@ where .map(|p| PathBuf::from(p.to_str().unwrap()))?) } + async fn rename_from(&self, old_parent: Arc, old_name: &str, new_name: &str, no_replace: bool) -> Result<()> { + let old_parent_id = old_parent.id(); + let old_parent_inode = ext4_view::Inode::read( + &self.fs_ref.upgrade().unwrap().inner, + (old_parent_id.inode_id() as u32).try_into().unwrap(), + ).await?; + let fs = self.fs_ref.upgrade().unwrap(); + let inner = self.inner.lock().await; + let old_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?; + if no_replace { + // Check if new name already exists + if let Ok(_) = get_dir_entry_inode_by_name( + &fs.inner, + &inner, + ext4_view::DirEntryName::try_from(new_name) + .map_err(|_| KernelError::Fs(FsError::InvalidInput))?, + ).await { + return Err(KernelError::Fs(FsError::AlreadyExists)); + } + } + fs.inner.link(&inner, new_name.to_string(), &mut old_inode.clone()).await?; + fs.inner.unlink(&old_parent_inode, old_name.to_string(), old_inode).await?; + Ok(()) + } + async fn sync(&self) -> Result<()> { let mut inner = self.inner.lock().await; let fs = self.fs_ref.upgrade().ok_or(FsError::InvalidFs)?; From 93e540bc5c7e2a50c043d140dfa47b804a286784 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 26 Feb 2026 16:11:35 -0800 Subject: [PATCH 08/10] support symlink creation --- Cargo.lock | 2 +- libkernel/src/fs/filesystems/ext4/mod.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6c8c2dd6..f371a3c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ dependencies = [ [[package]] name = "ext4-view" version = "0.9.3" -source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#1dca998db9d4b0b7dda31856638b19a26ae4aca0" +source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#c95f2de083f50c2d04e370048ac6d2a9fbe8c516" dependencies = [ "async-trait", "bitflags", diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs index e9377b27..b29ac8b2 100644 --- a/libkernel/src/fs/filesystems/ext4/mod.rs +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -28,12 +28,14 @@ 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, InodeCreationOptions, InodeFlags, InodeMode, Metadata, ReadDir, get_dir_entry_inode_by_name, write_at, }; use log::error; +use crate::fs::path::Path; #[async_trait] impl Ext4Read for BlockBuffer { @@ -409,6 +411,16 @@ where 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)?; From 2ecfda3cb903247a61a14f617463cdd971d64aaf Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 26 Feb 2026 16:46:54 -0800 Subject: [PATCH 09/10] fix unneeded inode cloning --- libkernel/src/fs/filesystems/ext4/mod.rs | 48 ++++++++++++++++++------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs index b29ac8b2..997c1bea 100644 --- a/libkernel/src/fs/filesystems/ext4/mod.rs +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -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}; @@ -35,7 +36,6 @@ use ext4_view::{ write_at, }; use log::error; -use crate::fs::path::Path; #[async_trait] impl Ext4Read for BlockBuffer { @@ -272,7 +272,7 @@ where ) -> Result> { let fs = self.fs_ref.upgrade().unwrap(); let mut inner = self.inner.lock().await; - let new_inode = if matches!(file_type, FileType::File) { + let mut new_inode = if matches!(file_type, FileType::File) { fs.inner .create_inode(InodeCreationOptions { file_type: ext4_view::FileType::Regular, @@ -286,7 +286,8 @@ where } 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 + let mut inode = fs + .inner .create_inode(InodeCreationOptions { file_type: ext4_view::FileType::Directory, mode: InodeMode::S_IFDIR | InodeMode::from_bits(permissions.bits()).unwrap(), @@ -302,7 +303,7 @@ where return Err(KernelError::NotSupported); }; fs.inner - .link(&inner, name.to_string(), &mut new_inode.clone()) + .link(&inner, name.to_string(), &mut new_inode) .await?; let new_path = self.path.join(name); Ok(Arc::new(Ext4Inode:: { @@ -381,20 +382,28 @@ where .map(|p| PathBuf::from(p.to_str().unwrap()))?) } - async fn rename_from(&self, old_parent: Arc, old_name: &str, new_name: &str, no_replace: bool) -> Result<()> { + async fn rename_from( + &self, + old_parent: Arc, + old_name: &str, + new_name: &str, + no_replace: bool, + ) -> Result<()> { let old_parent_id = old_parent.id(); let old_parent_inode = ext4_view::Inode::read( &self.fs_ref.upgrade().unwrap().inner, (old_parent_id.inode_id() as u32).try_into().unwrap(), - ).await?; + ) + .await?; let fs = self.fs_ref.upgrade().unwrap(); let inner = self.inner.lock().await; - let old_inode = get_dir_entry_inode_by_name( + 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?; + ) + .await?; if no_replace { // Check if new name already exists if let Ok(_) = get_dir_entry_inode_by_name( @@ -402,12 +411,18 @@ where &inner, ext4_view::DirEntryName::try_from(new_name) .map_err(|_| KernelError::Fs(FsError::InvalidInput))?, - ).await { + ) + .await + { return Err(KernelError::Fs(FsError::AlreadyExists)); } } - fs.inner.link(&inner, new_name.to_string(), &mut old_inode.clone()).await?; - fs.inner.unlink(&old_parent_inode, old_name.to_string(), old_inode).await?; + 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(()) } @@ -417,7 +432,16 @@ where 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?; + fs.inner + .symlink( + &inner, + name.to_string(), + ext4_view::PathBuf::new(target.as_str().as_bytes()), + 0, + 0, + Duration::from_secs(0), + ) + .await?; Ok(()) } From bd5235fc7130654867743b98c9ec19c382fc7dc7 Mon Sep 17 00:00:00 2001 From: Ashwin Naren Date: Thu, 26 Feb 2026 17:02:03 -0800 Subject: [PATCH 10/10] fix rename semantics --- Cargo.lock | 2 +- libkernel/src/fs/filesystems/ext4/mod.rs | 72 +++++++++++++++++++----- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f371a3c9..40ead83e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,7 +142,7 @@ dependencies = [ [[package]] name = "ext4-view" version = "0.9.3" -source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#c95f2de083f50c2d04e370048ac6d2a9fbe8c516" +source = "git+https://github.com/arihant2math/ext4-view-rs.git?branch=main#cb9a222d37f7b90d3b92068cad8d86d042171471" dependencies = [ "async-trait", "bitflags", diff --git a/libkernel/src/fs/filesystems/ext4/mod.rs b/libkernel/src/fs/filesystems/ext4/mod.rs index 997c1bea..e20d8443 100644 --- a/libkernel/src/fs/filesystems/ext4/mod.rs +++ b/libkernel/src/fs/filesystems/ext4/mod.rs @@ -389,14 +389,22 @@ where 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( - &self.fs_ref.upgrade().unwrap().inner, + &fs.inner, (old_parent_id.inode_id() as u32).try_into().unwrap(), ) .await?; - let fs = self.fs_ref.upgrade().unwrap(); + 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, @@ -404,19 +412,57 @@ where .map_err(|_| KernelError::Fs(FsError::InvalidInput))?, ) .await?; - if no_replace { - // Check if new name already exists - if let Ok(_) = get_dir_entry_inode_by_name( - &fs.inner, - &inner, - ext4_view::DirEntryName::try_from(new_name) - .map_err(|_| KernelError::Fs(FsError::InvalidInput))?, - ) - .await - { - return Err(KernelError::Fs(FsError::AlreadyExists)); + + // 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?;