From 576c98cd3cc4de7cc661daa864d06c372b7130a3 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Mon, 25 May 2026 16:30:28 +0800 Subject: [PATCH 01/15] Implement high-level filesystem context management in axfs - Introduced `fs.rs` to manage filesystem context, including root and current directory handling. - Added functionality for resolving paths, reading, writing, and managing directories and files. - Implemented symlink resolution with a limit on the number of symlinks followed. - Created a registry for filesystem contexts to support task-local storage and root pivoting. - Added `ReadDir` iterator for directory entries. - Created `mod.rs` to organize and expose the `file` and `fs` modules. --- Cargo.lock | 93 +- Cargo.toml | 12 +- components/axfs-ng-vfs/.gitignore | 2 - components/axfs-ng-vfs/CHANGELOG.md | 44 - components/axfs-ng-vfs/Cargo.toml | 20 - components/axfs-ng-vfs/LICENSE | 201 ---- components/axfs-ng-vfs/README.md | 81 -- components/axfs-ng-vfs/README_CN.md | 81 -- components/axfs-ng-vfs/src/lib.rs | 19 - components/axfs-ng-vfs/src/path.rs | 420 -------- components/axfs_crates/README.md | 4 +- components/axfs_crates/README_CN.md | 4 +- .../axfs_crates/axfs_devfs/CHANGELOG.md | 26 - components/axfs_crates/axfs_devfs/Cargo.toml | 12 +- components/axfs_crates/axfs_devfs/README.md | 88 +- .../axfs_crates/axfs_devfs/README_CN.md | 87 +- .../axfs_crates/axfs_devfs/src/device.rs | 165 ++++ components/axfs_crates/axfs_devfs/src/dir.rs | 242 ++--- components/axfs_crates/axfs_devfs/src/lib.rs | 101 +- components/axfs_crates/axfs_devfs/src/null.rs | 31 - .../axfs_crates/axfs_devfs/src/tests.rs | 115 --- .../axfs_crates/axfs_devfs/src/urandom.rs | 75 -- components/axfs_crates/axfs_devfs/src/zero.rs | 32 - .../axfs_crates/axfs_ramfs/CHANGELOG.md | 32 - components/axfs_crates/axfs_ramfs/Cargo.toml | 12 +- components/axfs_crates/axfs_ramfs/README.md | 89 +- .../axfs_crates/axfs_ramfs/README_CN.md | 88 +- components/axfs_crates/axfs_ramfs/src/dir.rs | 364 ++++--- components/axfs_crates/axfs_ramfs/src/file.rs | 172 +++- components/axfs_crates/axfs_ramfs/src/lib.rs | 91 +- .../axfs_crates/axfs_ramfs/src/tests.rs | 136 --- components/axfs_crates/axfs_vfs/Cargo.toml | 10 +- .../axfs_vfs}/src/fs.rs | 0 components/axfs_crates/axfs_vfs/src/lib.rs | 173 +--- components/axfs_crates/axfs_vfs/src/macros.rs | 66 -- .../axfs_vfs}/src/mount.rs | 0 .../axfs_vfs}/src/node/dir.rs | 20 +- .../axfs_vfs}/src/node/file.rs | 0 .../axfs_vfs}/src/node/mod.rs | 20 + components/axfs_crates/axfs_vfs/src/path.rs | 462 ++++++++- .../axfs_crates/axfs_vfs/src/structs.rs | 305 ------ .../axfs_vfs}/src/types.rs | 37 + os/StarryOS/kernel/Cargo.toml | 6 +- os/StarryOS/kernel/src/file/fs.rs | 6 +- os/StarryOS/kernel/src/file/mod.rs | 2 +- os/StarryOS/kernel/src/kcov/mod.rs | 2 +- .../kernel/src/mm/aspace/backend/file.rs | 2 +- os/StarryOS/kernel/src/mm/loader.rs | 2 +- os/StarryOS/kernel/src/pseudofs/debug.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dev/card0.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dev/card1.rs | 2 +- .../kernel/src/pseudofs/dev/cvi_camera.rs | 2 +- .../kernel/src/pseudofs/dev/cvi_usb_camera.rs | 2 +- .../kernel/src/pseudofs/dev/dma_heap.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dev/event.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dev/fb.rs | 2 +- .../kernel/src/pseudofs/dev/ion/device.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dev/loop.rs | 2 +- .../kernel/src/pseudofs/dev/loop_block.rs | 2 +- .../kernel/src/pseudofs/dev/memtrack.rs | 6 +- os/StarryOS/kernel/src/pseudofs/dev/mod.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dev/pinmux.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dev/pwm.rs | 2 +- .../kernel/src/pseudofs/dev/rknpu_card.rs | 2 +- .../kernel/src/pseudofs/dev/tpu/device.rs | 6 +- .../kernel/src/pseudofs/dev/tty/mod.rs | 2 +- .../kernel/src/pseudofs/dev/tty/ptm.rs | 2 +- .../kernel/src/pseudofs/dev/tty/pts.rs | 2 +- .../kernel/src/pseudofs/dev/tty_serial.rs | 2 +- os/StarryOS/kernel/src/pseudofs/device.rs | 4 +- os/StarryOS/kernel/src/pseudofs/dir.rs | 2 +- os/StarryOS/kernel/src/pseudofs/dyn_debug.rs | 2 +- os/StarryOS/kernel/src/pseudofs/file.rs | 4 +- os/StarryOS/kernel/src/pseudofs/fs.rs | 4 +- os/StarryOS/kernel/src/pseudofs/mod.rs | 2 +- os/StarryOS/kernel/src/pseudofs/proc.rs | 2 +- os/StarryOS/kernel/src/pseudofs/sysfs.rs | 2 +- os/StarryOS/kernel/src/pseudofs/tmp.rs | 6 +- .../kernel/src/pseudofs/usbfs/descriptor.rs | 2 +- os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs | 2 +- .../kernel/src/pseudofs/usbfs/sysfs.rs | 2 +- os/StarryOS/kernel/src/pseudofs/usbfs/tree.rs | 2 +- os/StarryOS/kernel/src/syscall/fs/ctl.rs | 2 +- os/StarryOS/kernel/src/syscall/fs/fd_ops.rs | 4 +- os/StarryOS/kernel/src/syscall/fs/io.rs | 2 +- os/StarryOS/kernel/src/syscall/fs/memfd.rs | 4 +- os/StarryOS/kernel/src/syscall/fs/mount.rs | 4 +- os/StarryOS/kernel/src/syscall/fs/stat.rs | 2 +- os/StarryOS/kernel/src/syscall/net/socket.rs | 2 +- os/StarryOS/kernel/src/tracepoint/control.rs | 2 +- os/StarryOS/kernel/src/tracepoint/mod.rs | 2 +- os/StarryOS/kernel/src/tracepoint/trace.rs | 2 +- .../kernel/src/tracepoint/trace_pipe.rs | 2 +- os/StarryOS/starryos/Cargo.toml | 2 +- os/arceos/api/arceos_api/Cargo.toml | 3 +- os/arceos/api/arceos_api/src/imp/fs.rs | 173 +++- os/arceos/api/arceos_posix_api/Cargo.toml | 3 +- os/arceos/api/arceos_posix_api/src/imp/fs.rs | 141 ++- os/arceos/api/axfeat/Cargo.toml | 8 +- os/arceos/modules/axfs-ng/CHANGELOG.md | 55 -- os/arceos/modules/axfs-ng/Cargo.toml | 52 - .../modules/axfs-ng/resources/make_fs.sh | 51 - os/arceos/modules/axfs-ng/src/fs/mod.rs | 36 - os/arceos/modules/axfs-ng/src/lib.rs | 169 ---- os/arceos/modules/axfs-ng/src/root.rs | 544 ----------- os/arceos/modules/axfs/Cargo.toml | 55 +- os/arceos/modules/axfs/src/api/dir.rs | 162 ---- os/arceos/modules/axfs/src/api/file.rs | 208 ---- os/arceos/modules/axfs/src/api/mod.rs | 106 -- .../modules/{axfs-ng => axfs}/src/block.rs | 53 +- os/arceos/modules/axfs/src/dev.rs | 235 ----- os/arceos/modules/axfs/src/fops.rs | 428 -------- .../src/fs/ext4/lwext4/fs.rs | 4 +- .../src/fs/ext4/lwext4/inode.rs | 2 +- .../src/fs/ext4/lwext4/mod.rs | 4 +- .../src/fs/ext4/lwext4/util.rs | 2 +- .../{axfs-ng => axfs}/src/fs/ext4/mod.rs | 0 .../src/fs/ext4/rsext4/fs.rs | 61 +- .../src/fs/ext4/rsext4/inode.rs | 84 +- .../src/fs/ext4/rsext4/mod.rs | 0 .../src/fs/ext4/rsext4/util.rs | 2 +- os/arceos/modules/axfs/src/fs/ext4fs.rs | 652 ------------- .../{axfs-ng => axfs}/src/fs/fat/dir.rs | 2 +- .../{axfs-ng => axfs}/src/fs/fat/disk.rs | 18 +- .../{axfs-ng => axfs}/src/fs/fat/ff.rs | 0 .../{axfs-ng => axfs}/src/fs/fat/file.rs | 2 +- .../{axfs-ng => axfs}/src/fs/fat/fs.rs | 4 +- .../{axfs-ng => axfs}/src/fs/fat/mod.rs | 0 .../{axfs-ng => axfs}/src/fs/fat/util.rs | 2 +- os/arceos/modules/axfs/src/fs/fatfs.rs | 427 -------- os/arceos/modules/axfs/src/fs/mod.rs | 109 ++- .../{axfs-ng => axfs}/src/highlevel/file.rs | 25 +- .../{axfs-ng => axfs}/src/highlevel/fs.rs | 24 +- .../{axfs-ng => axfs}/src/highlevel/mod.rs | 0 os/arceos/modules/axfs/src/lib.rs | 911 ++++++++++++++---- os/arceos/modules/axfs/src/mounts.rs | 79 -- os/arceos/modules/axfs/src/partition.rs | 761 --------------- os/arceos/modules/axfs/src/root.rs | 569 ----------- os/arceos/modules/axnet-ng/Cargo.toml | 4 +- os/arceos/modules/axnet-ng/src/unix/mod.rs | 4 +- os/arceos/modules/axruntime/Cargo.toml | 9 - os/arceos/modules/axruntime/src/devices.rs | 72 +- os/arceos/modules/axruntime/src/lib.rs | 13 +- os/arceos/ulib/arceos-rust/lib/Cargo.toml | 2 +- os/arceos/ulib/axstd/Cargo.toml | 4 +- os/arceos/ulib/axstd/src/fs/dir.rs | 3 +- os/axvisor/Cargo.toml | 4 +- os/axvisor/src/shell/command/base.rs | 30 - os/axvisor/src/shell/command/mod.rs | 3 - os/axvisor/src/shell/command/vm.rs | 7 - os/axvisor/src/shell/mod.rs | 17 - os/axvisor/src/vmm/config.rs | 9 +- os/axvisor/src/vmm/fdt/mod.rs | 1 - os/axvisor/src/vmm/images/mod.rs | 8 +- test-suit/arceos/rust/fs/shell/Cargo.toml | 2 +- test-suit/arceos/rust/task/lockdep/Cargo.toml | 4 +- test-suit/arceos/rust/task/lockdep/README.md | 2 +- .../arceos/rust/task/lockdep/src/main.rs | 4 +- .../build-aarch64-unknown-none-softfloat.toml | 1 - .../build-aarch64-unknown-none-softfloat.toml | 1 - ...ld-loongarch64-unknown-none-softfloat.toml | 1 - .../build-riscv64gc-unknown-none-elf.toml | 1 - .../qemu/build-x86_64-unknown-none.toml | 1 - .../svm/qemu/build-x86_64-unknown-none.toml | 1 - 164 files changed, 2724 insertions(+), 7878 deletions(-) delete mode 100644 components/axfs-ng-vfs/.gitignore delete mode 100644 components/axfs-ng-vfs/CHANGELOG.md delete mode 100644 components/axfs-ng-vfs/Cargo.toml delete mode 100644 components/axfs-ng-vfs/LICENSE delete mode 100644 components/axfs-ng-vfs/README.md delete mode 100644 components/axfs-ng-vfs/README_CN.md delete mode 100644 components/axfs-ng-vfs/src/lib.rs delete mode 100644 components/axfs-ng-vfs/src/path.rs delete mode 100644 components/axfs_crates/axfs_devfs/CHANGELOG.md create mode 100644 components/axfs_crates/axfs_devfs/src/device.rs delete mode 100644 components/axfs_crates/axfs_devfs/src/null.rs delete mode 100644 components/axfs_crates/axfs_devfs/src/tests.rs delete mode 100644 components/axfs_crates/axfs_devfs/src/urandom.rs delete mode 100644 components/axfs_crates/axfs_devfs/src/zero.rs delete mode 100644 components/axfs_crates/axfs_ramfs/CHANGELOG.md delete mode 100644 components/axfs_crates/axfs_ramfs/src/tests.rs rename components/{axfs-ng-vfs => axfs_crates/axfs_vfs}/src/fs.rs (100%) delete mode 100644 components/axfs_crates/axfs_vfs/src/macros.rs rename components/{axfs-ng-vfs => axfs_crates/axfs_vfs}/src/mount.rs (100%) rename components/{axfs-ng-vfs => axfs_crates/axfs_vfs}/src/node/dir.rs (95%) rename components/{axfs-ng-vfs => axfs_crates/axfs_vfs}/src/node/file.rs (100%) rename components/{axfs-ng-vfs => axfs_crates/axfs_vfs}/src/node/mod.rs (94%) delete mode 100644 components/axfs_crates/axfs_vfs/src/structs.rs rename components/{axfs-ng-vfs => axfs_crates/axfs_vfs}/src/types.rs (83%) delete mode 100644 os/arceos/modules/axfs-ng/CHANGELOG.md delete mode 100644 os/arceos/modules/axfs-ng/Cargo.toml delete mode 100755 os/arceos/modules/axfs-ng/resources/make_fs.sh delete mode 100644 os/arceos/modules/axfs-ng/src/fs/mod.rs delete mode 100644 os/arceos/modules/axfs-ng/src/lib.rs delete mode 100644 os/arceos/modules/axfs-ng/src/root.rs delete mode 100644 os/arceos/modules/axfs/src/api/dir.rs delete mode 100644 os/arceos/modules/axfs/src/api/file.rs delete mode 100644 os/arceos/modules/axfs/src/api/mod.rs rename os/arceos/modules/{axfs-ng => axfs}/src/block.rs (77%) delete mode 100644 os/arceos/modules/axfs/src/dev.rs delete mode 100644 os/arceos/modules/axfs/src/fops.rs rename os/arceos/modules/{axfs-ng => axfs}/src/fs/ext4/lwext4/fs.rs (99%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/ext4/lwext4/inode.rs (99%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/ext4/lwext4/mod.rs (100%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/ext4/lwext4/util.rs (96%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/ext4/mod.rs (100%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/ext4/rsext4/fs.rs (71%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/ext4/rsext4/inode.rs (94%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/ext4/rsext4/mod.rs (100%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/ext4/rsext4/util.rs (98%) delete mode 100644 os/arceos/modules/axfs/src/fs/ext4fs.rs rename os/arceos/modules/{axfs-ng => axfs}/src/fs/fat/dir.rs (99%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/fat/disk.rs (88%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/fat/ff.rs (100%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/fat/file.rs (99%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/fat/fs.rs (99%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/fat/mod.rs (100%) rename os/arceos/modules/{axfs-ng => axfs}/src/fs/fat/util.rs (97%) delete mode 100644 os/arceos/modules/axfs/src/fs/fatfs.rs rename os/arceos/modules/{axfs-ng => axfs}/src/highlevel/file.rs (98%) rename os/arceos/modules/{axfs-ng => axfs}/src/highlevel/fs.rs (97%) rename os/arceos/modules/{axfs-ng => axfs}/src/highlevel/mod.rs (100%) delete mode 100644 os/arceos/modules/axfs/src/mounts.rs delete mode 100644 os/arceos/modules/axfs/src/partition.rs delete mode 100644 os/arceos/modules/axfs/src/root.rs diff --git a/Cargo.lock b/Cargo.lock index 28751eaa9f..a2a639d4b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,10 +342,10 @@ name = "arceos-lockdep" version = "0.3.1" dependencies = [ "ax-driver", + "ax-fs-vfs", "ax-hal", "ax-kspin", "ax-std", - "axfs-ng-vfs", ] [[package]] @@ -723,6 +723,7 @@ dependencies = [ "ax-errno", "ax-feat", "ax-fs", + "ax-fs-vfs", "ax-hal", "ax-io", "ax-ipi", @@ -928,7 +929,6 @@ dependencies = [ "ax-display", "ax-driver", "ax-fs", - "ax-fs-ng", "ax-hal", "ax-input", "ax-ipi", @@ -944,9 +944,9 @@ dependencies = [ [[package]] name = "ax-fs" -version = "0.5.14" +version = "0.6.0" dependencies = [ - "ax-cap-access", + "ax-alloc", "ax-errno", "ax-fs-devfs", "ax-fs-ramfs", @@ -954,33 +954,7 @@ dependencies = [ "ax-hal", "ax-io", "ax-kspin", - "ax-lazyinit", - "axfatfs", - "log", - "rsext4", - "spin", -] - -[[package]] -name = "ax-fs-devfs" -version = "0.3.11" -dependencies = [ - "ax-fs-vfs", - "log", - "spin", -] - -[[package]] -name = "ax-fs-ng" -version = "0.5.15" -dependencies = [ - "ax-alloc", - "ax-errno", - "ax-hal", - "ax-io", - "ax-kspin", "ax-sync", - "axfs-ng-vfs", "axpoll", "bitflags 2.11.1", "cfg-if", @@ -997,22 +971,39 @@ dependencies = [ "starry-fatfs", ] +[[package]] +name = "ax-fs-devfs" +version = "0.4.0" +dependencies = [ + "ax-fs-vfs", + "ax-kspin", + "axpoll", + "spin", +] + [[package]] name = "ax-fs-ramfs" -version = "0.3.12" +version = "0.4.0" dependencies = [ "ax-fs-vfs", - "log", + "ax-kspin", + "axpoll", "spin", ] [[package]] name = "ax-fs-vfs" -version = "0.3.11" +version = "0.4.0" dependencies = [ "ax-errno", + "ax-kspin", + "axpoll", "bitflags 2.11.1", + "cfg-if", + "hashbrown 0.16.1", + "inherit-methods-macro", "log", + "smallvec", ] [[package]] @@ -1240,13 +1231,13 @@ dependencies = [ "async-trait", "ax-config", "ax-errno", - "ax-fs-ng", + "ax-fs", + "ax-fs-vfs", "ax-hal", "ax-io", "ax-kspin", "ax-sync", "ax-task", - "axfs-ng-vfs", "axpoll", "bitflags 2.11.1", "cfg-if", @@ -1489,6 +1480,7 @@ dependencies = [ "ax-errno", "ax-feat", "ax-fs", + "ax-fs-vfs", "ax-hal", "ax-io", "ax-log", @@ -1521,7 +1513,6 @@ dependencies = [ "ax-driver", "ax-errno", "ax-fs", - "ax-fs-ng", "ax-hal", "ax-input", "ax-ipi", @@ -1734,31 +1725,6 @@ dependencies = [ "serde", ] -[[package]] -name = "axfatfs" -version = "0.1.0-pre.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf1c27753a96a0f835cca49e6fb354912107d018c905d13c7eff39be757eb5a" -dependencies = [ - "bitflags 2.11.1", - "log", -] - -[[package]] -name = "axfs-ng-vfs" -version = "0.4.2" -dependencies = [ - "ax-errno", - "ax-kspin", - "axpoll", - "bitflags 2.11.1", - "cfg-if", - "hashbrown 0.16.1", - "inherit-methods-macro", - "log", - "smallvec", -] - [[package]] name = "axhvc" version = "0.4.8" @@ -1943,6 +1909,7 @@ dependencies = [ "ax-crate-interface", "ax-driver", "ax-errno", + "ax-feat", "ax-hal", "ax-kernel-guard", "ax-kspin", @@ -7855,7 +7822,8 @@ dependencies = [ "ax-driver", "ax-errno", "ax-feat", - "ax-fs-ng", + "ax-fs", + "ax-fs-vfs", "ax-hal", "ax-input", "ax-io", @@ -7874,7 +7842,6 @@ dependencies = [ "ax-sync", "ax-task", "axbacktrace", - "axfs-ng-vfs", "axplat-dyn", "axpoll", "bitflags 2.11.1", diff --git a/Cargo.toml b/Cargo.toml index e5f46a6136..c6ee2ea959 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,6 @@ members = [ "components/axdevice", "components/axdevice_base", "components/axerrno", - "components/axfs-ng-vfs", "components/axhvc", "components/axio", "components/axklib", @@ -130,7 +129,6 @@ members = [ "os/arceos/modules/axdisplay", "os/arceos/modules/axdma", "os/arceos/modules/axfs", - "os/arceos/modules/axfs-ng", "os/arceos/modules/axhal", "os/arceos/modules/axinput", "os/arceos/modules/axipi", @@ -214,11 +212,10 @@ ax-driver = { version = "0.6.0", path = "drivers/ax-driver" } virtio-drivers = { version = "0.13.0", default-features = false } ax-errno = { version = "0.6.0", path = "components/axerrno" } ax-feat = { version = "0.5.16", path = "os/arceos/api/axfeat" } -ax-fs = { version = "0.5.14", path = "os/arceos/modules/axfs" } -ax-fs-devfs = { version = "0.3.11", path = "components/axfs_crates/axfs_devfs" } -ax-fs-ng = { version = "0.5.15", path = "os/arceos/modules/axfs-ng" } -ax-fs-ramfs = { version = "0.3.12", path = "components/axfs_crates/axfs_ramfs" } -ax-fs-vfs = { version = "0.3.11", path = "components/axfs_crates/axfs_vfs" } +ax-fs = { version = "0.6.0", path = "os/arceos/modules/axfs" } +ax-fs-devfs = { version = "0.4.0", path = "components/axfs_crates/axfs_devfs" } +ax-fs-ramfs = { version = "0.4.0", path = "components/axfs_crates/axfs_ramfs" } +ax-fs-vfs = { version = "0.4.0", path = "components/axfs_crates/axfs_vfs" } ax-hal = { version = "0.5.15", path = "os/arceos/modules/axhal" } ax-handler-table = { version = "0.3.7", path = "components/handler_table" } ax-helloworld = { version = "0.3.1", path = "os/arceos/examples/helloworld" } @@ -271,7 +268,6 @@ axbacktrace = { version = "0.4.0", path = "components/axbacktrace" } axbuild = { version = "0.4.8", path = "scripts/axbuild" } axdevice = { version = "0.4.10", path = "components/axdevice" } axdevice_base = { version = "0.4.11", path = "components/axdevice_base" } -axfs-ng-vfs = { version = "0.4.2", path = "components/axfs-ng-vfs" } axhvc = { version = "0.4.8", path = "components/axhvc" } axklib = { version = "0.5.9", path = "components/axklib" } axplat-dyn = { version = "0.6.2", path = "platform/axplat-dyn", default-features = false } diff --git a/components/axfs-ng-vfs/.gitignore b/components/axfs-ng-vfs/.gitignore deleted file mode 100644 index 4fffb2f89c..0000000000 --- a/components/axfs-ng-vfs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -/Cargo.lock diff --git a/components/axfs-ng-vfs/CHANGELOG.md b/components/axfs-ng-vfs/CHANGELOG.md deleted file mode 100644 index 784ad4f188..0000000000 --- a/components/axfs-ng-vfs/CHANGELOG.md +++ /dev/null @@ -1,44 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.4.2](https://github.com/rcore-os/tgoskits/compare/axfs-ng-vfs-v0.4.1...axfs-ng-vfs-v0.4.2) - 2026-05-22 - -### Fixed - -- *(starry-kernel)* open/openat deep — 6 类跨子系统改造 (stacked on #719) ([#720](https://github.com/rcore-os/tgoskits/pull/720)) -- *(axfs-ng-vfs)* allow file rename into child dirs and fix ext4 dentry delete ([#807](https://github.com/rcore-os/tgoskits/pull/807)) - -## [0.4.1](https://github.com/rcore-os/tgoskits/compare/axfs-ng-vfs-v0.4.0...axfs-ng-vfs-v0.4.1) - 2026-05-19 - -### Other - -- updated the following local packages: ax-errno - -## [0.4.0](https://github.com/rcore-os/tgoskits/compare/axfs-ng-vfs-v0.3.8...axfs-ng-vfs-v0.4.0) - 2026-05-15 - -### Added - -- *(starry-kernel)* add runtime dynamic debug control ([#446](https://github.com/rcore-os/tgoskits/pull/446)) -- *(mm)* track backend split metadata and generate real /proc maps output ([#306](https://github.com/rcore-os/tgoskits/pull/306)) - -### Fixed - -- *(ext4)* use Linux-compatible old/new_encode_dev for device rdev ([#518](https://github.com/rcore-os/tgoskits/pull/518)) -- *(loop)* replace map_or with is_none_or to silence clippy unnecessary_map_or ([#501](https://github.com/rcore-os/tgoskits/pull/501)) -- *(vfs)* hard links on tmpfs return empty data — propagate page cache on link ([#378](https://github.com/rcore-os/tgoskits/pull/378)) - -### Other - -- Merge pull request #366 from rcore-os/fix-deps - -## [0.3.8](https://github.com/rcore-os/tgoskits/compare/axfs-ng-vfs-v0.3.7...axfs-ng-vfs-v0.3.8) - 2026-04-27 - -### Fixed - -- *(vfs)* preserve source DirEntry across rename ([#312](https://github.com/rcore-os/tgoskits/pull/312)) diff --git a/components/axfs-ng-vfs/Cargo.toml b/components/axfs-ng-vfs/Cargo.toml deleted file mode 100644 index e4792524fc..0000000000 --- a/components/axfs-ng-vfs/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "axfs-ng-vfs" -version = "0.4.2" -edition.workspace = true -authors = ["Mivik ", "朝倉水希 "] -description = "Virtual filesystem layer for ArceOS" -keywords = ["vfs", "filesystem", "ax-fs-ng", "arceos"] -repository = "https://github.com/Starry-OS/axfs-ng-vfs" -license = "Apache-2.0" - -[dependencies] -ax-errno = { workspace = true } -ax-kspin = { workspace = true } -axpoll = { workspace = true } -bitflags = "2.10" -cfg-if = "1" -hashbrown = "0.16" -inherit-methods-macro = "0.1" -log = "0.4" -smallvec = "1.15" diff --git a/components/axfs-ng-vfs/LICENSE b/components/axfs-ng-vfs/LICENSE deleted file mode 100644 index 261eeb9e9f..0000000000 --- a/components/axfs-ng-vfs/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/components/axfs-ng-vfs/README.md b/components/axfs-ng-vfs/README.md deleted file mode 100644 index 45cf636ccf..0000000000 --- a/components/axfs-ng-vfs/README.md +++ /dev/null @@ -1,81 +0,0 @@ -

axfs-ng-vfs

- -

Virtual filesystem layer for ArceOS

- -
- -[![Crates.io](https://img.shields.io/crates/v/axfs-ng-vfs.svg)](https://crates.io/crates/axfs-ng-vfs) -[![Docs.rs](https://docs.rs/axfs-ng-vfs/badge.svg)](https://docs.rs/axfs-ng-vfs) -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -English | [中文](README_CN.md) - -# Introduction - -`axfs-ng-vfs` provides Virtual filesystem layer for ArceOS. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -axfs-ng-vfs = "0.3.1" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axfs-ng-vfs - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example - -```rust -use axfs_ng_vfs as _; - -fn main() { - // Integrate `axfs-ng-vfs` into your project here. -} -``` - -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/axfs-ng-vfs](https://docs.rs/axfs-ng-vfs) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. diff --git a/components/axfs-ng-vfs/README_CN.md b/components/axfs-ng-vfs/README_CN.md deleted file mode 100644 index e41cf534a3..0000000000 --- a/components/axfs-ng-vfs/README_CN.md +++ /dev/null @@ -1,81 +0,0 @@ -

axfs-ng-vfs

- -

Virtual filesystem layer for ArceOS

- -
- -[![Crates.io](https://img.shields.io/crates/v/axfs-ng-vfs.svg)](https://crates.io/crates/axfs-ng-vfs) -[![Docs.rs](https://docs.rs/axfs-ng-vfs/badge.svg)](https://docs.rs/axfs-ng-vfs) -[![Rust](https://img.shields.io/badge/edition-2024-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) - -
- -[English](README.md) | 中文 - -# 介绍 - -`axfs-ng-vfs` 提供了 Virtual filesystem layer for ArceOS。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -axfs-ng-vfs = "0.3.1" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axfs-ng-vfs - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 - -```rust -use axfs_ng_vfs as _; - -fn main() { - // 在这里将 `axfs-ng-vfs` 集成到你的项目中。 -} -``` - -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/axfs-ng-vfs](https://docs.rs/axfs-ng-vfs) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 diff --git a/components/axfs-ng-vfs/src/lib.rs b/components/axfs-ng-vfs/src/lib.rs deleted file mode 100644 index 6b959e6432..0000000000 --- a/components/axfs-ng-vfs/src/lib.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_std] - -extern crate alloc; - -mod fs; -mod mount; -mod node; -pub mod path; -mod types; - -pub use fs::*; -pub use mount::*; -pub use node::*; -pub use types::*; - -pub type VfsError = ax_errno::AxError; -pub type VfsResult = Result; - -use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; diff --git a/components/axfs-ng-vfs/src/path.rs b/components/axfs-ng-vfs/src/path.rs deleted file mode 100644 index d42df859d7..0000000000 --- a/components/axfs-ng-vfs/src/path.rs +++ /dev/null @@ -1,420 +0,0 @@ -use alloc::{borrow::ToOwned, string::String, sync::Arc}; -use core::{borrow::Borrow, fmt, ops::Deref}; - -use crate::{VfsError, VfsResult}; - -pub const DOT: &str = "."; -pub const DOTDOT: &str = ".."; - -pub const MAX_NAME_LEN: usize = 255; - -pub(crate) fn verify_entry_name(name: &str) -> VfsResult<()> { - if name == DOT || name == DOTDOT { - return Err(VfsError::InvalidInput); - } - if name.len() > MAX_NAME_LEN { - return Err(VfsError::NameTooLong); - } - Ok(()) -} - -/// A single component of a [`Path`]. -/// -/// This corresponds to [`std::path::Component`]. -#[derive(Debug, PartialEq, Eq, Hash)] -pub enum Component<'a> { - RootDir, - CurDir, - ParentDir, - Normal(&'a str), -} - -impl<'a> Component<'a> { - pub fn as_str(&self) -> &'a str { - match self { - Component::RootDir => "/", - Component::CurDir => ".", - Component::ParentDir => "..", - Component::Normal(s) => s, - } - } -} - -/// An iterator over the [`Component`]s of a [`Path`]. -/// -/// This corresponds to [`std::path::Components`]. -#[doc(hidden)] -pub struct Components<'a> { - path: &'a str, - at_start: bool, -} - -impl<'a> Components<'a> { - pub fn as_path(&self) -> &'a Path { - Path::new(self.path) - } - - fn parse_forward(&mut self, comp: &'a str) -> Option> { - let comp = match comp { - "" => { - if self.at_start { - Some(Component::RootDir) - } else { - None - } - } - "." => { - if self.at_start { - Some(Component::CurDir) - } else { - None - } - } - ".." => Some(Component::ParentDir), - _ => Some(Component::Normal(comp)), - }; - self.at_start = false; - comp - } - - fn parse_backward(&mut self, comp: &'a str, no_rest: bool) -> Option> { - match comp { - "" => { - if self.at_start && no_rest { - Some(Component::RootDir) - } else { - None - } - } - "." => { - if self.at_start && no_rest { - Some(Component::CurDir) - } else { - None - } - } - ".." => Some(Component::ParentDir), - _ => Some(Component::Normal(comp)), - } - } -} - -impl<'a> Iterator for Components<'a> { - type Item = Component<'a>; - - fn next(&mut self) -> Option { - loop { - if self.path.is_empty() { - return None; - } - let (comp, rest) = match self.path.find('/') { - Some(index) => (&self.path[..index], &self.path[index + 1..]), - None => (self.path, ""), - }; - self.path = rest; - if let Some(comp) = self.parse_forward(comp) { - return Some(comp); - } - } - } -} - -impl<'a> DoubleEndedIterator for Components<'a> { - fn next_back(&mut self) -> Option { - loop { - if self.path.is_empty() { - return None; - } - let (comp, rest) = match self.path.rfind('/') { - Some(index) => ( - &self.path[index + 1..], - &self.path[..(index + 1).min(self.path.len() - 1)], - ), - None => (self.path, ""), - }; - self.path = rest; - if let Some(comp) = self.parse_backward(comp, rest.is_empty()) { - return Some(comp); - } - } - } -} - -/// A slice of path (akin to [`str`]). -/// -/// Different from [`std::path::Path`], this type is always -/// UTF-8 encoded. -#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct Path { - inner: str, -} - -impl Path { - pub fn new + ?Sized>(s: &S) -> &Path { - unsafe { &*(s.as_ref() as *const str as *const Path) } - } - - pub fn as_str(&self) -> &str { - &self.inner - } - - pub fn as_bytes(&self) -> &[u8] { - self.inner.as_bytes() - } - - /// Returns true if pathname has a trailing slash (other than the root - /// path "/" itself). POSIX requires that such paths refer to a directory; - /// callers should check `!loc.is_dir()` and return ENOTDIR. The trailing - /// slash is stripped at the Components iterator layer (parse_forward - /// drops empty trailing components), so this method preserves the - /// otherwise-lost signal. - pub fn has_trailing_slash(&self) -> bool { - self.inner.len() > 1 && self.inner.ends_with('/') - } - - /// Produces an iterator over the [`Components`] of the path. - pub fn components(&self) -> Components<'_> { - Components { - path: &self.inner, - at_start: true, - } - } - - /// Returns the final component of the `Path`, if there is one. - pub fn file_name(&self) -> Option<&str> { - self.components().next_back().and_then(|p| match p { - Component::Normal(p) => Some(p), - _ => None, - }) - } - - /// Creates an owned [`PathBuf`] with path adjoined to `self`. - pub fn join(&self, other: impl AsRef) -> PathBuf { - let mut path = self.to_owned(); - path.push(other); - path - } - - /// Returns the `Path` without its final component, if there is one. - pub fn parent(&self) -> Option<&Path> { - let mut comps = self.components(); - let comp = comps.next_back(); - comp.and_then(move |p| match p { - Component::Normal(_) | Component::CurDir | Component::ParentDir => { - Some(comps.as_path()) - } - _ => None, - }) - } - - /// Returns `true` if the `Path` is absolute, i.e., if it is independent of - /// the current directory. - pub fn is_absolute(&self) -> bool { - self.inner.starts_with('/') - } - - /// Normalizes a path without performing I/O. - pub fn normalize(&self) -> Option { - let mut ret = PathBuf::new(); - for component in self.components() { - match component { - Component::RootDir => { - ret.push("/"); - } - Component::CurDir => {} - Component::ParentDir => { - if !ret.pop() { - return None; - } - } - Component::Normal(c) => { - ret.push(c); - } - } - } - Some(ret) - } -} - -impl fmt::Display for Path { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner.fmt(f) - } -} - -impl<'a> From<&'a str> for &'a Path { - fn from(value: &'a str) -> Self { - Path::new(value) - } -} - -impl ToOwned for Path { - type Owned = PathBuf; - - fn to_owned(&self) -> Self::Owned { - PathBuf { - inner: self.inner.to_owned(), - } - } -} - -impl From<&Path> for Arc { - #[inline] - fn from(v: &Path) -> Arc { - let arc = Arc::::from(&v.inner); - unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Path) } - } -} - -impl AsRef for Path { - #[inline] - fn as_ref(&self) -> &str { - &self.inner[..] - } -} - -impl AsRef for Path { - #[inline] - fn as_ref(&self) -> &Path { - self - } -} - -macro_rules! impl_as_ref { - ($($t:ty),+) => { - $(impl AsRef for $t { - fn as_ref(&self) -> &Path { - Path::new(self) - } - })+ - }; -} - -impl_as_ref!(str, String); - -/// An owned, mutable [`Path`] (akin to [`String`]). -/// -/// Different from [`std::path::PathBuf`], this type is always -/// UTF-8 encoded. -#[derive(Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct PathBuf { - inner: String, -} - -impl PathBuf { - pub const fn new() -> Self { - Self { - inner: String::new(), - } - } - - pub fn pop(&mut self) -> bool { - match self.parent().map(|p| p.as_str().len()) { - Some(len) => { - self.inner.truncate(len); - true - } - None => false, - } - } - - pub fn push(&mut self, path: impl AsRef) { - self._push(path.as_ref()); - } - - fn _push(&mut self, path: &Path) { - if path.as_str().is_empty() { - return; - } - if path.is_absolute() { - self.inner.clear(); - } else if !self.inner.ends_with('/') { - self.inner.push('/'); - } - self.inner += path.as_str(); - } -} - -impl> FromIterator for PathBuf { - fn from_iter>(iter: I) -> Self { - let mut path = PathBuf::new(); - for item in iter { - path.push(item); - } - path - } -} - -impl Borrow for PathBuf { - fn borrow(&self) -> &Path { - self - } -} - -impl Deref for PathBuf { - type Target = Path; - - #[inline] - fn deref(&self) -> &Path { - Path::new(&self.inner) - } -} - -impl AsRef for PathBuf { - #[inline] - fn as_ref(&self) -> &str { - &self.inner[..] - } -} - -impl AsRef for PathBuf { - #[inline] - fn as_ref(&self) -> &Path { - self - } -} - -impl From for PathBuf { - fn from(value: String) -> Self { - Self { inner: value } - } -} - -impl From<&str> for PathBuf { - fn from(value: &str) -> Self { - Self { - inner: value.to_owned(), - } - } -} - -impl fmt::Display for PathBuf { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.inner.fmt(f) - } -} - -#[cfg(test)] -mod test { - use alloc::vec::Vec; - - use super::*; - - #[test] - fn test_back_components() { - for path in ["../fds/", "./fs", "fs", "../", "..", ".", "./."] { - let path = Path::new(path); - let forward: Vec<_> = path.components().collect(); - let mut backward: Vec<_> = path.components().rev().collect(); - backward.reverse(); - assert_eq!(forward, backward); - } - } - - #[test] - fn test_file_name() { - assert_eq!(Some("c"), Path::new("../a/b/c").file_name()); - assert_eq!(Some("b"), Path::new("a/b/.").file_name()); - assert_eq!(None, Path::new("a/..").file_name()); - } -} diff --git a/components/axfs_crates/README.md b/components/axfs_crates/README.md index c3f3409d0d..aa812f3e3f 100644 --- a/components/axfs_crates/README.md +++ b/components/axfs_crates/README.md @@ -19,9 +19,9 @@ English | [中文](README_CN.md) ## Workspace Members +- `axfs_devfs` +- `axfs_ramfs` - `axfs_vfs` -- `ax-fs-devfs` -- `ax-fs-ramfs` ## Quick Start diff --git a/components/axfs_crates/README_CN.md b/components/axfs_crates/README_CN.md index 945956c8bb..32307d1a73 100644 --- a/components/axfs_crates/README_CN.md +++ b/components/axfs_crates/README_CN.md @@ -19,9 +19,9 @@ ## 工作区成员 +- `axfs_devfs` +- `axfs_ramfs` - `axfs_vfs` -- `ax-fs-devfs` -- `ax-fs-ramfs` ## 快速开始 diff --git a/components/axfs_crates/axfs_devfs/CHANGELOG.md b/components/axfs_crates/axfs_devfs/CHANGELOG.md deleted file mode 100644 index e8285dcf3a..0000000000 --- a/components/axfs_crates/axfs_devfs/CHANGELOG.md +++ /dev/null @@ -1,26 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.3.11](https://github.com/rcore-os/tgoskits/compare/ax-fs-devfs-v0.3.10...ax-fs-devfs-v0.3.11) - 2026-05-22 - -### Other - -- updated the following local packages: ax-fs-vfs - -## [0.3.10](https://github.com/rcore-os/tgoskits/compare/ax-fs-devfs-v0.3.9...ax-fs-devfs-v0.3.10) - 2026-05-19 - -### Other - -- updated the following local packages: ax-fs-vfs - -## [0.3.9](https://github.com/rcore-os/tgoskits/compare/ax-fs-devfs-v0.3.8...ax-fs-devfs-v0.3.9) - 2026-05-15 - -### Other - -- *(axfs-crates)* inherit workspace metadata diff --git a/components/axfs_crates/axfs_devfs/Cargo.toml b/components/axfs_crates/axfs_devfs/Cargo.toml index 38d73570b4..8b35bf4700 100644 --- a/components/axfs_crates/axfs_devfs/Cargo.toml +++ b/components/axfs_crates/axfs_devfs/Cargo.toml @@ -1,15 +1,19 @@ [package] name = "ax-fs-devfs" -description = "Device filesystem used by ArceOS" +description = "Device filesystem implementation for ArceOS" keywords = ["arceos", "filesystem", "devfs"] edition.workspace = true -version = "0.3.11" +version = "0.4.0" repository = "https://github.com/rcore-os/tgoskits" authors = ["Yuekai Jia "] categories = ["os", "no-std", "filesystem"] license = "Apache-2.0" +[features] +default = [] + [dependencies] -ax-fs-vfs.workspace = true +ax-fs-vfs = { workspace = true } +ax-kspin = { workspace = true } +axpoll = { workspace = true } spin = { workspace = true } -log = "0.4" diff --git a/components/axfs_crates/axfs_devfs/README.md b/components/axfs_crates/axfs_devfs/README.md index 9855f774db..c4548bd3a1 100644 --- a/components/axfs_crates/axfs_devfs/README.md +++ b/components/axfs_crates/axfs_devfs/README.md @@ -1,84 +1,22 @@ -

ax-fs-devfs

+# ax-fs-devfs -

Device filesystem used by ArceOS

+Device filesystem implementation for ArceOS. -
+This crate implements the current `ax-fs-vfs` object model directly. It does +not expose or depend on the removed legacy `VfsOps` / `VfsNodeOps` interface. -[![Crates.io](https://img.shields.io/crates/v/ax-fs-devfs.svg)](https://crates.io/crates/ax-fs-devfs) -[![Docs.rs](https://docs.rs/ax-fs-devfs/badge.svg)](https://docs.rs/ax-fs-devfs) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) +## Provided Nodes -
+- `null` +- `zero` +- `urandom` -English | [中文](README_CN.md) - -# Introduction - -`ax-fs-devfs` provides Device filesystem used by ArceOS. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-fs-devfs was derived from https://github.com/arceos-org/axfs_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-fs-devfs = "0.3.2" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axfs_crates/axfs_devfs - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example +## Usage ```rust -use ax_fs_devfs as _; - -fn main() { - // Integrate `ax-fs-devfs` into your project here. -} +let fs = ax_fs_devfs::new(); +let root = ax_fs_vfs::Mountpoint::new_root(&fs).root_location(); ``` -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-fs-devfs](https://docs.rs/ax-fs-devfs) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. +The returned value is an `ax_fs_vfs::Filesystem` and can be mounted through the +normal `Location::mount()` path. diff --git a/components/axfs_crates/axfs_devfs/README_CN.md b/components/axfs_crates/axfs_devfs/README_CN.md index b1f1b883cc..3fa21e0dcf 100644 --- a/components/axfs_crates/axfs_devfs/README_CN.md +++ b/components/axfs_crates/axfs_devfs/README_CN.md @@ -1,84 +1,21 @@ -

ax-fs-devfs

+# ax-fs-devfs -

Device filesystem used by ArceOS

+ArceOS 的设备文件系统实现。 -
+该 crate 直接实现当前 `ax-fs-vfs` 对象模型,不暴露也不依赖已经移除的旧 +`VfsOps` / `VfsNodeOps` 接口。 -[![Crates.io](https://img.shields.io/crates/v/ax-fs-devfs.svg)](https://crates.io/crates/ax-fs-devfs) -[![Docs.rs](https://docs.rs/ax-fs-devfs/badge.svg)](https://docs.rs/ax-fs-devfs) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) +## 提供的节点 -
+- `null` +- `zero` +- `urandom` -[English](README.md) | 中文 - -# 介绍 - -`ax-fs-devfs` 提供了 Device filesystem used by ArceOS。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-fs-devfs 派生自 https://github.com/arceos-org/axfs_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-fs-devfs = "0.3.2" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axfs_crates/axfs_devfs - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 +## 使用方式 ```rust -use ax_fs_devfs as _; - -fn main() { - // 在这里将 `ax-fs-devfs` 集成到你的项目中。 -} +let fs = ax_fs_devfs::new(); +let root = ax_fs_vfs::Mountpoint::new_root(&fs).root_location(); ``` -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-fs-devfs](https://docs.rs/ax-fs-devfs) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 +返回值是 `ax_fs_vfs::Filesystem`,可以通过标准的 `Location::mount()` 路径挂载。 diff --git a/components/axfs_crates/axfs_devfs/src/device.rs b/components/axfs_crates/axfs_devfs/src/device.rs new file mode 100644 index 0000000000..2e15e73974 --- /dev/null +++ b/components/axfs_crates/axfs_devfs/src/device.rs @@ -0,0 +1,165 @@ +use alloc::sync::Arc; +use core::{ + any::Any, + ops::Deref, + sync::atomic::{AtomicU64, Ordering}, + task::Context, + time::Duration, +}; + +use ax_fs_vfs::{ + DeviceId, FileNode, FileNodeOps, FilesystemOps, Metadata, MetadataUpdate, NodeFlags, NodeOps, + NodePermission, NodeType, VfsResult, +}; +use axpoll::{IoEvents, Pollable}; + +use crate::DeviceFileSystem; + +struct DevNode { + fs: Arc, + inode: u64, + kind: DeviceKind, +} + +#[derive(Clone, Copy)] +enum DeviceKind { + Null, + Zero, + Urandom, +} + +impl DevNode { + fn make(fs: Arc, inode: u64, kind: DeviceKind) -> FileNode { + FileNode::new(Arc::new(Self { fs, inode, kind })) + } +} + +impl NodeOps for DevNode { + fn inode(&self) -> u64 { + self.inode + } + + fn metadata(&self) -> VfsResult { + Ok(Metadata { + device: 0, + inode: self.inode, + nlink: 1, + mode: NodePermission::default(), + node_type: NodeType::CharacterDevice, + uid: 0, + gid: 0, + size: 0, + block_size: 4096, + blocks: 0, + rdev: DeviceId::default(), + atime: Duration::default(), + mtime: Duration::default(), + ctime: Duration::default(), + }) + } + + fn update_metadata(&self, _update: MetadataUpdate) -> VfsResult<()> { + Ok(()) + } + + fn filesystem(&self) -> &dyn FilesystemOps { + self.fs.deref() + } + + fn sync(&self, _data_only: bool) -> VfsResult<()> { + Ok(()) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn flags(&self) -> NodeFlags { + NodeFlags::STREAM | NodeFlags::NON_CACHEABLE + } +} + +impl FileNodeOps for DevNode { + fn read_at(&self, buf: &mut [u8], _offset: u64) -> VfsResult { + match self.kind { + DeviceKind::Null => Ok(0), + DeviceKind::Zero => { + buf.fill(0); + Ok(buf.len()) + } + DeviceKind::Urandom => { + fill_random(buf); + Ok(buf.len()) + } + } + } + + fn write_at(&self, buf: &[u8], _offset: u64) -> VfsResult { + Ok(buf.len()) + } + + fn append(&self, buf: &[u8]) -> VfsResult<(usize, u64)> { + Ok((buf.len(), 0)) + } + + fn set_len(&self, _len: u64) -> VfsResult<()> { + Ok(()) + } + + fn set_symlink(&self, _target: &str) -> VfsResult<()> { + Err(ax_fs_vfs::VfsError::InvalidInput) + } +} + +impl Pollable for DevNode { + fn poll(&self) -> IoEvents { + IoEvents::IN | IoEvents::OUT + } + + fn register(&self, _context: &mut Context<'_>, _events: IoEvents) {} +} + +static RANDOM_SEED: AtomicU64 = AtomicU64::new(0xa2ce_a2ce); + +fn next_u64() -> u64 { + let mut current = RANDOM_SEED.load(Ordering::Relaxed); + loop { + let next = current.wrapping_mul(6364136223846793005).wrapping_add(1); + match RANDOM_SEED.compare_exchange_weak(current, next, Ordering::Relaxed, Ordering::Relaxed) + { + Ok(_) => return next, + Err(observed) => current = observed, + } + } +} + +fn fill_random(buf: &mut [u8]) { + for chunk in buf.chunks_mut(8) { + let bytes = next_u64().to_ne_bytes(); + chunk.copy_from_slice(&bytes[..chunk.len()]); + } +} + +pub struct NullDev; + +impl NullDev { + pub(crate) fn make(fs: Arc, inode: u64) -> FileNode { + DevNode::make(fs, inode, DeviceKind::Null) + } +} + +pub struct ZeroDev; + +impl ZeroDev { + pub(crate) fn make(fs: Arc, inode: u64) -> FileNode { + DevNode::make(fs, inode, DeviceKind::Zero) + } +} + +pub struct UrandomDev; + +impl UrandomDev { + pub(crate) fn make(fs: Arc, inode: u64) -> FileNode { + DevNode::make(fs, inode, DeviceKind::Urandom) + } +} diff --git a/components/axfs_crates/axfs_devfs/src/dir.rs b/components/axfs_crates/axfs_devfs/src/dir.rs index cde64d6d35..09f75585e0 100644 --- a/components/axfs_crates/axfs_devfs/src/dir.rs +++ b/components/axfs_crates/axfs_devfs/src/dir.rs @@ -1,142 +1,158 @@ -use alloc::{ - collections::BTreeMap, - sync::{Arc, Weak}, -}; +use alloc::{collections::BTreeMap, string::String, sync::Arc}; +use core::{any::Any, ops::Deref, task::Context, time::Duration}; use ax_fs_vfs::{ - VfsDirEntry, VfsError, VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsResult, + DeviceId, DirEntry, DirEntrySink, DirNode as VfsDirNode, DirNodeOps, FileNode, FilesystemOps, + Metadata, MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, Reference, VfsError, + VfsResult, WeakDirEntry, +}; +use ax_kspin::SpinNoIrq as Mutex; +use axpoll::{IoEvents, Pollable}; + +use crate::{ + DeviceFileSystem, + device::{NullDev, UrandomDev, ZeroDev}, }; -use spin::RwLock; - -/// The directory node in the device filesystem. -/// -/// It implements [`ax_fs_vfs::VfsNodeOps`]. -pub struct DirNode { - parent: RwLock>, - children: RwLock>, + +pub(crate) struct DevDirNode { + fs: Arc, + this: WeakDirEntry, + inode: u64, + children: Mutex>, } -impl DirNode { - pub(super) fn new(parent: Option<&VfsNodeRef>) -> Arc { - let parent = parent.map_or(Weak::::new() as _, Arc::downgrade); - Arc::new(Self { - parent: RwLock::new(parent), - children: RwLock::new(BTreeMap::new()), - }) +impl DevDirNode { + pub(crate) fn make(fs: Arc, this: WeakDirEntry, inode: u64) -> VfsDirNode { + VfsDirNode::new(Arc::new(Self { + fs, + this, + inode, + children: Mutex::new(BTreeMap::new()), + })) } - pub(super) fn set_parent(&self, parent: Option<&VfsNodeRef>) { - *self.parent.write() = parent.map_or(Weak::::new() as _, Arc::downgrade); + pub(crate) fn populate_static_devices(&self) { + self.add_static( + "null", + NullDev::make(self.fs.clone(), self.fs.alloc_inode()), + ); + self.add_static( + "zero", + ZeroDev::make(self.fs.clone(), self.fs.alloc_inode()), + ); + self.add_static( + "urandom", + UrandomDev::make(self.fs.clone(), self.fs.alloc_inode()), + ); } - /// Create a subdirectory at this directory. - pub fn mkdir(self: &Arc, name: &'static str) -> Arc { - let parent = self.clone() as VfsNodeRef; - let node = Self::new(Some(&parent)); - self.children.write().insert(name, node.clone()); - node + fn add_static(&self, name: &str, file: FileNode) { + let entry = DirEntry::new_file( + file, + NodeType::CharacterDevice, + Reference::new(self.this.upgrade(), name.into()), + ); + self.children.lock().insert(name.into(), entry); } +} - /// Add a node to this directory. - pub fn add(&self, name: &'static str, node: VfsNodeRef) { - self.children.write().insert(name, node); +impl NodeOps for DevDirNode { + fn inode(&self) -> u64 { + self.inode } -} -impl VfsNodeOps for DirNode { - fn get_attr(&self) -> VfsResult { - Ok(VfsNodeAttr::new_dir(4096, 0)) + fn metadata(&self) -> VfsResult { + Ok(Metadata { + device: 0, + inode: self.inode, + nlink: 1, + mode: NodePermission::default(), + node_type: NodeType::Directory, + uid: 0, + gid: 0, + size: 4096, + block_size: 4096, + blocks: 8, + rdev: DeviceId::default(), + atime: Duration::default(), + mtime: Duration::default(), + ctime: Duration::default(), + }) } - fn parent(&self) -> Option { - self.parent.read().upgrade() + fn update_metadata(&self, _update: MetadataUpdate) -> VfsResult<()> { + Ok(()) } - fn lookup(self: Arc, path: &str) -> VfsResult { - let (name, rest) = split_path(path); - let node = match name { - "" | "." => Ok(self.clone() as VfsNodeRef), - ".." => self.parent().ok_or(VfsError::NotFound), - _ => self - .children - .read() - .get(name) - .cloned() - .ok_or(VfsError::NotFound), - }?; + fn filesystem(&self) -> &dyn FilesystemOps { + self.fs.deref() + } - if let Some(rest) = rest { - node.lookup(rest) - } else { - Ok(node) - } + fn sync(&self, _data_only: bool) -> VfsResult<()> { + Ok(()) } - fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { - let children = self.children.read(); - let mut children = children.iter().skip(start_idx.max(2) - 2); - for (i, ent) in dirents.iter_mut().enumerate() { - match i + start_idx { - 0 => *ent = VfsDirEntry::new(".", VfsNodeType::Dir), - 1 => *ent = VfsDirEntry::new("..", VfsNodeType::Dir), - _ => { - if let Some((name, node)) = children.next() { - *ent = VfsDirEntry::new(name, node.get_attr().unwrap().file_type()); - } else { - return Ok(i); - } - } - } - } - Ok(dirents.len()) - } - - fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { - log::debug!("create {ty:?} at devfs: {path}"); - let (name, rest) = split_path(path); - if let Some(rest) = rest { - match name { - "" | "." => self.create(rest, ty), - ".." => self.parent().ok_or(VfsError::NotFound)?.create(rest, ty), - _ => self - .children - .read() - .get(name) - .ok_or(VfsError::NotFound)? - .create(rest, ty), - } - } else if name.is_empty() || name == "." || name == ".." { - Ok(()) // already exists - } else { - Err(VfsError::PermissionDenied) // do not support to create nodes dynamically - } + fn into_any(self: Arc) -> Arc { + self } - fn remove(&self, path: &str) -> VfsResult { - log::debug!("remove at devfs: {path}"); - let (name, rest) = split_path(path); - if let Some(rest) = rest { - match name { - "" | "." => self.remove(rest), - ".." => self.parent().ok_or(VfsError::NotFound)?.remove(rest), - _ => self - .children - .read() - .get(name) - .ok_or(VfsError::NotFound)? - .remove(rest), + fn flags(&self) -> NodeFlags { + NodeFlags::NON_CACHEABLE + } +} + +impl DirNodeOps for DevDirNode { + fn read_dir(&self, offset: u64, sink: &mut dyn DirEntrySink) -> VfsResult { + let mut count = 0; + for (index, (name, entry)) in self + .children + .lock() + .iter() + .enumerate() + .skip(offset as usize) + { + if !sink.accept(name, entry.inode(), entry.node_type(), (index + 1) as u64) { + break; } - } else { - Err(VfsError::PermissionDenied) // do not support to remove nodes dynamically + count += 1; } + Ok(count) + } + + fn lookup(&self, name: &str) -> VfsResult { + self.children + .lock() + .get(name) + .cloned() + .ok_or(VfsError::NotFound) + } + + fn create( + &self, + _name: &str, + _node_type: NodeType, + _permission: NodePermission, + ) -> VfsResult { + Err(VfsError::PermissionDenied) } - ax_fs_vfs::impl_vfs_dir_default! {} + fn link(&self, _name: &str, _node: &DirEntry) -> VfsResult { + Err(VfsError::PermissionDenied) + } + + fn unlink(&self, _name: &str) -> VfsResult<()> { + Err(VfsError::PermissionDenied) + } + + fn rename(&self, _src_name: &str, _dst_dir: &VfsDirNode, _dst_name: &str) -> VfsResult<()> { + Err(VfsError::PermissionDenied) + } } -fn split_path(path: &str) -> (&str, Option<&str>) { - let trimmed_path = path.trim_start_matches('/'); - trimmed_path.find('/').map_or((trimmed_path, None), |n| { - (&trimmed_path[..n], Some(&trimmed_path[n + 1..])) - }) +impl Pollable for DevDirNode { + fn poll(&self) -> IoEvents { + IoEvents::IN | IoEvents::OUT + } + + fn register(&self, _context: &mut Context<'_>, _events: IoEvents) {} } diff --git a/components/axfs_crates/axfs_devfs/src/lib.rs b/components/axfs_crates/axfs_devfs/src/lib.rs index 99e8742a0a..31bc508308 100644 --- a/components/axfs_crates/axfs_devfs/src/lib.rs +++ b/components/axfs_crates/axfs_devfs/src/lib.rs @@ -1,71 +1,88 @@ -//! Device filesystem used by [ArceOS](https://github.com/arceos-org/arceos). +//! Device filesystem implementation for ArceOS. //! -//! The implementation is based on [`axfs_vfs`]. +//! The filesystem exposes a small static `/dev` tree and implements the +//! current `ax-fs-vfs` object model directly. #![cfg_attr(not(test), no_std)] extern crate alloc; +mod device; mod dir; -mod null; -mod urandom; -mod zero; - -#[cfg(test)] -mod tests; use alloc::sync::Arc; +use core::sync::atomic::{AtomicU64, Ordering}; + +use ax_fs_vfs::{ + DirEntry, Filesystem, FilesystemOps, Reference, StatFs, VfsResult, path::MAX_NAME_LEN, +}; +use spin::Once; -use ax_fs_vfs::{VfsNodeRef, VfsOps, VfsResult}; -use spin::once::Once; +use self::dir::DevDirNode; -pub use self::{dir::DirNode, null::NullDev, urandom::UrandomDev, zero::ZeroDev}; +/// Creates a new mountable device filesystem handle. +pub fn new() -> Filesystem { + DeviceFileSystem::new_filesystem() +} -/// A device filesystem that implements [`ax_fs_vfs::VfsOps`]. +/// Device filesystem. pub struct DeviceFileSystem { - parent: Once, - root: Arc, + next_inode: AtomicU64, + root_dir: Once, } impl DeviceFileSystem { - /// Create a new instance. - pub fn new() -> Self { - Self { - parent: Once::new(), - root: DirNode::new(None), - } + /// Creates a new device filesystem object with `null`, `zero`, and `urandom`. + pub fn new() -> Arc { + let fs = Arc::new(Self { + next_inode: AtomicU64::new(2), + root_dir: Once::new(), + }); + let root_fs = fs.clone(); + let root_dir = DirEntry::new_dir( + |this| DevDirNode::make(root_fs.clone(), this, 1), + Reference::root(), + ); + fs.root_dir.call_once(|| root_dir.clone()); + root_dir + .as_dir() + .and_then(|dir| dir.downcast::()) + .expect("devfs root downcast failed") + .populate_static_devices(); + fs } - /// Create a subdirectory at the root directory. - pub fn mkdir(&self, name: &'static str) -> Arc { - self.root.mkdir(name) + /// Creates a new mountable device filesystem handle. + pub fn new_filesystem() -> Filesystem { + Filesystem::new(Self::new()) } - /// Add a node to the root directory. - /// - /// The node must implement [`ax_fs_vfs::VfsNodeOps`], and be wrapped in [`Arc`]. - pub fn add(&self, name: &'static str, node: VfsNodeRef) { - self.root.add(name, node); + pub(crate) fn alloc_inode(&self) -> u64 { + self.next_inode.fetch_add(1, Ordering::Relaxed) } } -impl VfsOps for DeviceFileSystem { - fn mount(&self, _path: &str, mount_point: VfsNodeRef) -> VfsResult { - if let Some(parent) = mount_point.parent() { - self.root.set_parent(Some(self.parent.call_once(|| parent))); - } else { - self.root.set_parent(None); - } - Ok(()) +impl FilesystemOps for DeviceFileSystem { + fn name(&self) -> &str { + "devfs" } - fn root_dir(&self) -> VfsNodeRef { - self.root.clone() + fn root_dir(&self) -> DirEntry { + self.root_dir.get().unwrap().clone() } -} -impl Default for DeviceFileSystem { - fn default() -> Self { - Self::new() + fn stat(&self) -> VfsResult { + Ok(StatFs { + fs_type: 0x1373, + block_size: 4096, + blocks: 0, + blocks_free: 0, + blocks_available: 0, + file_count: self.next_inode.load(Ordering::Relaxed), + free_file_count: 0, + name_length: MAX_NAME_LEN as u32, + fragment_size: 4096, + mount_flags: 0, + }) } } diff --git a/components/axfs_crates/axfs_devfs/src/null.rs b/components/axfs_crates/axfs_devfs/src/null.rs deleted file mode 100644 index 6fbec3a7ee..0000000000 --- a/components/axfs_crates/axfs_devfs/src/null.rs +++ /dev/null @@ -1,31 +0,0 @@ -use ax_fs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodePerm, VfsNodeType, VfsResult}; - -/// A null device behaves like `/dev/null`. -/// -/// Nothing can be read and all writes are discarded. -pub struct NullDev; - -impl VfsNodeOps for NullDev { - fn get_attr(&self) -> VfsResult { - Ok(VfsNodeAttr::new( - VfsNodePerm::default_file(), - VfsNodeType::CharDevice, - 0, - 0, - )) - } - - fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> VfsResult { - Ok(0) - } - - fn write_at(&self, _offset: u64, buf: &[u8]) -> VfsResult { - Ok(buf.len()) - } - - fn truncate(&self, _size: u64) -> VfsResult { - Ok(()) - } - - ax_fs_vfs::impl_vfs_non_dir_default! {} -} diff --git a/components/axfs_crates/axfs_devfs/src/tests.rs b/components/axfs_crates/axfs_devfs/src/tests.rs deleted file mode 100644 index d754684a34..0000000000 --- a/components/axfs_crates/axfs_devfs/src/tests.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::sync::Arc; - -use ax_fs_vfs::{VfsError, VfsNodeType, VfsResult}; - -use crate::*; - -fn test_devfs_ops(devfs: &DeviceFileSystem) -> VfsResult { - const N: usize = 32; - let mut buf = [1; N]; - - let root = devfs.root_dir(); - assert!(root.get_attr()?.is_dir()); - assert_eq!(root.get_attr()?.file_type(), VfsNodeType::Dir); - assert_eq!( - root.clone().lookup("urandom").err(), - Some(VfsError::NotFound) - ); - assert_eq!( - root.clone().lookup("zero/").err(), - Some(VfsError::NotADirectory) - ); - - let node = root.lookup("////null")?; - assert_eq!(node.get_attr()?.file_type(), VfsNodeType::CharDevice); - assert!(!node.get_attr()?.is_dir()); - assert_eq!(node.get_attr()?.size(), 0); - assert_eq!(node.read_at(0, &mut buf)?, 0); - assert_eq!(buf, [1; N]); - assert_eq!(node.write_at(N as _, &buf)?, N); - assert_eq!(node.lookup("/").err(), Some(VfsError::NotADirectory)); - - let node = devfs.root_dir().lookup(".///.//././/.////zero")?; - assert_eq!(node.get_attr()?.file_type(), VfsNodeType::CharDevice); - assert!(!node.get_attr()?.is_dir()); - assert_eq!(node.get_attr()?.size(), 0); - assert_eq!(node.read_at(10, &mut buf)?, N); - assert_eq!(buf, [0; N]); - assert_eq!(node.write_at(0, &buf)?, N); - - let foo = devfs.root_dir().lookup(".///.//././/.////foo")?; - assert!(foo.get_attr()?.is_dir()); - assert_eq!( - foo.read_at(10, &mut buf).err(), - Some(VfsError::IsADirectory) - ); - assert!(Arc::ptr_eq( - &foo.clone().lookup("/f2")?, - &devfs.root_dir().lookup(".//./foo///f2")?, - )); - assert_eq!( - foo.clone().lookup("/bar//f1")?.get_attr()?.file_type(), - VfsNodeType::CharDevice - ); - assert_eq!( - foo.lookup("/bar///")?.get_attr()?.file_type(), - VfsNodeType::Dir - ); - - Ok(()) -} - -fn test_get_parent(devfs: &DeviceFileSystem) -> VfsResult { - let root = devfs.root_dir(); - assert!(root.parent().is_none()); - - let node = root.clone().lookup("null")?; - assert!(node.parent().is_none()); - - let node = root.clone().lookup(".//foo/bar")?; - assert!(node.parent().is_some()); - let parent = node.parent().unwrap(); - assert!(Arc::ptr_eq(&parent, &root.clone().lookup("foo")?)); - assert!(parent.lookup("bar").is_ok()); - - let node = root.clone().lookup("foo/..")?; - assert!(Arc::ptr_eq(&node, &root.clone().lookup(".")?)); - - assert!(Arc::ptr_eq( - &root.clone().lookup("/foo/..")?, - &devfs.root_dir().lookup(".//./foo/././bar/../..")?, - )); - assert!(Arc::ptr_eq( - &root.clone().lookup("././/foo//./../foo//bar///..//././")?, - &devfs.root_dir().lookup(".//./foo/")?, - )); - assert!(Arc::ptr_eq( - &root.clone().lookup("///foo//bar///../f2")?, - &root.lookup("foo/.//f2")?, - )); - - Ok(()) -} - -#[test] -fn test_devfs() { - // . - // ├── foo - // │   ├── bar - // │   │   └── f1 (null) - // │   └── f2 (zero) - // ├── null - // └── zero - - let devfs = DeviceFileSystem::new(); - devfs.add("null", Arc::new(NullDev)); - devfs.add("zero", Arc::new(ZeroDev)); - - let dir_foo = devfs.mkdir("foo"); - dir_foo.add("f2", Arc::new(ZeroDev)); - let dir_bar = dir_foo.mkdir("bar"); - dir_bar.add("f1", Arc::new(NullDev)); - - test_devfs_ops(&devfs).unwrap(); - test_get_parent(&devfs).unwrap(); -} diff --git a/components/axfs_crates/axfs_devfs/src/urandom.rs b/components/axfs_crates/axfs_devfs/src/urandom.rs deleted file mode 100644 index 35f756b233..0000000000 --- a/components/axfs_crates/axfs_devfs/src/urandom.rs +++ /dev/null @@ -1,75 +0,0 @@ -use core::sync::atomic::{AtomicU64, Ordering}; - -use ax_fs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodePerm, VfsNodeType, VfsResult}; - -/// A urandom device behaves like `/dev/urandom`. -/// -/// It produces random bytes when read. -pub struct UrandomDev { - seed: AtomicU64, -} - -impl UrandomDev { - /// Create a new instance of the urandom device. - pub const fn new(seed: u64) -> Self { - Self { - seed: AtomicU64::new(seed), - } - } - - /// Create a new instance with a default seed. - fn new_with_default_seed() -> Self { - Self::new(0xa2ce_a2ce) - } - - /// LCG pseudo-random number generator - fn next_u64(&self) -> u64 { - let new_seed = self - .seed - .load(Ordering::SeqCst) - .wrapping_mul(6364136223846793005) - + 1; - self.seed.store(new_seed, Ordering::SeqCst); - new_seed - } -} - -impl Default for UrandomDev { - fn default() -> Self { - Self::new_with_default_seed() - } -} - -impl VfsNodeOps for UrandomDev { - fn get_attr(&self) -> VfsResult { - Ok(VfsNodeAttr::new( - VfsNodePerm::default_file(), - VfsNodeType::CharDevice, - 0, - 0, - )) - } - - fn read_at(&self, _offset: u64, buf: &mut [u8]) -> VfsResult { - for chunk in buf.chunks_mut(8) { - let random_value = self.next_u64(); - let bytes = random_value.to_ne_bytes(); - for (i, byte) in chunk.iter_mut().enumerate() { - if i < bytes.len() { - *byte = bytes[i]; - } - } - } - Ok(buf.len()) - } - - fn write_at(&self, _offset: u64, buf: &[u8]) -> VfsResult { - Ok(buf.len()) - } - - fn truncate(&self, _size: u64) -> VfsResult { - Ok(()) - } - - ax_fs_vfs::impl_vfs_non_dir_default! {} -} diff --git a/components/axfs_crates/axfs_devfs/src/zero.rs b/components/axfs_crates/axfs_devfs/src/zero.rs deleted file mode 100644 index 16759936a6..0000000000 --- a/components/axfs_crates/axfs_devfs/src/zero.rs +++ /dev/null @@ -1,32 +0,0 @@ -use ax_fs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodePerm, VfsNodeType, VfsResult}; - -/// A zero device behaves like `/dev/zero`. -/// -/// It always returns a chunk of `\0` bytes when read, and all writes are discarded. -pub struct ZeroDev; - -impl VfsNodeOps for ZeroDev { - fn get_attr(&self) -> VfsResult { - Ok(VfsNodeAttr::new( - VfsNodePerm::default_file(), - VfsNodeType::CharDevice, - 0, - 0, - )) - } - - fn read_at(&self, _offset: u64, buf: &mut [u8]) -> VfsResult { - buf.fill(0); - Ok(buf.len()) - } - - fn write_at(&self, _offset: u64, buf: &[u8]) -> VfsResult { - Ok(buf.len()) - } - - fn truncate(&self, _size: u64) -> VfsResult { - Ok(()) - } - - ax_fs_vfs::impl_vfs_non_dir_default! {} -} diff --git a/components/axfs_crates/axfs_ramfs/CHANGELOG.md b/components/axfs_crates/axfs_ramfs/CHANGELOG.md deleted file mode 100644 index 6b448dc779..0000000000 --- a/components/axfs_crates/axfs_ramfs/CHANGELOG.md +++ /dev/null @@ -1,32 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.3.12](https://github.com/rcore-os/tgoskits/compare/ax-fs-ramfs-v0.3.11...ax-fs-ramfs-v0.3.12) - 2026-05-22 - -### Other - -- updated the following local packages: ax-fs-vfs - -## [0.3.11](https://github.com/rcore-os/tgoskits/compare/ax-fs-ramfs-v0.3.10...ax-fs-ramfs-v0.3.11) - 2026-05-19 - -### Other - -- updated the following local packages: ax-fs-vfs - -## [0.3.10](https://github.com/rcore-os/tgoskits/compare/ax-fs-ramfs-v0.3.9...ax-fs-ramfs-v0.3.10) - 2026-05-15 - -### Other - -- *(axfs-crates)* inherit workspace metadata - -## [0.3.9](https://github.com/rcore-os/tgoskits/compare/ax-fs-ramfs-v0.3.8...ax-fs-ramfs-v0.3.9) - 2026-04-27 - -### Other - -- *(repo)* align touched crates with Rust 2024 diff --git a/components/axfs_crates/axfs_ramfs/Cargo.toml b/components/axfs_crates/axfs_ramfs/Cargo.toml index e63becbb04..2edf8338b7 100644 --- a/components/axfs_crates/axfs_ramfs/Cargo.toml +++ b/components/axfs_crates/axfs_ramfs/Cargo.toml @@ -1,15 +1,19 @@ [package] name = "ax-fs-ramfs" -description = "RAM filesystem used by ArceOS" +description = "In-memory filesystem implementation for ArceOS" keywords = ["arceos", "filesystem", "ramfs"] edition.workspace = true -version = "0.3.12" +version = "0.4.0" repository = "https://github.com/rcore-os/tgoskits" authors = ["Yuekai Jia "] categories = ["os", "no-std", "filesystem"] license = "Apache-2.0" +[features] +default = [] + [dependencies] -ax-fs-vfs.workspace = true +ax-fs-vfs = { workspace = true } +ax-kspin = { workspace = true } +axpoll = { workspace = true } spin = { workspace = true } -log = "0.4" diff --git a/components/axfs_crates/axfs_ramfs/README.md b/components/axfs_crates/axfs_ramfs/README.md index 20a15896f1..6d2521cb09 100644 --- a/components/axfs_crates/axfs_ramfs/README.md +++ b/components/axfs_crates/axfs_ramfs/README.md @@ -1,84 +1,23 @@ -

ax-fs-ramfs

+# ax-fs-ramfs -

RAM filesystem used by ArceOS

+In-memory filesystem implementation for ArceOS. -
+This crate implements the current `ax-fs-vfs` object model directly. It does +not expose or depend on the removed legacy `VfsOps` / `VfsNodeOps` interface. -[![Crates.io](https://img.shields.io/crates/v/ax-fs-ramfs.svg)](https://crates.io/crates/ax-fs-ramfs) -[![Docs.rs](https://docs.rs/ax-fs-ramfs/badge.svg)](https://docs.rs/ax-fs-ramfs) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) +## Features -
+- In-memory directories and regular files. +- Hard links share the same underlying file node. +- Symbolic links store their target in memory. +- `rename` rebuilds directory entry references so absolute paths remain correct. -English | [中文](README_CN.md) - -# Introduction - -`ax-fs-ramfs` provides RAM filesystem used by ArceOS. It is maintained as part of the TGOSKits component set and is intended for Rust projects that integrate with ArceOS, AxVisor, or related low-level systems software. - - -> ax-fs-ramfs was derived from https://github.com/arceos-org/axfs_crates - -## Quick Start - -### Installation - -Add this crate to your `Cargo.toml`: - -```toml -[dependencies] -ax-fs-ramfs = "0.3.2" -``` - -### Run Check and Test - -```bash -# Enter the crate directory -cd components/axfs_crates/axfs_ramfs - -# Format code -cargo fmt --all - -# Run clippy -cargo clippy --all-targets --all-features - -# Run tests -cargo test --all-features - -# Build documentation -cargo doc --no-deps -``` - -## Integration - -### Example +## Usage ```rust -use ax_fs_ramfs as _; - -fn main() { - // Integrate `ax-fs-ramfs` into your project here. -} +let fs = ax_fs_ramfs::new(); +let root = ax_fs_vfs::Mountpoint::new_root(&fs).root_location(); ``` -### Documentation - -Generate and view API documentation: - -```bash -cargo doc --no-deps --open -``` - -Online documentation: [docs.rs/ax-fs-ramfs](https://docs.rs/ax-fs-ramfs) - -# Contributing - -1. Fork the repository and create a branch -2. Run local format and checks -3. Run local tests relevant to this crate -4. Submit a PR and ensure CI passes - -# License - -Licensed under the Apache License, Version 2.0. See [LICENSE](./LICENSE) for details. +The returned value is an `ax_fs_vfs::Filesystem` and can be mounted through the +normal `Location::mount()` path. diff --git a/components/axfs_crates/axfs_ramfs/README_CN.md b/components/axfs_crates/axfs_ramfs/README_CN.md index 30d80a7607..2b590695bb 100644 --- a/components/axfs_crates/axfs_ramfs/README_CN.md +++ b/components/axfs_crates/axfs_ramfs/README_CN.md @@ -1,84 +1,22 @@ -

ax-fs-ramfs

+# ax-fs-ramfs -

RAM filesystem used by ArceOS

+ArceOS 的内存文件系统实现。 -
+该 crate 直接实现当前 `ax-fs-vfs` 对象模型,不暴露也不依赖已经移除的旧 +`VfsOps` / `VfsNodeOps` 接口。 -[![Crates.io](https://img.shields.io/crates/v/ax-fs-ramfs.svg)](https://crates.io/crates/ax-fs-ramfs) -[![Docs.rs](https://docs.rs/ax-fs-ramfs/badge.svg)](https://docs.rs/ax-fs-ramfs) -[![Rust](https://img.shields.io/badge/edition-2021-orange.svg)](https://www.rust-lang.org/) -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE) +## 功能 -
+- 内存目录和普通文件。 +- 硬链接共享同一个底层文件节点。 +- 符号链接在内存中保存 target。 +- `rename` 会重建目录项引用,保证绝对路径仍然正确。 -[English](README.md) | 中文 - -# 介绍 - -`ax-fs-ramfs` 提供了 RAM filesystem used by ArceOS。它是 TGOSKits 组件集合的一部分,可用于集成 ArceOS、AxVisor 及相关底层系统软件的 Rust 项目。 - - -> ax-fs-ramfs 派生自 https://github.com/arceos-org/axfs_crates - -## 快速开始 - -### 添加依赖 - -在 `Cargo.toml` 中加入: - -```toml -[dependencies] -ax-fs-ramfs = "0.3.2" -``` - -### 检查与测试 - -```bash -# 进入 crate 目录 -cd components/axfs_crates/axfs_ramfs - -# 代码格式化 -cargo fmt --all - -# 运行 clippy -cargo clippy --all-targets --all-features - -# 运行测试 -cargo test --all-features - -# 生成文档 -cargo doc --no-deps -``` - -## 集成方式 - -### 示例 +## 使用方式 ```rust -use ax_fs_ramfs as _; - -fn main() { - // 在这里将 `ax-fs-ramfs` 集成到你的项目中。 -} +let fs = ax_fs_ramfs::new(); +let root = ax_fs_vfs::Mountpoint::new_root(&fs).root_location(); ``` -### 文档 - -生成并查看 API 文档: - -```bash -cargo doc --no-deps --open -``` - -在线文档:[docs.rs/ax-fs-ramfs](https://docs.rs/ax-fs-ramfs) - -# 贡献 - -1. Fork 仓库并创建分支 -2. 在本地运行格式化与检查 -3. 运行与该 crate 相关的测试 -4. 提交 PR 并确保 CI 通过 - -# 许可证 - -本项目采用 Apache License 2.0 许可证。详情见 [LICENSE](./LICENSE)。 +返回值是 `ax_fs_vfs::Filesystem`,可以通过标准的 `Location::mount()` 路径挂载。 diff --git a/components/axfs_crates/axfs_ramfs/src/dir.rs b/components/axfs_crates/axfs_ramfs/src/dir.rs index d19558b1ed..e34a2c3185 100644 --- a/components/axfs_crates/axfs_ramfs/src/dir.rs +++ b/components/axfs_crates/axfs_ramfs/src/dir.rs @@ -1,182 +1,268 @@ -use alloc::{ - collections::BTreeMap, - string::String, - sync::{Arc, Weak}, - vec::Vec, -}; +use alloc::{collections::BTreeMap, string::String, sync::Arc}; +use core::{any::Any, ops::Deref, task::Context, time::Duration}; use ax_fs_vfs::{ - VfsDirEntry, VfsError, VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsResult, + DeviceId, DirEntry, DirEntrySink, DirNode as VfsDirNode, DirNodeOps, FilesystemOps, Metadata, + MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, Reference, VfsError, VfsResult, + WeakDirEntry, }; -use spin::RwLock; +use ax_kspin::SpinNoIrq as Mutex; +use axpoll::{IoEvents, Pollable}; -use crate::file::FileNode; +use crate::{RamFileSystem, file::FileNode}; -/// The directory node in the RAM filesystem. -/// -/// It implements [`ax_fs_vfs::VfsNodeOps`]. -pub struct DirNode { - this: Weak, - parent: RwLock>, - children: RwLock>, +pub(crate) struct RamDirNode { + fs: Arc, + this: Mutex, + inode: u64, + mode: Mutex, + children: Mutex>, } -impl DirNode { - pub(super) fn new(parent: Option>) -> Arc { - Arc::new_cyclic(|this| Self { - this: this.clone(), - parent: RwLock::new(parent.unwrap_or_else(|| Weak::::new())), - children: RwLock::new(BTreeMap::new()), - }) +impl RamDirNode { + pub(crate) fn make( + fs: Arc, + this: WeakDirEntry, + inode: u64, + mode: NodePermission, + ) -> VfsDirNode { + VfsDirNode::new(Arc::new(Self { + fs, + this: Mutex::new(this), + inode, + mode: Mutex::new(mode), + children: Mutex::new(BTreeMap::new()), + })) } - pub(super) fn set_parent(&self, parent: Option<&VfsNodeRef>) { - *self.parent.write() = parent.map_or(Weak::::new() as _, Arc::downgrade); + fn make_entry( + &self, + name: &str, + node_type: NodeType, + mode: NodePermission, + ) -> VfsResult { + let fs = self.fs.clone(); + let reference = Reference::new(self.this.lock().upgrade(), name.into()); + let inode = fs.alloc_inode(); + match node_type { + NodeType::RegularFile | NodeType::Symlink => Ok(DirEntry::new_file( + FileNode::make(fs, inode, mode, None), + node_type, + reference, + )), + NodeType::Directory => Ok(DirEntry::new_dir( + |this| { + VfsDirNode::new(Arc::new(Self { + fs, + this: Mutex::new(this), + inode, + mode: Mutex::new(mode), + children: Mutex::new(BTreeMap::new()), + })) + }, + reference, + )), + _ => Err(VfsError::Unsupported), + } } - /// Returns a string list of all entries in this directory. - pub fn get_entries(&self) -> Vec { - self.children.read().keys().cloned().collect() - } + fn rebind_entry( + &self, + entry: &DirEntry, + parent: Option, + name: &str, + ) -> VfsResult { + let reference = Reference::new(parent, name.into()); + if entry.is_file() { + return Ok(DirEntry::new_file( + ax_fs_vfs::FileNode::new(entry.as_file()?.inner().clone()), + entry.node_type(), + reference, + )); + } - /// Checks whether a node with the given name exists in this directory. - pub fn exist(&self, name: &str) -> bool { - self.children.read().contains_key(name) + let old_dir = entry.as_dir()?.downcast::()?; + let node = old_dir.clone(); + let rebound = DirEntry::new_dir_node_cyclic( + |this| { + *node.this.lock() = this; + VfsDirNode::new(node) + }, + reference, + ); + old_dir.rebind_children_to(&rebound)?; + Ok(rebound) } - /// Creates a new node with the given name and type in this directory. - pub fn create_node(&self, name: &str, ty: VfsNodeType) -> VfsResult { - if self.exist(name) { - log::error!("AlreadyExists {name}"); - return Err(VfsError::AlreadyExists); + fn rebind_children_to(&self, parent: &DirEntry) -> VfsResult<()> { + let mut children = self.children.lock(); + for (name, child) in children.iter_mut() { + *child = self.rebind_entry(child, Some(parent.clone()), name)?; } - let node: VfsNodeRef = match ty { - VfsNodeType::File => Arc::new(FileNode::new()), - VfsNodeType::Dir => Self::new(Some(self.this.clone())), - _ => return Err(VfsError::Unsupported), - }; - self.children.write().insert(name.into(), node); Ok(()) } - /// Removes a node by the given name in this directory. - pub fn remove_node(&self, name: &str) -> VfsResult { - let mut children = self.children.write(); - let node = children.get(name).ok_or(VfsError::NotFound)?; - if node - .as_any() - .downcast_ref::() - .is_some_and(|dir| !dir.children.read().is_empty()) + fn check_replace_target(children: &BTreeMap, name: &str) -> VfsResult<()> { + if let Some(existing) = children.get(name) + && existing.is_dir() + && existing.as_dir()?.has_children()? { return Err(VfsError::DirectoryNotEmpty); } - children.remove(name); Ok(()) } } -impl VfsNodeOps for DirNode { - fn get_attr(&self) -> VfsResult { - Ok(VfsNodeAttr::new_dir(4096, 0)) +impl NodeOps for RamDirNode { + fn inode(&self) -> u64 { + self.inode } - fn parent(&self) -> Option { - self.parent.read().upgrade() + fn metadata(&self) -> VfsResult { + Ok(Metadata { + device: 0, + inode: self.inode, + nlink: 1, + mode: *self.mode.lock(), + node_type: NodeType::Directory, + uid: 0, + gid: 0, + size: 4096, + block_size: 4096, + blocks: 8, + rdev: DeviceId::default(), + atime: Duration::default(), + mtime: Duration::default(), + ctime: Duration::default(), + }) } - fn lookup(self: Arc, path: &str) -> VfsResult { - let (name, rest) = split_path(path); - let node = match name { - "" | "." => Ok(self.clone() as VfsNodeRef), - ".." => self.parent().ok_or(VfsError::NotFound), - _ => self - .children - .read() - .get(name) - .cloned() - .ok_or(VfsError::NotFound), - }?; - - if let Some(rest) = rest { - node.lookup(rest) - } else { - Ok(node) + fn update_metadata(&self, update: MetadataUpdate) -> VfsResult<()> { + if let Some(mode) = update.mode { + *self.mode.lock() = mode; } + Ok(()) + } + + fn filesystem(&self) -> &dyn FilesystemOps { + self.fs.deref() } - fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { - let children = self.children.read(); - let mut children = children.iter().skip(start_idx.max(2) - 2); - for (i, ent) in dirents.iter_mut().enumerate() { - match i + start_idx { - 0 => *ent = VfsDirEntry::new(".", VfsNodeType::Dir), - 1 => *ent = VfsDirEntry::new("..", VfsNodeType::Dir), - _ => { - if let Some((name, node)) = children.next() { - *ent = VfsDirEntry::new(name, node.get_attr().unwrap().file_type()); - } else { - return Ok(i); - } - } + fn sync(&self, _data_only: bool) -> VfsResult<()> { + Ok(()) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn flags(&self) -> NodeFlags { + NodeFlags::ALWAYS_CACHE + } +} + +impl DirNodeOps for RamDirNode { + fn read_dir(&self, offset: u64, sink: &mut dyn DirEntrySink) -> VfsResult { + let mut count = 0; + for (index, (name, entry)) in self + .children + .lock() + .iter() + .enumerate() + .skip(offset as usize) + { + if !sink.accept(name, entry.inode(), entry.node_type(), (index + 1) as u64) { + break; } + count += 1; } - Ok(dirents.len()) - } - - fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { - log::debug!("create {ty:?} at ramfs: {path}"); - let (name, rest) = split_path(path); - if let Some(rest) = rest { - match name { - "" | "." => self.create(rest, ty), - ".." => self.parent().ok_or(VfsError::NotFound)?.create(rest, ty), - _ => { - let subdir = self - .children - .read() - .get(name) - .ok_or(VfsError::NotFound)? - .clone(); - subdir.create(rest, ty) - } - } - } else if name.is_empty() || name == "." || name == ".." { - Ok(()) // already exists - } else { - self.create_node(name, ty) + Ok(count) + } + + fn lookup(&self, name: &str) -> VfsResult { + self.children + .lock() + .get(name) + .cloned() + .ok_or(VfsError::NotFound) + } + + fn create( + &self, + name: &str, + node_type: NodeType, + permission: NodePermission, + ) -> VfsResult { + let mut children = self.children.lock(); + if children.contains_key(name) { + return Err(VfsError::AlreadyExists); } + let entry = self.make_entry(name, node_type, permission)?; + children.insert(name.into(), entry.clone()); + Ok(entry) } - fn remove(&self, path: &str) -> VfsResult { - log::debug!("remove at ramfs: {path}"); - let (name, rest) = split_path(path); - if let Some(rest) = rest { - match name { - "" | "." => self.remove(rest), - ".." => self.parent().ok_or(VfsError::NotFound)?.remove(rest), - _ => { - let subdir = self - .children - .read() - .get(name) - .ok_or(VfsError::NotFound)? - .clone(); - subdir.remove(rest) - } - } - } else if name.is_empty() || name == "." || name == ".." { - Err(VfsError::InvalidInput) // remove '.' or '.. - } else { - self.remove_node(name) + fn link(&self, name: &str, node: &DirEntry) -> VfsResult { + if node.is_dir() { + return Err(VfsError::OperationNotPermitted); + } + let mut children = self.children.lock(); + if children.contains_key(name) { + return Err(VfsError::AlreadyExists); } + let reference = Reference::new(self.this.lock().upgrade(), name.into()); + let entry = DirEntry::new_file( + ax_fs_vfs::FileNode::new(node.as_file()?.inner().clone()), + node.node_type(), + reference, + ); + children.insert(name.into(), entry.clone()); + Ok(entry) } - ax_fs_vfs::impl_vfs_dir_default! {} + fn unlink(&self, name: &str) -> VfsResult<()> { + let mut children = self.children.lock(); + let entry = children.get(name).ok_or(VfsError::NotFound)?; + if let Ok(dir) = entry.as_dir() + && dir.has_children()? + { + return Err(VfsError::DirectoryNotEmpty); + } + children.remove(name); + Ok(()) + } + + fn rename(&self, src_name: &str, dst_dir: &VfsDirNode, dst_name: &str) -> VfsResult<()> { + let dst = dst_dir.downcast::()?; + if core::ptr::eq(self, dst.as_ref()) { + if src_name == dst_name { + return Ok(()); + } + let mut children = self.children.lock(); + let entry = children.get(src_name).cloned().ok_or(VfsError::NotFound)?; + Self::check_replace_target(&children, dst_name)?; + let rebound = self.rebind_entry(&entry, self.this.lock().upgrade(), dst_name)?; + children.remove(src_name); + children.insert(dst_name.into(), rebound); + return Ok(()); + } + + let entry = self.lookup(src_name)?; + { + let dst_children = dst.children.lock(); + Self::check_replace_target(&dst_children, dst_name)?; + } + let rebound = dst.rebind_entry(&entry, dst.this.lock().upgrade(), dst_name)?; + self.children.lock().remove(src_name); + dst.children.lock().insert(dst_name.into(), rebound); + Ok(()) + } } -fn split_path(path: &str) -> (&str, Option<&str>) { - let trimmed_path = path.trim_start_matches('/'); - trimmed_path.find('/').map_or((trimmed_path, None), |n| { - (&trimmed_path[..n], Some(&trimmed_path[n + 1..])) - }) +impl Pollable for RamDirNode { + fn poll(&self) -> IoEvents { + IoEvents::IN | IoEvents::OUT + } + + fn register(&self, _context: &mut Context<'_>, _events: IoEvents) {} } diff --git a/components/axfs_crates/axfs_ramfs/src/file.rs b/components/axfs_crates/axfs_ramfs/src/file.rs index d5518b0ef4..676a14e9a6 100644 --- a/components/axfs_crates/axfs_ramfs/src/file.rs +++ b/components/axfs_crates/axfs_ramfs/src/file.rs @@ -1,57 +1,167 @@ -use alloc::vec::Vec; +use alloc::{string::String, sync::Arc, vec::Vec}; +use core::{any::Any, cmp::min, ops::Deref, task::Context, time::Duration}; -use ax_fs_vfs::{VfsNodeAttr, VfsNodeOps, VfsResult, impl_vfs_non_dir_default}; -use spin::RwLock; +use ax_fs_vfs::{ + DeviceId, FileNode as VfsFileNode, FileNodeOps, FilesystemOps, Metadata, MetadataUpdate, + NodeFlags, NodeOps, NodePermission, NodeType, VfsError, VfsResult, +}; +use ax_kspin::SpinNoIrq as Mutex; +use axpoll::{IoEvents, Pollable}; + +use crate::RamFileSystem; -/// The file node in the RAM filesystem. -/// -/// It implements [`ax_fs_vfs::VfsNodeOps`]. pub struct FileNode { - content: RwLock>, + fs: Arc, + inode: u64, + mode: Mutex, + inner: Mutex, +} + +#[derive(Default)] +struct FileInner { + content: Vec, + symlink: Option, } impl FileNode { - pub(super) const fn new() -> Self { - Self { - content: RwLock::new(Vec::new()), + pub(crate) fn make( + fs: Arc, + inode: u64, + mode: NodePermission, + symlink: Option, + ) -> VfsFileNode { + VfsFileNode::new(Arc::new(Self { + fs, + inode, + mode: Mutex::new(mode), + inner: Mutex::new(FileInner { + content: Vec::new(), + symlink, + }), + })) + } + + fn node_type(&self) -> NodeType { + if self.inner.lock().symlink.is_some() { + NodeType::Symlink + } else { + NodeType::RegularFile } } } -impl VfsNodeOps for FileNode { - fn get_attr(&self) -> VfsResult { - Ok(VfsNodeAttr::new_file(self.content.read().len() as _, 0)) +impl NodeOps for FileNode { + fn inode(&self) -> u64 { + self.inode } - fn truncate(&self, size: u64) -> VfsResult { - let mut content = self.content.write(); - if size < content.len() as u64 { - content.truncate(size as _); - } else { - content.resize(size as _, 0); + fn metadata(&self) -> VfsResult { + let inner = self.inner.lock(); + Ok(Metadata { + device: 0, + inode: self.inode, + nlink: 1, + mode: *self.mode.lock(), + node_type: if inner.symlink.is_some() { + NodeType::Symlink + } else { + NodeType::RegularFile + }, + uid: 0, + gid: 0, + size: inner + .symlink + .as_ref() + .map_or(inner.content.len(), String::len) as u64, + block_size: 4096, + blocks: inner.content.len().div_ceil(512) as u64, + rdev: DeviceId::default(), + atime: Duration::default(), + mtime: Duration::default(), + ctime: Duration::default(), + }) + } + + fn update_metadata(&self, update: MetadataUpdate) -> VfsResult<()> { + if let Some(mode) = update.mode { + *self.mode.lock() = mode; } Ok(()) } - fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { - let content = self.content.read(); - let start = content.len().min(offset as usize); - let end = content.len().min(offset as usize + buf.len()); - let src = &content[start..end]; + fn filesystem(&self) -> &dyn FilesystemOps { + self.fs.deref() + } + + fn sync(&self, _data_only: bool) -> VfsResult<()> { + Ok(()) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn flags(&self) -> NodeFlags { + NodeFlags::ALWAYS_CACHE + } +} + +impl FileNodeOps for FileNode { + fn read_at(&self, buf: &mut [u8], offset: u64) -> VfsResult { + let inner = self.inner.lock(); + let data = inner + .symlink + .as_ref() + .map_or(inner.content.as_slice(), String::as_bytes); + let start = min(offset as usize, data.len()); + let end = min(start + buf.len(), data.len()); + let src = &data[start..end]; buf[..src.len()].copy_from_slice(src); Ok(src.len()) } - fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { + fn write_at(&self, buf: &[u8], offset: u64) -> VfsResult { + if self.node_type() == NodeType::Symlink { + return Err(VfsError::InvalidInput); + } let offset = offset as usize; - let mut content = self.content.write(); - if offset + buf.len() > content.len() { - content.resize(offset + buf.len(), 0); + let mut inner = self.inner.lock(); + if offset + buf.len() > inner.content.len() { + inner.content.resize(offset + buf.len(), 0); } - let dst = &mut content[offset..offset + buf.len()]; - dst.copy_from_slice(&buf[..dst.len()]); + inner.content[offset..offset + buf.len()].copy_from_slice(buf); Ok(buf.len()) } - impl_vfs_non_dir_default! {} + fn append(&self, buf: &[u8]) -> VfsResult<(usize, u64)> { + if self.node_type() == NodeType::Symlink { + return Err(VfsError::InvalidInput); + } + let mut inner = self.inner.lock(); + inner.content.extend_from_slice(buf); + Ok((buf.len(), inner.content.len() as u64)) + } + + fn set_len(&self, len: u64) -> VfsResult<()> { + if self.node_type() == NodeType::Symlink { + return Err(VfsError::InvalidInput); + } + self.inner.lock().content.resize(len as usize, 0); + Ok(()) + } + + fn set_symlink(&self, target: &str) -> VfsResult<()> { + let mut inner = self.inner.lock(); + inner.content.clear(); + inner.symlink = Some(target.into()); + Ok(()) + } +} + +impl Pollable for FileNode { + fn poll(&self) -> IoEvents { + IoEvents::IN | IoEvents::OUT + } + + fn register(&self, _context: &mut Context<'_>, _events: IoEvents) {} } diff --git a/components/axfs_crates/axfs_ramfs/src/lib.rs b/components/axfs_crates/axfs_ramfs/src/lib.rs index 4a2406a761..e7eaa386de 100644 --- a/components/axfs_crates/axfs_ramfs/src/lib.rs +++ b/components/axfs_crates/axfs_ramfs/src/lib.rs @@ -1,6 +1,8 @@ -//! RAM filesystem used by [ArceOS](https://github.com/arceos-org/arceos). +//! In-memory filesystem implementation for ArceOS. //! -//! The implementation is based on [`axfs_vfs`]. +//! This crate implements the current `ax-fs-vfs` object model directly. It is +//! intentionally small: directory entries live in memory and regular file +//! contents are stored in growable byte buffers. #![cfg_attr(not(test), no_std)] @@ -9,54 +11,75 @@ extern crate alloc; mod dir; mod file; -#[cfg(test)] -mod tests; - use alloc::sync::Arc; +use core::sync::atomic::{AtomicU64, Ordering}; + +use ax_fs_vfs::{ + DirEntry, Filesystem, FilesystemOps, Reference, StatFs, VfsResult, path::MAX_NAME_LEN, +}; +use spin::Once; -use ax_fs_vfs::{VfsNodeRef, VfsOps, VfsResult}; -use spin::once::Once; +use self::dir::RamDirNode; -pub use self::{dir::DirNode, file::FileNode}; +/// Creates a new mountable RAM filesystem handle. +pub fn new() -> Filesystem { + RamFileSystem::new_filesystem() +} -/// A RAM filesystem that implements [`ax_fs_vfs::VfsOps`]. +/// In-memory filesystem. pub struct RamFileSystem { - parent: Once, - root: Arc, + next_inode: AtomicU64, + root_dir: Once, } impl RamFileSystem { - /// Create a new instance. - pub fn new() -> Self { - Self { - parent: Once::new(), - root: DirNode::new(None), - } + /// Creates a new RAM filesystem object. + pub fn new() -> Arc { + let fs = Arc::new(Self { + next_inode: AtomicU64::new(2), + root_dir: Once::new(), + }); + let root_fs = fs.clone(); + fs.root_dir.call_once(|| { + DirEntry::new_dir( + |this| RamDirNode::make(root_fs, this, 1, ax_fs_vfs::NodePermission::default()), + Reference::root(), + ) + }); + fs } - /// Returns the root directory node in [`Arc`](DirNode). - pub fn root_dir_node(&self) -> Arc { - self.root.clone() + /// Creates a new mountable RAM filesystem handle. + pub fn new_filesystem() -> Filesystem { + Filesystem::new(Self::new()) + } + + pub(crate) fn alloc_inode(&self) -> u64 { + self.next_inode.fetch_add(1, Ordering::Relaxed) } } -impl VfsOps for RamFileSystem { - fn mount(&self, _path: &str, mount_point: VfsNodeRef) -> VfsResult { - if let Some(parent) = mount_point.parent() { - self.root.set_parent(Some(self.parent.call_once(|| parent))); - } else { - self.root.set_parent(None); - } - Ok(()) +impl FilesystemOps for RamFileSystem { + fn name(&self) -> &str { + "ramfs" } - fn root_dir(&self) -> VfsNodeRef { - self.root.clone() + fn root_dir(&self) -> DirEntry { + self.root_dir.get().unwrap().clone() } -} -impl Default for RamFileSystem { - fn default() -> Self { - Self::new() + fn stat(&self) -> VfsResult { + Ok(StatFs { + fs_type: 0x8584_58f6, + block_size: 4096, + blocks: 0, + blocks_free: 0, + blocks_available: 0, + file_count: self.next_inode.load(Ordering::Relaxed), + free_file_count: 0, + name_length: MAX_NAME_LEN as u32, + fragment_size: 4096, + mount_flags: 0, + }) } } diff --git a/components/axfs_crates/axfs_ramfs/src/tests.rs b/components/axfs_crates/axfs_ramfs/src/tests.rs deleted file mode 100644 index 1b8aaf7732..0000000000 --- a/components/axfs_crates/axfs_ramfs/src/tests.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::sync::Arc; - -use ax_fs_vfs::{VfsError, VfsNodeType, VfsResult}; - -use crate::*; - -fn test_ramfs_ops(devfs: &RamFileSystem) -> VfsResult { - const N: usize = 32; - const N_HALF: usize = N / 2; - let mut buf = [1; N]; - - let root = devfs.root_dir(); - assert!(root.get_attr()?.is_dir()); - assert_eq!(root.get_attr()?.file_type(), VfsNodeType::Dir); - assert_eq!( - root.clone().lookup("urandom").err(), - Some(VfsError::NotFound) - ); - assert_eq!( - root.clone().lookup("f1/").err(), - Some(VfsError::NotADirectory) - ); - - let node = root.lookup("////f1")?; - assert_eq!(node.get_attr()?.file_type(), VfsNodeType::File); - assert!(!node.get_attr()?.is_dir()); - assert_eq!(node.get_attr()?.size(), 0); - assert_eq!(node.read_at(0, &mut buf)?, 0); - assert_eq!(buf, [1; N]); - - assert_eq!(node.write_at(N_HALF as _, &buf[..N_HALF])?, N_HALF); - assert_eq!(node.read_at(0, &mut buf)?, N); - assert_eq!(buf[..N_HALF], [0; N_HALF]); - assert_eq!(buf[N_HALF..], [1; N_HALF]); - assert_eq!(node.lookup("/").err(), Some(VfsError::NotADirectory)); - - let foo = devfs.root_dir().lookup(".///.//././/.////foo")?; - assert!(foo.get_attr()?.is_dir()); - assert_eq!( - foo.read_at(10, &mut buf).err(), - Some(VfsError::IsADirectory) - ); - assert!(Arc::ptr_eq( - &foo.clone().lookup("/f3")?, - &devfs.root_dir().lookup(".//./foo///f3")?, - )); - assert_eq!( - foo.clone().lookup("/bar//f4")?.get_attr()?.file_type(), - VfsNodeType::File - ); - assert_eq!( - foo.lookup("/bar///")?.get_attr()?.file_type(), - VfsNodeType::Dir - ); - - Ok(()) -} - -fn test_get_parent(devfs: &RamFileSystem) -> VfsResult { - let root = devfs.root_dir(); - assert!(root.parent().is_none()); - - let node = root.clone().lookup("f1")?; - assert!(node.parent().is_none()); - - let node = root.clone().lookup(".//foo/bar")?; - assert!(node.parent().is_some()); - let parent = node.parent().unwrap(); - assert!(Arc::ptr_eq(&parent, &root.clone().lookup("foo")?)); - assert!(parent.lookup("bar").is_ok()); - - let node = root.clone().lookup("foo/..")?; - assert!(Arc::ptr_eq(&node, &root.clone().lookup(".")?)); - - assert!(Arc::ptr_eq( - &root.clone().lookup("/foo/..")?, - &devfs.root_dir().lookup(".//./foo/././bar/../..")?, - )); - assert!(Arc::ptr_eq( - &root.clone().lookup("././/foo//./../foo//bar///..//././")?, - &devfs.root_dir().lookup(".//./foo/")?, - )); - assert!(Arc::ptr_eq( - &root.clone().lookup("///foo//bar///../f3")?, - &root.lookup("foo/.//f3")?, - )); - - Ok(()) -} - -#[test] -fn test_ramfs() { - // . - // ├── foo - // │   ├── bar - // │   │   └── f4 - // │   └── f3 - // ├── f1 - // └── f2 - - let ramfs = RamFileSystem::new(); - let root = ramfs.root_dir(); - root.create("f1", VfsNodeType::File).unwrap(); - root.create("f2", VfsNodeType::File).unwrap(); - root.create("foo", VfsNodeType::Dir).unwrap(); - - let dir_foo = root.lookup("foo").unwrap(); - dir_foo.create("f3", VfsNodeType::File).unwrap(); - dir_foo.create("bar", VfsNodeType::Dir).unwrap(); - - let dir_bar = dir_foo.lookup("bar").unwrap(); - dir_bar.create("f4", VfsNodeType::File).unwrap(); - - let mut entries = ramfs.root_dir_node().get_entries(); - entries.sort(); - assert_eq!(entries, ["f1", "f2", "foo"]); - - test_ramfs_ops(&ramfs).unwrap(); - test_get_parent(&ramfs).unwrap(); - - let root = ramfs.root_dir(); - assert_eq!(root.remove("f1"), Ok(())); - assert_eq!(root.remove("//f2"), Ok(())); - assert_eq!(root.remove("f3").err(), Some(VfsError::NotFound)); - assert_eq!(root.remove("foo").err(), Some(VfsError::DirectoryNotEmpty)); - assert_eq!(root.remove("foo/..").err(), Some(VfsError::InvalidInput)); - assert_eq!( - root.remove("foo/./bar").err(), - Some(VfsError::DirectoryNotEmpty) - ); - assert_eq!(root.remove("foo/bar/f4"), Ok(())); - assert_eq!(root.remove("foo/bar"), Ok(())); - assert_eq!(root.remove("./foo//.//f3"), Ok(())); - assert_eq!(root.remove("./foo"), Ok(())); - assert!(ramfs.root_dir_node().get_entries().is_empty()); -} diff --git a/components/axfs_crates/axfs_vfs/Cargo.toml b/components/axfs_crates/axfs_vfs/Cargo.toml index 2a04ef8845..8d953d5a1e 100644 --- a/components/axfs_crates/axfs_vfs/Cargo.toml +++ b/components/axfs_crates/axfs_vfs/Cargo.toml @@ -3,7 +3,7 @@ name = "ax-fs-vfs" description = "Virtual filesystem interfaces used by ArceOS" keywords = ["arceos", "filesystem", "vfs"] edition.workspace = true -version = "0.3.11" +version = "0.4.0" repository = "https://github.com/rcore-os/tgoskits" authors = ["Yuekai Jia "] categories = ["os", "no-std", "filesystem"] @@ -14,5 +14,11 @@ default = [] [dependencies] log = "0.4" -bitflags = "2.6" +bitflags = "2.10" ax-errno = { workspace = true } +ax-kspin = { workspace = true } +axpoll = { workspace = true } +cfg-if = "1" +hashbrown = "0.16" +inherit-methods-macro = "0.1" +smallvec = "1.15" diff --git a/components/axfs-ng-vfs/src/fs.rs b/components/axfs_crates/axfs_vfs/src/fs.rs similarity index 100% rename from components/axfs-ng-vfs/src/fs.rs rename to components/axfs_crates/axfs_vfs/src/fs.rs diff --git a/components/axfs_crates/axfs_vfs/src/lib.rs b/components/axfs_crates/axfs_vfs/src/lib.rs index fb5a45fe72..1eca49439c 100644 --- a/components/axfs_crates/axfs_vfs/src/lib.rs +++ b/components/axfs_crates/axfs_vfs/src/lib.rs @@ -1,179 +1,26 @@ -//! Virtual filesystem interfaces used by [ArceOS](https://github.com/arceos-org/arceos). +//! Virtual filesystem object model used by ArceOS, StarryOS, and Axvisor. //! -//! A filesystem is a set of files and directories (symbol links are not -//! supported currently), collectively referred to as **nodes**, which are -//! conceptually similar to [inodes] in Linux. A file system needs to implement -//! the [`VfsOps`] trait, its files and directories need to implement the -//! [`VfsNodeOps`] trait. -//! -//! The [`VfsOps`] trait provides the following operations on a filesystem: -//! -//! - [`mount()`](VfsOps::mount): Do something when the filesystem is mounted. -//! - [`umount()`](VfsOps::umount): Do something when the filesystem is unmounted. -//! - [`format()`](VfsOps::format): Format the filesystem. -//! - [`statfs()`](VfsOps::statfs): Get the attributes of the filesystem. -//! - [`root_dir()`](VfsOps::root_dir): Get root directory of the filesystem. -//! -//! The [`VfsNodeOps`] trait provides the following operations on a file or a -//! directory: -//! -//! | Operation | Description | file/directory | -//! | --- | --- | --- | -//! | [`open()`](VfsNodeOps::open) | Do something when the node is opened | both | -//! | [`release()`](VfsNodeOps::release) | Do something when the node is closed | both | -//! | [`get_attr()`](VfsNodeOps::get_attr) | Get the attributes of the node | both | -//! | [`read_at()`](VfsNodeOps::read_at) | Read data from the file | file | -//! | [`write_at()`](VfsNodeOps::write_at) | Write data to the file | file | -//! | [`fsync()`](VfsNodeOps::fsync) | Synchronize the file data to disk | file | -//! | [`truncate()`](VfsNodeOps::truncate) | Truncate the file | file | -//! | [`parent()`](VfsNodeOps::parent) | Get the parent directory | directory | -//! | [`lookup()`](VfsNodeOps::lookup) | Lookup the node with the given path | directory | -//! | [`create()`](VfsNodeOps::create) | Create a new node with the given path | directory | -//! | [`remove()`](VfsNodeOps::remove) | Remove the node with the given path | directory | -//! | [`read_dir()`](VfsNodeOps::read_dir) | Read directory entries | directory | -//! -//! [inodes]: https://en.wikipedia.org/wiki/Inode +//! This crate defines the shared filesystem, mount, path, node, metadata, and +//! directory-entry abstractions used by the unified `ax-fs` stack. #![no_std] extern crate alloc; -mod macros; -mod structs; +mod fs; +mod mount; +mod node; +mod types; pub mod path; -use alloc::sync::Arc; - -use ax_errno::{AxError, AxResult, ax_err}; +use ax_errno::{AxError, AxResult}; +use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; -pub use self::structs::{FileSystemInfo, VfsDirEntry, VfsNodeAttr, VfsNodePerm, VfsNodeType}; - -/// A wrapper of [`Arc`]. -pub type VfsNodeRef = Arc; +pub use self::{fs::*, mount::*, node::*, types::*}; /// Alias of [`AxError`]. pub type VfsError = AxError; /// Alias of [`AxResult`]. pub type VfsResult = AxResult; - -/// Filesystem operations. -pub trait VfsOps: Send + Sync { - /// Do something when the filesystem is mounted. - fn mount(&self, _path: &str, _mount_point: VfsNodeRef) -> VfsResult { - Ok(()) - } - - /// Do something when the filesystem is unmounted. - fn umount(&self) -> VfsResult { - Ok(()) - } - - /// Format the filesystem. - fn format(&self) -> VfsResult { - ax_err!(Unsupported) - } - - /// Get the attributes of the filesystem. - fn statfs(&self) -> VfsResult { - ax_err!(Unsupported) - } - - /// Get the root directory of the filesystem. - fn root_dir(&self) -> VfsNodeRef; -} - -/// Node (file/directory) operations. -pub trait VfsNodeOps: Send + Sync { - /// Do something when the node is opened. - fn open(&self) -> VfsResult { - Ok(()) - } - - /// Do something when the node is closed. - fn release(&self) -> VfsResult { - Ok(()) - } - - /// Get the attributes of the node. - fn get_attr(&self) -> VfsResult { - ax_err!(Unsupported) - } - - // file operations: - - /// Read data from the file at the given offset. - fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> VfsResult { - ax_err!(InvalidInput) - } - - /// Write data to the file at the given offset. - fn write_at(&self, _offset: u64, _buf: &[u8]) -> VfsResult { - ax_err!(InvalidInput) - } - - /// Flush the file, synchronize the data to disk. - fn fsync(&self) -> VfsResult { - ax_err!(InvalidInput) - } - - /// Truncate the file to the given size. - fn truncate(&self, _size: u64) -> VfsResult { - ax_err!(InvalidInput) - } - - // directory operations: - - /// Get the parent directory of this directory. - /// - /// Return `None` if the node is a file. - fn parent(&self) -> Option { - None - } - - /// Lookup the node with given `path` in the directory. - /// - /// Return the node if found. - fn lookup(self: Arc, _path: &str) -> VfsResult { - ax_err!(Unsupported) - } - - /// Create a new node with the given `path` in the directory - /// - /// Return [`Ok(())`](Ok) if it already exists. - fn create(&self, _path: &str, _ty: VfsNodeType) -> VfsResult { - ax_err!(Unsupported) - } - - /// Remove the node with the given `path` in the directory. - fn remove(&self, _path: &str) -> VfsResult { - ax_err!(Unsupported) - } - - /// Read directory entries into `dirents`, starting from `start_idx`. - fn read_dir(&self, _start_idx: usize, _dirents: &mut [VfsDirEntry]) -> VfsResult { - ax_err!(Unsupported) - } - - /// Renames or moves existing file or directory. - fn rename(&self, _src_path: &str, _dst_path: &str) -> VfsResult { - ax_err!(Unsupported) - } - - /// Convert `&self` to [`&dyn Any`][1] that can use - /// [`Any::downcast_ref`][2]. - /// - /// [1]: core::any::Any - /// [2]: core::any::Any#method.downcast_ref - fn as_any(&self) -> &dyn core::any::Any { - unimplemented!() - } -} - -#[doc(hidden)] -pub mod __priv { - pub use alloc::sync::Arc; - - pub use ax_errno::ax_err; -} diff --git a/components/axfs_crates/axfs_vfs/src/macros.rs b/components/axfs_crates/axfs_vfs/src/macros.rs deleted file mode 100644 index 4cfd24da26..0000000000 --- a/components/axfs_crates/axfs_vfs/src/macros.rs +++ /dev/null @@ -1,66 +0,0 @@ -/// When implement [`VfsNodeOps`] on a directory node, add dummy file operations -/// that just return an error. -/// -/// [`VfsNodeOps`]: crate::VfsNodeOps -#[macro_export] -macro_rules! impl_vfs_dir_default { - () => { - fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> $crate::VfsResult { - $crate::__priv::ax_err!(IsADirectory) - } - - fn write_at(&self, _offset: u64, _buf: &[u8]) -> $crate::VfsResult { - $crate::__priv::ax_err!(IsADirectory) - } - - fn fsync(&self) -> $crate::VfsResult { - $crate::__priv::ax_err!(IsADirectory) - } - - fn truncate(&self, _size: u64) -> $crate::VfsResult { - $crate::__priv::ax_err!(IsADirectory) - } - - #[inline] - fn as_any(&self) -> &dyn core::any::Any { - self - } - }; -} - -/// When implement [`VfsNodeOps`] on a non-directory node, add dummy directory -/// operations that just return an error. -/// -/// [`VfsNodeOps`]: crate::VfsNodeOps -#[macro_export] -macro_rules! impl_vfs_non_dir_default { - () => { - fn lookup( - self: $crate::__priv::Arc, - _path: &str, - ) -> $crate::VfsResult<$crate::VfsNodeRef> { - $crate::__priv::ax_err!(NotADirectory) - } - - fn create(&self, _path: &str, _ty: $crate::VfsNodeType) -> $crate::VfsResult { - $crate::__priv::ax_err!(NotADirectory) - } - - fn remove(&self, _path: &str) -> $crate::VfsResult { - $crate::__priv::ax_err!(NotADirectory) - } - - fn read_dir( - &self, - _start_idx: usize, - _dirents: &mut [$crate::VfsDirEntry], - ) -> $crate::VfsResult { - $crate::__priv::ax_err!(NotADirectory) - } - - #[inline] - fn as_any(&self) -> &dyn core::any::Any { - self - } - }; -} diff --git a/components/axfs-ng-vfs/src/mount.rs b/components/axfs_crates/axfs_vfs/src/mount.rs similarity index 100% rename from components/axfs-ng-vfs/src/mount.rs rename to components/axfs_crates/axfs_vfs/src/mount.rs diff --git a/components/axfs-ng-vfs/src/node/dir.rs b/components/axfs_crates/axfs_vfs/src/node/dir.rs similarity index 95% rename from components/axfs-ng-vfs/src/node/dir.rs rename to components/axfs_crates/axfs_vfs/src/node/dir.rs index 70d449f919..09bae6ff7c 100644 --- a/components/axfs-ng-vfs/src/node/dir.rs +++ b/components/axfs_crates/axfs_vfs/src/node/dir.rs @@ -291,13 +291,21 @@ impl DirNode { MutexGuard<'a, DirChildren>, Option>, ) { - let src_children = self.cache.lock(); - let dst_children = if core::ptr::eq(self, other) { - None + if core::ptr::eq(self, other) { + return (self.cache.lock(), None); + } + + let self_addr = self as *const _ as usize; + let other_addr = other as *const _ as usize; + if self_addr < other_addr { + let src_children = self.cache.lock(); + let dst_children = other.cache.lock(); + (src_children, Some(dst_children)) } else { - Some(other.cache.lock()) - }; - (src_children, dst_children) + let dst_children = other.cache.lock(); + let src_children = self.cache.lock(); + (src_children, Some(dst_children)) + } } /// Renames a directory entry. diff --git a/components/axfs-ng-vfs/src/node/file.rs b/components/axfs_crates/axfs_vfs/src/node/file.rs similarity index 100% rename from components/axfs-ng-vfs/src/node/file.rs rename to components/axfs_crates/axfs_vfs/src/node/file.rs diff --git a/components/axfs-ng-vfs/src/node/mod.rs b/components/axfs_crates/axfs_vfs/src/node/mod.rs similarity index 94% rename from components/axfs-ng-vfs/src/node/mod.rs rename to components/axfs_crates/axfs_vfs/src/node/mod.rs index 636010260c..c04790be54 100644 --- a/components/axfs-ng-vfs/src/node/mod.rs +++ b/components/axfs_crates/axfs_vfs/src/node/mod.rs @@ -207,6 +207,12 @@ pub struct DirEntry(Arc); #[derive(Debug, Clone)] pub struct WeakDirEntry(Weak); +impl Default for WeakDirEntry { + fn default() -> Self { + Self(Weak::new()) + } +} + impl WeakDirEntry { pub fn upgrade(&self) -> Option { self.0.upgrade().map(DirEntry) @@ -257,6 +263,20 @@ impl DirEntry { })) } + /// Creates a new directory entry that references an existing directory + /// node and receives its own weak reference. + pub fn new_dir_node_cyclic( + node_fn: impl FnOnce(WeakDirEntry) -> DirNode, + reference: Reference, + ) -> Self { + Self(Arc::new_cyclic(|this| Inner { + node: Node::Dir(node_fn(WeakDirEntry(this.clone()))), + node_type: NodeType::Directory, + reference, + user_data: Mutex::new(TypeMap::default()), + })) + } + pub fn metadata(&self) -> VfsResult { self.0.node.metadata().map(|mut metadata| { metadata.node_type = self.0.node_type; diff --git a/components/axfs_crates/axfs_vfs/src/path.rs b/components/axfs_crates/axfs_vfs/src/path.rs index 92052b0d5e..99135c3923 100644 --- a/components/axfs_crates/axfs_vfs/src/path.rs +++ b/components/axfs_crates/axfs_vfs/src/path.rs @@ -1,21 +1,17 @@ -//! Utilities for path manipulation. +use alloc::{borrow::ToOwned, string::String, sync::Arc}; +use core::{borrow::Borrow, fmt, ops::Deref}; -use alloc::string::String; +use crate::{VfsError, VfsResult}; + +pub const DOT: &str = "."; +pub const DOTDOT: &str = ".."; + +pub const MAX_NAME_LEN: usize = 255; /// Returns the canonical form of the path with all intermediate components /// normalized. /// /// It won't force convert the path to an absolute form. -/// -/// # Examples -/// -/// ``` -/// use ax_fs_vfs::path::canonicalize; -/// -/// assert_eq!(canonicalize("/path/./to//foo"), "/path/to/foo"); -/// assert_eq!(canonicalize("/./path/to/../bar.rs"), "/path/bar.rs"); -/// assert_eq!(canonicalize("./foo/./bar"), "foo/bar"); -/// ``` pub fn canonicalize(path: &str) -> String { let mut buf = String::new(); let is_absolute = path.starts_with('/'); @@ -51,47 +47,413 @@ pub fn canonicalize(path: &str) -> String { buf } +pub(crate) fn verify_entry_name(name: &str) -> VfsResult<()> { + if name == DOT || name == DOTDOT { + return Err(VfsError::InvalidInput); + } + if name.len() > MAX_NAME_LEN { + return Err(VfsError::NameTooLong); + } + Ok(()) +} + +/// A single component of a [`Path`]. +/// +/// This corresponds to [`std::path::Component`]. +#[derive(Debug, PartialEq, Eq, Hash)] +pub enum Component<'a> { + RootDir, + CurDir, + ParentDir, + Normal(&'a str), +} + +impl<'a> Component<'a> { + pub fn as_str(&self) -> &'a str { + match self { + Component::RootDir => "/", + Component::CurDir => ".", + Component::ParentDir => "..", + Component::Normal(s) => s, + } + } +} + +/// An iterator over the [`Component`]s of a [`Path`]. +/// +/// This corresponds to [`std::path::Components`]. +#[doc(hidden)] +pub struct Components<'a> { + path: &'a str, + at_start: bool, +} + +impl<'a> Components<'a> { + pub fn as_path(&self) -> &'a Path { + Path::new(self.path) + } + + fn parse_forward(&mut self, comp: &'a str) -> Option> { + let comp = match comp { + "" => { + if self.at_start { + Some(Component::RootDir) + } else { + None + } + } + "." => { + if self.at_start { + Some(Component::CurDir) + } else { + None + } + } + ".." => Some(Component::ParentDir), + _ => Some(Component::Normal(comp)), + }; + self.at_start = false; + comp + } + + fn parse_backward(&mut self, comp: &'a str, no_rest: bool) -> Option> { + match comp { + "" => { + if self.at_start && no_rest { + Some(Component::RootDir) + } else { + None + } + } + "." => { + if self.at_start && no_rest { + Some(Component::CurDir) + } else { + None + } + } + ".." => Some(Component::ParentDir), + _ => Some(Component::Normal(comp)), + } + } +} + +impl<'a> Iterator for Components<'a> { + type Item = Component<'a>; + + fn next(&mut self) -> Option { + loop { + if self.path.is_empty() { + return None; + } + let (comp, rest) = match self.path.find('/') { + Some(index) => (&self.path[..index], &self.path[index + 1..]), + None => (self.path, ""), + }; + self.path = rest; + if let Some(comp) = self.parse_forward(comp) { + return Some(comp); + } + } + } +} + +impl<'a> DoubleEndedIterator for Components<'a> { + fn next_back(&mut self) -> Option { + loop { + if self.path.is_empty() { + return None; + } + let (comp, rest) = match self.path.rfind('/') { + Some(index) => ( + &self.path[index + 1..], + &self.path[..(index + 1).min(self.path.len() - 1)], + ), + None => (self.path, ""), + }; + self.path = rest; + if let Some(comp) = self.parse_backward(comp, rest.is_empty()) { + return Some(comp); + } + } + } +} + +/// A slice of path (akin to [`str`]). +/// +/// Different from [`std::path::Path`], this type is always +/// UTF-8 encoded. +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct Path { + inner: str, +} + +impl Path { + pub fn new + ?Sized>(s: &S) -> &Path { + unsafe { &*(s.as_ref() as *const str as *const Path) } + } + + pub fn as_str(&self) -> &str { + &self.inner + } + + pub fn as_bytes(&self) -> &[u8] { + self.inner.as_bytes() + } + + /// Returns true if pathname has a trailing slash (other than the root + /// path "/" itself). POSIX requires that such paths refer to a directory; + /// callers should check `!loc.is_dir()` and return ENOTDIR. The trailing + /// slash is stripped at the Components iterator layer (parse_forward + /// drops empty trailing components), so this method preserves the + /// otherwise-lost signal. + pub fn has_trailing_slash(&self) -> bool { + self.inner.len() > 1 && self.inner.ends_with('/') + } + + /// Produces an iterator over the [`Components`] of the path. + pub fn components(&self) -> Components<'_> { + Components { + path: &self.inner, + at_start: true, + } + } + + /// Returns the final component of the `Path`, if there is one. + pub fn file_name(&self) -> Option<&str> { + self.components().next_back().and_then(|p| match p { + Component::Normal(p) => Some(p), + _ => None, + }) + } + + /// Creates an owned [`PathBuf`] with path adjoined to `self`. + pub fn join(&self, other: impl AsRef) -> PathBuf { + let mut path = self.to_owned(); + path.push(other); + path + } + + /// Returns the `Path` without its final component, if there is one. + pub fn parent(&self) -> Option<&Path> { + let mut comps = self.components(); + let comp = comps.next_back(); + comp.and_then(move |p| match p { + Component::Normal(_) | Component::CurDir | Component::ParentDir => { + Some(comps.as_path()) + } + _ => None, + }) + } + + /// Returns `true` if the `Path` is absolute, i.e., if it is independent of + /// the current directory. + pub fn is_absolute(&self) -> bool { + self.inner.starts_with('/') + } + + /// Normalizes a path without performing I/O. + pub fn normalize(&self) -> Option { + let mut ret = PathBuf::new(); + for component in self.components() { + match component { + Component::RootDir => { + ret.push("/"); + } + Component::CurDir => {} + Component::ParentDir => { + if !ret.pop() { + return None; + } + } + Component::Normal(c) => { + ret.push(c); + } + } + } + Some(ret) + } +} + +impl fmt::Display for Path { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl<'a> From<&'a str> for &'a Path { + fn from(value: &'a str) -> Self { + Path::new(value) + } +} + +impl ToOwned for Path { + type Owned = PathBuf; + + fn to_owned(&self) -> Self::Owned { + PathBuf { + inner: self.inner.to_owned(), + } + } +} + +impl From<&Path> for Arc { + #[inline] + fn from(v: &Path) -> Arc { + let arc = Arc::::from(&v.inner); + unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Path) } + } +} + +impl AsRef for Path { + #[inline] + fn as_ref(&self) -> &str { + &self.inner[..] + } +} + +impl AsRef for Path { + #[inline] + fn as_ref(&self) -> &Path { + self + } +} + +macro_rules! impl_as_ref { + ($($t:ty),+) => { + $(impl AsRef for $t { + fn as_ref(&self) -> &Path { + Path::new(self) + } + })+ + }; +} + +impl_as_ref!(str, String); + +/// An owned, mutable [`Path`] (akin to [`String`]). +/// +/// Different from [`std::path::PathBuf`], this type is always +/// UTF-8 encoded. +#[derive(Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct PathBuf { + inner: String, +} + +impl PathBuf { + pub const fn new() -> Self { + Self { + inner: String::new(), + } + } + + pub fn pop(&mut self) -> bool { + match self.parent().map(|p| p.as_str().len()) { + Some(len) => { + self.inner.truncate(len); + true + } + None => false, + } + } + + pub fn push(&mut self, path: impl AsRef) { + self._push(path.as_ref()); + } + + fn _push(&mut self, path: &Path) { + if path.as_str().is_empty() { + return; + } + if path.is_absolute() { + self.inner.clear(); + } else if !self.inner.ends_with('/') { + self.inner.push('/'); + } + self.inner += path.as_str(); + } +} + +impl> FromIterator for PathBuf { + fn from_iter>(iter: I) -> Self { + let mut path = PathBuf::new(); + for item in iter { + path.push(item); + } + path + } +} + +impl Borrow for PathBuf { + fn borrow(&self) -> &Path { + self + } +} + +impl Deref for PathBuf { + type Target = Path; + + #[inline] + fn deref(&self) -> &Path { + Path::new(&self.inner) + } +} + +impl AsRef for PathBuf { + #[inline] + fn as_ref(&self) -> &str { + &self.inner[..] + } +} + +impl AsRef for PathBuf { + #[inline] + fn as_ref(&self) -> &Path { + self + } +} + +impl From for PathBuf { + fn from(value: String) -> Self { + Self { inner: value } + } +} + +impl From<&str> for PathBuf { + fn from(value: &str) -> Self { + Self { + inner: value.to_owned(), + } + } +} + +impl fmt::Display for PathBuf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + #[cfg(test)] -mod tests { +mod test { + use alloc::vec::Vec; + use super::*; #[test] - fn test_path_canonicalize() { - assert_eq!(canonicalize(""), ""); - assert_eq!(canonicalize("///"), "/"); - assert_eq!(canonicalize("//a//.//b///c//"), "/a/b/c"); - assert_eq!(canonicalize("/a/../"), "/"); - assert_eq!(canonicalize("/a/../..///"), "/"); - assert_eq!(canonicalize("a/../"), ""); - assert_eq!(canonicalize("a/..//.."), ""); - assert_eq!(canonicalize("././a"), "a"); - assert_eq!(canonicalize(".././a"), "a"); - assert_eq!(canonicalize("/././a"), "/a"); - assert_eq!(canonicalize("/abc/../abc"), "/abc"); - assert_eq!(canonicalize("/test"), "/test"); - assert_eq!(canonicalize("/test/"), "/test"); - assert_eq!(canonicalize("test/"), "test"); - assert_eq!(canonicalize("test"), "test"); - assert_eq!(canonicalize("/test//"), "/test"); - assert_eq!(canonicalize("/test/foo"), "/test/foo"); - assert_eq!(canonicalize("/test/foo/"), "/test/foo"); - assert_eq!(canonicalize("/test/foo/bar"), "/test/foo/bar"); - assert_eq!(canonicalize("/test/foo/bar//"), "/test/foo/bar"); - assert_eq!(canonicalize("/test//foo/bar//"), "/test/foo/bar"); - assert_eq!(canonicalize("/test//./foo/bar//"), "/test/foo/bar"); - assert_eq!(canonicalize("/test//./.foo/bar//"), "/test/.foo/bar"); - assert_eq!(canonicalize("/test//./..foo/bar//"), "/test/..foo/bar"); - assert_eq!(canonicalize("/test//./../foo/bar//"), "/foo/bar"); - assert_eq!(canonicalize("/test/../foo"), "/foo"); - assert_eq!(canonicalize("/test/bar/../foo"), "/test/foo"); - assert_eq!(canonicalize("../foo"), "foo"); - assert_eq!(canonicalize("../foo/"), "foo"); - assert_eq!(canonicalize("/../foo"), "/foo"); - assert_eq!(canonicalize("/../foo/"), "/foo"); - assert_eq!(canonicalize("/../../foo"), "/foo"); - assert_eq!(canonicalize("/bleh/../../foo"), "/foo"); - assert_eq!(canonicalize("/bleh/bar/../../foo"), "/foo"); - assert_eq!(canonicalize("/bleh/bar/../../foo/.."), "/"); - assert_eq!(canonicalize("/bleh/bar/../../foo/../meh"), "/meh"); + fn test_back_components() { + for path in ["../fds/", "./fs", "fs", "../", "..", ".", "./."] { + let path = Path::new(path); + let forward: Vec<_> = path.components().collect(); + let mut backward: Vec<_> = path.components().rev().collect(); + backward.reverse(); + assert_eq!(forward, backward); + } + } + + #[test] + fn test_file_name() { + assert_eq!(Some("c"), Path::new("../a/b/c").file_name()); + assert_eq!(Some("b"), Path::new("a/b/.").file_name()); + assert_eq!(None, Path::new("a/..").file_name()); } } diff --git a/components/axfs_crates/axfs_vfs/src/structs.rs b/components/axfs_crates/axfs_vfs/src/structs.rs deleted file mode 100644 index a7829fa352..0000000000 --- a/components/axfs_crates/axfs_vfs/src/structs.rs +++ /dev/null @@ -1,305 +0,0 @@ -/// Filesystem attributes. -/// -/// Currently not used. -#[non_exhaustive] -pub struct FileSystemInfo; - -/// Node (file/directory) attributes. -#[allow(dead_code)] -#[derive(Debug, Clone, Copy)] -pub struct VfsNodeAttr { - /// File permission mode. - mode: VfsNodePerm, - /// File type. - ty: VfsNodeType, - /// Total size, in bytes. - size: u64, - /// Number of 512B blocks allocated. - blocks: u64, -} - -bitflags::bitflags! { - /// Node (file/directory) permission mode. - #[derive(Debug, Clone, Copy)] - pub struct VfsNodePerm: u16 { - /// Owner has read permission. - const OWNER_READ = 0o400; - /// Owner has write permission. - const OWNER_WRITE = 0o200; - /// Owner has execute permission. - const OWNER_EXEC = 0o100; - - /// Group has read permission. - const GROUP_READ = 0o40; - /// Group has write permission. - const GROUP_WRITE = 0o20; - /// Group has execute permission. - const GROUP_EXEC = 0o10; - - /// Others have read permission. - const OTHER_READ = 0o4; - /// Others have write permission. - const OTHER_WRITE = 0o2; - /// Others have execute permission. - const OTHER_EXEC = 0o1; - } -} - -/// Node (file/directory) type. -#[repr(u8)] -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum VfsNodeType { - /// FIFO (named pipe) - Fifo = 0o1, - /// Character device - CharDevice = 0o2, - /// Directory - Dir = 0o4, - /// Block device - BlockDevice = 0o6, - /// Regular file - File = 0o10, - /// Symbolic link - SymLink = 0o12, - /// Socket - Socket = 0o14, -} - -/// Directory entry. -pub struct VfsDirEntry { - d_type: VfsNodeType, - d_name: [u8; 63], -} - -impl VfsNodePerm { - /// Returns the default permission for a file. - /// - /// The default permission is `0o666` (owner/group/others can read and write). - pub const fn default_file() -> Self { - Self::from_bits_truncate(0o666) - } - - /// Returns the default permission for a directory. - /// - /// The default permission is `0o755` (owner can read, write and execute, - /// group/others can read and execute). - pub const fn default_dir() -> Self { - Self::from_bits_truncate(0o755) - } - - /// Returns the underlying raw `st_mode` bits that contain the standard - /// Unix permissions for this file. - pub const fn mode(&self) -> u32 { - self.bits() as u32 - } - - /// Returns a 9-bytes string representation of the permission. - /// - /// For example, `0o755` is represented as `rwxr-xr-x`. - pub const fn rwx_buf(&self) -> [u8; 9] { - let mut perm = [b'-'; 9]; - if self.contains(Self::OWNER_READ) { - perm[0] = b'r'; - } - if self.contains(Self::OWNER_WRITE) { - perm[1] = b'w'; - } - if self.contains(Self::OWNER_EXEC) { - perm[2] = b'x'; - } - if self.contains(Self::GROUP_READ) { - perm[3] = b'r'; - } - if self.contains(Self::GROUP_WRITE) { - perm[4] = b'w'; - } - if self.contains(Self::GROUP_EXEC) { - perm[5] = b'x'; - } - if self.contains(Self::OTHER_READ) { - perm[6] = b'r'; - } - if self.contains(Self::OTHER_WRITE) { - perm[7] = b'w'; - } - if self.contains(Self::OTHER_EXEC) { - perm[8] = b'x'; - } - perm - } - - /// Whether the owner has read permission. - pub const fn owner_readable(&self) -> bool { - self.contains(Self::OWNER_READ) - } - - /// Whether the owner has write permission. - pub const fn owner_writable(&self) -> bool { - self.contains(Self::OWNER_WRITE) - } - - /// Whether the owner has execute permission. - pub const fn owner_executable(&self) -> bool { - self.contains(Self::OWNER_EXEC) - } -} - -impl VfsNodeType { - /// Tests whether this node type represents a regular file. - pub const fn is_file(self) -> bool { - matches!(self, Self::File) - } - - /// Tests whether this node type represents a directory. - pub const fn is_dir(self) -> bool { - matches!(self, Self::Dir) - } - - /// Tests whether this node type represents a symbolic link. - pub const fn is_symlink(self) -> bool { - matches!(self, Self::SymLink) - } - - /// Returns `true` if this node type is a block device. - pub const fn is_block_device(self) -> bool { - matches!(self, Self::BlockDevice) - } - - /// Returns `true` if this node type is a char device. - pub const fn is_char_device(self) -> bool { - matches!(self, Self::CharDevice) - } - - /// Returns `true` if this node type is a fifo. - pub const fn is_fifo(self) -> bool { - matches!(self, Self::Fifo) - } - - /// Returns `true` if this node type is a socket. - pub const fn is_socket(self) -> bool { - matches!(self, Self::Socket) - } - - /// Returns a character representation of the node type. - /// - /// For example, `d` for directory, `-` for regular file, etc. - pub const fn as_char(self) -> char { - match self { - Self::Fifo => 'p', - Self::CharDevice => 'c', - Self::Dir => 'd', - Self::BlockDevice => 'b', - Self::File => '-', - Self::SymLink => 'l', - Self::Socket => 's', - } - } -} - -impl VfsNodeAttr { - /// Creates a new `VfsNodeAttr` with the given permission mode, type, size - /// and number of blocks. - pub const fn new(mode: VfsNodePerm, ty: VfsNodeType, size: u64, blocks: u64) -> Self { - Self { - mode, - ty, - size, - blocks, - } - } - - /// Creates a new `VfsNodeAttr` for a file, with the default file permission. - pub const fn new_file(size: u64, blocks: u64) -> Self { - Self { - mode: VfsNodePerm::default_file(), - ty: VfsNodeType::File, - size, - blocks, - } - } - - /// Creates a new `VfsNodeAttr` for a directory, with the default directory - /// permission. - pub const fn new_dir(size: u64, blocks: u64) -> Self { - Self { - mode: VfsNodePerm::default_dir(), - ty: VfsNodeType::Dir, - size, - blocks, - } - } - - /// Returns the size of the node. - pub const fn size(&self) -> u64 { - self.size - } - - /// Returns the number of blocks the node occupies on the disk. - pub const fn blocks(&self) -> u64 { - self.blocks - } - - /// Returns the permission of the node. - pub const fn perm(&self) -> VfsNodePerm { - self.mode - } - - /// Sets the permission of the node. - pub fn set_perm(&mut self, perm: VfsNodePerm) { - self.mode = perm - } - - /// Returns the type of the node. - pub const fn file_type(&self) -> VfsNodeType { - self.ty - } - - /// Whether the node is a file. - pub const fn is_file(&self) -> bool { - self.ty.is_file() - } - - /// Whether the node is a directory. - pub const fn is_dir(&self) -> bool { - self.ty.is_dir() - } -} - -impl VfsDirEntry { - /// Creates an empty `VfsDirEntry`. - pub const fn default() -> Self { - Self { - d_type: VfsNodeType::File, - d_name: [0; 63], - } - } - - /// Creates a new `VfsDirEntry` with the given name and type. - pub fn new(name: &str, ty: VfsNodeType) -> Self { - let mut d_name = [0; 63]; - if name.len() > d_name.len() { - log::warn!( - "directory entry name too long: {} > {}", - name.len(), - d_name.len() - ); - } - d_name[..name.len()].copy_from_slice(name.as_bytes()); - Self { d_type: ty, d_name } - } - - /// Returns the type of the entry. - pub fn entry_type(&self) -> VfsNodeType { - self.d_type - } - - /// Converts the name of the entry to a byte slice. - pub fn name_as_bytes(&self) -> &[u8] { - let len = self - .d_name - .iter() - .position(|&c| c == 0) - .unwrap_or(self.d_name.len()); - &self.d_name[..len] - } -} diff --git a/components/axfs-ng-vfs/src/types.rs b/components/axfs_crates/axfs_vfs/src/types.rs similarity index 83% rename from components/axfs-ng-vfs/src/types.rs rename to components/axfs_crates/axfs_vfs/src/types.rs index 7b86fbc942..345e5ac2f0 100644 --- a/components/axfs-ng-vfs/src/types.rs +++ b/components/axfs_crates/axfs_vfs/src/types.rs @@ -14,6 +14,36 @@ pub enum NodeType { Socket = 0o14, } +impl NodeType { + pub const fn is_fifo(self) -> bool { + matches!(self, Self::Fifo) + } + + pub const fn is_char_device(self) -> bool { + matches!(self, Self::CharacterDevice) + } + + pub const fn is_dir(self) -> bool { + matches!(self, Self::Directory) + } + + pub const fn is_block_device(self) -> bool { + matches!(self, Self::BlockDevice) + } + + pub const fn is_file(self) -> bool { + matches!(self, Self::RegularFile) + } + + pub const fn is_symlink(self) -> bool { + matches!(self, Self::Symlink) + } + + pub const fn is_socket(self) -> bool { + matches!(self, Self::Socket) + } +} + impl From for NodeType { fn from(value: u8) -> Self { match value { @@ -69,6 +99,13 @@ impl Default for NodePermission { } } +impl NodePermission { + /// Returns the Unix permission bits as a `st_mode`-compatible value. + pub const fn mode(&self) -> u32 { + self.bits() as u32 + } +} + /// Filesystem node metadata. #[derive(Clone, Debug)] pub struct Metadata { diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 64ac6c5bf1..8fe23960f2 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -53,14 +53,14 @@ ax-feat = { workspace = true, features = [ "rtc", - "fs-ng-ext4", + "fs-ext4", "net-ng", ] } ax-alloc = { workspace = true, features = ["default"] } ax-config.workspace = true ax-display.workspace = true -ax-fs = { version = "0.5.15", path = "../../arceos/modules/axfs-ng", package = "ax-fs-ng" } +ax-fs.workspace = true ax-hal.workspace = true ax-input = { workspace = true, optional = true } ax-lazyinit.workspace = true @@ -79,7 +79,7 @@ rdrive = { workspace = true, optional = true } axbacktrace = { workspace = true } ax-errno = { workspace = true } -axfs-ng-vfs = { workspace = true } +ax-fs-vfs = { workspace = true } ax-io = { workspace = true } axpoll = { workspace = true } bitflags = "2.10" diff --git a/os/StarryOS/kernel/src/file/fs.rs b/os/StarryOS/kernel/src/file/fs.rs index 5c3ae9163d..92ebb329b4 100644 --- a/os/StarryOS/kernel/src/file/fs.rs +++ b/os/StarryOS/kernel/src/file/fs.rs @@ -8,10 +8,10 @@ use core::{ use ax_errno::{AxError, AxResult}; use ax_fs::{FS_CONTEXT, FileBackend, FileFlags, FsContext}; +use ax_fs_vfs::{Location, Metadata, NodeFlags}; use ax_io::{Seek, SeekFrom}; use ax_sync::Mutex; use ax_task::future::{block_on, poll_io}; -use axfs_ng_vfs::{Location, Metadata, NodeFlags}; use axpoll::{IoEvents, Pollable}; use linux_raw_sys::general::{AT_EMPTY_PATH, AT_FDCWD, AT_SYMLINK_NOFOLLOW, O_APPEND, O_EXCL}; @@ -113,7 +113,7 @@ pub fn metadata_to_kstat(metadata: &Metadata) -> Kstat { } } -/// File wrapper for `ax_fs::fops::File`. +/// File wrapper for `ax_fs::File`. pub struct File { inner: ax_fs::File, open_flags: u32, @@ -309,7 +309,7 @@ impl Pollable for File { } } -/// Directory wrapper for `ax_fs::fops::Directory`. +/// Directory wrapper for `ax_fs::ReadDir`. pub struct Directory { inner: Location, pub offset: Mutex, diff --git a/os/StarryOS/kernel/src/file/mod.rs b/os/StarryOS/kernel/src/file/mod.rs index 83986bea28..ac26841742 100644 --- a/os/StarryOS/kernel/src/file/mod.rs +++ b/os/StarryOS/kernel/src/file/mod.rs @@ -18,9 +18,9 @@ use core::{ffi::c_int, time::Duration}; use ax_errno::{AxError, AxResult}; use ax_fs::{FS_CONTEXT, FileBackend, FileFlags, OpenOptions}; +use ax_fs_vfs::DeviceId; use ax_io::prelude::*; use ax_task::current; -use axfs_ng_vfs::DeviceId; use axpoll::Pollable; use downcast_rs::{DowncastSync, impl_downcast}; use flatten_objects::FlattenObjects; diff --git a/os/StarryOS/kernel/src/kcov/mod.rs b/os/StarryOS/kernel/src/kcov/mod.rs index 556c1ce65a..f45855038c 100644 --- a/os/StarryOS/kernel/src/kcov/mod.rs +++ b/os/StarryOS/kernel/src/kcov/mod.rs @@ -9,10 +9,10 @@ use alloc::sync::Arc; use core::any::Any; use ax_errno::AxError; +use ax_fs_vfs::{NodeFlags, VfsError, VfsResult}; use ax_hal::{kcov::KCOV_GLOBAL_GATE, mem::phys_to_virt, paging::PageSize}; use ax_memory_addr::PAGE_SIZE_4K; use ax_sync::Mutex; -use axfs_ng_vfs::{NodeFlags, VfsError, VfsResult}; use crate::{ mm::SharedPages, diff --git a/os/StarryOS/kernel/src/mm/aspace/backend/file.rs b/os/StarryOS/kernel/src/mm/aspace/backend/file.rs index 4221f25f3f..99db7c6315 100644 --- a/os/StarryOS/kernel/src/mm/aspace/backend/file.rs +++ b/os/StarryOS/kernel/src/mm/aspace/backend/file.rs @@ -8,10 +8,10 @@ use core::sync::atomic::{AtomicUsize, Ordering}; use ax_errno::{AxError, AxResult}; use ax_fs::{CachedFile, FileFlags}; +use ax_fs_vfs::Location; use ax_hal::paging::{MappingFlags, PageSize, PageTableCursor, PagingError}; use ax_memory_addr::{PAGE_SIZE_4K, VirtAddr, VirtAddrRange}; use ax_sync::Mutex; -use axfs_ng_vfs::Location; use weak_map::StrongRef; use super::{AddrSpace, Backend, BackendFileInfo, BackendOps, PopulateCallback, pages_in}; diff --git a/os/StarryOS/kernel/src/mm/loader.rs b/os/StarryOS/kernel/src/mm/loader.rs index 28946f4072..0f09fc84b6 100644 --- a/os/StarryOS/kernel/src/mm/loader.rs +++ b/os/StarryOS/kernel/src/mm/loader.rs @@ -5,13 +5,13 @@ use core::{ffi::CStr, iter}; use ax_errno::{AxError, AxResult}; use ax_fs::{CachedFile, FS_CONTEXT, FileBackend}; +use ax_fs_vfs::Location; use ax_hal::{ mem::virt_to_phys, paging::{MappingFlags, PageSize}, }; use ax_memory_addr::{MemoryAddr, PAGE_SIZE_4K, VirtAddr}; use ax_sync::Mutex; -use axfs_ng_vfs::Location; use kernel_elf_parser::{AuxEntry, ELFHeaders, ELFHeadersBuilder, ELFParser, app_stack_region}; use ouroboros::self_referencing; use uluru::LRUCache; diff --git a/os/StarryOS/kernel/src/pseudofs/debug.rs b/os/StarryOS/kernel/src/pseudofs/debug.rs index 9d373cd6c8..ab4d7f66dc 100644 --- a/os/StarryOS/kernel/src/pseudofs/debug.rs +++ b/os/StarryOS/kernel/src/pseudofs/debug.rs @@ -5,7 +5,7 @@ use super::{DirMaker, DirMapping, SimpleDir, SimpleFs}; const DEBUGFS_MAGIC: u32 = 0x64626720; /// Create a new debugfs filesystem. -pub fn new_debugfs() -> axfs_ng_vfs::Filesystem { +pub fn new_debugfs() -> ax_fs_vfs::Filesystem { // TODO: update fs_type SimpleFs::new_with("debug".into(), DEBUGFS_MAGIC, debugfs_builder) } diff --git a/os/StarryOS/kernel/src/pseudofs/dev/card0.rs b/os/StarryOS/kernel/src/pseudofs/dev/card0.rs index ce7c4f8fd4..75282e21c1 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/card0.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/card0.rs @@ -37,10 +37,10 @@ use core::{ task::Context, }; +use ax_fs_vfs::{NodeFlags, VfsError, VfsResult}; use ax_hal::{mem::virt_to_phys, paging::PageSize, time::monotonic_time}; use ax_memory_addr::{PhysAddr, PhysAddrRange, VirtAddr}; use ax_sync::Mutex; -use axfs_ng_vfs::{NodeFlags, VfsError, VfsResult}; use axpoll::{IoEvents, PollSet, Pollable}; use bytemuck::bytes_of; use starry_vm::{VmMutPtr, VmPtr, vm_load, vm_write_slice}; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/card1.rs b/os/StarryOS/kernel/src/pseudofs/dev/card1.rs index 66ff22ffd8..2d24818308 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/card1.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/card1.rs @@ -9,9 +9,9 @@ use core::{ use ax_driver::rknpu::{self, RknpuAction, RknpuMemCreate, RknpuMemMap, RknpuMemSync, RknpuSubmit}; use ax_errno::{AxError, AxResult}; +use ax_fs_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; use ax_hal::mem::virt_to_phys; use ax_memory_addr::PhysAddrRange; -use axfs_ng_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; use axpoll::{IoEvents, Pollable}; use linux_raw_sys::general::O_CLOEXEC; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs b/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs index dd2698a881..0eaa97f8f0 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/cvi_camera.rs @@ -3,11 +3,11 @@ use alloc::{collections::vec_deque::VecDeque, vec::Vec}; use core::{any::Any, time::Duration}; use ax_errno::{AxError, LinuxError}; +use ax_fs_vfs::{NodeFlags, VfsResult}; use ax_hal::mem::phys_to_virt; use ax_kspin::SpinNoIrq as Mutex; use ax_memory_addr::PhysAddr; use ax_task::sleep; -use axfs_ng_vfs::{NodeFlags, VfsResult}; use sg200x_bsp::{ pinmux::{FMUX_SD1_D1, FMUX_SD1_D2, Pinmux}, soc::{FMUX_BASE, IOBLK_BASE, IOBLK_GRTC_BASE}, diff --git a/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs b/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs index 09a0ba5b61..573c04955f 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/cvi_usb_camera.rs @@ -2,13 +2,13 @@ use core::{any::Any, time::Duration}; use ax_config::plat::PHYS_VIRT_OFFSET; use ax_errno::AxError; +use ax_fs_vfs::{NodeFlags, VfsResult}; use ax_hal::{ mem::{phys_to_virt, virt_to_phys}, time::busy_wait, }; use ax_kspin::SpinNoIrq as Mutex; use ax_memory_addr::{PhysAddr, VirtAddr}; -use axfs_ng_vfs::{NodeFlags, VfsResult}; use sg200x_bsp::{ gpio::{Direction, GPIO, GPIO1_BASE}, pinmux::{FMUX_USB_VBUS_DET, Pinmux}, diff --git a/os/StarryOS/kernel/src/pseudofs/dev/dma_heap.rs b/os/StarryOS/kernel/src/pseudofs/dev/dma_heap.rs index 1576b68d07..4e004f51e8 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/dma_heap.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/dma_heap.rs @@ -1,6 +1,6 @@ use core::any::Any; -use axfs_ng_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; +use ax_fs_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; use starry_vm::VmMutPtr; use crate::pseudofs::DeviceOps; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/event.rs b/os/StarryOS/kernel/src/pseudofs/dev/event.rs index 1d3bc2fc86..6d3f93faa0 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/event.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/event.rs @@ -17,10 +17,10 @@ pub fn input_device_count() -> u32 { } use ax_errno::{AxError, AxResult}; +use ax_fs_vfs::{DeviceId, NodeFlags, NodeType, VfsResult}; use ax_hal::time::wall_time; use ax_input::{ErasedInputDevice, Event, EventType, InputDevice, InputDeviceId, InputError}; use ax_sync::Mutex; -use axfs_ng_vfs::{DeviceId, NodeFlags, NodeType, VfsResult}; use axpoll::{IoEvents, Pollable}; use bitmaps::Bitmap; use linux_raw_sys::{ diff --git a/os/StarryOS/kernel/src/pseudofs/dev/fb.rs b/os/StarryOS/kernel/src/pseudofs/dev/fb.rs index 0bf8df5502..13d53388f5 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/fb.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/fb.rs @@ -1,9 +1,9 @@ use core::{any::Any, slice}; use ax_errno::AxError; +use ax_fs_vfs::{NodeFlags, VfsError, VfsResult}; use ax_hal::mem::virt_to_phys; use ax_memory_addr::{PhysAddrRange, VirtAddr}; -use axfs_ng_vfs::{NodeFlags, VfsError, VfsResult}; use starry_vm::VmMutPtr; use crate::pseudofs::{DeviceMmap, DeviceOps}; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/ion/device.rs b/os/StarryOS/kernel/src/pseudofs/dev/ion/device.rs index b342ed1843..e29316a4ba 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/ion/device.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/ion/device.rs @@ -4,8 +4,8 @@ use alloc::sync::Arc; use core::any::Any; use ax_errno::AxError; +use ax_fs_vfs::{NodeFlags, VfsResult}; use ax_memory_addr::PhysAddrRange; -use axfs_ng_vfs::{NodeFlags, VfsResult}; use sg2002_tpu::ion::{ IonAllocData, IonBufferManager, IonFdData, IonHandle, IonHandleData, IonHeapData, IonHeapManager, IonHeapQuery, IonHeapType, MAX_HEAP_NAME, diff --git a/os/StarryOS/kernel/src/pseudofs/dev/loop.rs b/os/StarryOS/kernel/src/pseudofs/dev/loop.rs index a1f9e5a1cf..75545667ae 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/loop.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/loop.rs @@ -5,8 +5,8 @@ use core::{ use ax_errno::{AxError, AxResult, LinuxError}; use ax_fs::FileBackend; +use ax_fs_vfs::{DeviceId, NodeFlags, VfsResult}; use ax_sync::Mutex; -use axfs_ng_vfs::{DeviceId, NodeFlags, VfsResult}; use linux_raw_sys::{ general::{O_ACCMODE, O_RDONLY}, ioctl::{ diff --git a/os/StarryOS/kernel/src/pseudofs/dev/loop_block.rs b/os/StarryOS/kernel/src/pseudofs/dev/loop_block.rs index 76d14f429b..37d1b00d1d 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/loop_block.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/loop_block.rs @@ -3,8 +3,8 @@ use core::sync::atomic::{AtomicBool, Ordering}; use ax_errno::{AxError, AxResult, LinuxError}; use ax_fs::FileBackend; +use ax_fs_vfs::VfsResult; use ax_kspin::SpinNoIrq; -use axfs_ng_vfs::VfsResult; use super::r#loop::LoopDevice; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/memtrack.rs b/os/StarryOS/kernel/src/pseudofs/dev/memtrack.rs index c2b5e9c6f6..4f7b59f419 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/memtrack.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/memtrack.rs @@ -7,8 +7,8 @@ use core::{ }; use ax_alloc::tracking::{allocations_in, current_generation, disable_tracking, enable_tracking}; +use ax_fs_vfs::{NodeFlags, VfsResult}; use axbacktrace::Backtrace; -use axfs_ng_vfs::{NodeFlags, VfsResult}; use crate::{ mm::clear_elf_cache, @@ -66,8 +66,8 @@ impl MemoryCategory { "ax_task::timers::set_alarm_wakeup" => { return Some("timer"); } - "axfs_ng_vfs::node::dir::DirNode::lookup_locked" - | "axfs_ng_vfs::node::dir::DirNode::create_locked" => { + "ax_fs_vfs::node::dir::DirNode::lookup_locked" + | "ax_fs_vfs::node::dir::DirNode::create_locked" => { return Some("dentry"); } "ext4_user_malloc" => { diff --git a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs index 8c2ab6448e..d15dfc3ac7 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs @@ -43,8 +43,8 @@ use alloc::{format, sync::Arc}; use core::any::Any; use ax_errno::AxError; +use ax_fs_vfs::{DeviceId, Filesystem, NodeFlags, NodeType, VfsResult}; use ax_sync::Mutex; -use axfs_ng_vfs::{DeviceId, Filesystem, NodeFlags, NodeType, VfsResult}; #[cfg(feature = "sg2002")] use spin::Once; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/pinmux.rs b/os/StarryOS/kernel/src/pseudofs/dev/pinmux.rs index 320ee4679a..3161506dee 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/pinmux.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/pinmux.rs @@ -2,7 +2,7 @@ use core::{any::Any, str}; use ax_config::plat::PHYS_VIRT_OFFSET; use ax_errno::AxError; -use axfs_ng_vfs::{NodeFlags, VfsResult}; +use ax_fs_vfs::{NodeFlags, VfsResult}; use bytemuck::AnyBitPattern; use starry_vm::VmPtr; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/pwm.rs b/os/StarryOS/kernel/src/pseudofs/dev/pwm.rs index 0d71c9ff1d..8d77ef08b3 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/pwm.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/pwm.rs @@ -1,7 +1,7 @@ use alloc::{borrow::Cow, boxed::Box, format, sync::Arc, vec::Vec}; +use ax_fs_vfs::{VfsError, VfsResult}; use ax_sync::Mutex; -use axfs_ng_vfs::{VfsError, VfsResult}; use sg200x_bsp::{ pwm::{Pwm, PwmChannel, PwmMode, PwmPolarity}, soc::PWM0_BASE, diff --git a/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs b/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs index 02b64942b4..b57249bae6 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs @@ -4,8 +4,8 @@ use core::{ ffi::{c_char, c_ulong}, }; +use ax_fs_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; use ax_hal::asm::user_copy; -use axfs_ng_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; use super::rknpu_drm::DrmVersion; use crate::pseudofs::{ diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tpu/device.rs b/os/StarryOS/kernel/src/pseudofs/dev/tpu/device.rs index eacb272599..0dde04df32 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tpu/device.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tpu/device.rs @@ -167,15 +167,15 @@ impl TpuDevice { } impl DeviceOps for TpuDevice { - fn read_at(&self, _buf: &mut [u8], _offset: u64) -> axfs_ng_vfs::VfsResult { + fn read_at(&self, _buf: &mut [u8], _offset: u64) -> ax_fs_vfs::VfsResult { Ok(0) } - fn write_at(&self, _buf: &[u8], _offset: u64) -> axfs_ng_vfs::VfsResult { + fn write_at(&self, _buf: &[u8], _offset: u64) -> ax_fs_vfs::VfsResult { Ok(0) } - fn ioctl(&self, cmd: u32, arg: usize) -> axfs_ng_vfs::VfsResult { + fn ioctl(&self, cmd: u32, arg: usize) -> ax_fs_vfs::VfsResult { debug!("TPU ioctl: cmd=0x{:x}, arg=0x{:x}", cmd, arg); let result = match cmd { diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty/mod.rs index 3b5c331e6e..a7c370556f 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty/mod.rs @@ -8,9 +8,9 @@ use alloc::sync::{Arc, Weak}; use core::{any::Any, ops::Deref, sync::atomic::Ordering, task::Context}; use ax_errno::{AxError, AxResult}; +use ax_fs_vfs::NodeFlags; use ax_sync::Mutex; use ax_task::current; -use axfs_ng_vfs::NodeFlags; use axpoll::{IoEvents, Pollable}; use starry_process::Process; use starry_vm::{VmMutPtr, VmPtr}; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty/ptm.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty/ptm.rs index 5b8bd12cac..20b2d3dac8 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty/ptm.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty/ptm.rs @@ -2,7 +2,7 @@ use alloc::sync::Arc; use core::any::Any; use ax_errno::AxResult; -use axfs_ng_vfs::{DeviceId, NodeType}; +use ax_fs_vfs::{DeviceId, NodeType}; use crate::pseudofs::{Device, DeviceOps, SimpleFs}; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty/pts.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty/pts.rs index 436f83bb1f..5804e6a9f3 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty/pts.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty/pts.rs @@ -2,8 +2,8 @@ use alloc::{borrow::Cow, boxed::Box, string::ToString, sync::Arc, vec::Vec}; use core::sync::atomic::Ordering; use ax_errno::{AxError, AxResult}; +use ax_fs_vfs::{DeviceId, NodeType, VfsResult}; use ax_kspin::SpinNoIrq; -use axfs_ng_vfs::{DeviceId, NodeType, VfsResult}; use flatten_objects::FlattenObjects; use crate::pseudofs::{Device, NodeOpsMux, SimpleDirOps, SimpleFs, dev::tty::pty::PtyDriver}; diff --git a/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs b/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs index 8c8bb21d0d..765a8d0f3f 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/tty_serial.rs @@ -2,12 +2,12 @@ use alloc::collections::vec_deque::VecDeque; use core::{any::Any, task::Context}; use ax_errno::{AxError, LinuxError}; +use ax_fs_vfs::{NodeFlags, VfsResult}; use ax_hal::mem::phys_to_virt; use ax_kspin::SpinNoIrq; use ax_memory_addr::{PhysAddr, pa}; use ax_sync::Mutex; use ax_task::future::{block_on, poll_io}; -use axfs_ng_vfs::{NodeFlags, VfsResult}; use axpoll::{IoEvents, PollSet, Pollable}; use bytemuck::AnyBitPattern; use dw_apb_uart::DW8250; diff --git a/os/StarryOS/kernel/src/pseudofs/device.rs b/os/StarryOS/kernel/src/pseudofs/device.rs index bfe3c02522..4442c11aaf 100644 --- a/os/StarryOS/kernel/src/pseudofs/device.rs +++ b/os/StarryOS/kernel/src/pseudofs/device.rs @@ -2,11 +2,11 @@ use alloc::sync::Arc; use core::{any::Any, task::Context}; use ax_fs::CachedFile; -use ax_memory_addr::PhysAddrRange; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DeviceId, FileNodeOps, FilesystemOps, Metadata, MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, VfsError, VfsResult, }; +use ax_memory_addr::PhysAddrRange; use axpoll::{IoEvents, Pollable}; use inherit_methods_macro::inherit_methods; diff --git a/os/StarryOS/kernel/src/pseudofs/dir.rs b/os/StarryOS/kernel/src/pseudofs/dir.rs index 6b49961fa7..471c04b987 100644 --- a/os/StarryOS/kernel/src/pseudofs/dir.rs +++ b/os/StarryOS/kernel/src/pseudofs/dir.rs @@ -7,7 +7,7 @@ use alloc::{ }; use core::any::Any; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DirEntry, DirEntrySink, DirNode, DirNodeOps, FileNode, FilesystemOps, Metadata, MetadataUpdate, NodeOps, NodePermission, NodeType, Reference, VfsError, VfsResult, WeakDirEntry, path::{DOT, DOTDOT}, diff --git a/os/StarryOS/kernel/src/pseudofs/dyn_debug.rs b/os/StarryOS/kernel/src/pseudofs/dyn_debug.rs index 2f377a80e2..f9018db887 100644 --- a/os/StarryOS/kernel/src/pseudofs/dyn_debug.rs +++ b/os/StarryOS/kernel/src/pseudofs/dyn_debug.rs @@ -1,7 +1,7 @@ use alloc::{string::String, sync::Arc, vec::Vec}; +use ax_fs_vfs::{NodePermission, VfsError, VfsResult}; use ax_sync::Mutex; -use axfs_ng_vfs::{NodePermission, VfsError, VfsResult}; use ddebug::ControlFile; use super::SimpleFs; diff --git a/os/StarryOS/kernel/src/pseudofs/file.rs b/os/StarryOS/kernel/src/pseudofs/file.rs index 73476f1bb4..b1556efe43 100644 --- a/os/StarryOS/kernel/src/pseudofs/file.rs +++ b/os/StarryOS/kernel/src/pseudofs/file.rs @@ -1,11 +1,11 @@ use alloc::{borrow::Cow, sync::Arc, vec::Vec}; use core::{any::Any, cmp::Ordering, task::Context}; -use ax_sync::Mutex; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ FileNodeOps, FilesystemOps, Metadata, MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, VfsError, VfsResult, }; +use ax_sync::Mutex; use axpoll::{IoEvents, Pollable}; use inherit_methods_macro::inherit_methods; diff --git a/os/StarryOS/kernel/src/pseudofs/fs.rs b/os/StarryOS/kernel/src/pseudofs/fs.rs index a1127e39a3..e2d24b4c6d 100644 --- a/os/StarryOS/kernel/src/pseudofs/fs.rs +++ b/os/StarryOS/kernel/src/pseudofs/fs.rs @@ -1,11 +1,11 @@ use alloc::{string::String, sync::Arc}; use core::{any::Any, time::Duration}; -use ax_kspin::SpinNoIrq; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DeviceId, DirEntry, DirNode, Filesystem, FilesystemOps, Metadata, MetadataUpdate, NodeOps, NodePermission, NodeType, Reference, StatFs, VfsResult, path::MAX_NAME_LEN, }; +use ax_kspin::SpinNoIrq; use slab::Slab; use super::DirMaker; diff --git a/os/StarryOS/kernel/src/pseudofs/mod.rs b/os/StarryOS/kernel/src/pseudofs/mod.rs index cb04e1ba5c..94c6cce5c8 100644 --- a/os/StarryOS/kernel/src/pseudofs/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/mod.rs @@ -18,8 +18,8 @@ use alloc::{boxed::Box, sync::Arc}; use ax_errno::LinuxResult; use ax_fs::{FS_CONTEXT, FsContext}; +use ax_fs_vfs::{DirNodeOps, FileNodeOps, Filesystem, NodePermission, WeakDirEntry}; use ax_lazyinit::LazyInit; -use axfs_ng_vfs::{DirNodeOps, FileNodeOps, Filesystem, NodePermission, WeakDirEntry}; pub use tmp::MemoryFs; pub use self::{device::*, dir::*, file::*, fs::*}; diff --git a/os/StarryOS/kernel/src/pseudofs/proc.rs b/os/StarryOS/kernel/src/pseudofs/proc.rs index c639257a97..6568968a04 100644 --- a/os/StarryOS/kernel/src/pseudofs/proc.rs +++ b/os/StarryOS/kernel/src/pseudofs/proc.rs @@ -14,13 +14,13 @@ use core::{ sync::atomic::{AtomicUsize, Ordering}, }; +use ax_fs_vfs::{DeviceId, Filesystem, NodePermission, NodeType, VfsError, VfsResult}; use ax_hal::{ paging::MappingFlags, time::{monotonic_time, wall_time}, }; use ax_memory_addr::PAGE_SIZE_4K; use ax_task::{AxCpuMask, AxTaskRef, TaskState, WeakAxTaskRef, current}; -use axfs_ng_vfs::{DeviceId, Filesystem, NodePermission, NodeType, VfsError, VfsResult}; use starry_process::{Pid, Process}; use crate::{ diff --git a/os/StarryOS/kernel/src/pseudofs/sysfs.rs b/os/StarryOS/kernel/src/pseudofs/sysfs.rs index b22a5c38d9..2bb083ba00 100644 --- a/os/StarryOS/kernel/src/pseudofs/sysfs.rs +++ b/os/StarryOS/kernel/src/pseudofs/sysfs.rs @@ -33,7 +33,7 @@ use alloc::{ vec::Vec, }; -use axfs_ng_vfs::{Filesystem, NodeType, VfsError, VfsResult}; +use ax_fs_vfs::{Filesystem, NodeType, VfsError, VfsResult}; use crate::pseudofs::{ DirMaker, DirMapping, NodeOpsMux, SimpleDir, SimpleDirOps, SimpleFile, SimpleFs, diff --git a/os/StarryOS/kernel/src/pseudofs/tmp.rs b/os/StarryOS/kernel/src/pseudofs/tmp.rs index ba6c3ba27d..aa7ef1532c 100644 --- a/os/StarryOS/kernel/src/pseudofs/tmp.rs +++ b/os/StarryOS/kernel/src/pseudofs/tmp.rs @@ -1,13 +1,13 @@ use alloc::{borrow::ToOwned, string::String, sync::Arc}; use core::{any::Any, borrow::Borrow, cmp::Ordering, task::Context, time::Duration}; -use ax_kspin::SpinNoIrq; -use ax_sync::Mutex; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DeviceId, DirEntry, DirEntrySink, DirNode, DirNodeOps, FileNode, FileNodeOps, Filesystem, FilesystemOps, Metadata, MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, Reference, StatFs, VfsError, VfsResult, WeakDirEntry, }; +use ax_kspin::SpinNoIrq; +use ax_sync::Mutex; use axpoll::{IoEvents, Pollable}; use hashbrown::HashMap; use slab::Slab; diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/descriptor.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/descriptor.rs index 6c3e762f2e..967a62436e 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/descriptor.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/descriptor.rs @@ -1,7 +1,7 @@ use alloc::{collections::BTreeMap, format, string::String, vec::Vec}; use core::mem::size_of; -use axfs_ng_vfs::{DeviceId, VfsResult}; +use ax_fs_vfs::{DeviceId, VfsResult}; use bytemuck::AnyBitPattern; use crab_usb::{ ProbedDevice, diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs index c14cd77dc6..ff4e73f494 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs @@ -18,9 +18,9 @@ use core::{ }; use ax_errno::{AxError, AxResult, LinuxError, LinuxResult}; +use ax_fs_vfs::Filesystem; use ax_kspin::SpinNoIrq as Mutex; use ax_sync::Mutex as BlockingMutex; -use axfs_ng_vfs::Filesystem; use axpoll::{IoEvents, PollSet, Pollable}; use crab_usb::usb_if::endpoint::{TransferCompletion, TransferRequest}; use event_listener::Event as NotifyEvent; diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/sysfs.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/sysfs.rs index 6b672a95d8..b1f4d84405 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/sysfs.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/sysfs.rs @@ -1,6 +1,6 @@ use alloc::{borrow::Cow, boxed::Box, format, string::String, sync::Arc, vec::Vec}; -use axfs_ng_vfs::{Filesystem, NodeType, VfsResult}; +use ax_fs_vfs::{Filesystem, NodeType, VfsResult}; use super::{ descriptor::{UsbDeviceSnapshot, bus_name, device_name}, diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/tree.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/tree.rs index 52bb734539..8f5fef37e0 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/tree.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/tree.rs @@ -2,7 +2,7 @@ use alloc::{borrow::Cow, boxed::Box, sync::Arc, vec::Vec}; use core::{any::Any, task::Context}; use ax_errno::AxError; -use axfs_ng_vfs::{NodeFlags, NodeType, VfsResult}; +use ax_fs_vfs::{NodeFlags, NodeType, VfsResult}; use axpoll::{IoEvents, Pollable}; use starry_vm::VmMutPtr; diff --git a/os/StarryOS/kernel/src/syscall/fs/ctl.rs b/os/StarryOS/kernel/src/syscall/fs/ctl.rs index b120ed7da4..e1722c888c 100644 --- a/os/StarryOS/kernel/src/syscall/fs/ctl.rs +++ b/os/StarryOS/kernel/src/syscall/fs/ctl.rs @@ -12,9 +12,9 @@ use core::{ use ax_errno::{AxError, AxResult, LinuxError}; use ax_fs::{FS_CONTEXT, FsContext}; +use ax_fs_vfs::{DeviceId, MetadataUpdate, NodePermission, NodeType, path::Path}; use ax_hal::time::wall_time; use ax_task::current; -use axfs_ng_vfs::{DeviceId, MetadataUpdate, NodePermission, NodeType, path::Path}; use linux_raw_sys::{ general::*, ioctl::{BLKGETSIZE64, BLKRAGET, BLKSSZGET, FIOASYNC, FIONBIO, TIOCGWINSZ}, diff --git a/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs b/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs index 263b0b3321..9eac0fa951 100644 --- a/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs +++ b/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs @@ -7,8 +7,8 @@ use core::{ use ax_errno::{AxError, AxResult}; use ax_fs::{FS_CONTEXT, FileBackend, OpenOptions, OpenResult}; +use ax_fs_vfs::{DirEntry, FileNode, Location, NodeOps, NodeType, Reference}; use ax_task::current; -use axfs_ng_vfs::{DirEntry, FileNode, Location, NodeOps, NodeType, Reference}; use bitflags::bitflags; use linux_raw_sys::general::*; @@ -99,7 +99,7 @@ fn add_to_fd(result: OpenResult, flags: u32) -> AxResult { // if fifo.reader_count() == 0 { return Err(...ENXIO...); } // } // 同时阻塞模式(非 NONBLOCK)的 WRONLY 应等待 reader 到来(更复杂)。 - // 该完整修复需联动 axfs-ng-vfs::Fifo 节点定义(目前无独立类型,FIFO 走通用 + // 该完整修复需联动 ax-fs-vfs 的 FIFO 节点定义(目前无独立类型,FIFO 走通用 // File backend 即不区分 reader / writer),属 IPC 子系统专项。 // // Fixes bug-open-fifo-wronly-no-reader-no-enxio (no-reader case only). diff --git a/os/StarryOS/kernel/src/syscall/fs/io.rs b/os/StarryOS/kernel/src/syscall/fs/io.rs index a07ab189cb..94b955f500 100644 --- a/os/StarryOS/kernel/src/syscall/fs/io.rs +++ b/os/StarryOS/kernel/src/syscall/fs/io.rs @@ -6,9 +6,9 @@ use core::{ use ax_errno::{AxError, AxResult, LinuxError}; use ax_fs::{FS_CONTEXT, FileFlags, OpenOptions}; +use ax_fs_vfs::{NodePermission, NodeType}; use ax_io::{IoBuf, Read, Seek, SeekFrom}; use ax_task::current; -use axfs_ng_vfs::{NodePermission, NodeType}; use axpoll::{IoEvents, Pollable}; use linux_raw_sys::general::{__kernel_off_t, O_APPEND}; use starry_vm::{VmMutPtr, VmPtr}; diff --git a/os/StarryOS/kernel/src/syscall/fs/memfd.rs b/os/StarryOS/kernel/src/syscall/fs/memfd.rs index f7ef9be72b..b35ce9611f 100644 --- a/os/StarryOS/kernel/src/syscall/fs/memfd.rs +++ b/os/StarryOS/kernel/src/syscall/fs/memfd.rs @@ -66,9 +66,9 @@ pub fn sys_memfd_create(name: *const c_char, flags: u32) -> AxResult { let mountpoint = fs.resolve(mount_path)?.mountpoint().clone(); let entry = tmpfs.create_anonymous_file( &name_str, - axfs_ng_vfs::NodePermission::from_bits_truncate(0o666), + ax_fs_vfs::NodePermission::from_bits_truncate(0o666), ); - let loc = axfs_ng_vfs::Location::new(mountpoint, entry); + let loc = ax_fs_vfs::Location::new(mountpoint, entry); let file = OpenOptions::new() .read(true) diff --git a/os/StarryOS/kernel/src/syscall/fs/mount.rs b/os/StarryOS/kernel/src/syscall/fs/mount.rs index 4c1432b90d..71639e4ee6 100644 --- a/os/StarryOS/kernel/src/syscall/fs/mount.rs +++ b/os/StarryOS/kernel/src/syscall/fs/mount.rs @@ -30,7 +30,7 @@ const MS_SHARED: i32 = 1 << 20; const PROPAGATION_FLAGS: i32 = MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE; const VALID_UMOUNT_FLAGS: i32 = MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW; -fn fd_points_to_mount(fd: &dyn FileLike, mp: &Arc) -> bool { +fn fd_points_to_mount(fd: &dyn FileLike, mp: &Arc) -> bool { fd.downcast_ref::() .is_some_and(|f| Arc::ptr_eq(f.inner().location().mountpoint(), mp)) || fd @@ -38,7 +38,7 @@ fn fd_points_to_mount(fd: &dyn FileLike, mp: &Arc) -> b .is_some_and(|d| Arc::ptr_eq(d.inner().mountpoint(), mp)) } -fn is_mount_busy(mp: &Arc) -> bool { +fn is_mount_busy(mp: &Arc) -> bool { if fs_is_mount_busy(mp) { return true; } diff --git a/os/StarryOS/kernel/src/syscall/fs/stat.rs b/os/StarryOS/kernel/src/syscall/fs/stat.rs index b252820efb..717b1e7a94 100644 --- a/os/StarryOS/kernel/src/syscall/fs/stat.rs +++ b/os/StarryOS/kernel/src/syscall/fs/stat.rs @@ -5,8 +5,8 @@ use core::{ use ax_errno::{AxError, AxResult, LinuxError}; use ax_fs::FS_CONTEXT; +use ax_fs_vfs::{Location, NodePermission}; use ax_task::current; -use axfs_ng_vfs::{Location, NodePermission}; use linux_raw_sys::general::{ __kernel_fsid_t, AT_EACCESS, AT_EMPTY_PATH, AT_NO_AUTOMOUNT, AT_STATX_SYNC_TYPE, AT_SYMLINK_FOLLOW, AT_SYMLINK_NOFOLLOW, R_OK, STATX__RESERVED, W_OK, X_OK, stat, statfs, statx, diff --git a/os/StarryOS/kernel/src/syscall/net/socket.rs b/os/StarryOS/kernel/src/syscall/net/socket.rs index 6ef871230a..071659e3c8 100644 --- a/os/StarryOS/kernel/src/syscall/net/socket.rs +++ b/os/StarryOS/kernel/src/syscall/net/socket.rs @@ -2,8 +2,8 @@ use alloc::boxed::Box; use ax_errno::{AxError, AxResult, LinuxError}; use ax_fs::FS_CONTEXT; +use ax_fs_vfs::{MetadataUpdate, NodeType}; use ax_task::current; -use axfs_ng_vfs::{MetadataUpdate, NodeType}; #[cfg(feature = "vsock")] use axnet::vsock::{VsockSocket, VsockStreamTransport}; use axnet::{ diff --git a/os/StarryOS/kernel/src/tracepoint/control.rs b/os/StarryOS/kernel/src/tracepoint/control.rs index d3d17b944f..8a5b6bbc97 100644 --- a/os/StarryOS/kernel/src/tracepoint/control.rs +++ b/os/StarryOS/kernel/src/tracepoint/control.rs @@ -1,5 +1,5 @@ +use ax_fs_vfs::{VfsError, VfsResult}; use ax_sync::Mutex; -use axfs_ng_vfs::{VfsError, VfsResult}; use ktracepoint::{TraceFilterFile, TracePoint, TracePointEnableFile}; use crate::{ diff --git a/os/StarryOS/kernel/src/tracepoint/mod.rs b/os/StarryOS/kernel/src/tracepoint/mod.rs index 5ccea5bc6a..a61d40624e 100644 --- a/os/StarryOS/kernel/src/tracepoint/mod.rs +++ b/os/StarryOS/kernel/src/tracepoint/mod.rs @@ -7,12 +7,12 @@ use alloc::{collections::BTreeMap, string::ToString, sync::Arc, vec::Vec}; use core::{num::NonZero, ops::Deref}; use ax_errno::{AxError, AxResult}; +use ax_fs_vfs::NodePermission; use ax_hal::{percpu::this_cpu_id, time::monotonic_time_nanos}; use ax_lazyinit::LazyInit; use ax_memory_addr::VirtAddr; use ax_sync::Mutex; use ax_task::current; -use axfs_ng_vfs::NodePermission; use axpoll::PollSet; use ktracepoint::*; diff --git a/os/StarryOS/kernel/src/tracepoint/trace.rs b/os/StarryOS/kernel/src/tracepoint/trace.rs index b4c1ee50dc..adfefac6d3 100644 --- a/os/StarryOS/kernel/src/tracepoint/trace.rs +++ b/os/StarryOS/kernel/src/tracepoint/trace.rs @@ -1,5 +1,5 @@ +use ax_fs_vfs::VfsResult; use ax_sync::Mutex; -use axfs_ng_vfs::VfsResult; use ktracepoint::{TraceCmdLineCacheSnapshot, TracePipeSnapshot}; use crate::pseudofs::DirectRwFsFileOps; diff --git a/os/StarryOS/kernel/src/tracepoint/trace_pipe.rs b/os/StarryOS/kernel/src/tracepoint/trace_pipe.rs index aa5c586084..c0d8dc470e 100644 --- a/os/StarryOS/kernel/src/tracepoint/trace_pipe.rs +++ b/os/StarryOS/kernel/src/tracepoint/trace_pipe.rs @@ -1,8 +1,8 @@ use core::{future::poll_fn, task::Poll}; +use ax_fs_vfs::VfsResult; use ax_sync::Mutex; use ax_task::future::{block_on, interruptible}; -use axfs_ng_vfs::VfsResult; use ktracepoint::TracePipeOps; use crate::pseudofs::DirectRwFsFileOps; diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index 1171a16c9f..06ee719fdd 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -61,7 +61,7 @@ name = "xtask" path = "xtask/main.rs" [dependencies] -ax-feat = { workspace = true, features = ["fs-ng-times", "irq"] } +ax-feat = { workspace = true, features = ["fs-times", "irq"] } ax-driver.workspace = true ax-hal.workspace = true axplat-dyn = { workspace = true, optional = true } diff --git a/os/arceos/api/arceos_api/Cargo.toml b/os/arceos/api/arceos_api/Cargo.toml index 8ff1536b04..2178c9ca84 100644 --- a/os/arceos/api/arceos_api/Cargo.toml +++ b/os/arceos/api/arceos_api/Cargo.toml @@ -17,7 +17,7 @@ paging = ["dep:ax-mm", "ax-feat/paging"] dma = ["dep:ax-dma", "ax-feat/dma"] multitask = ["ax-task/multitask", "ax-sync/multitask", "ax-feat/multitask"] stack-guard-page = ["multitask", "paging", "ax-feat/stack-guard-page"] -fs = ["dep:ax-fs", "ax-feat/fs"] +fs = ["dep:ax-fs", "dep:ax-fs-vfs", "ax-feat/fs"] net = ["dep:ax-net", "ax-feat/net"] display = ["dep:ax-display", "ax-feat/display"] @@ -33,6 +33,7 @@ ax-dma = { workspace = true, optional = true } ax-errno.workspace = true ax-feat.workspace = true ax-fs = { workspace = true, optional = true } +ax-fs-vfs = { workspace = true, optional = true } ax-hal.workspace = true ax-io.workspace = true ax-ipi = { workspace = true, optional = true } diff --git a/os/arceos/api/arceos_api/src/imp/fs.rs b/os/arceos/api/arceos_api/src/imp/fs.rs index 2bbefd1bbd..37a95a4c26 100644 --- a/os/arceos/api/arceos_api/src/imp/fs.rs +++ b/os/arceos/api/arceos_api/src/imp/fs.rs @@ -1,27 +1,136 @@ use alloc::string::String; use ax_errno::AxResult; -use ax_fs::fops::{Directory, File}; - -pub use ax_fs::fops::DirEntry as AxDirEntry; -pub use ax_fs::fops::FileAttr as AxFileAttr; -pub use ax_fs::fops::FilePerm as AxFilePerm; -pub use ax_fs::fops::FileType as AxFileType; -pub use ax_fs::fops::OpenOptions as AxOpenOptions; +use ax_fs::{File, ReadDir}; +use ax_fs_vfs::{Metadata, NodePermission, NodeType}; pub use ax_io::SeekFrom as AxSeekFrom; +use ax_io::prelude::*; + +pub type AxFileType = NodeType; +pub type AxFilePerm = NodePermission; + +/// File metadata exposed through the stable ArceOS API. +pub struct AxFileAttr(Metadata); + +impl AxFileAttr { + pub const fn file_type(&self) -> AxFileType { + self.0.node_type + } + + pub const fn is_dir(&self) -> bool { + self.0.node_type.is_dir() + } + + pub const fn is_file(&self) -> bool { + self.0.node_type.is_file() + } + + pub const fn perm(&self) -> AxFilePerm { + self.0.mode + } + + pub const fn size(&self) -> u64 { + self.0.size + } + + pub const fn blocks(&self) -> u64 { + self.0.blocks + } +} + +/// A single directory entry exposed through the stable ArceOS API. +#[derive(Clone, Debug)] +pub struct AxDirEntry { + name: String, + entry_type: AxFileType, +} + +impl Default for AxDirEntry { + fn default() -> Self { + Self { + name: String::new(), + entry_type: AxFileType::Unknown, + } + } +} + +impl AxDirEntry { + pub fn name_as_bytes(&self) -> &[u8] { + self.name.as_bytes() + } + pub const fn entry_type(&self) -> AxFileType { + self.entry_type + } +} + +/// Options and flags used when opening files and directories. +#[derive(Clone)] +pub struct AxOpenOptions(ax_fs::OpenOptions); + +impl AxOpenOptions { + pub const fn new() -> Self { + Self(ax_fs::OpenOptions::new()) + } + + pub fn read(&mut self, read: bool) { + self.0.read(read); + } + + pub fn write(&mut self, write: bool) { + self.0.write(write); + } + + pub fn append(&mut self, append: bool) { + self.0.append(append); + } + + pub fn truncate(&mut self, truncate: bool) { + self.0.truncate(truncate); + } + + pub fn create(&mut self, create: bool) { + self.0.create(create); + } + + pub fn create_new(&mut self, create_new: bool) { + self.0.create_new(create_new); + } + + fn inner(&self) -> &ax_fs::OpenOptions { + &self.0 + } +} + +impl core::fmt::Debug for AxOpenOptions { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("AxOpenOptions").finish_non_exhaustive() + } +} + +impl Default for AxOpenOptions { + fn default() -> Self { + Self::new() + } +} /// A handle to an opened file. pub struct AxFileHandle(File); /// A handle to an opened directory. -pub struct AxDirHandle(Directory); +pub struct AxDirHandle(ReadDir); pub fn ax_open_file(path: &str, opts: &AxOpenOptions) -> AxResult { - Ok(AxFileHandle(File::open(path, opts)?)) + let ctx = ax_fs::FS_CONTEXT.lock(); + let file = opts.inner().open(&ctx, path)?.into_file()?; + Ok(AxFileHandle(file)) } pub fn ax_open_dir(path: &str, opts: &AxOpenOptions) -> AxResult { - Ok(AxDirHandle(Directory::open_dir(path, opts)?)) + let mut opts = opts.clone(); + opts.0.directory(true); + let ctx = ax_fs::FS_CONTEXT.lock(); + let dir = opts.0.open(&ctx, path)?.into_dir()?; + Ok(AxDirHandle(ReadDir::new(dir))) } pub fn ax_read_file(file: &mut AxFileHandle, buf: &mut [u8]) -> AxResult { @@ -29,7 +138,7 @@ pub fn ax_read_file(file: &mut AxFileHandle, buf: &mut [u8]) -> AxResult } pub fn ax_read_file_at(file: &AxFileHandle, offset: u64, buf: &mut [u8]) -> AxResult { - file.0.read_at(offset, buf) + file.0.read_at(buf, offset) } pub fn ax_write_file(file: &mut AxFileHandle, buf: &[u8]) -> AxResult { @@ -37,11 +146,11 @@ pub fn ax_write_file(file: &mut AxFileHandle, buf: &[u8]) -> AxResult { } pub fn ax_write_file_at(file: &AxFileHandle, offset: u64, buf: &[u8]) -> AxResult { - file.0.write_at(offset, buf) + file.0.write_at(buf, offset) } pub fn ax_truncate_file(file: &AxFileHandle, size: u64) -> AxResult { - file.0.truncate(size) + file.0.backend()?.set_len(size) } pub fn ax_flush_file(file: &AxFileHandle) -> AxResult { @@ -49,37 +158,57 @@ pub fn ax_flush_file(file: &AxFileHandle) -> AxResult { } pub fn ax_seek_file(file: &mut AxFileHandle, pos: AxSeekFrom) -> AxResult { - file.0.seek(pos) + (&file.0).seek(pos) } pub fn ax_file_attr(file: &AxFileHandle) -> AxResult { - file.0.get_attr() + Ok(AxFileAttr(file.0.location().metadata()?)) } pub fn ax_read_dir(dir: &mut AxDirHandle, dirents: &mut [AxDirEntry]) -> AxResult { - dir.0.read_dir(dirents) + let mut count = 0; + for slot in dirents { + let Some(entry) = dir.0.next().transpose()? else { + break; + }; + *slot = AxDirEntry { + name: entry.name, + entry_type: entry.node_type, + }; + count += 1; + } + Ok(count) } pub fn ax_create_dir(path: &str) -> AxResult { - ax_fs::api::create_dir(path) + ax_fs::FS_CONTEXT + .lock() + .create_dir(path, NodePermission::default()) + .map(|_| ()) } pub fn ax_remove_dir(path: &str) -> AxResult { - ax_fs::api::remove_dir(path) + ax_fs::FS_CONTEXT.lock().remove_dir(path) } pub fn ax_remove_file(path: &str) -> AxResult { - ax_fs::api::remove_file(path) + ax_fs::FS_CONTEXT.lock().remove_file(path) } pub fn ax_rename(old: &str, new: &str) -> AxResult { - ax_fs::api::rename(old, new) + ax_fs::FS_CONTEXT.lock().rename(old, new) } pub fn ax_current_dir() -> AxResult { - ax_fs::api::current_dir() + ax_fs::FS_CONTEXT + .lock() + .current_dir() + .absolute_path() + .map(|path| path.as_str().into()) } pub fn ax_set_current_dir(path: &str) -> AxResult { - ax_fs::api::set_current_dir(path) + let mut ctx = ax_fs::FS_CONTEXT.lock(); + let loc = ctx.resolve(path)?; + ctx.set_current_dir(loc) } diff --git a/os/arceos/api/arceos_posix_api/Cargo.toml b/os/arceos/api/arceos_posix_api/Cargo.toml index 73efef989e..c6b73cc164 100644 --- a/os/arceos/api/arceos_posix_api/Cargo.toml +++ b/os/arceos/api/arceos_posix_api/Cargo.toml @@ -26,7 +26,7 @@ alloc = ["dep:ax-alloc", "ax-feat/alloc"] multitask = ["alloc", "ax-task/multitask", "ax-feat/multitask", "ax-sync/multitask"] lockdep = ["multitask", "ax-feat/lockdep", "ax-sync/lockdep"] fd = ["alloc", "dep:scope-local"] -fs = ["dep:ax-fs", "ax-feat/fs", "fd"] +fs = ["dep:ax-fs", "dep:ax-fs-vfs", "ax-feat/fs", "fd"] net = ["dep:ax-net", "ax-feat/net", "fd"] pipe = ["fd"] select = ["fd"] @@ -39,6 +39,7 @@ ax-alloc = { workspace = true, optional = true, features = ["default"] } ax-config.workspace = true ax-feat.workspace = true ax-fs = { workspace = true, optional = true } +ax-fs-vfs = { workspace = true, optional = true } ax-hal.workspace = true ax-log.workspace = true ax-net = { workspace = true, optional = true } diff --git a/os/arceos/api/arceos_posix_api/src/imp/fs.rs b/os/arceos/api/arceos_posix_api/src/imp/fs.rs index 20ba2e5517..7520b4c870 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/fs.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/fs.rs @@ -4,19 +4,20 @@ use core::ffi::{c_char, c_int}; use core::mem::size_of; use ax_errno::{LinuxError, LinuxResult}; -use ax_fs::fops::OpenOptions; -use ax_io::{PollState, SeekFrom}; +use ax_fs::{OpenOptions, ReadDir}; +use ax_fs_vfs::{Metadata, NodeType}; +use ax_io::{PollState, SeekFrom, prelude::*}; use ax_sync::Mutex; use super::fd_ops::{FileLike, get_file_like}; use crate::{ctypes, utils::char_ptr_to_str}; pub struct File { - inner: Mutex, + inner: Mutex, } pub struct Directory { - inner: Mutex, + inner: Mutex, } // ============================================================================ @@ -159,17 +160,34 @@ impl<'a> HermitDirBuffer<'a> { // Common file type conversion // ============================================================================ -fn file_type_to_d_type(ty: ax_fs::fops::FileType) -> u8 { +fn file_type_to_d_type(ty: NodeType) -> u8 { match ty { - ax_fs::fops::FileType::Dir => 4, // DT_DIR - ax_fs::fops::FileType::File => 8, // DT_REG - ax_fs::fops::FileType::SymLink => 10, // DT_LNK - _ => 0, // DT_UNKNOWN + NodeType::Directory => 4, // DT_DIR + NodeType::RegularFile => 8, // DT_REG + NodeType::Symlink => 10, // DT_LNK + _ => 0, // DT_UNKNOWN + } +} + +fn metadata_to_stat(metadata: Metadata) -> ctypes::stat { + let ty = metadata.node_type as u8; + let perm = metadata.mode.bits() as u32; + let st_mode = ((ty as u32) << 12) | perm; + ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + st_uid: 1000, + st_gid: 1000, + st_size: metadata.size as _, + st_blocks: metadata.blocks as _, + st_blksize: 512, + ..Default::default() } } impl File { - fn new(inner: ax_fs::fops::File) -> Self { + fn new(inner: ax_fs::File) -> Self { Self { inner: Mutex::new(inner), } @@ -188,7 +206,7 @@ impl File { } impl Directory { - fn new(inner: ax_fs::fops::Directory) -> Self { + fn new(inner: ReadDir) -> Self { Self { inner: Mutex::new(inner), } @@ -216,21 +234,7 @@ impl FileLike for File { } fn stat(&self) -> LinuxResult { - let metadata = self.inner.lock().get_attr()?; - let ty = metadata.file_type() as u8; - let perm = metadata.perm().bits() as u32; - let st_mode = ((ty as u32) << 12) | perm; - Ok(ctypes::stat { - st_ino: 1, - st_nlink: 1, - st_mode, - st_uid: 1000, - st_gid: 1000, - st_size: metadata.size() as _, - st_blocks: metadata.blocks() as _, - st_blksize: 512, - ..Default::default() - }) + Ok(metadata_to_stat(self.inner.lock().location().metadata()?)) } fn into_any(self: Arc) -> Arc { @@ -294,8 +298,12 @@ fn flags_to_options(flags: c_int, _mode: ctypes::mode_t) -> OpenOptions { let flags = flags as u32; let mut options = OpenOptions::new(); match flags & 0b11 { - ctypes::O_RDONLY => options.read(true), - ctypes::O_WRONLY => options.write(true), + ctypes::O_RDONLY => { + options.read(true); + } + ctypes::O_WRONLY => { + options.write(true); + } _ => { options.read(true); options.write(true); @@ -327,10 +335,14 @@ pub fn sys_open(filename: *const c_char, flags: c_int, mode: ctypes::mode_t) -> let options = flags_to_options(flags, mode); let filename = filename?; if (flags as u32) & ctypes::O_DIRECTORY != 0 { - let dir = ax_fs::fops::Directory::open_dir(filename, &options)?; - Directory::new(dir).add_to_fd_table() + let mut options = options; + options.directory(true); + let ctx = ax_fs::FS_CONTEXT.lock(); + let dir = options.open(&ctx, filename)?.into_dir()?; + Directory::new(ReadDir::new(dir)).add_to_fd_table() } else { - let file = ax_fs::fops::File::open(filename, &options)?; + let ctx = ax_fs::FS_CONTEXT.lock(); + let file = options.open(&ctx, filename)?.into_file()?; File::new(file).add_to_fd_table() } }) @@ -358,20 +370,17 @@ pub unsafe fn sys_getdents64(fd: c_int, buf: *mut u8, len: usize) -> ctypes::ssi let out = unsafe { core::slice::from_raw_parts_mut(buf, len) }; let mut dir_buf = DirBuffer::new(out); - let mut entries: [ax_fs::fops::DirEntry; 16] = - core::array::from_fn(|_| ax_fs::fops::DirEntry::default()); - loop { - let nr = dir.read_dir(&mut entries)?; - if nr == 0 { - break; - } - - for entry in entries.iter().take(nr) { - let d_type = file_type_to_d_type(entry.entry_type()); - // Linux style: d_ino, d_off both present - if !dir_buf.write_entry(1, 0, d_type, entry.name_as_bytes()) { - return Ok(dir_buf.used_len() as ctypes::ssize_t); - } + for entry in dir.by_ref() { + let entry = entry?; + let d_type = file_type_to_d_type(entry.node_type); + // Linux style: d_ino, d_off both present + if !dir_buf.write_entry( + entry.ino, + entry.offset as i64, + d_type, + entry.name.as_bytes(), + ) { + return Ok(dir_buf.used_len() as ctypes::ssize_t); } } @@ -412,20 +421,12 @@ pub unsafe fn sys_getdents64(fd: c_int, buf: *mut u8, len: usize) -> ctypes::ssi let out = unsafe { core::slice::from_raw_parts_mut(buf, len) }; let mut dir_buf = HermitDirBuffer::new(out); - let mut entries: [ax_fs::fops::DirEntry; 16] = - core::array::from_fn(|_| ax_fs::fops::DirEntry::default()); - loop { - let nr = dir.read_dir(&mut entries)?; - if nr == 0 { - break; - } - - for entry in entries.iter().take(nr) { - let d_type = file_type_to_d_type(entry.entry_type()); - // Hermit style: only d_ino and d_type, d_off is not meaningful - if !dir_buf.write_entry(1, d_type, entry.name_as_bytes()) { - return Ok(dir_buf.used_len() as ctypes::ssize_t); - } + for entry in dir.by_ref() { + let entry = entry?; + let d_type = file_type_to_d_type(entry.node_type); + // Hermit style: only d_ino and d_type, d_off is not meaningful + if !dir_buf.write_entry(entry.ino, d_type, entry.name.as_bytes()) { + return Ok(dir_buf.used_len() as ctypes::ssize_t); } } @@ -445,7 +446,9 @@ pub fn sys_lseek(fd: c_int, offset: ctypes::off_t, whence: c_int) -> ctypes::off 2 => SeekFrom::End(offset as _), _ => return Err(LinuxError::EINVAL), }; - let off = File::from_fd(fd)?.inner.lock().seek(pos)?; + let file = File::from_fd(fd)?; + let file = file.inner.lock(); + let off = (&*file).seek(pos)?; Ok(off) }) } @@ -460,10 +463,8 @@ pub unsafe fn sys_stat(path: *const c_char, buf: *mut ctypes::stat) -> c_int { if buf.is_null() { return Err(LinuxError::EFAULT); } - let mut options = OpenOptions::new(); - options.read(true); - let file = ax_fs::fops::File::open(path?, &options)?; - let st = File::new(file).stat()?; + let ctx = ax_fs::FS_CONTEXT.lock(); + let st = metadata_to_stat(ctx.metadata(path?)?); unsafe { *buf = st }; Ok(0) }) @@ -495,10 +496,8 @@ pub unsafe fn sys_lstat(path: *const c_char, buf: *mut ctypes::stat) -> ctypes:: return Err(LinuxError::EFAULT); } // ArceOS currently doesn't support symbolic links, so lstat behaves the same as stat - let mut options = OpenOptions::new(); - options.read(true); - let file = ax_fs::fops::File::open(path?, &options)?; - let st = File::new(file).stat()?; + let ctx = ax_fs::FS_CONTEXT.lock(); + let st = metadata_to_stat(ctx.metadata(path?)?); unsafe { *buf = st }; Ok(0) }) @@ -513,8 +512,8 @@ pub fn sys_getcwd(buf: *mut c_char, size: usize) -> *mut c_char { return Ok(core::ptr::null::() as _); } let dst = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, size as _) }; - let cwd = ax_fs::api::current_dir()?; - let cwd = cwd.as_bytes(); + let cwd = ax_fs::FS_CONTEXT.lock().current_dir().absolute_path()?; + let cwd = cwd.as_str().as_bytes(); if cwd.len() < size { dst[..cwd.len()].copy_from_slice(cwd); dst[cwd.len()] = 0; @@ -534,7 +533,7 @@ pub fn sys_rename(old: *const c_char, new: *const c_char) -> c_int { let old_path = char_ptr_to_str(old)?; let new_path = char_ptr_to_str(new)?; debug!("sys_rename <= old: {old_path:?}, new: {new_path:?}"); - ax_fs::api::rename(old_path, new_path)?; + ax_fs::FS_CONTEXT.lock().rename(old_path, new_path)?; Ok(0) }) } diff --git a/os/arceos/api/axfeat/Cargo.toml b/os/arceos/api/axfeat/Cargo.toml index 835eea21f7..cb84601a9d 100644 --- a/os/arceos/api/axfeat/Cargo.toml +++ b/os/arceos/api/axfeat/Cargo.toml @@ -81,10 +81,9 @@ fs = [ "dep:ax-fs", "ax-runtime/fs", ] # TODO: try to remove "paging" -fs-ng = ["alloc", "paging", "dep:ax-fs-ng", "ax-runtime/fs-ng"] -fs-ng-ext4 = ["fs-ng", "ax-fs-ng/ext4"] -fs-ng-fat = ["fs-ng", "ax-fs-ng/fat"] -fs-ng-times = ["fs-ng", "ax-fs-ng/times"] +fs-ext4 = ["fs", "ax-fs/ext4"] +fs-fat = ["fs", "ax-fs/fat"] +fs-times = ["fs", "ax-fs/times"] # Networking net = [ @@ -128,7 +127,6 @@ ax-config = { workspace = true, optional = true } ax-driver = { workspace = true, optional = true } ax-display = { workspace = true, optional = true } ax-fs = { workspace = true, optional = true } -ax-fs-ng = { workspace = true, optional = true } ax-hal.workspace = true ax-input = { workspace = true, optional = true } ax-ipi = { workspace = true, optional = true } diff --git a/os/arceos/modules/axfs-ng/CHANGELOG.md b/os/arceos/modules/axfs-ng/CHANGELOG.md deleted file mode 100644 index 8ef54029c5..0000000000 --- a/os/arceos/modules/axfs-ng/CHANGELOG.md +++ /dev/null @@ -1,55 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.5.15](https://github.com/rcore-os/tgoskits/compare/ax-fs-ng-v0.5.14...ax-fs-ng-v0.5.15) - 2026-05-22 - -### Added - -- *(starryos)* add Lua/LuaRocks runtime coverage and fix rmdir ENOTEMPTY ([#777](https://github.com/rcore-os/tgoskits/pull/777)) - -### Fixed - -- *(starry-kernel)* open/openat deep — 6 类跨子系统改造 (stacked on #719) ([#720](https://github.com/rcore-os/tgoskits/pull/720)) -- *(starry-kernel)* open/openat 15 类局部修复 ([#719](https://github.com/rcore-os/tgoskits/pull/719)) - -## [0.5.14](https://github.com/rcore-os/tgoskits/compare/ax-fs-ng-v0.5.13...ax-fs-ng-v0.5.14) - 2026-05-19 - -### Other - -- updated the following local packages: ax-errno, ax-alloc, ax-driver, axfs-ng-vfs, ax-io, ax-hal, ax-sync - -## [0.5.13](https://github.com/rcore-os/tgoskits/compare/ax-fs-ng-v0.5.12...ax-fs-ng-v0.5.13) - 2026-05-15 - -### Added - -- *(starry-kernel)* add runtime dynamic debug control ([#446](https://github.com/rcore-os/tgoskits/pull/446)) -- *(mm)* track backend split metadata and generate real /proc maps output ([#306](https://github.com/rcore-os/tgoskits/pull/306)) - -### Fixed - -- *(axdriver_block)* when the partition type is MBR, select ext4 and active partition as rootfs -- *(rsext4)* bound data block cache growth ([#408](https://github.com/rcore-os/tgoskits/pull/408)) -- *(fs)* mkdir("/") returns EINVAL instead of EEXIST ([#375](https://github.com/rcore-os/tgoskits/pull/375)) -- implement close_all_fds function and enhance pipe and syscall handling ([#305](https://github.com/rcore-os/tgoskits/pull/305)) - -### Other - -- Merge pull request #554 from rcore-os/feat/sg2002-pr383 -- *(sys_fallocate)* validate negative offset/len, use EOPNOTSUPP for unsupported modes, reject huge offsets ([#441](https://github.com/rcore-os/tgoskits/pull/441)) - -## [0.5.12](https://github.com/rcore-os/tgoskits/compare/ax-fs-ng-v0.5.11...ax-fs-ng-v0.5.12) - 2026-04-27 - -### Fixed - -- *(fcntl)* return correct access mode flags in F_GETFL ([#260](https://github.com/rcore-os/tgoskits/pull/260)) -- *(file)* reject O_WRONLY/O_RDWR on directories with EISDIR ([#253](https://github.com/rcore-os/tgoskits/pull/253)) - -### Other - -- sync ext4/rsext4 crash-consistency fixes from x-kernel ([#284](https://github.com/rcore-os/tgoskits/pull/284)) diff --git a/os/arceos/modules/axfs-ng/Cargo.toml b/os/arceos/modules/axfs-ng/Cargo.toml deleted file mode 100644 index 868e4c9695..0000000000 --- a/os/arceos/modules/axfs-ng/Cargo.toml +++ /dev/null @@ -1,52 +0,0 @@ -[package] -name = "ax-fs-ng" -version = "0.5.15" -repository = "https://github.com/rcore-os/tgoskits" -edition.workspace = true -authors = ["Mivik "] -description = "ArceOS filesystem module" -license.workspace = true - -[features] -fat = ["dep:fatfs"] -ext4-lwext4 = ["dep:lwext4_rust"] -ext4-rsext4 = ["dep:rsext4"] -ext4 = ["ext4-rsext4"] -times = [] -std = ["lwext4_rust?/std"] - -[dependencies] -ax-alloc = { workspace = true, features = ["default"] } -ax-errno = { workspace = true } -axfs-ng-vfs = { workspace = true } -ax-hal = { workspace = true } -ax-io = { workspace = true, features = ["alloc"] } -axpoll = { workspace = true } -ax-sync = { workspace = true } -bitflags = "2.10" -cfg-if = { workspace = true } -chrono = { workspace = true } -intrusive-collections = "0.9" -ax-kspin = { workspace = true } -log = { workspace = true } -lru = "0.16" -rd-block-volume = { workspace = true } -scope-local = { workspace = true } -slab = { version = "0.4", default-features = false } -spin = { workspace = true } - -[dependencies.lwext4_rust] -version = "0.2" -default-features = false -optional = true - -[dependencies.rsext4] -workspace = true -optional = true - -[dependencies.fatfs] -package = "starry-fatfs" -version = "0.4.1-preview.2" -default-features = false -optional = true -features = ["alloc", "lfn", "log_level_trace", "unicode"] diff --git a/os/arceos/modules/axfs-ng/resources/make_fs.sh b/os/arceos/modules/axfs-ng/resources/make_fs.sh deleted file mode 100755 index 0ac53dd072..0000000000 --- a/os/arceos/modules/axfs-ng/resources/make_fs.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -CUR_DIR=$(dirname $0) - -write_fs() { - for i in $(seq 1 1000); do - echo "Rust is cool!" >>long.txt - done - echo "Rust is cool!" >short.txt - mkdir -p a/long/path - echo "Rust is cool!" >a/long/path/test.txt - mkdir -p very-long-dir-name - echo "Rust is cool!" >>very-long-dir-name/very-long-file-name.txt -} -init_fs() { - local name=$1 - local options=$2 - mkdir -p mnt - sudo mount -o loop "$name" mnt -o "$options" - - sudo chmod 777 mnt - - cd mnt - write_fs - cd .. - - sudo umount mnt - rm -r mnt -} -create_fat_img() { - local name=$1 - local kb=$2 - local fatSize=$3 - dd if=/dev/zero of="$name" bs=1K count=$kb - mkfs.vfat -s 1 -F $fatSize "$name" - - init_fs "$name" rw,uid=$USER,gid=$USER -} -create_ext4_img() { - local name=$1 - local kb=$2 - dd if=/dev/zero of="$name" bs=1K count=$kb - mkfs.ext4 -O ^metadata_csum "$name" - - init_fs "$name" rw -} - -create_fat_img "$CUR_DIR/fat16.img" 2500 16 -create_fat_img "$CUR_DIR/fat32.img" 34000 32 - -create_ext4_img "$CUR_DIR/ext4.img" 30000 diff --git a/os/arceos/modules/axfs-ng/src/fs/mod.rs b/os/arceos/modules/axfs-ng/src/fs/mod.rs deleted file mode 100644 index 12b725666c..0000000000 --- a/os/arceos/modules/axfs-ng/src/fs/mod.rs +++ /dev/null @@ -1,36 +0,0 @@ -use alloc::boxed::Box; - -use axfs_ng_vfs::{Filesystem, VfsResult}; - -use crate::block::{BlockRegion, FsBlockDevice}; - -cfg_if::cfg_if! { - if #[cfg(feature = "ext4")] { - mod ext4; - type DefaultFilesystem = ext4::Ext4Filesystem; - } else if #[cfg(feature = "fat")] { - mod fat; - type DefaultFilesystem = fat::FatFilesystem; - } else { - struct DefaultFilesystem; - impl DefaultFilesystem { - pub fn new(_dev: Box, _region: BlockRegion) -> VfsResult { - panic!("No filesystem feature enabled"); - } - } - } -} - -/// Create a filesystem instance from a block device. -pub fn new_default(dev: Box, region: BlockRegion) -> VfsResult { - DefaultFilesystem::new(dev, region) -} - -/// Create a filesystem instance from a boxed block device. -/// -/// Use this for loop devices and other block backends created outside the -/// platform probe path. -#[cfg(feature = "ext4")] -pub fn new_from_dyn(dev: Box, region: BlockRegion) -> VfsResult { - ext4::Ext4Filesystem::new_from_boxed(dev, region) -} diff --git a/os/arceos/modules/axfs-ng/src/lib.rs b/os/arceos/modules/axfs-ng/src/lib.rs deleted file mode 100644 index 0b9344f4df..0000000000 --- a/os/arceos/modules/axfs-ng/src/lib.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! ArceOS filesystem module. -//! -//! Provides high-level filesystem operations built on top of the VFS layer, -//! including file I/O with page caching, directory traversal, and -//! `std::fs`-like APIs. - -#![cfg_attr(all(not(test), not(doc)), no_std)] -#![allow(clippy::new_ret_no_self)] - -extern crate alloc; - -#[macro_use] -extern crate log; - -use alloc::{boxed::Box, vec::Vec}; - -mod block; -mod fs; -mod highlevel; -mod root; - -pub use block::{BlockRegion, FsBlockDevice}; -/// Create a filesystem from a dynamic (boxed) block device. -#[cfg(feature = "ext4")] -pub use fs::new_from_dyn as new_filesystem_from_dyn; -pub use highlevel::*; -use root::FilesystemKind; - -/// Initializes the filesystem subsystem by selecting a root device from the -/// available block devices and optional boot arguments. -pub fn init_filesystems(block_devs: Vec>, bootargs: Option<&str>) { - info!("Initialize filesystem subsystem..."); - - let root_spec = root::parse_root_spec(bootargs); - let mut disks = root::collect_disks(block_devs); - let candidates = root::collect_root_candidates(&disks); - let (selected_disk_index, selected_partition) = - root::select_root_candidate(&candidates, &root_spec).unwrap_or_else(|| { - panic!("failed to determine root device from available block devices") - }); - let selected_disk_pos = disks - .iter() - .position(|disk| disk.disk_index == selected_disk_index) - .unwrap_or_else(|| panic!("selected root disk disappeared during initialization")); - let selected = disks.swap_remove(selected_disk_pos); - let (description, region) = { - let selected_partition_info = selected_partition.and_then(|part_index| { - selected - .partitions - .iter() - .find(|partition| partition.info.index == part_index) - }); - ( - root::describe_selection(selected.disk_index, selected_partition_info), - selected_partition_info - .map_or_else(|| full_region(&selected.dev), |part| part.info.region), - ) - }; - info!(" selected root device: {}", description); - - let fs = fs::new_default(selected.dev, region).unwrap_or_else(|err| { - panic!( - "failed to initialize filesystem on {}: {err:?}", - description - ) - }); - info!(" filesystem type: {:?}", fs.name()); - - let mp = axfs_ng_vfs::Mountpoint::new_root(&fs); - ROOT_FS_CONTEXT.call_once(|| FsContext::new(mp.root_location())); -} - -fn detect_filesystem(dev: &mut dyn FsBlockDevice, region: BlockRegion) -> Option { - #[cfg(not(any(feature = "ext4", feature = "fat")))] - let _ = (&mut *dev, region); - - #[cfg(feature = "ext4")] - if region_has_ext4(dev, region) { - return Some(FilesystemKind::Ext4); - } - - #[cfg(feature = "fat")] - if region_has_fat(dev, region) { - return Some(FilesystemKind::Fat); - } - - None -} - -#[cfg(feature = "ext4")] -fn region_has_ext4(dev: &mut dyn FsBlockDevice, region: BlockRegion) -> bool { - const EXT4_SUPERBLOCK_OFFSET: usize = 1024; - const EXT4_MAGIC_OFFSET: usize = 0x38; - const EXT4_MAGIC: u16 = 0xEF53; - region_has_magic_u16( - dev, - region, - EXT4_SUPERBLOCK_OFFSET + EXT4_MAGIC_OFFSET, - EXT4_MAGIC, - ) -} - -#[cfg(feature = "fat")] -fn region_has_fat(dev: &mut dyn FsBlockDevice, region: BlockRegion) -> bool { - const FAT16_MAGIC: &[u8; 5] = b"FAT16"; - const FAT32_MAGIC: &[u8; 5] = b"FAT32"; - let start_lba = region.start_lba; - let visible_blocks = region.num_blocks(); - if visible_blocks == 0 { - return false; - } - - let block_size = dev.block_size(); - if block_size < 512 { - return false; - } - - let mut buf = alloc::vec![0u8; block_size]; - if dev.read_block(start_lba, &mut buf).is_err() { - return false; - } - - buf.get(510..512) == Some([0x55, 0xAA].as_slice()) - && (buf.get(54..59) == Some(FAT16_MAGIC.as_slice()) - || buf.get(82..87) == Some(FAT32_MAGIC.as_slice())) -} - -#[cfg(feature = "ext4")] -fn region_has_magic_u16( - dev: &mut dyn FsBlockDevice, - region: BlockRegion, - byte_offset: usize, - magic: u16, -) -> bool { - let block_size = dev.block_size(); - if block_size == 0 { - return false; - } - - let start_lba = region.start_lba; - let visible_blocks = region.num_blocks(); - let block_index = byte_offset / block_size; - let within_block = byte_offset % block_size; - if visible_blocks == 0 || within_block + 2 > block_size { - return false; - } - - let Some(block_index_u64) = u64::try_from(block_index).ok() else { - return false; - }; - let Some(end_lba) = start_lba.checked_add(visible_blocks) else { - return false; - }; - let block_id = match start_lba.checked_add(block_index_u64) { - Some(block_id) if block_id < end_lba => block_id, - _ => return false, - }; - - let mut buf = alloc::vec![0u8; block_size]; - if dev.read_block(block_id, &mut buf).is_err() { - return false; - } - - u16::from_le_bytes([buf[within_block], buf[within_block + 1]]) == magic -} - -fn full_region(dev: &dyn FsBlockDevice) -> BlockRegion { - BlockRegion::from_num_blocks(dev.num_blocks()) -} diff --git a/os/arceos/modules/axfs-ng/src/root.rs b/os/arceos/modules/axfs-ng/src/root.rs deleted file mode 100644 index cf64221ea9..0000000000 --- a/os/arceos/modules/axfs-ng/src/root.rs +++ /dev/null @@ -1,544 +0,0 @@ -use alloc::{ - boxed::Box, - format, - string::{String, ToString}, - vec::Vec, -}; - -use rd_block_volume::{BlockVolume, DiskId, PartitionTableKind as VolumeTableKind, scan_volumes}; - -use crate::block::{BlockRegion, FsBlockDevice, VolumeReader}; - -#[derive(Debug, Default)] -pub(crate) struct RootSpec { - disk_index: Option, - partition_index: Option, - partuuid: Option, - partlabel: Option, -} - -pub(crate) struct RootCandidate { - pub(crate) disk_index: usize, - pub(crate) partition: Option, -} - -pub(crate) struct DiscoveredDisk { - pub(crate) disk_index: usize, - pub(crate) dev: Box, - pub(crate) partitions: Vec, -} - -#[derive(Clone)] -pub(crate) struct DetectedPartition { - pub(crate) info: PartitionInfo, - pub(crate) filesystem: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) struct PartitionInfo { - pub(crate) index: usize, - pub(crate) table_kind: PartitionTableKind, - pub(crate) region: BlockRegion, - pub(crate) name: Option, - pub(crate) part_uuid: Option, - pub(crate) bootable: bool, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(crate) enum PartitionTableKind { - Raw, - Gpt, - Mbr, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub(crate) enum FilesystemKind { - #[cfg(feature = "ext4")] - Ext4, - #[cfg(feature = "fat")] - Fat, -} - -impl RootCandidate { - pub(crate) fn description(&self) -> String { - if let Some(partition) = &self.partition { - describe_partition(self.disk_index, partition) - } else { - format!("disk{} raw device", self.disk_index) - } - } -} - -pub(crate) fn collect_disks( - block_devs: impl IntoIterator>, -) -> Vec { - let mut disks = Vec::new(); - - for (disk_index, mut dev) in block_devs.into_iter().enumerate() { - let device_name = dev.name().to_string(); - let mut reader = VolumeReader::new(&mut *dev); - match scan_volumes(&mut reader, DiskId(disk_index as u64)) { - Ok(volumes) => { - let partitions = collect_partitions(&mut *dev, volumes); - log_disk(disk_index, &device_name, &partitions); - disks.push(DiscoveredDisk { - disk_index, - dev, - partitions, - }); - } - Err(err) => { - warn!( - " failed to scan partitions on block device {} ({}): {err:?}", - disk_index, device_name - ); - } - } - } - - disks -} - -fn collect_partitions( - dev: &mut dyn FsBlockDevice, - volumes: Vec, -) -> Vec { - let mut partitions = Vec::new(); - for volume in volumes { - if volume.table_kind == VolumeTableKind::Raw { - let region = region_from_volume(&volume); - let raw_fs = super::detect_filesystem(dev, region); - info!(" raw device fs={:?}", raw_fs); - continue; - } - - let info = partition_info_from_volume(&volume); - let filesystem = super::detect_filesystem(dev, info.region); - info!( - " partition {} name={:?} fs={:?} lba {}..{}", - info.index + 1, - info.name, - filesystem, - info.region.start_lba, - info.region.end_lba - ); - partitions.push(DetectedPartition { info, filesystem }); - } - - partitions -} - -fn log_disk(disk_index: usize, device_name: &str, partitions: &[DetectedPartition]) { - if let Some(first) = partitions.first() { - info!( - " block device {} ({}) has {:?} partition table with {} partitions", - disk_index, - device_name, - first.info.table_kind, - partitions.len() - ); - } else { - info!( - " block device {} ({}) has no usable partition table; treating the whole disk as a \ - candidate", - disk_index, device_name - ); - } -} - -fn partition_info_from_volume(volume: &BlockVolume) -> PartitionInfo { - PartitionInfo { - index: volume - .partition_id - .0 - .checked_sub(1) - .map(|index| index as usize) - .unwrap_or(0), - table_kind: table_kind_from_volume(volume.table_kind), - region: region_from_volume(volume), - name: volume.partlabel.as_ref().map(|label| label.0.clone()), - part_uuid: volume.partuuid.as_ref().map(|uuid| uuid.0.clone()), - bootable: volume.bootable, - } -} - -fn region_from_volume(volume: &BlockVolume) -> BlockRegion { - BlockRegion::new(volume.region.start_block, volume.region.num_blocks) -} - -fn table_kind_from_volume(kind: VolumeTableKind) -> PartitionTableKind { - match kind { - VolumeTableKind::Raw => PartitionTableKind::Raw, - VolumeTableKind::Gpt => PartitionTableKind::Gpt, - VolumeTableKind::Mbr => PartitionTableKind::Mbr, - } -} - -pub(crate) fn collect_root_candidates(disks: &[DiscoveredDisk]) -> Vec { - let mut candidates = Vec::new(); - - for disk in disks { - if disk.partitions.is_empty() { - candidates.push(RootCandidate { - disk_index: disk.disk_index, - partition: None, - }); - continue; - } - - for partition in &disk.partitions { - candidates.push(RootCandidate { - disk_index: disk.disk_index, - partition: Some(partition.clone()), - }); - } - } - - candidates -} - -pub(crate) fn select_root_candidate( - candidates: &[RootCandidate], - spec: &RootSpec, -) -> Option<(usize, Option)> { - if let Some(index) = select_explicit_root(candidates, spec) { - return Some(index); - } - - select_default_root(candidates) -} - -fn select_explicit_root( - candidates: &[RootCandidate], - spec: &RootSpec, -) -> Option<(usize, Option)> { - for candidate in candidates { - if let Some(partition) = candidate.partition.as_ref() { - if let Some(partuuid) = &spec.partuuid - && partition - .info - .part_uuid - .as_ref() - .is_some_and(|candidate_uuid| candidate_uuid.eq_ignore_ascii_case(partuuid)) - { - info!(" matched root by PARTUUID on {}", candidate.description()); - return Some((candidate.disk_index, Some(partition.info.index))); - } - - if let Some(partlabel) = &spec.partlabel - && partition.info.name.as_deref() == Some(partlabel.as_str()) - { - info!(" matched root by PARTLABEL on {}", candidate.description()); - return Some((candidate.disk_index, Some(partition.info.index))); - } - } - - if let Some(disk_index) = spec.disk_index - && candidate.disk_index == disk_index - { - match (spec.partition_index, &candidate.partition) { - (Some(partition_index), Some(partition)) - if partition.info.index == partition_index => - { - info!( - " matched root by device path on {}", - candidate.description() - ); - return Some((candidate.disk_index, Some(partition.info.index))); - } - (None, None) => { - info!( - " matched root by raw device path on {}", - candidate.description() - ); - return Some((candidate.disk_index, None)); - } - _ => {} - } - } - } - - if spec.disk_index.is_some() || spec.partuuid.is_some() || spec.partlabel.is_some() { - panic!("configured root device was not found in discovered block devices"); - } - - None -} - -fn select_default_root(candidates: &[RootCandidate]) -> Option<(usize, Option)> { - let rootfs_matches: Vec<_> = candidates - .iter() - .filter(|candidate| { - candidate - .partition - .as_ref() - .and_then(|part| part.info.name.as_deref()) - == Some("rootfs") - }) - .map(|candidate| { - ( - candidate.disk_index, - candidate.partition.as_ref().map(|part| part.info.index), - ) - }) - .collect(); - if rootfs_matches.len() == 1 { - info!(" falling back to PARTLABEL=rootfs"); - return rootfs_matches.into_iter().next(); - } - if rootfs_matches.len() > 1 { - panic!("multiple partitions are labeled 'rootfs'; specify root= explicitly"); - } - - let partition_matches = supported_filesystem_partition_matches(candidates); - let bootable_mbr_partition_matches: Vec<_> = partition_matches - .iter() - .copied() - .filter(|(_, partition)| { - partition.info.table_kind == PartitionTableKind::Mbr && partition.info.bootable - }) - .map(|(disk_index, partition)| (disk_index, Some(partition.info.index))) - .collect(); - if bootable_mbr_partition_matches.len() == 1 { - info!(" only one bootable MBR filesystem partition is available; using it as root"); - return bootable_mbr_partition_matches.into_iter().next(); - } - - let partition_matches: Vec<_> = partition_matches - .into_iter() - .map(|(disk_index, partition)| (disk_index, Some(partition.info.index))) - .collect(); - if partition_matches.len() == 1 { - info!(" only one supported filesystem partition is available; using it as root"); - return partition_matches.into_iter().next(); - } - - let raw_matches: Vec<_> = candidates - .iter() - .filter(|candidate| candidate.partition.is_none()) - .map(|candidate| (candidate.disk_index, None)) - .collect(); - if partition_matches.is_empty() && raw_matches.len() == 1 { - info!(" only one raw block device is available; using it as root"); - return raw_matches.into_iter().next(); - } - - None -} - -fn supported_filesystem_partition_matches( - candidates: &[RootCandidate], -) -> Vec<(usize, &DetectedPartition)> { - candidates - .iter() - .filter_map(|candidate| { - let partition = candidate.partition.as_ref()?; - if !supported_default_root_partition(partition) { - return None; - } - Some((candidate.disk_index, partition)) - }) - .collect() -} - -fn supported_default_root_partition(partition: &DetectedPartition) -> bool { - if !partition.filesystem.is_some() { - return false; - } - match partition.info.table_kind { - PartitionTableKind::Mbr => { - #[cfg(feature = "ext4")] - { - partition.filesystem == Some(FilesystemKind::Ext4) - } - #[cfg(all(not(feature = "ext4"), feature = "fat"))] - { - partition.filesystem == Some(FilesystemKind::Fat) - } - #[cfg(all(not(feature = "ext4"), not(feature = "fat")))] - { - false - } - } - PartitionTableKind::Gpt | PartitionTableKind::Raw => { - #[cfg(feature = "ext4")] - { - partition.filesystem == Some(FilesystemKind::Ext4) - } - #[cfg(all(not(feature = "ext4"), feature = "fat"))] - { - partition.filesystem == Some(FilesystemKind::Fat) - } - #[cfg(all(not(feature = "ext4"), not(feature = "fat")))] - { - false - } - } - } -} - -pub(crate) fn describe_selection( - disk_index: usize, - partition: Option<&DetectedPartition>, -) -> String { - if let Some(partition) = partition { - describe_partition(disk_index, partition) - } else { - format!("disk{} raw device", disk_index) - } -} - -fn describe_partition(disk_index: usize, partition: &DetectedPartition) -> String { - let name = partition.info.name.as_deref().unwrap_or(""); - let fs = partition - .filesystem - .map(filesystem_name) - .unwrap_or("unknown"); - format!( - "disk{} partition {} ({}, fs={}, lba {}..{})", - disk_index, - partition.info.index + 1, - name, - fs, - partition.info.region.start_lba, - partition.info.region.end_lba - ) -} - -pub(crate) fn parse_root_spec(bootargs: Option<&str>) -> RootSpec { - let mut spec = RootSpec::default(); - - if let Some(bootargs) = bootargs - && let Some(root_arg) = bootargs - .split_whitespace() - .find(|arg| arg.starts_with("root=")) - { - let root_value = root_arg.strip_prefix("root=").unwrap_or(""); - spec = match root_value { - value if value.starts_with("/dev/mmcblk") => parse_mmcblk_path(value), - value if value.starts_with("/dev/sd") => parse_sd_path(value), - value if value.starts_with("PARTUUID=") => RootSpec { - partuuid: Some(value.strip_prefix("PARTUUID=").unwrap_or("").to_uppercase()), - ..RootSpec::default() - }, - value if value.starts_with("PARTLABEL=") => RootSpec { - partlabel: Some(value.strip_prefix("PARTLABEL=").unwrap_or("").to_string()), - ..RootSpec::default() - }, - _ => RootSpec::default(), - }; - } - - spec -} - -fn parse_mmcblk_path(path: &str) -> RootSpec { - if let Some(remaining) = path.strip_prefix("/dev/mmcblk") { - if let Some(p_pos) = remaining.find('p') { - let disk = remaining[..p_pos].parse::().ok(); - let part = remaining[p_pos + 1..] - .parse::() - .ok() - .and_then(|part| part.checked_sub(1)); - return RootSpec { - disk_index: disk, - partition_index: part, - ..RootSpec::default() - }; - } - - if let Ok(disk) = remaining.parse::() { - return RootSpec { - disk_index: Some(disk), - ..RootSpec::default() - }; - } - } - - RootSpec::default() -} - -fn parse_sd_path(path: &str) -> RootSpec { - if let Some(remaining) = path.strip_prefix("/dev/sd") { - let bytes = remaining.as_bytes(); - if bytes.is_empty() { - return RootSpec::default(); - } - - let disk_index = bytes[0] - .is_ascii_lowercase() - .then(|| usize::from(bytes[0] - b'a')); - let partition_index = if bytes.len() > 1 { - core::str::from_utf8(&bytes[1..]) - .ok() - .and_then(|part| part.parse::().ok()) - .and_then(|part| part.checked_sub(1)) - } else { - None - }; - return RootSpec { - disk_index, - partition_index, - ..RootSpec::default() - }; - } - - RootSpec::default() -} - -const fn filesystem_name(fs: FilesystemKind) -> &'static str { - match fs { - #[cfg(feature = "ext4")] - FilesystemKind::Ext4 => "ext4", - #[cfg(feature = "fat")] - FilesystemKind::Fat => "fat", - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn mbr_partition( - index: usize, - filesystem: Option, - bootable: bool, - ) -> RootCandidate { - RootCandidate { - disk_index: 0, - partition: Some(DetectedPartition { - info: PartitionInfo { - index, - table_kind: PartitionTableKind::Mbr, - region: BlockRegion::new(index as u64 * 100, 100), - name: None, - part_uuid: None, - bootable, - }, - filesystem, - }), - } - } - - #[test] - #[cfg(feature = "ext4")] - fn default_root_uses_only_supported_mbr_filesystem_partition_without_boot_flag() { - let candidates = [ - mbr_partition(0, None, false), - mbr_partition(1, Some(FilesystemKind::Ext4), false), - ]; - - assert_eq!(select_default_root(&candidates), Some((0, Some(1)))); - } - - #[test] - #[cfg(feature = "ext4")] - fn default_root_prefers_only_bootable_mbr_filesystem_partition() { - let candidates = [ - mbr_partition(0, Some(FilesystemKind::Ext4), false), - mbr_partition(1, Some(FilesystemKind::Ext4), true), - ]; - - assert_eq!(select_default_root(&candidates), Some((0, Some(1)))); - } -} diff --git a/os/arceos/modules/axfs/Cargo.toml b/os/arceos/modules/axfs/Cargo.toml index 7ce0dee20e..e0d4dbedfc 100644 --- a/os/arceos/modules/axfs/Cargo.toml +++ b/os/arceos/modules/axfs/Cargo.toml @@ -1,38 +1,57 @@ [package] name = "ax-fs" -version = "0.5.14" +version = "0.6.0" repository = "https://github.com/rcore-os/tgoskits" edition.workspace = true -authors = ["Yuekai Jia "] +authors = ["Mivik "] description = "ArceOS filesystem module" license.workspace = true [features] -default = [] +fat = ["dep:fatfs"] +devfs = ["dep:ax-fs-devfs"] +ext4-lwext4 = ["dep:lwext4_rust"] +ext4-rsext4 = ["dep:rsext4"] +ext4 = ["ext4-rsext4"] +ramfs = ["dep:ax-fs-ramfs"] times = [] +std = ["lwext4_rust?/std"] use-ramdisk = [] [dependencies] +ax-alloc = { workspace = true, features = ["default"] } ax-errno = { workspace = true } -ax-hal = { workspace = true } -ax-fs-devfs = { workspace = true } -ax-fs-ramfs = { workspace = true } +ax-fs-devfs = { workspace = true, optional = true } +ax-fs-ramfs = { workspace = true, optional = true } ax-fs-vfs = { workspace = true } +ax-hal = { workspace = true } ax-io = { workspace = true, features = ["alloc"] } -ax-cap-access = { workspace = true } +axpoll = { workspace = true } +ax-sync = { workspace = true } +bitflags = "2.10" +cfg-if = { workspace = true } +chrono = { workspace = true } +intrusive-collections = "0.9" ax-kspin = { workspace = true } -ax-lazyinit = { workspace = true } log = { workspace = true } -rsext4 = { workspace = true } +lru = "0.16" +rd-block-volume = { workspace = true } +scope-local = { workspace = true } +slab = { version = "0.4", default-features = false } spin = { workspace = true } -[dependencies.axfatfs] -version = "0.1.0-pre.0" +[dependencies.lwext4_rust] +version = "0.2" +default-features = false +optional = true + +[dependencies.rsext4] +workspace = true +optional = true + +[dependencies.fatfs] +package = "starry-fatfs" +version = "0.4.1-preview.2" default-features = false -features = [ - # no std - "alloc", - "lfn", - "log_level_trace", - "unicode", -] +optional = true +features = ["alloc", "lfn", "log_level_trace", "unicode"] diff --git a/os/arceos/modules/axfs/src/api/dir.rs b/os/arceos/modules/axfs/src/api/dir.rs deleted file mode 100644 index 58cf024592..0000000000 --- a/os/arceos/modules/axfs/src/api/dir.rs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use alloc::string::String; -use core::fmt; - -use ax_io::Result; - -use super::FileType; -use crate::fops; - -/// Iterator over the entries in a directory. -pub struct ReadDir<'a> { - path: &'a str, - inner: fops::Directory, - buf_pos: usize, - buf_end: usize, - end_of_stream: bool, - dirent_buf: [fops::DirEntry; 31], -} - -/// Entries returned by the [`ReadDir`] iterator. -pub struct DirEntry<'a> { - dir_path: &'a str, - entry_name: String, - entry_type: FileType, -} - -/// A builder used to create directories in various manners. -#[derive(Default, Debug)] -pub struct DirBuilder { - recursive: bool, -} - -impl<'a> ReadDir<'a> { - pub(super) fn new(path: &'a str) -> Result { - let mut opts = fops::OpenOptions::new(); - opts.read(true); - let inner = fops::Directory::open_dir(path, &opts)?; - const EMPTY: fops::DirEntry = fops::DirEntry::default(); - let dirent_buf = [EMPTY; 31]; - Ok(ReadDir { - path, - inner, - end_of_stream: false, - buf_pos: 0, - buf_end: 0, - dirent_buf, - }) - } -} - -impl<'a> Iterator for ReadDir<'a> { - type Item = Result>; - - fn next(&mut self) -> Option>> { - if self.end_of_stream { - return None; - } - - loop { - if self.buf_pos >= self.buf_end { - match self.inner.read_dir(&mut self.dirent_buf) { - Ok(n) => { - if n == 0 { - self.end_of_stream = true; - return None; - } - self.buf_pos = 0; - self.buf_end = n; - } - Err(e) => { - self.end_of_stream = true; - return Some(Err(e)); - } - } - } - let entry = &self.dirent_buf[self.buf_pos]; - self.buf_pos += 1; - let name_bytes = entry.name_as_bytes(); - if name_bytes == b"." || name_bytes == b".." { - continue; - } - let entry_name = unsafe { core::str::from_utf8_unchecked(name_bytes).into() }; - let entry_type = entry.entry_type(); - - return Some(Ok(DirEntry { - dir_path: self.path, - entry_name, - entry_type, - })); - } - } -} - -impl DirEntry<'_> { - /// Returns the full path to the file that this entry represents. - /// - /// The full path is created by joining the original path to `read_dir` - /// with the filename of this entry. - pub fn path(&self) -> String { - String::from(self.dir_path.trim_end_matches('/')) + "/" + &self.entry_name - } - - /// Returns the bare file name of this directory entry without any other - /// leading path component. - pub fn file_name(&self) -> String { - self.entry_name.clone() - } - - /// Returns the file type for the file that this entry points at. - pub fn file_type(&self) -> FileType { - self.entry_type - } -} - -impl fmt::Debug for DirEntry<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("DirEntry").field(&self.path()).finish() - } -} - -impl DirBuilder { - /// Creates a new set of options with default mode/security settings for all - /// platforms and also non-recursive. - pub fn new() -> Self { - Self { recursive: false } - } - - /// Indicates that directories should be created recursively, creating all - /// parent directories. Parents that do not exist are created with the same - /// security and permissions settings. - pub fn recursive(&mut self, recursive: bool) -> &mut Self { - self.recursive = recursive; - self - } - - /// Creates the specified directory with the options configured in this - /// builder. - pub fn create(&self, path: &str) -> Result<()> { - if self.recursive { - self.create_dir_all(path) - } else { - crate::root::create_dir(None, path) - } - } - - fn create_dir_all(&self, _path: &str) -> Result<()> { - Err(ax_errno::AxError::Unsupported) - } -} diff --git a/os/arceos/modules/axfs/src/api/file.rs b/os/arceos/modules/axfs/src/api/file.rs deleted file mode 100644 index 896f9fc574..0000000000 --- a/os/arceos/modules/axfs/src/api/file.rs +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use core::fmt; - -use ax_io::{Result, SeekFrom, prelude::*}; - -use crate::fops; - -/// A structure representing a type of file with accessors for each file type. -/// It is returned by [`Metadata::file_type`] method. -pub type FileType = fops::FileType; - -/// Representation of the various permissions on a file. -pub type Permissions = fops::FilePerm; - -/// An object providing access to an open file on the filesystem. -pub struct File { - inner: fops::File, -} - -/// Metadata information about a file. -pub struct Metadata(fops::FileAttr); - -/// Options and flags which can be used to configure how a file is opened. -#[derive(Clone, Debug)] -pub struct OpenOptions(fops::OpenOptions); - -impl Default for OpenOptions { - fn default() -> Self { - Self::new() - } -} - -impl OpenOptions { - /// Creates a blank new set of options ready for configuration. - pub const fn new() -> Self { - OpenOptions(fops::OpenOptions::new()) - } - - /// Sets the option for read access. - pub fn read(&mut self, read: bool) -> &mut Self { - self.0.read(read); - self - } - - /// Sets the option for write access. - pub fn write(&mut self, write: bool) -> &mut Self { - self.0.write(write); - self - } - - /// Sets the option for the append mode. - pub fn append(&mut self, append: bool) -> &mut Self { - self.0.append(append); - self - } - - /// Sets the option for truncating a previous file. - pub fn truncate(&mut self, truncate: bool) -> &mut Self { - self.0.truncate(truncate); - self - } - - /// Sets the option to create a new file, or open it if it already exists. - pub fn create(&mut self, create: bool) -> &mut Self { - self.0.create(create); - self - } - - /// Sets the option to create a new file, failing if it already exists. - pub fn create_new(&mut self, create_new: bool) -> &mut Self { - self.0.create_new(create_new); - self - } - - /// Opens a file at `path` with the options specified by `self`. - pub fn open(&self, path: &str) -> Result { - fops::File::open(path, &self.0).map(|inner| File { inner }) - } -} - -impl Metadata { - /// Returns the file type for this metadata. - pub const fn file_type(&self) -> FileType { - self.0.file_type() - } - - /// Returns `true` if this metadata is for a directory. The - /// result is mutually exclusive to the result of - /// [`Metadata::is_file`]. - pub const fn is_dir(&self) -> bool { - self.0.is_dir() - } - - /// Returns `true` if this metadata is for a regular file. The - /// result is mutually exclusive to the result of - /// [`Metadata::is_dir`]. - pub const fn is_file(&self) -> bool { - self.0.is_file() - } - - /// Returns the size of the file, in bytes, this metadata is for. - #[allow(clippy::len_without_is_empty)] - pub const fn len(&self) -> u64 { - self.0.size() - } - - /// Returns the permissions of the file this metadata is for. - pub const fn permissions(&self) -> Permissions { - self.0.perm() - } - - /// Returns the total size of this file in bytes. - pub const fn size(&self) -> u64 { - self.0.size() - } - - /// Returns the number of blocks allocated to the file, in 512-byte units. - pub const fn blocks(&self) -> u64 { - self.0.blocks() - } -} - -impl fmt::Debug for Metadata { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Metadata") - .field("file_type", &self.file_type()) - .field("is_dir", &self.is_dir()) - .field("is_file", &self.is_file()) - .field("permissions", &self.permissions()) - .finish_non_exhaustive() - } -} - -impl File { - /// Attempts to open a file in read-only mode. - pub fn open(path: &str) -> Result { - OpenOptions::new().read(true).open(path) - } - - /// Opens a file in write-only mode. - pub fn create(path: &str) -> Result { - OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(path) - } - - /// Creates a new file in read-write mode; error if the file exists. - pub fn create_new(path: &str) -> Result { - OpenOptions::new() - .read(true) - .write(true) - .create_new(true) - .open(path) - } - - /// Returns a new OpenOptions object. - pub fn options() -> OpenOptions { - OpenOptions::new() - } - - /// Truncates or extends the underlying file, updating the size of - /// this file to become `size`. - pub fn set_len(&self, size: u64) -> Result<()> { - self.inner.truncate(size) - } - - /// Queries metadata about the underlying file. - pub fn metadata(&self) -> Result { - self.inner.get_attr().map(Metadata) - } -} - -impl Read for File { - fn read(&mut self, buf: &mut [u8]) -> Result { - self.inner.read(buf) - } -} - -impl Write for File { - fn write(&mut self, buf: &[u8]) -> Result { - self.inner.write(buf) - } - - fn flush(&mut self) -> Result<()> { - self.inner.flush() - } -} - -impl Seek for File { - fn seek(&mut self, pos: SeekFrom) -> Result { - self.inner.seek(pos) - } -} diff --git a/os/arceos/modules/axfs/src/api/mod.rs b/os/arceos/modules/axfs/src/api/mod.rs deleted file mode 100644 index 4a6f9d8ca0..0000000000 --- a/os/arceos/modules/axfs/src/api/mod.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! [`std::fs`]-like high-level filesystem manipulation operations. - -mod dir; -mod file; - -use alloc::{string::String, vec::Vec}; - -use ax_io::{self as io, prelude::*}; - -pub use self::{ - dir::{DirBuilder, DirEntry, ReadDir}, - file::{File, FileType, Metadata, OpenOptions, Permissions}, -}; - -/// Returns an iterator over the entries within a directory. -pub fn read_dir(path: &str) -> io::Result> { - ReadDir::new(path) -} - -/// Returns the canonical, absolute form of a path with all intermediate -/// components normalized. -pub fn canonicalize(path: &str) -> io::Result { - crate::root::absolute_path(path) -} - -/// Returns the current working directory as a [`String`]. -pub fn current_dir() -> io::Result { - crate::root::current_dir() -} - -/// Changes the current working directory to the specified path. -pub fn set_current_dir(path: &str) -> io::Result<()> { - crate::root::set_current_dir(path) -} - -/// Read the entire contents of a file into a bytes vector. -pub fn read(path: &str) -> io::Result> { - let mut file = File::open(path)?; - let size = file.metadata().map(|m| m.len()).unwrap_or(0); - let mut bytes = Vec::with_capacity(size as usize); - file.read_to_end(&mut bytes)?; - Ok(bytes) -} - -/// Read the entire contents of a file into a string. -pub fn read_to_string(path: &str) -> io::Result { - let mut file = File::open(path)?; - let size = file.metadata().map(|m| m.len()).unwrap_or(0); - let mut string = String::with_capacity(size as usize); - file.read_to_string(&mut string)?; - Ok(string) -} - -/// Write a slice as the entire contents of a file. -pub fn write>(path: &str, contents: C) -> io::Result<()> { - File::create(path)?.write_all(contents.as_ref()) -} - -/// Given a path, query the file system to get information about a file, -/// directory, etc. -pub fn metadata(path: &str) -> io::Result { - File::open(path)?.metadata() -} - -/// Creates a new, empty directory at the provided path. -pub fn create_dir(path: &str) -> io::Result<()> { - DirBuilder::new().create(path) -} - -/// Recursively create a directory and all of its parent components if they -/// are missing. -pub fn create_dir_all(path: &str) -> io::Result<()> { - DirBuilder::new().recursive(true).create(path) -} - -/// Removes an empty directory. -pub fn remove_dir(path: &str) -> io::Result<()> { - crate::root::remove_dir(None, path) -} - -/// Removes a file from the filesystem. -pub fn remove_file(path: &str) -> io::Result<()> { - crate::root::remove_file(None, path) -} - -/// Rename a file or directory to a new name. -/// Delete the original file if `old` already exists. -/// -/// This only works then the new path is in the same mounted fs. -pub fn rename(old: &str, new: &str) -> io::Result<()> { - crate::root::rename(old, new) -} diff --git a/os/arceos/modules/axfs-ng/src/block.rs b/os/arceos/modules/axfs/src/block.rs similarity index 77% rename from os/arceos/modules/axfs-ng/src/block.rs rename to os/arceos/modules/axfs/src/block.rs index 3cd8124563..d764d7d3d0 100644 --- a/os/arceos/modules/axfs-ng/src/block.rs +++ b/os/arceos/modules/axfs/src/block.rs @@ -1,8 +1,9 @@ -use alloc::boxed::Box; +use alloc::{boxed::Box, string::String, sync::Arc}; #[cfg(any(feature = "ext4", feature = "fat"))] use ax_errno::AxError; use ax_errno::AxResult; +use ax_kspin::SpinNoIrq; use rd_block_volume::{BlockReader, Error as VolumeError}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -66,6 +67,56 @@ impl FsBlockDevice for Box { } } +pub struct SharedBlockDevice { + name: String, + inner: Arc>>, +} + +impl SharedBlockDevice { + pub fn new(dev: Box) -> Self { + let name = dev.name().into(); + Self { + name, + inner: Arc::new(SpinNoIrq::new(dev)), + } + } +} + +impl Clone for SharedBlockDevice { + fn clone(&self) -> Self { + Self { + name: self.name.clone(), + inner: self.inner.clone(), + } + } +} + +impl FsBlockDevice for SharedBlockDevice { + fn name(&self) -> &str { + &self.name + } + + fn num_blocks(&self) -> u64 { + self.inner.lock().num_blocks() + } + + fn block_size(&self) -> usize { + self.inner.lock().block_size() + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult { + self.inner.lock().read_block(block_id, buf) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult { + self.inner.lock().write_block(block_id, buf) + } + + fn flush(&mut self) -> AxResult { + self.inner.lock().flush() + } +} + #[cfg(any(feature = "ext4", feature = "fat"))] pub struct RegionBlockDevice { inner: T, diff --git a/os/arceos/modules/axfs/src/dev.rs b/os/arceos/modules/axfs/src/dev.rs deleted file mode 100644 index 4c6d11341b..0000000000 --- a/os/arceos/modules/axfs/src/dev.rs +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Block device abstraction for disk operations - -use alloc::sync::Arc; - -use ax_errno::AxResult; -use ax_kspin::SpinNoIrq as Mutex; - -const BLOCK_SIZE: usize = 512; - -pub trait FsBlockDevice: Send { - fn name(&self) -> &str; - fn num_blocks(&self) -> u64; - fn block_size(&self) -> usize; - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult; - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult; - fn flush(&mut self) -> AxResult; -} - -impl FsBlockDevice for alloc::boxed::Box { - fn name(&self) -> &str { - (**self).name() - } - - fn num_blocks(&self) -> u64 { - (**self).num_blocks() - } - - fn block_size(&self) -> usize { - (**self).block_size() - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> AxResult { - (**self).read_block(block_id, buf) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> AxResult { - (**self).write_block(block_id, buf) - } - - fn flush(&mut self) -> AxResult { - (**self).flush() - } -} - -/// A disk device with a cursor. -#[derive(Clone)] -pub struct Disk { - block_id: u64, - offset: usize, - dev: Arc>>, -} - -impl Disk { - /// Create a new disk. - pub fn new(dev: alloc::boxed::Box) -> Self { - assert_eq!(BLOCK_SIZE, dev.block_size()); - Self { - block_id: 0, - offset: 0, - dev: Arc::new(Mutex::new(dev)), - } - } - - /// Get the size of the disk. - pub fn size(&self) -> u64 { - let dev = self.dev.lock(); - dev.num_blocks() * BLOCK_SIZE as u64 - } - - /// Get the position of the cursor. - pub fn position(&self) -> u64 { - self.block_id * BLOCK_SIZE as u64 + self.offset as u64 - } - - /// Set the position of the cursor. - pub fn set_position(&mut self, pos: u64) { - self.block_id = pos / BLOCK_SIZE as u64; - self.offset = pos as usize % BLOCK_SIZE; - } - - /// Read within one block, returns the number of bytes read. - pub fn read_one(&mut self, buf: &mut [u8]) -> AxResult { - let read_size = if self.offset == 0 && buf.len() >= BLOCK_SIZE { - // whole block - let mut dev = self.dev.lock(); - dev.read_block(self.block_id, &mut buf[0..BLOCK_SIZE])?; - self.block_id += 1; - BLOCK_SIZE - } else { - // partial block - let mut data = [0u8; BLOCK_SIZE]; - let start = self.offset; - let count = buf.len().min(BLOCK_SIZE - self.offset); - - { - let mut dev = self.dev.lock(); - dev.read_block(self.block_id, &mut data)?; - } - buf[..count].copy_from_slice(&data[start..start + count]); - self.offset += count; - if self.offset >= BLOCK_SIZE { - self.block_id += 1; - self.offset -= BLOCK_SIZE; - } - count - }; - Ok(read_size) - } - - /// Write within one block, returns the number of bytes written. - pub fn write_one(&mut self, buf: &[u8]) -> AxResult { - let write_size = if self.offset == 0 && buf.len() >= BLOCK_SIZE { - // whole block - let mut dev = self.dev.lock(); - dev.write_block(self.block_id, &buf[0..BLOCK_SIZE])?; - self.block_id += 1; - BLOCK_SIZE - } else { - // partial block - let mut data = [0u8; BLOCK_SIZE]; - let start = self.offset; - let count = buf.len().min(BLOCK_SIZE - self.offset); - - let mut dev = self.dev.lock(); - dev.read_block(self.block_id, &mut data)?; - data[start..start + count].copy_from_slice(&buf[..count]); - dev.write_block(self.block_id, &data)?; - - self.offset += count; - if self.offset >= BLOCK_SIZE { - self.block_id += 1; - self.offset -= BLOCK_SIZE; - } - count - }; - Ok(write_size) - } -} - -/// A partition wrapper that provides access to a specific partition of a disk. -pub struct Partition { - disk: Arc>, - start_lba: u64, - end_lba: u64, - position: u64, -} - -impl Partition { - /// Create a new partition wrapper. - pub fn new(disk: Disk, start_lba: u64, end_lba: u64) -> Self { - Self { - disk: Arc::new(Mutex::new(disk)), - start_lba, - end_lba, - position: 0, - } - } - - /// Get the size of the partition. - pub fn size(&self) -> u64 { - (self.end_lba - self.start_lba + 1) * BLOCK_SIZE as u64 - } - - /// Get the position of the cursor. - pub fn position(&self) -> u64 { - self.position - } - - /// Set the position of the cursor. - pub fn set_position(&mut self, pos: u64) { - self.position = pos.min(self.size()); - } - - /// Read within one block, returns the number of bytes read. - pub fn read_one(&mut self, buf: &mut [u8]) -> AxResult { - if self.position >= self.size() { - return Ok(0); - } - - let remaining = self.size() - self.position; - let to_read = buf.len().min(remaining as usize); - let buf = &mut buf[..to_read]; - - // Calculate the absolute position on the disk - let abs_pos = self.start_lba * BLOCK_SIZE as u64 + self.position; - - // Set disk position and read - let read_len = { - let mut disk = self.disk.lock(); - disk.set_position(abs_pos); - disk.read_one(buf)? - }; - - self.position += read_len as u64; - Ok(read_len) - } - - /// Write within one block, returns the number of bytes written. - pub fn write_one(&mut self, buf: &[u8]) -> AxResult { - if self.position >= self.size() { - return Ok(0); - } - - let remaining = self.size() - self.position; - let to_write = buf.len().min(remaining as usize); - let buf = &buf[..to_write]; - - // Calculate the absolute position on the disk - let abs_pos = self.start_lba * BLOCK_SIZE as u64 + self.position; - - // Set disk position and write - let write_len = { - let mut disk = self.disk.lock(); - disk.set_position(abs_pos); - disk.write_one(buf)? - }; - - self.position += write_len as u64; - Ok(write_len) - } -} diff --git a/os/arceos/modules/axfs/src/fops.rs b/os/arceos/modules/axfs/src/fops.rs deleted file mode 100644 index 19437671d9..0000000000 --- a/os/arceos/modules/axfs/src/fops.rs +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Low-level filesystem operations. - -use core::fmt; - -use ax_cap_access::{Cap, WithCap}; -use ax_errno::{AxError, AxResult, ax_err_type}; -use ax_fs_vfs::VfsNodeRef; -use ax_io::SeekFrom; - -/// Alias of [`ax_fs_vfs::VfsNodeType`]. -pub type FileType = ax_fs_vfs::VfsNodeType; -/// Alias of [`ax_fs_vfs::VfsDirEntry`]. -pub type DirEntry = ax_fs_vfs::VfsDirEntry; -/// Alias of [`ax_fs_vfs::VfsNodeAttr`]. -pub type FileAttr = ax_fs_vfs::VfsNodeAttr; -/// Alias of [`ax_fs_vfs::VfsNodePerm`]. -pub type FilePerm = ax_fs_vfs::VfsNodePerm; - -/// An opened file object, with open permissions and a cursor. -pub struct File { - node: WithCap, - is_append: bool, - offset: u64, -} - -/// An opened directory object, with open permissions and a cursor for -/// [`read_dir`](Directory::read_dir). -pub struct Directory { - node: WithCap, - entry_idx: usize, -} - -/// Options and flags which can be used to configure how a file is opened. -#[derive(Clone)] -pub struct OpenOptions { - // generic - read: bool, - write: bool, - append: bool, - truncate: bool, - create: bool, - create_new: bool, - // system-specific - _custom_flags: i32, - _mode: u32, -} - -impl Default for OpenOptions { - fn default() -> Self { - Self::new() - } -} - -impl OpenOptions { - /// Creates a blank new set of options ready for configuration. - pub const fn new() -> Self { - Self { - // generic - read: false, - write: false, - append: false, - truncate: false, - create: false, - create_new: false, - // system-specific - _custom_flags: 0, - _mode: 0o666, - } - } - /// Sets the option for read access. - pub fn read(&mut self, read: bool) { - self.read = read; - } - /// Sets the option for write access. - pub fn write(&mut self, write: bool) { - self.write = write; - } - /// Sets the option for the append mode. - pub fn append(&mut self, append: bool) { - self.append = append; - } - /// Sets the option for truncating a previous file. - pub fn truncate(&mut self, truncate: bool) { - self.truncate = truncate; - } - /// Sets the option to create a new file, or open it if it already exists. - pub fn create(&mut self, create: bool) { - self.create = create; - } - /// Sets the option to create a new file, failing if it already exists. - pub fn create_new(&mut self, create_new: bool) { - self.create_new = create_new; - } - - const fn is_valid(&self) -> bool { - if !self.read && !self.write && !self.append { - return false; - } - match (self.write, self.append) { - (true, false) => {} - (false, false) => { - if self.truncate || self.create || self.create_new { - return false; - } - } - (_, true) => { - if self.truncate && !self.create_new { - return false; - } - } - } - true - } -} - -impl File { - fn access_node(&self, cap: Cap) -> AxResult<&VfsNodeRef> { - self.node.access_or_err(cap, AxError::PermissionDenied) - } - - fn _open_at(dir: Option<&VfsNodeRef>, path: &str, opts: &OpenOptions) -> AxResult { - debug!("open file: {} {:?}", path, opts); - if !opts.is_valid() { - return Err(AxError::InvalidInput); - } - - let node_option = crate::root::lookup(dir, path); - let node = if opts.create || opts.create_new { - match node_option { - Ok(node) => { - // already exists - if opts.create_new { - return Err(AxError::AlreadyExists); - } - node - } - // not exists, create new - Err(AxError::NotFound) => crate::root::create_file(dir, path)?, - Err(e) => return Err(e), - } - } else { - // just open the existing - node_option? - }; - - let attr = node.get_attr()?; - if attr.is_dir() - && (opts.create || opts.create_new || opts.write || opts.append || opts.truncate) - { - return Err(AxError::IsADirectory); - } - let access_cap = opts.into(); - if !perm_to_cap(attr.perm()).contains(access_cap) { - return Err(AxError::PermissionDenied); - } - - node.open()?; - if opts.truncate { - node.truncate(0)?; - } - Ok(Self { - node: WithCap::new(node, access_cap), - is_append: opts.append, - offset: 0, - }) - } - - /// Opens a file at the path relative to the current directory. Returns a - /// [`File`] object. - pub fn open(path: &str, opts: &OpenOptions) -> AxResult { - Self::_open_at(None, path, opts) - } - - /// Truncates the file to the specified size. - pub fn truncate(&self, size: u64) -> AxResult { - self.access_node(Cap::WRITE)?.truncate(size)?; - Ok(()) - } - - /// Reads the file at the current position. Returns the number of bytes - /// read. - /// - /// After the read, the cursor will be advanced by the number of bytes read. - pub fn read(&mut self, buf: &mut [u8]) -> AxResult { - let node = self.access_node(Cap::READ)?; - let read_len = node.read_at(self.offset, buf)?; - self.offset += read_len as u64; - Ok(read_len) - } - - /// Reads the file at the given position. Returns the number of bytes read. - /// - /// It does not update the file cursor. - pub fn read_at(&self, offset: u64, buf: &mut [u8]) -> AxResult { - let node = self.access_node(Cap::READ)?; - let read_len = node.read_at(offset, buf)?; - Ok(read_len) - } - - /// Writes the file at the current position. Returns the number of bytes - /// written. - /// - /// After the write, the cursor will be advanced by the number of bytes - /// written. - pub fn write(&mut self, buf: &[u8]) -> AxResult { - let offset = if self.is_append { - self.get_attr()?.size() - } else { - self.offset - }; - let node = self.access_node(Cap::WRITE)?; - let write_len = node.write_at(offset, buf)?; - self.offset = offset + write_len as u64; - Ok(write_len) - } - - /// Writes the file at the given position. Returns the number of bytes - /// written. - /// - /// It does not update the file cursor. - pub fn write_at(&self, offset: u64, buf: &[u8]) -> AxResult { - let node = self.access_node(Cap::WRITE)?; - let write_len = node.write_at(offset, buf)?; - Ok(write_len) - } - - /// Flushes the file, writes all buffered data to the underlying device. - pub fn flush(&self) -> AxResult { - self.access_node(Cap::WRITE)?.fsync()?; - Ok(()) - } - - /// Sets the cursor of the file to the specified offset. Returns the new - /// position after the seek. - pub fn seek(&mut self, pos: SeekFrom) -> AxResult { - let size = self.get_attr()?.size(); - let new_offset = match pos { - SeekFrom::Start(pos) => Some(pos), - SeekFrom::Current(off) => self.offset.checked_add_signed(off), - SeekFrom::End(off) => size.checked_add_signed(off), - } - .ok_or_else(|| ax_err_type!(InvalidInput))?; - self.offset = new_offset; - Ok(new_offset) - } - - /// Gets the file attributes. - pub fn get_attr(&self) -> AxResult { - self.access_node(Cap::empty())?.get_attr() - } -} - -impl Directory { - fn access_node(&self, cap: Cap) -> AxResult<&VfsNodeRef> { - self.node.access_or_err(cap, AxError::PermissionDenied) - } - - fn _open_dir_at(dir: Option<&VfsNodeRef>, path: &str, opts: &OpenOptions) -> AxResult { - debug!("open dir: {}", path); - if !opts.read { - return Err(AxError::InvalidInput); - } - if opts.create || opts.create_new || opts.write || opts.append || opts.truncate { - return Err(AxError::InvalidInput); - } - - let node = crate::root::lookup(dir, path)?; - let attr = node.get_attr()?; - if !attr.is_dir() { - return Err(AxError::NotADirectory); - } - let access_cap = opts.into(); - if !perm_to_cap(attr.perm()).contains(access_cap) { - return Err(AxError::PermissionDenied); - } - - node.open()?; - Ok(Self { - node: WithCap::new(node, access_cap), - entry_idx: 0, - }) - } - - fn access_at(&self, path: &str) -> AxResult> { - if path.starts_with('/') { - Ok(None) - } else { - Ok(Some(self.access_node(Cap::EXECUTE)?)) - } - } - - /// Opens a directory at the path relative to the current directory. - /// Returns a [`Directory`] object. - pub fn open_dir(path: &str, opts: &OpenOptions) -> AxResult { - Self::_open_dir_at(None, path, opts) - } - - /// Opens a directory at the path relative to this directory. Returns a - /// [`Directory`] object. - pub fn open_dir_at(&self, path: &str, opts: &OpenOptions) -> AxResult { - Self::_open_dir_at(self.access_at(path)?, path, opts) - } - - /// Opens a file at the path relative to this directory. Returns a [`File`] - /// object. - pub fn open_file_at(&self, path: &str, opts: &OpenOptions) -> AxResult { - File::_open_at(self.access_at(path)?, path, opts) - } - - /// Creates an empty file at the path relative to this directory. - pub fn create_file(&self, path: &str) -> AxResult { - crate::root::create_file(self.access_at(path)?, path) - } - - /// Creates an empty directory at the path relative to this directory. - pub fn create_dir(&self, path: &str) -> AxResult { - crate::root::create_dir(self.access_at(path)?, path) - } - - /// Removes a file at the path relative to this directory. - pub fn remove_file(&self, path: &str) -> AxResult { - crate::root::remove_file(self.access_at(path)?, path) - } - - /// Removes a directory at the path relative to this directory. - pub fn remove_dir(&self, path: &str) -> AxResult { - crate::root::remove_dir(self.access_at(path)?, path) - } - - /// Reads directory entries starts from the current position into the - /// given buffer. Returns the number of entries read. - /// - /// After the read, the cursor will be advanced by the number of entries - /// read. - pub fn read_dir(&mut self, dirents: &mut [DirEntry]) -> AxResult { - let n = self - .access_node(Cap::READ)? - .read_dir(self.entry_idx, dirents)?; - self.entry_idx += n; - Ok(n) - } - - /// Rename a file or directory to a new name. - /// Delete the original file if `old` already exists. - /// - /// This only works then the new path is in the same mounted fs. - pub fn rename(&self, old: &str, new: &str) -> AxResult { - crate::root::rename(old, new) - } -} - -impl Drop for File { - fn drop(&mut self) { - unsafe { self.node.access_unchecked().release().ok() }; - } -} - -impl Drop for Directory { - fn drop(&mut self) { - unsafe { self.node.access_unchecked().release().ok() }; - } -} - -impl fmt::Debug for OpenOptions { - #[allow(unused_assignments)] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut written = false; - macro_rules! fmt_opt { - ($field:ident, $label:literal) => { - if self.$field { - if written { - write!(f, " | ")?; - } - write!(f, $label)?; - written = true; - } - }; - } - fmt_opt!(read, "READ"); - fmt_opt!(write, "WRITE"); - fmt_opt!(append, "APPEND"); - fmt_opt!(truncate, "TRUNC"); - fmt_opt!(create, "CREATE"); - fmt_opt!(create_new, "CREATE_NEW"); - Ok(()) - } -} - -impl From<&OpenOptions> for Cap { - fn from(opts: &OpenOptions) -> Cap { - let mut cap = Cap::empty(); - if opts.read { - cap |= Cap::READ; - } - if opts.write | opts.append { - cap |= Cap::WRITE; - } - cap - } -} - -fn perm_to_cap(perm: FilePerm) -> Cap { - let mut cap = Cap::empty(); - if perm.owner_readable() { - cap |= Cap::READ; - } - if perm.owner_writable() { - cap |= Cap::WRITE; - } - if perm.owner_executable() { - cap |= Cap::EXECUTE; - } - cap -} diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/fs.rs b/os/arceos/modules/axfs/src/fs/ext4/lwext4/fs.rs similarity index 99% rename from os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/fs.rs rename to os/arceos/modules/axfs/src/fs/ext4/lwext4/fs.rs index f9318efaf6..addf41e088 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/fs.rs +++ b/os/arceos/modules/axfs/src/fs/ext4/lwext4/fs.rs @@ -1,10 +1,10 @@ use alloc::{boxed::Box, sync::Arc}; use core::cell::OnceCell; -use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DirEntry, DirNode, Filesystem, FilesystemOps, Reference, StatFs, VfsResult, path::MAX_NAME_LEN, }; +use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; use lwext4_rust::{FsConfig, ffi::EXT4_ROOT_INO}; use super::{ diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/inode.rs b/os/arceos/modules/axfs/src/fs/ext4/lwext4/inode.rs similarity index 99% rename from os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/inode.rs rename to os/arceos/modules/axfs/src/fs/ext4/lwext4/inode.rs index 68b24423a2..481269fca5 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/inode.rs +++ b/os/arceos/modules/axfs/src/fs/ext4/lwext4/inode.rs @@ -1,7 +1,7 @@ use alloc::{borrow::ToOwned, string::String, sync::Arc}; use core::{any::Any, task::Context}; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DeviceId, DirEntry, DirEntrySink, DirNode, DirNodeOps, FileNode, FileNodeOps, FilesystemOps, Metadata, MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, Reference, VfsError, VfsResult, WeakDirEntry, diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/mod.rs b/os/arceos/modules/axfs/src/fs/ext4/lwext4/mod.rs similarity index 100% rename from os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/mod.rs rename to os/arceos/modules/axfs/src/fs/ext4/lwext4/mod.rs index bf4c048456..2bfe4443bb 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/mod.rs +++ b/os/arceos/modules/axfs/src/fs/ext4/lwext4/mod.rs @@ -1,9 +1,9 @@ -use alloc::boxed::Box; - mod fs; mod inode; mod util; +use alloc::boxed::Box; + pub use fs::*; pub use inode::*; use lwext4_rust::{BlockDevice, Ext4Error, Ext4Result, ffi::EIO}; diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/util.rs b/os/arceos/modules/axfs/src/fs/ext4/lwext4/util.rs similarity index 96% rename from os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/util.rs rename to os/arceos/modules/axfs/src/fs/ext4/lwext4/util.rs index c516ed82ad..c0d40690eb 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/lwext4/util.rs +++ b/os/arceos/modules/axfs/src/fs/ext4/lwext4/util.rs @@ -1,5 +1,5 @@ use ax_errno::LinuxError; -use axfs_ng_vfs::{NodeType, VfsError}; +use ax_fs_vfs::{NodeType, VfsError}; use lwext4_rust::{Ext4Error, InodeType, SystemHal}; use super::Ext4Disk; diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/mod.rs b/os/arceos/modules/axfs/src/fs/ext4/mod.rs similarity index 100% rename from os/arceos/modules/axfs-ng/src/fs/ext4/mod.rs rename to os/arceos/modules/axfs/src/fs/ext4/mod.rs diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/fs.rs b/os/arceos/modules/axfs/src/fs/ext4/rsext4/fs.rs similarity index 71% rename from os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/fs.rs rename to os/arceos/modules/axfs/src/fs/ext4/rsext4/fs.rs index 2639600503..e97162695e 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/fs.rs +++ b/os/arceos/modules/axfs/src/fs/ext4/rsext4/fs.rs @@ -1,10 +1,10 @@ use alloc::{boxed::Box, sync::Arc}; use core::cell::OnceCell; -use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DirEntry, DirNode, Filesystem, FilesystemOps, Reference, StatFs, VfsResult, path::MAX_NAME_LEN, }; +use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; use rsext4::{Jbd2Dev, bmalloc::InodeNumber, superblock::Ext4Superblock}; use super::{Ext4Disk, Inode, util::into_vfs_err}; @@ -15,6 +15,7 @@ const EXT4_ROOT_INO: u32 = 2; pub(crate) struct Ext4State { pub fs: rsext4::Ext4FileSystem, pub dev: Jbd2Dev, + dirty: bool, } impl Ext4State { @@ -32,19 +33,15 @@ pub struct Ext4Filesystem { impl Ext4Filesystem { pub fn new(dev: Box, region: BlockRegion) -> VfsResult { - Self::new_from_boxed(dev, region) - } - - /// Create from a dynamic (boxed) block device (e.g. loop device). - pub fn new_from_boxed( - dev: Box, - region: BlockRegion, - ) -> VfsResult { let mut dev = Jbd2Dev::initial_jbd2dev(0, Ext4Disk::new(dev, region), true); let fs = rsext4::mount(&mut dev).map_err(into_vfs_err)?; let fs = Arc::new(Self { - inner: Mutex::new(Ext4State { fs, dev }), + inner: Mutex::new(Ext4State { + fs, + dev, + dirty: true, + }), root_dir: OnceCell::new(), }); let _ = fs.root_dir.set(DirEntry::new_dir( @@ -73,21 +70,39 @@ impl Ext4Filesystem { self.inner.lock() } - pub(crate) fn sync_to_disk(&self) -> VfsResult<()> { + pub(crate) fn mutate( + &self, + f: impl FnOnce(&mut rsext4::Ext4FileSystem, &mut Jbd2Dev) -> VfsResult, + ) -> VfsResult { let mut state = self.inner.lock(); + state.dirty = true; let (fs, dev) = state.split(); - fs.datablock_cache.flush_all(dev).map_err(into_vfs_err)?; - fs.bitmap_cache.flush_all(dev).map_err(into_vfs_err)?; - fs.inodetable_cahce.flush_all(dev).map_err(into_vfs_err)?; - // Mark the filesystem clean before writing the superblock so the - // on-disk state reflects a clean sync / unmount. - fs.superblock.s_state = Ext4Superblock::EXT4_VALID_FS; - fs.sync_superblock(dev).map_err(into_vfs_err)?; - fs.sync_group_descriptors(dev).map_err(into_vfs_err)?; - if dev.is_use_journal() { - dev.umount_commit(); + f(fs, dev) + } + + pub(crate) fn sync_to_disk(&self) -> VfsResult<()> { + let mut state = self.inner.lock(); + if state.dirty { + { + let (fs, dev) = state.split(); + fs.datablock_cache.flush_all(dev).map_err(into_vfs_err)?; + fs.bitmap_cache.flush_all(dev).map_err(into_vfs_err)?; + fs.inodetable_cahce.flush_all(dev).map_err(into_vfs_err)?; + // Mark the filesystem clean before writing the superblock so the + // on-disk state reflects a clean sync / unmount. + fs.superblock.s_state = Ext4Superblock::EXT4_VALID_FS; + fs.sync_superblock(dev).map_err(into_vfs_err)?; + fs.sync_group_descriptors(dev).map_err(into_vfs_err)?; + if dev.is_use_journal() { + dev.umount_commit(); + } + dev.cantflush().map_err(into_vfs_err)?; + } + state.dirty = false; + Ok(()) + } else { + state.dev.cantflush().map_err(into_vfs_err) } - dev.cantflush().map_err(into_vfs_err) } } diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/inode.rs b/os/arceos/modules/axfs/src/fs/ext4/rsext4/inode.rs similarity index 94% rename from os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/inode.rs rename to os/arceos/modules/axfs/src/fs/ext4/rsext4/inode.rs index 0ab1565a79..895ee95d2c 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/inode.rs +++ b/os/arceos/modules/axfs/src/fs/ext4/rsext4/inode.rs @@ -6,7 +6,7 @@ use alloc::{ }; use core::any::Any; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DeviceId, DirEntry, DirEntrySink, DirNode, DirNodeOps, FileNode, FileNodeOps, FilesystemOps, Metadata, MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, Reference, VfsError, VfsResult, WeakDirEntry, @@ -131,9 +131,7 @@ impl NodeOps for Inode { } fn update_metadata(&self, update: MetadataUpdate) -> VfsResult<()> { - { - let mut state = self.fs.lock(); - let (fs, dev) = state.split(); + self.fs.mutate(|fs, dev| { fs.modify_inode(dev, self.ino, |inode| { if let Some(mode) = update.mode { inode.i_mode = @@ -163,9 +161,8 @@ impl NodeOps for Inode { inode.i_ctime = ax_hal::time::wall_time().as_secs() as u32; } }) - .map_err(into_vfs_err)?; - } - self.fs.sync_to_disk() + .map_err(into_vfs_err) + }) } fn len(&self) -> VfsResult { @@ -274,44 +271,35 @@ impl FileNodeOps for Inode { } fn write_at(&self, buf: &[u8], offset: u64) -> VfsResult { - { - let mut state = self.fs.lock(); - let (fs, dev) = state.split(); + self.fs.mutate(|fs, dev| { // Use inode-number-based write to avoid path re-resolution. // Path-based write_file() fails with NotFound after rename/unlink, // which causes dirty page loss when jcode atomically replaces files. - rsext4::write_inode_data(dev, fs, self.ino, offset, buf).map_err(into_vfs_err)?; - } - self.fs.sync_to_disk()?; + rsext4::write_inode_data(dev, fs, self.ino, offset, buf).map_err(into_vfs_err) + })?; Ok(buf.len()) } fn append(&self, buf: &[u8]) -> VfsResult<(usize, u64)> { - let length = { - let mut state = self.fs.lock(); - let (fs, dev) = state.split(); + let length = self.fs.mutate(|fs, dev| { let inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?; let length = inode.size(); rsext4::write_inode_data(dev, fs, self.ino, length, buf).map_err(into_vfs_err)?; - length - }; - self.fs.sync_to_disk()?; + Ok(length) + })?; Ok((buf.len(), length + buf.len() as u64)) } fn set_len(&self, len: u64) -> VfsResult<()> { - { - let mut state = self.fs.lock(); - let (fs, dev) = state.split(); + self.fs.mutate(|fs, dev| { rsext4::truncate( dev, fs, &self.path.clone().ok_or(VfsError::InvalidInput)?, len, ) - .map_err(into_vfs_err)?; - } - self.fs.sync_to_disk() + .map_err(into_vfs_err) + }) } fn set_symlink(&self, target: &str) -> VfsResult<()> { @@ -319,9 +307,7 @@ impl FileNodeOps for Inode { return Err(VfsError::InvalidInput); }; - { - let mut state = self.fs.lock(); - let (fs, dev) = state.split(); + self.fs.mutate(|fs, dev| { let mut inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?; if !inode.is_symlink() { @@ -398,10 +384,8 @@ impl FileNodeOps for Inode { fs.modify_inode(dev, self.ino, |on_disk| { *on_disk = inode; }) - .map_err(into_vfs_err)?; - } - - self.fs.sync_to_disk() + .map_err(into_vfs_err) + }) } } @@ -483,9 +467,7 @@ impl DirNodeOps for Inode { return Err(VfsError::InvalidInput); }; let path = join_child_path(&dir_path, name); - let ino = { - let mut state = self.fs.lock(); - let (fs, dev) = state.split(); + let ino = self.fs.mutate(|fs, dev| { if rsext4::dir::get_inode_with_num(fs, dev, &path) .map_err(into_vfs_err)? .is_some() @@ -510,10 +492,8 @@ impl DirNodeOps for Inode { }) .map_err(into_vfs_err)?; Self::update_ctime_with(fs, dev, ino)?; - ino - }; - - self.fs.sync_to_disk()?; + Ok(ino) + })?; let reference = Reference::new( self.this.as_ref().and_then(WeakDirEntry::upgrade), @@ -537,10 +517,7 @@ impl DirNodeOps for Inode { let dir_path = self.dir_path()?; let link_path = join_child_path(&dir_path, name); let target_path = node.absolute_path()?.to_string(); - { - let mut state = self.fs.lock(); - let (fs, dev) = state.split(); - + self.fs.mutate(|fs, dev| { if rsext4::dir::get_inode_with_num(fs, dev, &target_path) .map_err(into_vfs_err)? .is_none() @@ -556,18 +533,15 @@ impl DirNodeOps for Inode { rsext4::link(fs, dev, &link_path, &target_path).map_err(into_vfs_err)?; let target_ino = InodeNumber::new(node.inode() as u32).map_err(into_vfs_err)?; - Self::update_ctime_with(fs, dev, target_ino)?; - } - self.fs.sync_to_disk()?; + Self::update_ctime_with(fs, dev, target_ino) + })?; self.lookup_locked(name) } fn unlink(&self, name: &str) -> VfsResult<()> { let dir_path = self.dir_path()?; let path = join_child_path(&dir_path, name); - { - let mut state = self.fs.lock(); - let (fs, dev) = state.split(); + self.fs.mutate(|fs, dev| { let inode_info = rsext4::dir::get_inode_with_num(fs, dev, &path).map_err(into_vfs_err)?; if inode_info.is_none() { @@ -583,20 +557,18 @@ impl DirNodeOps for Inode { } else { rsext4::unlink(fs, dev, &path).map_err(into_vfs_err)?; } - } - self.fs.sync_to_disk() + Ok(()) + }) } fn rename(&self, src_name: &str, dst_dir: &DirNode, dst_name: &str) -> VfsResult<()> { let dst_dir: Arc = dst_dir.downcast().map_err(|_| VfsError::InvalidInput)?; let src_path = join_child_path(&self.dir_path()?, src_name); let dst_path = join_child_path(&dst_dir.dir_path()?, dst_name); - { - let mut state = self.fs.lock(); - let (fs, dev) = state.split(); + self.fs.mutate(|fs, dev| { rsext4::rename(dev, fs, &src_path, &dst_path).map_err(into_vfs_err)?; - } - self.fs.sync_to_disk() + Ok(()) + }) } } diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/mod.rs b/os/arceos/modules/axfs/src/fs/ext4/rsext4/mod.rs similarity index 100% rename from os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/mod.rs rename to os/arceos/modules/axfs/src/fs/ext4/rsext4/mod.rs diff --git a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/util.rs b/os/arceos/modules/axfs/src/fs/ext4/rsext4/util.rs similarity index 98% rename from os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/util.rs rename to os/arceos/modules/axfs/src/fs/ext4/rsext4/util.rs index 0a7c858877..457452e8bf 100644 --- a/os/arceos/modules/axfs-ng/src/fs/ext4/rsext4/util.rs +++ b/os/arceos/modules/axfs/src/fs/ext4/rsext4/util.rs @@ -1,4 +1,4 @@ -use axfs_ng_vfs::{NodeType, VfsError}; +use ax_fs_vfs::{NodeType, VfsError}; use rsext4::{Ext4Error, entries::Ext4DirEntry2}; pub fn into_vfs_err(err: Ext4Error) -> VfsError { diff --git a/os/arceos/modules/axfs/src/fs/ext4fs.rs b/os/arceos/modules/axfs/src/fs/ext4fs.rs deleted file mode 100644 index f0ea2117b3..0000000000 --- a/os/arceos/modules/axfs/src/fs/ext4fs.rs +++ /dev/null @@ -1,652 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use alloc::{ - collections::BTreeMap, - format, - string::{String, ToString}, - sync::Arc, - vec::Vec, -}; - -use ax_fs_vfs::{ - VfsDirEntry, VfsError, VfsNodeAttr, VfsNodeOps, VfsNodePerm, VfsNodeRef, VfsNodeType, VfsOps, - VfsResult, -}; -use ax_kspin::SpinNoIrq as Mutex; -use rsext4::{ - Ext4Error, Ext4FileSystem as Rsext4FileSystem, Ext4Result, Ext4Timestamp, Jbd2Dev, - api::{OpenFile, fs_mount, lseek, open, read_at}, - dir::{get_inode_with_num, mkdir}, - entries::classic_dir::list_entries, - file::{delete_dir, is_dir_empty, mkfile, mv, truncate, unlink, write_file}, - loopfile::resolve_inode_block_allextend, -}; - -use crate::dev::{Disk, Partition}; - -/// Block size for ext4 filesystem operations -pub const BLOCK_SIZE: usize = 4096; - -/// Ext4 filesystem implementation that works with a disk device -#[allow(dead_code)] -pub struct Ext4FileSystem { - inner: Arc>>, - fs: Arc>, -} - -/// Ext4FileSystem that works with a partition -pub struct Ext4FileSystemPartition { - inner: Arc>>, - fs: Arc>, -} - -unsafe impl Sync for Ext4FileSystem {} -unsafe impl Send for Ext4FileSystem {} - -unsafe impl Sync for Ext4FileSystemPartition {} -unsafe impl Send for Ext4FileSystemPartition {} - -impl Ext4FileSystem { - /// Create a new ext4 filesystem from a disk device - #[allow(dead_code)] - pub fn new(disk: Disk) -> Self { - info!( - "Got Disk size:{}, position:{}", - disk.size(), - disk.position() - ); - let mut inner = Jbd2Dev::initial_jbd2dev(0, disk, false); - let fs = fs_mount(&mut inner).expect("failed to initialize EXT4 filesystem"); - Self { - inner: Arc::new(Mutex::new(inner)), - fs: Arc::new(Mutex::new(fs)), - } - } - - /// Create a new ext4 filesystem from a partition - pub fn from_partition(partition: Partition) -> Ext4FileSystemPartition { - info!( - "Got Partition size:{}, position:{}", - partition.size(), - partition.position() - ); - let mut inner = Jbd2Dev::initial_jbd2dev(0, partition, false); - let fs = fs_mount(&mut inner).expect("failed to initialize EXT4 filesystem on partition"); - Ext4FileSystemPartition { - inner: Arc::new(Mutex::new(inner)), - fs: Arc::new(Mutex::new(fs)), - } - } -} - -/// The [`VfsOps`] trait provides operations on a filesystem. -impl VfsOps for Ext4FileSystem { - fn root_dir(&self) -> VfsNodeRef { - debug!("Get root_dir"); - Arc::new(FileWrapper::new( - "/", - Ext4Inner::Disk(Arc::clone(&self.inner)), - Arc::clone(&self.fs), - )) - } -} - -/// The [`VfsOps`] trait provides operations on a filesystem. -impl VfsOps for Ext4FileSystemPartition { - fn root_dir(&self) -> VfsNodeRef { - debug!("Get root_dir"); - Arc::new(FileWrapper::new( - "/", - Ext4Inner::Partition(Arc::clone(&self.inner)), - Arc::clone(&self.fs), - )) - } -} - -/// Inner state for ext4 filesystem, either backed by a full disk or a partition -#[derive(Clone)] -pub enum Ext4Inner { - /// Full disk device - Disk(Arc>>), - /// Partition device - Partition(Arc>>), -} - -/// Wrapper for files and directories in the ext4 filesystem -pub struct FileWrapper { - path: String, - file: Mutex>, - inner: Ext4Inner, - fs: Arc>, -} - -unsafe impl Send for FileWrapper {} -unsafe impl Sync for FileWrapper {} - -impl FileWrapper { - fn new(path: &str, inner: Ext4Inner, fs: Arc>) -> Self { - debug!("FileWrapper new {}", path); - Self { - path: path.to_string(), - file: Mutex::new(None), - inner, - fs, - } - } - - fn path_deal_with(&self, path: &str) -> String { - if path.starts_with('/') { - debug!("path_deal_with: {}", path); - } - let trim_path = path.trim_matches('/'); - if trim_path.is_empty() || trim_path == "." { - return self.path.to_string(); - } - - if let Some(rest) = trim_path.strip_prefix("./") { - // if starts with "./" - return self.path_deal_with(rest); - } - let rest_p = trim_path.replace("//", "/"); - if trim_path != rest_p { - return self.path_deal_with(&rest_p); - } - - let base_path = self.path.trim_end_matches('/'); - if base_path == "/" { - format!("/{}", trim_path) - } else { - format!("{}/{}", base_path, trim_path) - } - } -} - -/// The [`VfsNodeOps`] trait provides operations on a file or a directory. -impl VfsNodeOps for FileWrapper { - fn get_attr(&self) -> VfsResult { - let mut fs = self.fs.lock(); - let perm = VfsNodePerm::from_bits_truncate(0o755); - let (_inode_num, inode) = match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - get_inode_with_num(&mut fs, &mut inner, &self.path) - .map_err(|_| VfsError::Io)? - .ok_or(VfsError::NotFound)? - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - get_inode_with_num(&mut fs, &mut inner, &self.path) - .map_err(|_| VfsError::Io)? - .ok_or(VfsError::NotFound)? - } - }; - let vtype = if inode.is_dir() { - VfsNodeType::Dir - } else { - VfsNodeType::File - }; - let size = inode.size(); - let blocks = inode.blocks_count(); - - trace!( - "get_attr of {:?}, size: {}, blocks: {}", - self.path, size, blocks - ); - - Ok(VfsNodeAttr::new(perm, vtype, size, blocks)) - } - - fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { - debug!("create {:?} on Ext4fs: {}", ty, path); - let fpath = self.path_deal_with(path); - if fpath.is_empty() { - return Ok(()); - } - - let mut fs = self.fs.lock(); - match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - match ty { - VfsNodeType::Dir => { - let _ = mkdir(&mut inner, &mut fs, &fpath); - } - _ => { - let _ = mkfile(&mut inner, &mut fs, &fpath, None, None); - } - } - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - match ty { - VfsNodeType::Dir => { - let _ = mkdir(&mut inner, &mut fs, &fpath); - } - _ => { - let _ = mkfile(&mut inner, &mut fs, &fpath, None, None); - } - } - } - } - Ok(()) - } - - fn remove(&self, path: &str) -> VfsResult { - debug!("remove ext4fs: {}", path); - let fpath = self.path_deal_with(path); - assert!(!fpath.is_empty()); // already check at `root.rs` - - let mut fs = self.fs.lock(); - let (_inode_num, inode) = match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - get_inode_with_num(&mut fs, &mut inner, &fpath) - .map_err(|_| VfsError::Io)? - .ok_or(VfsError::NotFound)? - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - get_inode_with_num(&mut fs, &mut inner, &fpath) - .map_err(|_| VfsError::Io)? - .ok_or(VfsError::NotFound)? - } - }; - - match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - if inode.is_dir() { - let mut dir_inode = inode; - if !is_dir_empty(&mut fs, &mut inner, &mut dir_inode) - .map_err(|_| VfsError::Io)? - { - return Err(VfsError::DirectoryNotEmpty); - } - delete_dir(&mut fs, &mut inner, &fpath).map_err(|_| VfsError::Io)?; - } else { - unlink(&mut fs, &mut inner, &fpath).map_err(|_| VfsError::Io)?; - } - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - if inode.is_dir() { - let mut dir_inode = inode; - if !is_dir_empty(&mut fs, &mut inner, &mut dir_inode) - .map_err(|_| VfsError::Io)? - { - return Err(VfsError::DirectoryNotEmpty); - } - delete_dir(&mut fs, &mut inner, &fpath).map_err(|_| VfsError::Io)?; - } else { - unlink(&mut fs, &mut inner, &fpath).map_err(|_| VfsError::Io)?; - } - } - } - Ok(()) - } - - /// Get the parent directory of this directory. - /// Return `None` if the node is a file. - fn parent(&self) -> Option { - let path = &self.path; - debug!("Get the parent dir of {}", path); - let path = path.trim_end_matches('/').trim_end_matches(|c| c != '/'); - if !path.is_empty() { - return Some(Arc::new(Self::new( - path, - self.inner.clone(), - Arc::clone(&self.fs), - ))); - } - None - } - - /// Read directory entries into `dirents`, starting from `start_idx`. - fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { - let mut fs = self.fs.lock(); - let (_inode_num, mut inode) = match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - get_inode_with_num(&mut fs, &mut inner, &self.path) - .map_err(|_| VfsError::Io)? - .ok_or(VfsError::NotFound)? - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - get_inode_with_num(&mut fs, &mut inner, &self.path) - .map_err(|_| VfsError::Io)? - .ok_or(VfsError::NotFound)? - } - }; - - if !inode.is_dir() { - return Err(VfsError::Unsupported); - } - - let blocks = match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - resolve_inode_block_allextend(&mut fs, &mut inner, &mut inode) - .map_err(|_| VfsError::Io)? - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - resolve_inode_block_allextend(&mut fs, &mut inner, &mut inode) - .map_err(|_| VfsError::Io)? - } - }; - - let mut data = Vec::new(); - for (_, phys_block) in blocks { - let cached = match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - fs.datablock_cache - .get_or_load(&mut inner, phys_block) - .map_err(|_| VfsError::Io)? - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - fs.datablock_cache - .get_or_load(&mut inner, phys_block) - .map_err(|_| VfsError::Io)? - } - }; - data.extend_from_slice(&cached.data); - } - - let entries = list_entries(&data); - let mut unique = BTreeMap::new(); - for entry in entries { - if let Some(name) = entry.name_str() - && name != "." - && name != ".." - { - unique.insert(name.to_string(), entry.file_type); - } - } - let unique_vec: Vec<_> = unique.into_iter().collect(); - let mut count = 0; - for (name, file_type) in unique_vec.iter().skip(start_idx) { - if count >= dirents.len() { - break; - } - let ty = match *file_type { - 2 => VfsNodeType::Dir, - _ => VfsNodeType::File, - }; - dirents[count] = VfsDirEntry::new(name, ty); - count += 1; - } - Ok(count) - } - - /// Lookup the node with given `path` in the directory. - /// Return the node if found. - fn lookup(self: Arc, path: &str) -> VfsResult { - trace!("lookup ext4fs: {}, {}", self.path, path); - let fpath = self.path_deal_with(path); - if fpath.is_empty() { - return Ok(self.clone()); - } - - let mut fs = self.fs.lock(); - let exists = match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - get_inode_with_num(&mut fs, &mut inner, &fpath) - .map_err(|_| VfsError::Io)? - .is_some() - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - get_inode_with_num(&mut fs, &mut inner, &fpath) - .map_err(|_| VfsError::Io)? - .is_some() - } - }; - - if exists { - Ok(Arc::new(Self::new( - &fpath, - self.inner.clone(), - Arc::clone(&self.fs), - ))) - } else { - Err(VfsError::NotFound) - } - } - - fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { - let mut file_guard = self.file.lock(); - if file_guard.is_none() { - let mut fs = self.fs.lock(); - *file_guard = match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - open(&mut inner, &mut fs, &self.path, false).ok() - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - open(&mut inner, &mut fs, &self.path, false).ok() - } - }; - } - - if let Some(ref mut file) = *file_guard { - let mut fs = self.fs.lock(); - let _ = lseek(file, offset); - let data = match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - read_at(&mut inner, &mut fs, file, buf.len()).map_err(|_| VfsError::Io)? - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - read_at(&mut inner, &mut fs, file, buf.len()).map_err(|_| VfsError::Io)? - } - }; - let len = data.len().min(buf.len()); - buf[..len].copy_from_slice(&data[..len]); - Ok(len) - } else { - Err(VfsError::NotFound) - } - } - - fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { - let mut fs = self.fs.lock(); - match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - write_file(&mut inner, &mut fs, &self.path, offset, buf) - .map_err(|_| VfsError::Io)?; - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - write_file(&mut inner, &mut fs, &self.path, offset, buf) - .map_err(|_| VfsError::Io)?; - } - }; - Ok(buf.len()) - } - - fn truncate(&self, size: u64) -> VfsResult { - let mut fs = self.fs.lock(); - match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - let _ = truncate(&mut inner, &mut fs, &self.path, size); - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - let _ = truncate(&mut inner, &mut fs, &self.path, size); - } - } - Ok(()) - } - - fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult { - debug!("rename from {} to {}", src_path, dst_path); - - let src_fpath = self.path_deal_with(src_path); - let dst_fpath = self.path_deal_with(dst_path); - - let mut fs = self.fs.lock(); - match self.inner { - Ext4Inner::Disk(ref inner) => { - let mut inner = inner.lock(); - let _ = mv(&mut fs, &mut inner, &src_fpath, &dst_fpath); - } - Ext4Inner::Partition(ref inner) => { - let mut inner = inner.lock(); - let _ = mv(&mut fs, &mut inner, &src_fpath, &dst_fpath); - } - } - Ok(()) - } - - fn as_any(&self) -> &dyn core::any::Any { - self as &dyn core::any::Any - } -} - -impl Drop for FileWrapper { - fn drop(&mut self) { - debug!("Drop struct FileWrapper {:?}", self.path); - // File will be automatically closed when OpenFile is dropped - } -} - -impl rsext4::BlockDevice for Disk { - fn write( - &mut self, - buffer: &[u8], - block_id: rsext4::bmalloc::AbsoluteBN, - count: u32, - ) -> Ext4Result<()> { - // RVlwext4 uses 4096 byte blocks, but Disk uses 512 byte blocks - self.set_position(block_id.raw() * BLOCK_SIZE as u64); - let mut total_written = 0; - let to_write = count as usize * BLOCK_SIZE; - - while total_written < to_write { - let remaining = &buffer[total_written..]; - let written = self.write_one(remaining).map_err(|_| Ext4Error::io())?; - total_written += written; - } - - Ok(()) - } - - fn read( - &mut self, - buffer: &mut [u8], - block_id: rsext4::bmalloc::AbsoluteBN, - count: u32, - ) -> Ext4Result<()> { - self.set_position(block_id.raw() * BLOCK_SIZE as u64); - let mut total_read = 0; - let to_read = count as usize * BLOCK_SIZE; - - while total_read < to_read { - let remaining = &mut buffer[total_read..]; - let read = self.read_one(remaining).map_err(|_| Ext4Error::io())?; - total_read += read; - } - - Ok(()) - } - - fn open(&mut self) -> Ext4Result<()> { - Ok(()) - } - - fn close(&mut self) -> Ext4Result<()> { - Ok(()) - } - - fn total_blocks(&self) -> u64 { - // RVlwext4 uses 4096 byte blocks - self.size() / BLOCK_SIZE as u64 - } - - fn current_time(&self) -> Ext4Result { - let now = ax_hal::time::wall_time(); - let sec = - i64::try_from(now.as_secs()).map_err(|_| Ext4Error::from(rsext4::Errno::EOVERFLOW))?; - Ok(Ext4Timestamp::new(sec, now.subsec_nanos())) - } -} - -impl rsext4::BlockDevice for Partition { - fn write( - &mut self, - buffer: &[u8], - block_id: rsext4::bmalloc::AbsoluteBN, - count: u32, - ) -> Ext4Result<()> { - self.set_position(block_id.raw() * BLOCK_SIZE as u64); - let mut total_written = 0; - let to_write = count as usize * BLOCK_SIZE; - - while total_written < to_write { - let remaining = &buffer[total_written..]; - let written = self.write_one(remaining).map_err(|_| Ext4Error::io())?; - total_written += written; - } - - Ok(()) - } - - fn read( - &mut self, - buffer: &mut [u8], - block_id: rsext4::bmalloc::AbsoluteBN, - count: u32, - ) -> Ext4Result<()> { - self.set_position(block_id.raw() * BLOCK_SIZE as u64); - let mut total_read = 0; - let to_read = count as usize * BLOCK_SIZE; - - while total_read < to_read { - let remaining = &mut buffer[total_read..]; - let read = self.read_one(remaining).map_err(|_| Ext4Error::io())?; - total_read += read; - } - - Ok(()) - } - - fn open(&mut self) -> Ext4Result<()> { - Ok(()) - } - - fn close(&mut self) -> Ext4Result<()> { - Ok(()) - } - - fn total_blocks(&self) -> u64 { - // RVlwext4 uses 4096 byte blocks - self.size() / BLOCK_SIZE as u64 - } - - fn current_time(&self) -> Ext4Result { - let now = ax_hal::time::wall_time(); - let sec = - i64::try_from(now.as_secs()).map_err(|_| Ext4Error::from(rsext4::Errno::EOVERFLOW))?; - Ok(Ext4Timestamp::new(sec, now.subsec_nanos())) - } -} diff --git a/os/arceos/modules/axfs-ng/src/fs/fat/dir.rs b/os/arceos/modules/axfs/src/fs/fat/dir.rs similarity index 99% rename from os/arceos/modules/axfs-ng/src/fs/fat/dir.rs rename to os/arceos/modules/axfs/src/fs/fat/dir.rs index 21adbe1e07..d4d519f9c2 100644 --- a/os/arceos/modules/axfs-ng/src/fs/fat/dir.rs +++ b/os/arceos/modules/axfs/src/fs/fat/dir.rs @@ -1,7 +1,7 @@ use alloc::{string::String, sync::Arc}; use core::{any::Any, mem, ops::Deref, time::Duration}; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DeviceId, DirEntry, DirEntrySink, DirNode, DirNodeOps, FilesystemOps, Metadata, MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, Reference, VfsError, VfsResult, WeakDirEntry, }; diff --git a/os/arceos/modules/axfs-ng/src/fs/fat/disk.rs b/os/arceos/modules/axfs/src/fs/fat/disk.rs similarity index 88% rename from os/arceos/modules/axfs-ng/src/fs/fat/disk.rs rename to os/arceos/modules/axfs/src/fs/fat/disk.rs index 72f093a6f9..ed284e941e 100644 --- a/os/arceos/modules/axfs-ng/src/fs/fat/disk.rs +++ b/os/arceos/modules/axfs/src/fs/fat/disk.rs @@ -1,7 +1,7 @@ use alloc::{boxed::Box, vec}; use core::mem; -use ax_errno::{AxError as FsBlockError, AxResult as FsBlockResult}; +use ax_errno::{AxError as DevError, AxResult as DevResult}; use crate::block::{BlockRegion, FsBlockDevice, RegionBlockDevice}; @@ -68,7 +68,7 @@ impl SeekableDisk { } /// Set the position of the cursor. - pub fn set_position(&mut self, pos: u64) -> FsBlockResult<()> { + pub fn set_position(&mut self, pos: u64) -> DevResult<()> { self.flush()?; self.block_id = pos >> self.block_size_log2; self.offset = pos as usize & (self.block_size() - 1); @@ -76,7 +76,7 @@ impl SeekableDisk { } /// Write all pending changes to the disk. - pub fn flush(&mut self) -> FsBlockResult<()> { + pub fn flush(&mut self) -> DevResult<()> { if self.write_buffer_dirty { self.dev.write_block(self.block_id, &self.write_buffer)?; self.write_buffer_dirty = false; @@ -84,7 +84,7 @@ impl SeekableDisk { Ok(()) } - fn read_partial(&mut self, buf: &mut &mut [u8]) -> FsBlockResult { + fn read_partial(&mut self, buf: &mut &mut [u8]) -> DevResult { self.flush()?; self.dev.read_block(self.block_id, &mut self.read_buffer)?; @@ -102,7 +102,7 @@ impl SeekableDisk { } /// Read from the disk, returns the number of bytes read. - pub fn read(&mut self, mut buf: &mut [u8]) -> FsBlockResult { + pub fn read(&mut self, mut buf: &mut [u8]) -> DevResult { let mut read = 0; if self.offset != 0 { read += self.read_partial(&mut buf)?; @@ -117,7 +117,7 @@ impl SeekableDisk { self.block_id = self .block_id .checked_add(blocks as u64) - .ok_or(FsBlockError::BadState)?; + .ok_or(DevError::BadState)?; } if !buf.is_empty() { read += self.read_partial(&mut buf)?; @@ -126,7 +126,7 @@ impl SeekableDisk { Ok(read) } - fn write_partial(&mut self, buf: &mut &[u8]) -> FsBlockResult { + fn write_partial(&mut self, buf: &mut &[u8]) -> DevResult { if !self.write_buffer_dirty { self.dev.read_block(self.block_id, &mut self.write_buffer)?; self.write_buffer_dirty = true; @@ -147,7 +147,7 @@ impl SeekableDisk { } /// Write to the disk, returns the number of bytes written. - pub fn write(&mut self, mut buf: &[u8]) -> FsBlockResult { + pub fn write(&mut self, mut buf: &[u8]) -> DevResult { let mut written = 0; if self.offset != 0 { written += self.write_partial(&mut buf)?; @@ -162,7 +162,7 @@ impl SeekableDisk { self.block_id = self .block_id .checked_add(blocks as u64) - .ok_or(FsBlockError::BadState)?; + .ok_or(DevError::BadState)?; } if !buf.is_empty() { written += self.write_partial(&mut buf)?; diff --git a/os/arceos/modules/axfs-ng/src/fs/fat/ff.rs b/os/arceos/modules/axfs/src/fs/fat/ff.rs similarity index 100% rename from os/arceos/modules/axfs-ng/src/fs/fat/ff.rs rename to os/arceos/modules/axfs/src/fs/fat/ff.rs diff --git a/os/arceos/modules/axfs-ng/src/fs/fat/file.rs b/os/arceos/modules/axfs/src/fs/fat/file.rs similarity index 99% rename from os/arceos/modules/axfs-ng/src/fs/fat/file.rs rename to os/arceos/modules/axfs/src/fs/fat/file.rs index 5e6381936a..22caa3c690 100644 --- a/os/arceos/modules/axfs-ng/src/fs/fat/file.rs +++ b/os/arceos/modules/axfs/src/fs/fat/file.rs @@ -1,7 +1,7 @@ use alloc::{sync::Arc, vec}; use core::{any::Any, mem, ops::Deref, task::Context}; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ FileNode, FileNodeOps, FilesystemOps, Metadata, MetadataUpdate, NodeFlags, NodeOps, NodeType, VfsError, VfsResult, }; diff --git a/os/arceos/modules/axfs-ng/src/fs/fat/fs.rs b/os/arceos/modules/axfs/src/fs/fat/fs.rs similarity index 99% rename from os/arceos/modules/axfs-ng/src/fs/fat/fs.rs rename to os/arceos/modules/axfs/src/fs/fat/fs.rs index 70f3daa3b2..99ab7eefed 100644 --- a/os/arceos/modules/axfs-ng/src/fs/fat/fs.rs +++ b/os/arceos/modules/axfs/src/fs/fat/fs.rs @@ -1,10 +1,10 @@ use alloc::{boxed::Box, sync::Arc}; use core::marker::PhantomPinned; -use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DirEntry, Filesystem, FilesystemOps, Reference, StatFs, VfsResult, path::MAX_NAME_LEN, }; +use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; use slab::Slab; use super::{dir::FatDirNode, disk::SeekableDisk, ff, util::into_vfs_err}; diff --git a/os/arceos/modules/axfs-ng/src/fs/fat/mod.rs b/os/arceos/modules/axfs/src/fs/fat/mod.rs similarity index 100% rename from os/arceos/modules/axfs-ng/src/fs/fat/mod.rs rename to os/arceos/modules/axfs/src/fs/fat/mod.rs diff --git a/os/arceos/modules/axfs-ng/src/fs/fat/util.rs b/os/arceos/modules/axfs/src/fs/fat/util.rs similarity index 97% rename from os/arceos/modules/axfs-ng/src/fs/fat/util.rs rename to os/arceos/modules/axfs/src/fs/fat/util.rs index add1affe76..8afba28e9c 100644 --- a/os/arceos/modules/axfs-ng/src/fs/fat/util.rs +++ b/os/arceos/modules/axfs/src/fs/fat/util.rs @@ -1,6 +1,6 @@ use core::time::Duration; -use axfs_ng_vfs::{DeviceId, Metadata, MetadataUpdate, NodePermission, NodeType, VfsError}; +use ax_fs_vfs::{DeviceId, Metadata, MetadataUpdate, NodePermission, NodeType, VfsError}; use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Timelike, Utc}; use super::{ff, fs::FatFilesystemInner}; diff --git a/os/arceos/modules/axfs/src/fs/fatfs.rs b/os/arceos/modules/axfs/src/fs/fatfs.rs deleted file mode 100644 index 7294737fee..0000000000 --- a/os/arceos/modules/axfs/src/fs/fatfs.rs +++ /dev/null @@ -1,427 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! FAT filesystem implementation - -use alloc::{boxed::Box, sync::Arc}; -use core::cell::OnceCell; - -use ax_fs_vfs::{ - VfsDirEntry, VfsError, VfsNodeAttr, VfsNodeOps, VfsNodePerm, VfsNodeRef, VfsNodeType, VfsOps, - VfsResult, -}; -use ax_kspin::SpinNoIrq as Mutex; -use axfatfs::{Dir, File, LossyOemCpConverter, NullTimeProvider, Read, Seek, SeekFrom, Write}; - -use crate::dev::{Disk, Partition}; - -const BLOCK_SIZE: usize = 512; - -/// FAT filesystem implementation -pub struct FatFileSystem { - inner: axfatfs::FileSystem, - root_dir: OnceCell, -} - -/// A wrapper for Partition to implement the required traits for axfatfs -pub struct PartitionWrapper { - partition: Partition, -} - -impl PartitionWrapper { - /// Creates a new partition wrapper - pub fn new(partition: Partition) -> Self { - Self { partition } - } -} - -impl axfatfs::IoBase for PartitionWrapper { - type Error = (); -} - -impl axfatfs::Read for PartitionWrapper { - fn read(&mut self, mut buf: &mut [u8]) -> Result { - let mut read_len = 0; - while !buf.is_empty() { - match self.partition.read_one(buf) { - Ok(0) => break, - Ok(n) => { - let tmp = buf; - buf = &mut tmp[n..]; - read_len += n; - } - Err(_) => return Err(()), - } - } - Ok(read_len) - } -} - -impl axfatfs::Write for PartitionWrapper { - fn write(&mut self, mut buf: &[u8]) -> Result { - let mut write_len = 0; - while !buf.is_empty() { - match self.partition.write_one(buf) { - Ok(0) => break, - Ok(n) => { - buf = &buf[n..]; - write_len += n; - } - Err(_) => return Err(()), - } - } - Ok(write_len) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl axfatfs::Seek for PartitionWrapper { - fn seek(&mut self, pos: axfatfs::SeekFrom) -> Result { - let size = self.partition.size(); - let new_pos = match pos { - axfatfs::SeekFrom::Start(pos) => Some(pos), - axfatfs::SeekFrom::Current(off) => self.partition.position().checked_add_signed(off), - axfatfs::SeekFrom::End(off) => size.checked_add_signed(off), - } - .ok_or(())?; - if new_pos > size { - warn!("Seek beyond the end of the partition"); - } - self.partition.set_position(new_pos); - Ok(new_pos) - } -} - -/// Wrapper for FAT file -pub struct FileWrapper<'a>( - Mutex>, -); -/// Wrapper for FAT directory -pub struct DirWrapper<'a>(Dir<'a, PartitionWrapper, NullTimeProvider, LossyOemCpConverter>); - -unsafe impl Sync for FatFileSystem {} -unsafe impl Send for FatFileSystem {} -unsafe impl Send for FileWrapper<'_> {} -unsafe impl Sync for FileWrapper<'_> {} -unsafe impl Send for DirWrapper<'_> {} -unsafe impl Sync for DirWrapper<'_> {} - -impl FatFileSystem { - /// Creates a new FAT filesystem from a disk - #[allow(dead_code)] - pub fn new(disk: Disk) -> Self { - let disk_size = disk.size(); - let wrapper = PartitionWrapper::new(crate::dev::Partition::new(disk, 0, disk_size / 512)); - let inner = axfatfs::FileSystem::new(wrapper, axfatfs::FsOptions::new()) - .expect("failed to initialize FAT filesystem"); - Self { - inner, - root_dir: OnceCell::new(), - } - } - - /// Create a new FAT filesystem from a partition - pub fn from_partition(partition: Partition) -> Self { - let wrapper = PartitionWrapper::new(partition); - let inner = axfatfs::FileSystem::new(wrapper, axfatfs::FsOptions::new()) - .expect("failed to initialize FAT filesystem on partition"); - Self { - inner, - root_dir: OnceCell::new(), - } - } - - /// Initializes the FAT filesystem - #[allow(dead_code)] - pub fn init(&'static self) { - // root_dir is already initialized in new(), so nothing to do here - } - - fn new_file( - file: File<'_, PartitionWrapper, NullTimeProvider, LossyOemCpConverter>, - ) -> VfsNodeRef { - // Use a Box to extend the lifetime of the file - let file_box = Box::new(file); - let file_static = unsafe { - core::mem::transmute::< - Box>, - Box>, - >(file_box) - }; - let file_wrapper = FileWrapper(Mutex::new(*file_static)); - Arc::new(file_wrapper) as VfsNodeRef - } - - fn new_dir( - dir: Dir<'_, PartitionWrapper, NullTimeProvider, LossyOemCpConverter>, - ) -> VfsNodeRef { - // Use a Box to extend the lifetime of the dir - let dir_box = Box::new(dir); - let dir_static = unsafe { - core::mem::transmute::< - Box>, - Box>, - >(dir_box) - }; - let dir_wrapper = DirWrapper(*dir_static); - Arc::new(dir_wrapper) as VfsNodeRef - } -} - -impl VfsNodeOps for FileWrapper<'static> { - ax_fs_vfs::impl_vfs_non_dir_default! {} - - fn get_attr(&self) -> VfsResult { - let size = self.0.lock().seek(SeekFrom::End(0)).map_err(as_vfs_err)?; - let blocks = size.div_ceil(BLOCK_SIZE as u64); - // FAT fs doesn't support permissions, we just set everything to 755 - let perm = VfsNodePerm::from_bits_truncate(0o755); - Ok(VfsNodeAttr::new(perm, VfsNodeType::File, size, blocks)) - } - - fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { - let mut file = self.0.lock(); - file.seek(SeekFrom::Start(offset)).map_err(as_vfs_err)?; // TODO: more efficient - file.read(buf).map_err(as_vfs_err) - } - - fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { - let mut file = self.0.lock(); - file.seek(SeekFrom::Start(offset)).map_err(as_vfs_err)?; // TODO: more efficient - file.write(buf).map_err(as_vfs_err) - } - - fn truncate(&self, size: u64) -> VfsResult { - let mut file = self.0.lock(); - let current_size = file.seek(SeekFrom::End(0)).map_err(as_vfs_err)?; - - if size <= current_size { - // If the target size is smaller than the current size, - // perform a standard truncation operation - file.seek(SeekFrom::Start(size)).map_err(as_vfs_err)?; // TODO: more efficient - file.truncate().map_err(as_vfs_err) - } else { - // Calculate the number of bytes to fill - let mut zeros_needed = size - current_size; - // Create a buffer of zeros - let zeros = [0u8; 4096]; - while zeros_needed > 0 { - let to_write = core::cmp::min(zeros_needed, zeros.len() as u64); - let write_buf = &zeros[..to_write as usize]; - file.write(write_buf).map_err(as_vfs_err)?; - zeros_needed -= to_write; - } - Ok(()) - } - } -} - -impl VfsNodeOps for DirWrapper<'static> { - ax_fs_vfs::impl_vfs_dir_default! {} - - fn get_attr(&self) -> VfsResult { - // FAT fs doesn't support permissions, we just set everything to 755 - Ok(VfsNodeAttr::new( - VfsNodePerm::from_bits_truncate(0o755), - VfsNodeType::Dir, - BLOCK_SIZE as u64, - 1, - )) - } - - fn parent(&self) -> Option { - self.0 - .open_dir("..") - .map_or(None, |dir| Some(FatFileSystem::new_dir(dir))) - } - - fn lookup(self: Arc, path: &str) -> VfsResult { - debug!("lookup at axfatfs: {}", path); - let path = path.trim_matches('/'); - if path.is_empty() || path == "." { - return Ok(self.clone()); - } - if let Some(rest) = path.strip_prefix("./") { - return self.lookup(rest); - } - - // TODO: use `axfatfs::Dir::find_entry`, but it's not public. - if let Ok(file) = self.0.open_file(path) { - Ok(FatFileSystem::new_file(file)) - } else if let Ok(dir) = self.0.open_dir(path) { - Ok(FatFileSystem::new_dir(dir)) - } else { - Err(VfsError::NotFound) - } - } - - fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { - debug!("create {:?} at axfatfs: {}", ty, path); - let path = path.trim_matches('/'); - if path.is_empty() || path == "." { - return Ok(()); - } - if let Some(rest) = path.strip_prefix("./") { - return self.create(rest, ty); - } - - match ty { - VfsNodeType::File => { - self.0.create_file(path).map_err(as_vfs_err)?; - Ok(()) - } - VfsNodeType::Dir => { - self.0.create_dir(path).map_err(as_vfs_err)?; - Ok(()) - } - _ => Err(VfsError::Unsupported), - } - } - - fn remove(&self, path: &str) -> VfsResult { - debug!("remove at axfatfs: {}", path); - let path = path.trim_matches('/'); - assert!(!path.is_empty()); // already check at `root.rs` - if let Some(rest) = path.strip_prefix("./") { - return self.remove(rest); - } - self.0.remove(path).map_err(as_vfs_err) - } - - fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { - let mut iter = self.0.iter().skip(start_idx); - for (i, out_entry) in dirents.iter_mut().enumerate() { - let x = iter.next(); - match x { - Some(Ok(entry)) => { - let ty = if entry.is_dir() { - VfsNodeType::Dir - } else if entry.is_file() { - VfsNodeType::File - } else { - unreachable!() - }; - *out_entry = VfsDirEntry::new(&entry.file_name(), ty); - } - _ => return Ok(i), - } - } - Ok(dirents.len()) - } - - fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult { - // `src_path` and `dst_path` should in the same mounted fs - debug!( - "rename at axfatfs, src_path: {}, dst_path: {}", - src_path, dst_path - ); - - self.0 - .rename(src_path, &self.0, dst_path) - .map_err(as_vfs_err) - } -} - -impl VfsOps for FatFileSystem { - fn root_dir(&self) -> VfsNodeRef { - self.root_dir - .get_or_init(|| { - debug!("Creating root directory for FAT filesystem"); - let root_dir = self.inner.root_dir(); - debug!("Successfully got root directory from FAT filesystem"); - Self::new_dir(root_dir) - }) - .clone() - } -} - -impl axfatfs::IoBase for Disk { - type Error = (); -} - -impl Read for Disk { - fn read(&mut self, mut buf: &mut [u8]) -> Result { - let mut read_len = 0; - while !buf.is_empty() { - match self.read_one(buf) { - Ok(0) => break, - Ok(n) => { - let tmp = buf; - buf = &mut tmp[n..]; - read_len += n; - } - Err(_) => return Err(()), - } - } - Ok(read_len) - } -} - -impl Write for Disk { - fn write(&mut self, mut buf: &[u8]) -> Result { - let mut write_len = 0; - while !buf.is_empty() { - match self.write_one(buf) { - Ok(0) => break, - Ok(n) => { - buf = &buf[n..]; - write_len += n; - } - Err(_) => return Err(()), - } - } - Ok(write_len) - } - fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl Seek for Disk { - fn seek(&mut self, pos: SeekFrom) -> Result { - let size = self.size(); - let new_pos = match pos { - SeekFrom::Start(pos) => Some(pos), - SeekFrom::Current(off) => self.position().checked_add_signed(off), - SeekFrom::End(off) => size.checked_add_signed(off), - } - .ok_or(())?; - if new_pos > size { - warn!("Seek beyond the end of the block device"); - } - self.set_position(new_pos); - Ok(new_pos) - } -} - -const fn as_vfs_err(err: axfatfs::Error<()>) -> VfsError { - use axfatfs::Error::*; - match err { - AlreadyExists => VfsError::AlreadyExists, - CorruptedFileSystem => VfsError::InvalidData, - DirectoryIsNotEmpty => VfsError::DirectoryNotEmpty, - InvalidInput | InvalidFileNameLength | UnsupportedFileNameCharacter => { - VfsError::InvalidInput - } - NotEnoughSpace => VfsError::StorageFull, - NotFound => VfsError::NotFound, - UnexpectedEof => VfsError::UnexpectedEof, - WriteZero => VfsError::WriteZero, - Io(_) => VfsError::Io, - _ => VfsError::Io, - } -} diff --git a/os/arceos/modules/axfs/src/fs/mod.rs b/os/arceos/modules/axfs/src/fs/mod.rs index 8df2becb9e..84c0dcfcae 100644 --- a/os/arceos/modules/axfs/src/fs/mod.rs +++ b/os/arceos/modules/axfs/src/fs/mod.rs @@ -1,23 +1,86 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Filesystem implementations - -/// Ext4 filesystem implementation -pub mod ext4fs; -/// FAT filesystem implementation -pub mod fatfs; - -pub use ax_fs_devfs as devfs; -pub use ax_fs_ramfs as ramfs; +use alloc::boxed::Box; + +use ax_fs_vfs::{Filesystem, VfsResult}; + +use crate::block::{BlockRegion, FsBlockDevice}; + +#[cfg(feature = "ext4")] +mod ext4; +#[cfg(feature = "fat")] +mod fat; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FilesystemKind { + #[cfg(feature = "ext4")] + Ext4, + #[cfg(feature = "fat")] + Fat, +} + +impl FilesystemKind { + pub const fn name(self) -> &'static str { + filesystem_name(self) + } +} + +pub(crate) fn new_default_from_dyn( + dev: Box, + region: BlockRegion, +) -> VfsResult { + cfg_if::cfg_if! { + if #[cfg(feature = "ext4")] { + ext4::Ext4Filesystem::new(dev, region) + } else if #[cfg(feature = "fat")] { + fat::FatFilesystem::new(dev, region) + } else { + let _ = (dev, region); + Err(ax_fs_vfs::VfsError::Unsupported) + } + } +} + +pub(crate) fn new_by_kind_from_dyn( + dev: Box, + region: BlockRegion, + kind: FilesystemKind, +) -> VfsResult { + cfg_if::cfg_if! { + if #[cfg(any(feature = "ext4", feature = "fat"))] { + match kind { + #[cfg(feature = "ext4")] + FilesystemKind::Ext4 => ext4::Ext4Filesystem::new(dev, region), + #[cfg(feature = "fat")] + FilesystemKind::Fat => fat::FatFilesystem::new(dev, region), + } + } else { + let _ = (dev, region, kind); + Err(ax_fs_vfs::VfsError::Unsupported) + } + } +} + +pub(crate) const fn filesystem_name(kind: FilesystemKind) -> &'static str { + match kind { + #[cfg(feature = "ext4")] + FilesystemKind::Ext4 => "ext4", + #[cfg(feature = "fat")] + FilesystemKind::Fat => "fat", + } +} + +/// Create a filesystem instance from a dynamic (boxed) block device. +/// +/// Use this for loop devices and other block backends that don't match +/// the compile-time `AxBlockDevice` type alias. +pub fn new_from_dyn(dev: Box, region: BlockRegion) -> VfsResult { + new_default_from_dyn(dev, region) +} + +/// Create a filesystem instance of a known kind from a dynamic block device. +pub fn new_from_dyn_by_kind( + dev: Box, + region: BlockRegion, + kind: FilesystemKind, +) -> VfsResult { + new_by_kind_from_dyn(dev, region, kind) +} diff --git a/os/arceos/modules/axfs-ng/src/highlevel/file.rs b/os/arceos/modules/axfs/src/highlevel/file.rs similarity index 98% rename from os/arceos/modules/axfs-ng/src/highlevel/file.rs rename to os/arceos/modules/axfs/src/highlevel/file.rs index 69462c8c33..a14182ae65 100644 --- a/os/arceos/modules/axfs-ng/src/highlevel/file.rs +++ b/os/arceos/modules/axfs/src/highlevel/file.rs @@ -11,12 +11,12 @@ use core::{ }; use ax_alloc::{UsageKind, global_allocator}; +use ax_fs_vfs::{ + FileNode, Location, NodeFlags, NodePermission, NodeType, VfsError, VfsResult, path::Path, +}; use ax_hal::mem::{PhysAddr, VirtAddr, virt_to_phys}; use ax_io::{SeekFrom, prelude::*}; use ax_sync::Mutex; -use axfs_ng_vfs::{ - FileNode, Location, NodeFlags, NodePermission, NodeType, VfsError, VfsResult, path::Path, -}; use axpoll::{IoEvents, Pollable}; use intrusive_collections::{LinkedList, LinkedListAtomicLink, intrusive_adapter}; use lru::LruCache; @@ -97,7 +97,7 @@ pub struct OpenOptions { impl OpenOptions { /// Creates a blank new set of options ready for configuration. - pub fn new() -> Self { + pub const fn new() -> Self { Self { // generic read: false, @@ -209,12 +209,6 @@ impl OpenOptions { return Err(VfsError::IsADirectory); } - if loc.is_readonly() - && (flags.intersects(FileFlags::WRITE | FileFlags::APPEND) || self.truncate) - { - return Err(VfsError::ReadOnlyFilesystem); - } - if self.directory { loc.check_is_dir()?; } @@ -305,7 +299,7 @@ impl OpenOptions { let effective_create_new = self.create_new && !must_be_dir; let mut loc = parent.open_file( &name, - &axfs_ng_vfs::OpenOptions { + &ax_fs_vfs::OpenOptions { create: effective_create, create_new: effective_create_new, node_type: self.node_type, @@ -817,6 +811,7 @@ impl CachedFile { if len > 0 { file.write_at(&page.data()[..len], page_start)?; } + page.dirty = false; } drop(guard); } @@ -842,6 +837,7 @@ impl CachedFile { if len > 0 { file.write_at(&page.data()[..len], page_start)?; } + page.dirty = false; } drop(guard); } @@ -1098,11 +1094,6 @@ impl File { /// Checks that the file has the required `flags` and returns the backend. pub fn access(&self, flags: FileFlags) -> VfsResult<&FileBackend> { if self.flags().contains(flags) && !self.is_path() { - if self.inner.location().is_readonly() - && flags.intersects(FileFlags::WRITE | FileFlags::APPEND) - { - return Err(VfsError::ReadOnlyFilesystem); - } Ok(&self.inner) } else { Err(VfsError::BadFileDescriptor) @@ -1272,7 +1263,7 @@ impl Drop for File { fn drop(&mut self) { let flags = self.access_flags.load(Ordering::Acquire); if flags != 0 { - let mut update = axfs_ng_vfs::MetadataUpdate::default(); + let mut update = ax_fs_vfs::MetadataUpdate::default(); if flags & 1 != 0 { update.atime = Some(ax_hal::time::wall_time()); } diff --git a/os/arceos/modules/axfs-ng/src/highlevel/fs.rs b/os/arceos/modules/axfs/src/highlevel/fs.rs similarity index 97% rename from os/arceos/modules/axfs-ng/src/highlevel/fs.rs rename to os/arceos/modules/axfs/src/highlevel/fs.rs index e9b424e792..374675cf4c 100644 --- a/os/arceos/modules/axfs-ng/src/highlevel/fs.rs +++ b/os/arceos/modules/axfs/src/highlevel/fs.rs @@ -6,13 +6,13 @@ use alloc::{ vec::Vec, }; -use ax_io::{Read, Write}; -use ax_kspin::SpinNoIrq; -use ax_sync::Mutex; -use axfs_ng_vfs::{ +use ax_fs_vfs::{ Location, Metadata, Mountpoint, NodePermission, NodeType, VfsError, VfsResult, path::{Component, Components, Path, PathBuf}, }; +use ax_io::{Read, Write}; +use ax_kspin::SpinNoIrq; +use ax_sync::Mutex; use spin::Once; use super::File; @@ -462,6 +462,22 @@ impl ReadDir { /// Maximum number of entries to buffer per `read_dir` syscall. // TODO: tune this pub const BUF_SIZE: usize = 128; + + /// Creates a directory iterator from an already-resolved directory + /// location. + pub fn new(dir: Location) -> Self { + Self { + dir, + buf: VecDeque::new(), + offset: 0, + ended: false, + } + } + + /// Returns the directory location backing this iterator. + pub fn location(&self) -> &Location { + &self.dir + } } impl Iterator for ReadDir { diff --git a/os/arceos/modules/axfs-ng/src/highlevel/mod.rs b/os/arceos/modules/axfs/src/highlevel/mod.rs similarity index 100% rename from os/arceos/modules/axfs-ng/src/highlevel/mod.rs rename to os/arceos/modules/axfs/src/highlevel/mod.rs diff --git a/os/arceos/modules/axfs/src/lib.rs b/os/arceos/modules/axfs/src/lib.rs index 79c059dc0e..040af1330b 100644 --- a/os/arceos/modules/axfs/src/lib.rs +++ b/os/arceos/modules/axfs/src/lib.rs @@ -1,128 +1,620 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! AXFS - Axvisor Filesystem Module +//! ArceOS filesystem module. +//! +//! Provides high-level filesystem operations built on top of the VFS layer, +//! including file I/O with page caching, directory traversal, and +//! `std::fs`-like APIs. +//! +//! Public API tiers: +//! +//! - Primary filesystem API: [`File`], [`OpenOptions`], [`FsContext`], and +//! [`FS_CONTEXT`] are the shared entry points used by ArceOS, StarryOS, and +//! Axvisor-facing library code. +//! - Runtime glue: [`init_filesystems`] consumes block devices from +//! `ax-driver`, selects the root filesystem, and initializes the global root +//! context. + #![cfg_attr(all(not(test), not(doc)), no_std)] -#[macro_use] -extern crate log; -extern crate alloc; +#![allow(clippy::new_ret_no_self)] -pub mod dev; -pub mod fs; -mod mounts; -mod partition; -mod root; +extern crate alloc; -pub mod api; -pub mod fops; +#[macro_use] +extern crate log; use alloc::{ boxed::Box, + format, string::{String, ToString}, - sync::Arc, vec::Vec, }; -pub use crate::dev::FsBlockDevice; -use crate::{dev::Disk, partition::PartitionInfo}; +use ax_fs_vfs::{VfsError, VfsResult, path::Path}; +use fs::filesystem_name; +use rd_block_volume::{BlockVolume, DiskId, PartitionTableKind as VolumeTableKind, scan_volumes}; +use spin::Once; + +use crate::block::{SharedBlockDevice, VolumeReader}; + +mod block; +mod fs; +mod highlevel; + +#[cfg(feature = "devfs")] +pub use ax_fs_devfs as devfs; +#[cfg(feature = "ramfs")] +pub use ax_fs_ramfs as ramfs; +pub use block::{BlockRegion, FsBlockDevice}; +/// Create a filesystem from a dynamic (boxed) block device. +pub use fs::{ + FilesystemKind, new_from_dyn as new_filesystem_from_dyn, + new_from_dyn_by_kind as new_filesystem_from_dyn_by_kind, +}; +pub use highlevel::*; + +#[derive(Debug, Default)] +struct RootSpec { + disk_index: Option, + partition_index: Option, + partuuid: Option, + partlabel: Option, +} + +struct RootCandidate { + disk_index: usize, + partition: Option, + filesystem: Option, +} + +#[derive(Clone)] +struct DiscoveredDisk { + disk_index: usize, + dev: SharedAxBlockDevice, + raw_filesystem: Option, + partitions: Vec, +} + +#[derive(Clone)] +struct DetectedPartition { + info: PartitionInfo, + filesystem: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct PartitionInfo { + index: usize, + table_kind: PartitionTableKind, + region: BlockRegion, + name: Option, + part_uuid: Option, + bootable: bool, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum PartitionTableKind { + Raw, + Gpt, + Mbr, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct RootSelection { + disk_index: usize, + partition_index: Option, + filesystem: Option, +} + +type SharedAxBlockDevice = SharedBlockDevice; + +static DISCOVERED_DISKS: Once> = Once::new(); +static ROOT_SELECTION: Once = Once::new(); + +/// A filesystem-bearing region discovered from the available block devices. +#[derive(Clone, Debug)] +pub struct DiscoveredFilesystem { + /// Zero-based physical block device index. + pub disk_index: usize, + /// Zero-based partition index, or `None` for a raw whole-disk filesystem. + pub partition_index: Option, + /// Block range occupied by this filesystem candidate. + pub region: BlockRegion, + /// Detected filesystem kind, if the region is recognizable. + pub filesystem: Option, + /// Partition label/name, when available. + pub name: Option, + /// Partition UUID, when available. + pub part_uuid: Option, + /// Whether an MBR partition is marked bootable. + pub bootable: bool, + /// Whether this region was selected as the root filesystem. + pub is_root: bool, +} + +impl RootCandidate { + fn selection(&self) -> RootSelection { + RootSelection { + disk_index: self.disk_index, + partition_index: self.partition.as_ref().map(|part| part.info.index), + filesystem: self.filesystem, + } + } -/// Initializes filesystems by block devices. -pub fn init_filesystems(mut block_devs: Vec>, bootargs: Option<&str>) { - info!("Initialize filesystems..."); + fn description(&self) -> String { + if let Some(partition) = &self.partition { + let name = partition.info.name.as_deref().unwrap_or(""); + let fs = partition + .filesystem + .map(filesystem_name) + .unwrap_or("unknown"); + format!( + "disk{} partition {} ({}, fs={}, lba {}..{})", + self.disk_index, + partition.info.index + 1, + name, + fs, + partition.info.region.start_lba, + partition.info.region.end_lba + ) + } else { + let fs = self.filesystem.map(filesystem_name).unwrap_or("unknown"); + format!("disk{} raw device (fs={})", self.disk_index, fs) + } + } +} - let dev = block_devs.pop().expect("No block device found!"); - info!(" use block device 0: {:?}", dev.name()); - let mut disk = Disk::new(dev); +/// Initializes the filesystem subsystem by selecting a root device from the +/// available block devices and optional boot arguments. +pub fn init_filesystems(block_devs: Vec>, bootargs: Option<&str>) { + info!("Initialize filesystem subsystem..."); - // Parse root parameter from bootargs let root_spec = parse_root_spec(bootargs); + let mut disks = collect_disks(block_devs); + let candidates = collect_root_candidates(&disks); + let selection = select_root_candidate(&candidates, &root_spec) + .unwrap_or_else(|| panic!("failed to determine root device from available block devices")); + let selected_disk_pos = disks + .iter() + .position(|disk| disk.disk_index == selection.disk_index) + .unwrap_or_else(|| panic!("selected root disk disappeared during initialization")); + DISCOVERED_DISKS.call_once(|| disks.clone()); + ROOT_SELECTION.call_once(|| selection); + let selected = disks.swap_remove(selected_disk_pos); + let (description, region) = { + let selected_partition_info = selection.partition_index.and_then(|part_index| { + selected + .partitions + .iter() + .find(|partition| partition.info.index == part_index) + }); + ( + describe_selection( + selected.disk_index, + selected_partition_info, + selection.filesystem, + ), + selected_partition_info + .map_or_else(|| full_region(&selected.dev), |part| part.info.region), + ) + }; + info!(" selected root device: {}", description); - // Try to scan GPT partitions first - match self::partition::scan_gpt_partitions(&mut disk) { - Ok(partitions) if !partitions.is_empty() => { - initialize_with_partitions(disk, partitions, &root_spec) + let fs = match selection.filesystem { + Some(kind) => fs::new_by_kind_from_dyn(Box::new(selected.dev.clone()), region, kind), + None => fs::new_default_from_dyn(Box::new(selected.dev.clone()), region), + } + .unwrap_or_else(|err| { + panic!( + "failed to initialize filesystem on {}: {err:?}", + description + ) + }); + info!(" filesystem type: {:?}", fs.name()); + + let mp = ax_fs_vfs::Mountpoint::new_root(&fs); + let root = mp.root_location(); + ROOT_FS_CONTEXT.call_once(|| FsContext::new(root)); +} + +fn collect_disks(block_devs: Vec>) -> Vec { + let mut disks = Vec::new(); + + for (disk_index, dev) in block_devs.into_iter().enumerate() { + let device_name = dev.name().to_string(); + let dev = SharedBlockDevice::new(dev); + let mut scan_dev = dev.clone(); + let mut reader = VolumeReader::new(&mut scan_dev); + match scan_volumes(&mut reader, DiskId(disk_index as u64)) { + Ok(volumes) => { + let raw_region = volumes + .iter() + .find(|volume| volume.table_kind == VolumeTableKind::Raw) + .map(region_from_volume) + .unwrap_or_else(|| full_region(&dev)); + let partitions: Vec = volumes + .into_iter() + .filter(|volume| volume.table_kind != VolumeTableKind::Raw) + .map(|volume| { + let partition = partition_info_from_volume(&volume); + let mut detect_dev = dev.clone(); + let filesystem = detect_filesystem(&mut detect_dev, partition.region); + info!( + " partition {} name={:?} fs={:?} lba {}..{}", + partition.index + 1, + partition.name, + filesystem, + partition.region.start_lba, + partition.region.end_lba + ); + DetectedPartition { + info: partition, + filesystem, + } + }) + .collect(); + let raw_filesystem = if partitions.is_empty() { + let mut detect_dev = dev.clone(); + let raw_fs = detect_filesystem(&mut detect_dev, raw_region); + info!(" raw device fs={:?}", raw_fs); + raw_fs + } else { + None + }; + if let Some(first) = partitions.first() { + info!( + " block device {} ({}) has {:?} partition table with {} partitions", + disk_index, + device_name, + first.info.table_kind, + partitions.len() + ); + } else { + info!( + " block device {} ({}) has no usable partition table; treating the whole \ + disk as a candidate", + disk_index, device_name + ); + } + disks.push(DiscoveredDisk { + disk_index, + dev, + raw_filesystem, + partitions, + }); + } + Err(err) => { + warn!( + " failed to scan partitions on block device {} ({}): {err:?}", + disk_index, device_name + ); + } } - Ok(_) => { - warn!("No partitions found, mount ramfs as rootfs"); - self::root::init_rootfs_with_ramfs(); + } + + disks +} + +fn partition_info_from_volume(volume: &BlockVolume) -> PartitionInfo { + PartitionInfo { + index: volume + .partition_id + .0 + .checked_sub(1) + .map(|index| index as usize) + .unwrap_or(0), + table_kind: table_kind_from_volume(volume.table_kind), + region: region_from_volume(volume), + name: volume.partlabel.as_ref().map(|label| label.0.clone()), + part_uuid: volume.partuuid.as_ref().map(|uuid| uuid.0.clone()), + bootable: volume.bootable, + } +} + +fn region_from_volume(volume: &BlockVolume) -> BlockRegion { + BlockRegion::new(volume.region.start_block, volume.region.num_blocks) +} + +fn table_kind_from_volume(kind: VolumeTableKind) -> PartitionTableKind { + match kind { + VolumeTableKind::Raw => PartitionTableKind::Raw, + VolumeTableKind::Gpt => PartitionTableKind::Gpt, + VolumeTableKind::Mbr => PartitionTableKind::Mbr, + } +} + +fn collect_root_candidates(disks: &[DiscoveredDisk]) -> Vec { + let mut candidates = Vec::new(); + + for disk in disks { + if disk.partitions.is_empty() { + candidates.push(RootCandidate { + disk_index: disk.disk_index, + partition: None, + filesystem: disk.raw_filesystem, + }); + continue; } - Err(e) => { - warn!("Failed to scan GPT partitions: {:?}", e); + + for partition in &disk.partitions { + candidates.push(RootCandidate { + disk_index: disk.disk_index, + filesystem: partition.filesystem, + partition: Some(partition.clone()), + }); } } + + candidates } -/// Initialize filesystems with detected partitions -fn initialize_with_partitions(disk: Disk, partitions: Vec, root_spec: &RootSpec) { - info!( - "Found {} partitions, initializing with dynamic filesystem detection", - partitions.len() - ); +fn select_root_candidate(candidates: &[RootCandidate], spec: &RootSpec) -> Option { + if let Some(index) = select_explicit_root(candidates, spec) { + return Some(index); + } - // Find root partition based on specification - let root_partition_index = find_root_partition(&partitions, root_spec); + select_default_root(candidates) +} - // Check if any partition has a supported filesystem - let has_supported_fs = partitions.iter().any(|p| p.filesystem_type.is_some()); +fn select_explicit_root(candidates: &[RootCandidate], spec: &RootSpec) -> Option { + for candidate in candidates { + if let Some(partition) = candidate.partition.as_ref() { + if let Some(partuuid) = &spec.partuuid + && partition + .info + .part_uuid + .as_ref() + .is_some_and(|candidate_uuid| candidate_uuid.eq_ignore_ascii_case(partuuid)) + { + info!(" matched root by PARTUUID on {}", candidate.description()); + return Some(candidate.selection()); + } - if has_supported_fs { - // Try to initialize with partitions - let disk_arc = Arc::new(disk); - if !self::root::init_rootfs_with_partitions(disk_arc, partitions, root_partition_index) { - warn!("Failed to initialize with partitions."); + if let Some(partlabel) = &spec.partlabel + && partition.info.name.as_deref() == Some(partlabel.as_str()) + { + info!(" matched root by PARTLABEL on {}", candidate.description()); + return Some(candidate.selection()); + } } - } else { - warn!("No supported filesystem found in partitions."); - } -} - -/// Format a GUID as a string in format used by Linux PARTUUID -fn format_guid_as_partuuid(guid: &[u8; 16]) -> alloc::string::String { - // Linux PARTUUID format is little-endian for first 3 fields, - // and big-endian for the rest - alloc::format!( - "{:02X}{:02X}{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:\ - 02X}{:02X}{:02X}", - guid[3], - guid[2], - guid[1], - guid[0], // First 4 bytes (little-endian) - guid[5], - guid[4], // Next 2 bytes (little-endian) - guid[7], - guid[6], // Next 2 bytes (little-endian) - guid[8], - guid[9], // Next 2 bytes (big-endian) - guid[10], - guid[11], - guid[12], - guid[13], - guid[14], - guid[15] // Last 6 bytes (big-endian) - ) + + if let Some(disk_index) = spec.disk_index + && candidate.disk_index == disk_index + { + match (spec.partition_index, &candidate.partition) { + (Some(partition_index), Some(partition)) + if partition.info.index == partition_index => + { + info!( + " matched root by device path on {}", + candidate.description() + ); + return Some(candidate.selection()); + } + (None, None) => { + info!( + " matched root by raw device path on {}", + candidate.description() + ); + return Some(candidate.selection()); + } + _ => {} + } + } + } + + if spec.disk_index.is_some() || spec.partuuid.is_some() || spec.partlabel.is_some() { + panic!("configured root device was not found in discovered block devices"); + } + + None } -/// Root filesystem specification -#[derive(Debug, Default)] -struct RootSpec { +fn select_default_root(candidates: &[RootCandidate]) -> Option { + let rootfs_matches: Vec<_> = candidates + .iter() + .filter(|candidate| { + candidate + .partition + .as_ref() + .and_then(|part| part.info.name.as_deref()) + == Some("rootfs") + && is_supported_filesystem(candidate.filesystem) + }) + .map(RootCandidate::selection) + .collect(); + if rootfs_matches.len() == 1 { + info!(" falling back to PARTLABEL=rootfs"); + return rootfs_matches.into_iter().next(); + } + if rootfs_matches.len() > 1 { + panic!("multiple partitions are labeled 'rootfs'; specify root= explicitly"); + } + + let partition_matches: Vec<_> = candidates + .iter() + .filter(|candidate| { + candidate + .partition + .as_ref() + .is_some_and(is_default_root_partition) + }) + .map(RootCandidate::selection) + .collect(); + if partition_matches.len() == 1 { + info!(" only one supported filesystem partition is available; using it as root"); + return partition_matches.into_iter().next(); + } + + let raw_matches: Vec<_> = candidates + .iter() + .filter(|candidate| candidate.partition.is_none()) + .map(RootCandidate::selection) + .collect(); + if partition_matches.is_empty() && raw_matches.len() == 1 { + info!(" only one raw block device is available; using it as root"); + return raw_matches.into_iter().next(); + } + + None +} + +fn is_default_root_partition(partition: &DetectedPartition) -> bool { + if !is_supported_filesystem(partition.filesystem) { + return false; + } + + match partition.info.table_kind { + PartitionTableKind::Mbr => { + #[cfg(feature = "ext4")] + if partition.filesystem == Some(FilesystemKind::Ext4) { + return partition.info.bootable; + } + #[cfg(feature = "fat")] + if partition.filesystem == Some(FilesystemKind::Fat) { + return true; + } + false + } + PartitionTableKind::Gpt | PartitionTableKind::Raw => true, + } +} + +/// Returns the block filesystems discovered during [`init_filesystems`]. +/// +/// The root filesystem is mounted automatically. Other regions are reported +/// here and can be mounted by policy code with +/// [`mount_discovered_filesystem`]. +pub fn discovered_filesystems() -> Vec { + let Some(disks) = DISCOVERED_DISKS.get() else { + return Vec::new(); + }; + let root_selection = ROOT_SELECTION.get().copied(); + + let mut filesystems = Vec::new(); + for disk in disks { + if disk.partitions.is_empty() { + let selection = RootSelection { + disk_index: disk.disk_index, + partition_index: None, + filesystem: disk.raw_filesystem, + }; + filesystems.push(DiscoveredFilesystem { + disk_index: disk.disk_index, + partition_index: None, + region: full_region(&disk.dev), + filesystem: disk.raw_filesystem, + name: None, + part_uuid: None, + bootable: false, + is_root: root_selection == Some(selection), + }); + continue; + } + + for partition in &disk.partitions { + let selection = RootSelection { + disk_index: disk.disk_index, + partition_index: Some(partition.info.index), + filesystem: partition.filesystem, + }; + filesystems.push(DiscoveredFilesystem { + disk_index: disk.disk_index, + partition_index: Some(partition.info.index), + region: partition.info.region, + filesystem: partition.filesystem, + name: partition.info.name.clone(), + part_uuid: partition.info.part_uuid.clone(), + bootable: partition.info.bootable, + is_root: root_selection == Some(selection), + }); + } + } + filesystems +} + +/// Mounts a discovered non-root filesystem at an existing VFS location. +pub fn mount_discovered_filesystem( + ctx: &FsContext, + disk_index: usize, partition_index: Option, - partuuid: Option, - uuid: Option, - partlabel: Option, + target: impl AsRef, +) -> VfsResult<()> { + let (dev, region, kind) = discovered_region(disk_index, partition_index)?; + let selection = RootSelection { + disk_index, + partition_index, + filesystem: Some(kind), + }; + if ROOT_SELECTION.get().copied() == Some(selection) { + return Err(VfsError::ResourceBusy); + } + + let fs = fs::new_by_kind_from_dyn(Box::new(dev), region, kind)?; + let target = ctx.resolve(target)?; + target.mount(&fs).map(|_| ()) +} + +fn discovered_region( + disk_index: usize, + partition_index: Option, +) -> VfsResult<(SharedAxBlockDevice, BlockRegion, FilesystemKind)> { + let disks = DISCOVERED_DISKS.get().ok_or(VfsError::NotFound)?; + let disk = disks + .iter() + .find(|disk| disk.disk_index == disk_index) + .ok_or(VfsError::NotFound)?; + + if let Some(partition_index) = partition_index { + let partition = disk + .partitions + .iter() + .find(|partition| partition.info.index == partition_index) + .ok_or(VfsError::NotFound)?; + let kind = partition.filesystem.ok_or(VfsError::Unsupported)?; + return Ok((disk.dev.clone(), partition.info.region, kind)); + } + + if !disk.partitions.is_empty() { + return Err(VfsError::InvalidInput); + } + let kind = disk.raw_filesystem.ok_or(VfsError::Unsupported)?; + Ok((disk.dev.clone(), full_region(&disk.dev), kind)) +} + +const fn is_supported_filesystem(fs: Option) -> bool { + match fs { + #[cfg(feature = "ext4")] + Some(FilesystemKind::Ext4) => true, + #[cfg(feature = "fat")] + Some(FilesystemKind::Fat) => true, + _ => false, + } +} + +fn describe_selection( + disk_index: usize, + partition: Option<&DetectedPartition>, + filesystem: Option, +) -> String { + if let Some(partition) = partition { + let name = partition.info.name.as_deref().unwrap_or(""); + let fs = partition + .filesystem + .map(filesystem_name) + .unwrap_or("unknown"); + format!( + "disk{} partition {} ({}, fs={}, lba {}..{})", + disk_index, + partition.info.index + 1, + name, + fs, + partition.info.region.start_lba, + partition.info.region.end_lba + ) + } else { + let fs = filesystem.map(filesystem_name).unwrap_or("unknown"); + format!("disk{} raw device (fs={})", disk_index, fs) + } } -/// Parse root parameter from bootargs fn parse_root_spec(bootargs: Option<&str>) -> RootSpec { let mut spec = RootSpec::default(); @@ -132,117 +624,172 @@ fn parse_root_spec(bootargs: Option<&str>) -> RootSpec { .find(|arg| arg.starts_with("root=")) { let root_value = root_arg.strip_prefix("root=").unwrap_or(""); - spec = match root_value { - v if v.starts_with("/dev/sda") => parse_device_path(v, "/dev/sda"), - v if v.starts_with("/dev/mmcblk") => parse_mmcblk_path(v), - v if v.starts_with("PARTUUID=") => { - let partuuid = v.strip_prefix("PARTUUID=").unwrap_or("").to_uppercase(); - info!("Looking for partition with PARTUUID: {}", partuuid); - RootSpec { - partuuid: Some(partuuid), - ..Default::default() - } - } - v if v.starts_with("UUID=") => { - let uuid = v.strip_prefix("UUID=").unwrap_or("").to_uppercase(); - info!("Looking for filesystem with UUID: {}", uuid); - RootSpec { - uuid: Some(uuid), - ..Default::default() - } - } - v if v.starts_with("PARTLABEL=") => { - let partlabel = v.strip_prefix("PARTLABEL=").unwrap_or("").to_string(); - info!("Looking for partition with PARTLABEL: {}", partlabel); - RootSpec { - partlabel: Some(partlabel), - ..Default::default() - } - } - _ => spec, + value if value.starts_with("/dev/mmcblk") => parse_mmcblk_path(value), + value if value.starts_with("/dev/sd") => parse_sd_path(value), + value if value.starts_with("PARTUUID=") => RootSpec { + partuuid: Some(value.strip_prefix("PARTUUID=").unwrap_or("").to_uppercase()), + ..RootSpec::default() + }, + value if value.starts_with("PARTLABEL=") => RootSpec { + partlabel: Some(value.strip_prefix("PARTLABEL=").unwrap_or("").to_string()), + ..RootSpec::default() + }, + _ => RootSpec::default(), }; } spec } -/// Parse device path like /dev/sdaX -fn parse_device_path(path: &str, prefix: &str) -> RootSpec { - if let Some(part_num) = path.strip_prefix(prefix) - && let Ok(num) = part_num.parse::() - && num > 0 - { - return RootSpec { - partition_index: Some(num - 1), - ..Default::default() - }; - } - RootSpec::default() -} - -/// Parse mmcblk path like /dev/mmcblkXpY fn parse_mmcblk_path(path: &str) -> RootSpec { - if let Some(remaining) = path.strip_prefix("/dev/mmcblk") - && let Some(p_pos) = remaining.find('p') - { - let part_str = &remaining[p_pos + 1..]; - if let Ok(num) = part_str.parse::() - && num > 0 - { + if let Some(remaining) = path.strip_prefix("/dev/mmcblk") { + if let Some(p_pos) = remaining.find('p') { + let disk = remaining[..p_pos].parse::().ok(); + let part = remaining[p_pos + 1..] + .parse::() + .ok() + .and_then(|part| part.checked_sub(1)); + return RootSpec { + disk_index: disk, + partition_index: part, + ..RootSpec::default() + }; + } + + if let Ok(disk) = remaining.parse::() { return RootSpec { - partition_index: Some(num - 1), - ..Default::default() + disk_index: Some(disk), + ..RootSpec::default() }; } } + RootSpec::default() } -/// Find partition index based on root specification -fn find_root_partition(partitions: &[PartitionInfo], root_spec: &RootSpec) -> Option { - // If we have a specific partition index, use it - if let Some(index) = root_spec.partition_index { - return if index < partitions.len() { - Some(index) +fn parse_sd_path(path: &str) -> RootSpec { + if let Some(remaining) = path.strip_prefix("/dev/sd") { + let bytes = remaining.as_bytes(); + if bytes.is_empty() { + return RootSpec::default(); + } + + let disk_index = bytes[0] + .is_ascii_lowercase() + .then(|| usize::from(bytes[0] - b'a')); + let partition_index = if bytes.len() > 1 { + core::str::from_utf8(&bytes[1..]) + .ok() + .and_then(|part| part.parse::().ok()) + .and_then(|part| part.checked_sub(1)) } else { None }; + return RootSpec { + disk_index, + partition_index, + ..RootSpec::default() + }; } - // Try to match by PARTUUID or UUID - for (i, partition) in partitions.iter().enumerate() { - if partition.filesystem_type.is_none() { - continue; - } + RootSpec::default() +} - // Check PARTUUID match - if let Some(ref partuuid) = root_spec.partuuid { - let partition_guid = format_guid_as_partuuid(&partition.unique_partition_guid); - debug!("Partition {} PARTUUID: {}", i, partition_guid); - if partition_guid.contains(partuuid) { - info!("Found matching partition by PARTUUID: {}", i); - return Some(i); - } - } +fn detect_filesystem(dev: &mut impl FsBlockDevice, region: BlockRegion) -> Option { + #[cfg(not(any(feature = "ext4", feature = "fat")))] + let _ = (&mut *dev, region); - // Check UUID match - if let Some(ref uuid) = root_spec.uuid - && let Some(ref partition_uuid) = partition.filesystem_uuid - && partition_uuid.to_uppercase() == *uuid - { - info!("UUID matches partition {} ({})", i, partition.name); - return Some(i); - } + #[cfg(feature = "ext4")] + if region_has_ext4(dev, region) { + return Some(FilesystemKind::Ext4); + } - // Check PARTLABEL match - if let Some(ref partlabel) = root_spec.partlabel - && partition.name == *partlabel - { - info!("PARTLABEL matches partition {} ({})", i, partition.name); - return Some(i); - } + #[cfg(feature = "fat")] + if region_has_fat(dev, region) { + return Some(FilesystemKind::Fat); } None } + +#[cfg(feature = "ext4")] +fn region_has_ext4(dev: &mut impl FsBlockDevice, region: BlockRegion) -> bool { + const EXT4_SUPERBLOCK_OFFSET: usize = 1024; + const EXT4_MAGIC_OFFSET: usize = 0x38; + const EXT4_MAGIC: u16 = 0xEF53; + region_has_magic_u16( + dev, + region, + EXT4_SUPERBLOCK_OFFSET + EXT4_MAGIC_OFFSET, + EXT4_MAGIC, + ) +} + +#[cfg(feature = "fat")] +fn region_has_fat(dev: &mut impl FsBlockDevice, region: BlockRegion) -> bool { + const FAT16_MAGIC: &[u8; 5] = b"FAT16"; + const FAT32_MAGIC: &[u8; 5] = b"FAT32"; + let start_lba = region.start_lba; + let visible_blocks = region.num_blocks(); + if visible_blocks == 0 { + return false; + } + + let block_size = dev.block_size(); + if block_size < 512 { + return false; + } + + let mut buf = alloc::vec![0u8; block_size]; + if dev.read_block(start_lba, &mut buf).is_err() { + return false; + } + + buf.get(510..512) == Some([0x55, 0xAA].as_slice()) + && (buf.get(54..59) == Some(FAT16_MAGIC.as_slice()) + || buf.get(82..87) == Some(FAT32_MAGIC.as_slice())) +} + +#[cfg(feature = "ext4")] +fn region_has_magic_u16( + dev: &mut impl FsBlockDevice, + region: BlockRegion, + byte_offset: usize, + magic: u16, +) -> bool { + let block_size = dev.block_size(); + if block_size == 0 { + return false; + } + + let start_lba = region.start_lba; + let visible_blocks = region.num_blocks(); + let block_index = byte_offset / block_size; + let within_block = byte_offset % block_size; + if visible_blocks == 0 || within_block + 2 > block_size { + return false; + } + + let Some(block_index_u64) = u64::try_from(block_index).ok() else { + return false; + }; + let Some(end_lba) = start_lba.checked_add(visible_blocks) else { + return false; + }; + let block_id = match start_lba.checked_add(block_index_u64) { + Some(block_id) if block_id < end_lba => block_id, + _ => return false, + }; + + let mut buf = alloc::vec![0u8; block_size]; + if dev.read_block(block_id, &mut buf).is_err() { + return false; + } + + u16::from_le_bytes([buf[within_block], buf[within_block + 1]]) == magic +} + +fn full_region(dev: &impl FsBlockDevice) -> BlockRegion { + BlockRegion::from_num_blocks(dev.num_blocks()) +} diff --git a/os/arceos/modules/axfs/src/mounts.rs b/os/arceos/modules/axfs/src/mounts.rs deleted file mode 100644 index a6f48f0849..0000000000 --- a/os/arceos/modules/axfs/src/mounts.rs +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use alloc::sync::Arc; - -use ax_fs_vfs::{VfsNodeType, VfsOps, VfsResult}; - -use crate::fs; - -pub(crate) fn ramfs() -> Arc { - Arc::new(fs::ramfs::RamFileSystem::new()) -} - -pub(crate) fn procfs() -> VfsResult> { - let procfs = fs::ramfs::RamFileSystem::new(); - let proc_root = procfs.root_dir(); - - // Create /proc/sys/net/core/somaxconn - proc_root.create("sys", VfsNodeType::Dir)?; - proc_root.create("sys/net", VfsNodeType::Dir)?; - proc_root.create("sys/net/core", VfsNodeType::Dir)?; - proc_root.create("sys/net/core/somaxconn", VfsNodeType::File)?; - let file_somaxconn = proc_root.clone().lookup("./sys/net/core/somaxconn")?; - file_somaxconn.write_at(0, b"4096\n")?; - - // Create /proc/sys/vm/overcommit_memory - proc_root.create("sys/vm", VfsNodeType::Dir)?; - proc_root.create("sys/vm/overcommit_memory", VfsNodeType::File)?; - let file_over = proc_root.clone().lookup("./sys/vm/overcommit_memory")?; - file_over.write_at(0, b"0\n")?; - - // Create /proc/self/stat - proc_root.create("self", VfsNodeType::Dir)?; - proc_root.create("self/stat", VfsNodeType::File)?; - - Ok(Arc::new(procfs)) -} - -pub(crate) fn sysfs() -> VfsResult> { - let sysfs = fs::ramfs::RamFileSystem::new(); - let sys_root = sysfs.root_dir(); - - // Create /sys/kernel/mm/transparent_hugepage/enabled - sys_root.create("kernel", VfsNodeType::Dir)?; - sys_root.create("kernel/mm", VfsNodeType::Dir)?; - sys_root.create("kernel/mm/transparent_hugepage", VfsNodeType::Dir)?; - sys_root.create("kernel/mm/transparent_hugepage/enabled", VfsNodeType::File)?; - let file_hp = sys_root - .clone() - .lookup("./kernel/mm/transparent_hugepage/enabled")?; - file_hp.write_at(0, b"always [madvise] never\n")?; - - // Create /sys/devices/system/clocksource/clocksource0/current_clocksource - sys_root.create("devices", VfsNodeType::Dir)?; - sys_root.create("devices/system", VfsNodeType::Dir)?; - sys_root.create("devices/system/clocksource", VfsNodeType::Dir)?; - sys_root.create("devices/system/clocksource/clocksource0", VfsNodeType::Dir)?; - sys_root.create( - "devices/system/clocksource/clocksource0/current_clocksource", - VfsNodeType::File, - )?; - let file_cc = sys_root - .clone() - .lookup("devices/system/clocksource/clocksource0/current_clocksource")?; - file_cc.write_at(0, b"tsc\n")?; - - Ok(Arc::new(sysfs)) -} diff --git a/os/arceos/modules/axfs/src/partition.rs b/os/arceos/modules/axfs/src/partition.rs deleted file mode 100644 index e37fae8383..0000000000 --- a/os/arceos/modules/axfs/src/partition.rs +++ /dev/null @@ -1,761 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Partition management and filesystem detection -//! -//! This module provides functionality to scan GPT partition tables and detect -//! filesystem types on each partition. - -use alloc::{format, string::String, sync::Arc, vec, vec::Vec}; - -use ax_errno::{AxError, AxResult}; -use ax_fs_vfs::VfsOps; -use log::{debug, info, warn}; - -use crate::dev::Disk; - -/// Partition information -#[derive(Debug, Clone)] -pub struct PartitionInfo { - /// Partition index (0-based) - pub index: u32, - /// Partition name - pub name: String, - /// Partition type GUID - #[allow(dead_code)] - pub partition_type_guid: [u8; 16], - /// Unique partition GUID - pub unique_partition_guid: [u8; 16], - /// Filesystem UUID (if available) - pub filesystem_uuid: Option, - /// Starting LBA - pub starting_lba: u64, - /// Ending LBA - pub ending_lba: u64, - /// Partition size in bytes - pub size_bytes: u64, - /// Detected filesystem type - pub filesystem_type: Option, -} - -/// Filesystem types that can be detected -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FilesystemType { - /// FAT32/FAT16 filesystem - Fat, - /// ext4/ext3/ext2 filesystem - Ext4, - /// Unknown filesystem - Unknown, -} - -/// GPT Header structure -#[repr(C, packed)] -struct GptHeader { - signature: [u8; 8], // "EFI PART" - revision: [u8; 4], - header_size: [u8; 4], - header_crc32: [u8; 4], - reserved: [u8; 4], - current_lba: [u8; 8], - backup_lba: [u8; 8], - first_usable_lba: [u8; 8], - last_usable_lba: [u8; 8], - disk_guid: [u8; 16], - partition_entry_lba: [u8; 8], - number_of_partition_entries: [u8; 4], - size_of_partition_entry: [u8; 4], - partition_entry_array_crc32: [u8; 4], -} - -/// GPT Partition Entry structure -#[repr(C, packed)] -struct GptPartitionEntry { - partition_type_guid: [u8; 16], - unique_partition_guid: [u8; 16], - starting_lba: [u8; 8], - ending_lba: [u8; 8], - attributes: [u8; 8], - partition_name: [u16; 36], // UTF-16LE -} - -/// GPT partition scanner -pub fn scan_gpt_partitions(disk: &mut Disk) -> AxResult> { - info!("Scanning for GPT partitions..."); - - let disk_size = disk.size(); - if disk_size == 0 { - return Ok(Vec::new()); - } - - // First, try to parse GPT partition table - match parse_gpt_partitions(disk) { - Ok(partitions) if !partitions.is_empty() => { - info!("Found {} GPT partitions", partitions.len()); - return Ok(partitions); - } - Ok(_) => { - info!("No GPT partitions found, trying MBR..."); - } - Err(e) => { - warn!("Failed to parse GPT: {:?}", e); - info!("Trying MBR..."); - } - } - - match parse_mbr_partitions(disk, disk_size) { - Ok(partitions) if !partitions.is_empty() => { - info!("Found {} MBR partitions", partitions.len()); - return Ok(partitions); - } - Ok(_) => { - info!("No MBR partitions found"); - } - Err(e) => { - warn!("Failed to parse MBR: {:?}", e); - } - } - - // If both GPT and MBR fail, treat the whole disk as a single partition - warn!("No partition table found, treating whole disk as single partition"); - let filesystem_type = detect_filesystem_type(disk, 0); - let partition = PartitionInfo { - index: 0, - name: String::from("disk"), - partition_type_guid: [0; 16], - unique_partition_guid: [0; 16], - filesystem_uuid: None, - starting_lba: 0, - ending_lba: disk_size.saturating_div(512).saturating_sub(1), - size_bytes: disk_size, - filesystem_type, - }; - - Ok(vec![partition]) -} - -fn parse_mbr_partitions(disk: &mut Disk, disk_size: u64) -> AxResult> { - let mut sector = [0u8; 512]; - disk.set_position(0); - read_exact(disk, &mut sector).map_err(|_| AxError::InvalidData)?; - - let disk_blocks = disk_size.saturating_div(512); - let mut partitions = - parse_mbr_partition_entries_with_reader(§or, disk_blocks, |lba, sector| { - disk.set_position(lba * 512); - read_exact(disk, sector).map_err(|_| AxError::InvalidData) - })?; - for partition in &mut partitions { - partition.filesystem_type = detect_filesystem_type(disk, partition.starting_lba); - partition.filesystem_uuid = partition - .filesystem_type - .as_ref() - .and_then(|fs| read_filesystem_uuid_simple(disk, partition.starting_lba, fs)); - info!( - "Found MBR partition {}: '{}' ({} bytes) with filesystem: {:?}, UUID: {:?}", - partition.index, - partition.name, - partition.size_bytes, - partition.filesystem_type, - partition.filesystem_uuid, - ); - } - - Ok(partitions) -} - -#[cfg(test)] -fn parse_mbr_partition_entries( - sector: &[u8; 512], - disk_blocks: u64, -) -> AxResult> { - parse_mbr_partition_entries_with_reader(sector, disk_blocks, |_, _| Err(AxError::Unsupported)) -} - -fn parse_mbr_partition_entries_with_reader( - sector: &[u8; 512], - disk_blocks: u64, - mut read_sector: impl FnMut(u64, &mut [u8; 512]) -> AxResult, -) -> AxResult> { - if !has_mbr_signature(sector) { - return Err(AxError::InvalidData); - } - - let mut partitions = Vec::new(); - for index in 0..4 { - let entry = MbrPartitionEntry::parse(sector, index); - if entry.is_unused() || entry.is_protective() { - continue; - } - if entry.is_extended() { - parse_extended_mbr_partitions( - entry.starting_lba, - disk_blocks, - &mut read_sector, - &mut partitions, - )?; - continue; - } - push_mbr_partition(&mut partitions, index as u32, entry, disk_blocks); - } - - Ok(partitions) -} - -fn parse_extended_mbr_partitions( - extended_base_lba: u64, - disk_blocks: u64, - read_sector: &mut impl FnMut(u64, &mut [u8; 512]) -> AxResult, - partitions: &mut Vec, -) -> AxResult { - let mut ebr_lba = extended_base_lba; - let mut visited = 0; - while visited < 128 { - let mut sector = [0u8; 512]; - read_sector(ebr_lba, &mut sector)?; - if !has_mbr_signature(§or) { - warn!( - "Stopping extended MBR scan at LBA {}: invalid EBR signature", - ebr_lba - ); - break; - } - - let logical = MbrPartitionEntry::parse(§or, 0); - if !logical.is_unused() && !logical.is_extended() && !logical.is_protective() { - let Some(starting_lba) = ebr_lba.checked_add(logical.starting_lba) else { - warn!("Skipping logical MBR partition with overflowing start LBA"); - break; - }; - let entry = MbrPartitionEntry { - starting_lba, - ..logical - }; - push_mbr_partition(partitions, partitions.len() as u32 + 4, entry, disk_blocks); - } - - let next = MbrPartitionEntry::parse(§or, 1); - if !next.is_extended() || next.starting_lba == 0 || next.sector_count == 0 { - break; - } - let Some(next_ebr_lba) = extended_base_lba.checked_add(next.starting_lba) else { - warn!("Stopping extended MBR scan after overflowing next EBR LBA"); - break; - }; - if next_ebr_lba == ebr_lba { - warn!( - "Stopping extended MBR scan at self-referential EBR LBA {}", - ebr_lba - ); - break; - } - ebr_lba = next_ebr_lba; - visited += 1; - } - - if visited == 128 { - warn!("Stopping extended MBR scan after reaching EBR chain limit"); - } - - Ok(()) -} - -fn push_mbr_partition( - partitions: &mut Vec, - index: u32, - entry: MbrPartitionEntry, - disk_blocks: u64, -) { - let Some(ending_lba) = entry.starting_lba.checked_add(entry.sector_count - 1) else { - warn!( - "Skipping MBR partition {} with overflowing LBA range", - index - ); - return; - }; - if disk_blocks != 0 && ending_lba >= disk_blocks { - warn!( - "Skipping MBR partition {} outside disk: start={}, sectors={}, disk_blocks={}", - index, entry.starting_lba, entry.sector_count, disk_blocks - ); - return; - } - - partitions.push(PartitionInfo { - index, - name: format!("partition_{}", index), - partition_type_guid: [0; 16], - unique_partition_guid: [0; 16], - filesystem_uuid: None, - starting_lba: entry.starting_lba, - ending_lba, - size_bytes: entry.sector_count * 512, - filesystem_type: None, - }); -} - -fn has_mbr_signature(sector: &[u8; 512]) -> bool { - sector[510] == 0x55 && sector[511] == 0xaa -} - -#[derive(Clone, Copy)] -struct MbrPartitionEntry { - partition_type: u8, - starting_lba: u64, - sector_count: u64, -} - -impl MbrPartitionEntry { - fn parse(sector: &[u8; 512], index: usize) -> Self { - let offset = 446 + index * 16; - Self { - partition_type: sector[offset + 4], - starting_lba: u32::from_le_bytes([ - sector[offset + 8], - sector[offset + 9], - sector[offset + 10], - sector[offset + 11], - ]) as u64, - sector_count: u32::from_le_bytes([ - sector[offset + 12], - sector[offset + 13], - sector[offset + 14], - sector[offset + 15], - ]) as u64, - } - } - - fn is_unused(&self) -> bool { - self.partition_type == 0 || self.starting_lba == 0 || self.sector_count == 0 - } - - fn is_extended(&self) -> bool { - matches!(self.partition_type, 0x05 | 0x0f | 0x85) - } - - fn is_protective(&self) -> bool { - self.partition_type == 0xee - } -} - -/// Parse GPT partition table -fn parse_gpt_partitions(disk: &mut Disk) -> AxResult> { - let mut partitions = Vec::new(); - - // Read GPT Header from LBA 1 - let mut header_data = [0u8; 512]; - disk.set_position(512); // LBA 1 - if read_exact(disk, &mut header_data).is_err() { - return Err(AxError::InvalidData); - } - - // Check GPT signature - if &header_data[0..8] != b"EFI PART" { - return Err(AxError::InvalidData); - } - - // Parse GPT header manually to avoid size mismatch - let header = GptHeader { - signature: header_data[0..8].try_into().unwrap(), - revision: header_data[8..12].try_into().unwrap(), - header_size: header_data[12..16].try_into().unwrap(), - header_crc32: header_data[16..20].try_into().unwrap(), - reserved: header_data[20..24].try_into().unwrap(), - current_lba: header_data[24..32].try_into().unwrap(), - backup_lba: header_data[32..40].try_into().unwrap(), - first_usable_lba: header_data[40..48].try_into().unwrap(), - last_usable_lba: header_data[48..56].try_into().unwrap(), - disk_guid: header_data[56..72].try_into().unwrap(), - partition_entry_lba: header_data[72..80].try_into().unwrap(), - number_of_partition_entries: header_data[80..84].try_into().unwrap(), - size_of_partition_entry: header_data[84..88].try_into().unwrap(), - partition_entry_array_crc32: header_data[88..92].try_into().unwrap(), - }; - - let partition_entry_lba = u64::from_le_bytes(header.partition_entry_lba); - let number_of_partition_entries = u32::from_le_bytes(header.number_of_partition_entries); - let size_of_partition_entry = u32::from_le_bytes(header.size_of_partition_entry); - - info!( - "GPT Header: {} entries at LBA {}", - number_of_partition_entries, partition_entry_lba - ); - - // Read partition entries - let partition_entry_offset = partition_entry_lba * 512; - disk.set_position(partition_entry_offset); - - debug!("Partition entry size: {} bytes", size_of_partition_entry); - debug!("Partition entry offset: {} bytes", partition_entry_offset); - - for i in 0..number_of_partition_entries { - // Ensure we're at the correct position for this partition entry - let current_entry_offset = - partition_entry_offset + (i as u64 * size_of_partition_entry as u64); - disk.set_position(current_entry_offset); - - let mut entry_data = vec![0u8; size_of_partition_entry as usize]; - if read_exact(disk, &mut entry_data).is_err() { - warn!("Failed to read partition entry {}", i); - continue; - } - - let entry = if size_of_partition_entry >= 128 { - // Safely parse the partition entry - let partition_type_guid: [u8; 16] = entry_data[0..16].try_into().unwrap(); - let unique_partition_guid: [u8; 16] = entry_data[16..32].try_into().unwrap(); - let starting_lba: [u8; 8] = entry_data[32..40].try_into().unwrap(); - let ending_lba: [u8; 8] = entry_data[40..48].try_into().unwrap(); - let attributes: [u8; 8] = entry_data[48..56].try_into().unwrap(); - - // Read partition name as UTF-16LE - let mut partition_name = [0u16; 36]; - for (j, name_char) in partition_name.iter_mut().enumerate() { - let offset = 56 + j * 2; - if offset + 1 < entry_data.len() { - *name_char = u16::from_le_bytes([entry_data[offset], entry_data[offset + 1]]); - } - } - - GptPartitionEntry { - partition_type_guid, - unique_partition_guid, - starting_lba, - ending_lba, - attributes, - partition_name, - } - } else { - continue; - }; - - // Check if partition is in use (all zeros means unused) - if entry.partition_type_guid.iter().all(|&b| b == 0) { - continue; - } - - let starting_lba = u64::from_le_bytes(entry.starting_lba); - let ending_lba = u64::from_le_bytes(entry.ending_lba); - let size_bytes = (ending_lba - starting_lba + 1) * 512; - - // Convert partition name from UTF-16LE to UTF-8 - let name_str = { - // First, copy the partition name to a local array to avoid packed field reference - let name_utf16 = entry.partition_name; - - // Find the null terminator - let name_len = name_utf16 - .iter() - .position(|&name_char| name_char == 0) - .unwrap_or(name_utf16.len()); - - // Convert only the valid portion - let name_slice = &name_utf16[..name_len]; - let name_str = String::from_utf16_lossy(name_slice); - debug!( - "Partition {}: UTF-16LE name (len={}): {:?}, UTF-8 name: '{}'", - i, name_len, name_slice, name_str - ); - name_str - }; - - // Generate default name if partition name is empty - let name_str = if name_str.is_empty() { - warn!("Partition {} has no name, using default", i); - format!("partition_{}", i) - } else { - name_str - }; - - // Detect filesystem type and read UUID in one go - let (filesystem_type, filesystem_uuid) = { - let fs_type = detect_filesystem_type(disk, starting_lba); - let uuid = if let Some(ref fs) = fs_type { - read_filesystem_uuid_simple(disk, starting_lba, fs) - } else { - None - }; - (fs_type, uuid) - }; - - let partition = PartitionInfo { - index: i, - name: name_str, - partition_type_guid: entry.partition_type_guid, - unique_partition_guid: entry.unique_partition_guid, - filesystem_uuid, - starting_lba, - ending_lba, - size_bytes, - filesystem_type, - }; - - info!( - "Found GPT partition {}: '{}' ({} bytes) with filesystem: {:?}, UUID: {:?}", - partition.index, - partition.name, - partition.size_bytes, - partition.filesystem_type, - partition.filesystem_uuid, - ); - - partitions.push(partition); - } - - Ok(partitions) -} - -/// Detect filesystem type on a partition -fn detect_filesystem_type(disk: &mut Disk, start_lba: u64) -> Option { - let mut boot_sector = [0u8; 512]; - - // Save current position - let original_position = disk.position(); - - // Set position to read from the specific LBA - disk.set_position(start_lba * 512); - - if read_exact(disk, &mut boot_sector).is_err() { - warn!("Failed to read boot sector at LBA {}", start_lba); - // Restore position - disk.set_position(original_position); - return None; - } - - // Restore position - disk.set_position(original_position); - - // Check for FAT filesystem - if is_fat_filesystem(&boot_sector) { - debug!("Detected FAT filesystem at LBA {}", start_lba); - return Some(FilesystemType::Fat); - } - - // Check for ext4 filesystem - if is_ext4_filesystem(disk, start_lba) { - debug!("Detected ext4 filesystem at LBA {}", start_lba); - return Some(FilesystemType::Ext4); - } - - debug!("Unknown filesystem type at LBA {}", start_lba); - None -} - -/// Read exactly the requested number of bytes -fn read_exact(disk: &mut Disk, mut buf: &mut [u8]) -> Result<(), ()> { - while !buf.is_empty() { - match disk.read_one(buf) { - Ok(0) => break, - Ok(n) => buf = &mut buf[n..], - Err(_) => return Err(()), - } - } - Ok(()) -} - -/// Check if the boot sector indicates a FAT filesystem -fn is_fat_filesystem(boot_sector: &[u8; 512]) -> bool { - // Check for FAT12/FAT16/FAT32 signature at offset 0x36 (FAT) or 0x52 (FAT32) - if boot_sector.len() >= 0x36 + 3 { - let fat_sig = &boot_sector[0x36..0x36 + 3]; - if fat_sig == b"FAT" { - return true; - } - } - - if boot_sector.len() >= 0x52 + 5 { - let fat32_sig = &boot_sector[0x52..0x52 + 5]; - if fat32_sig == b"FAT32" { - return true; - } - } - - false -} - -/// Check if the partition contains an ext4 filesystem -fn is_ext4_filesystem(disk: &mut Disk, start_lba: u64) -> bool { - // ext4 superblock is at offset 1024 (2 sectors) from the start of the partition - let superblock_offset = start_lba * 512 + 1024; - let mut superblock = [0u8; 2048]; // Increase buffer size to accommodate the magic number offset - - // Save current position - let pos = disk.position(); - - // Set position to read the superblock - disk.set_position(superblock_offset); - - let result = if read_exact(disk, &mut superblock).is_err() { - warn!( - "Failed to read ext4 superblock at offset {}", - superblock_offset - ); - false - } else { - // Check for ext4 magic number (0xEF53) at offset 1080 (0x438) in the superblock - // But since we're reading from offset 1024, the magic number will be at index 56 - if superblock.len() >= 58 { - let magic = u16::from_le_bytes([superblock[56], superblock[57]]); - magic == 0xEF53 - } else { - false - } - }; - - // Restore position - disk.set_position(pos); - - result -} - -/// Create a filesystem instance for the given partition and filesystem type -pub fn create_filesystem_for_partition( - disk: Disk, - partition: &PartitionInfo, -) -> AxResult> { - match partition.filesystem_type { - Some(FilesystemType::Fat) => { - info!("Creating FAT filesystem for partition '{}'", partition.name); - // Create a partition wrapper - let partition_wrapper = - crate::dev::Partition::new(disk, partition.starting_lba, partition.ending_lba); - let fs = crate::fs::fatfs::FatFileSystem::from_partition(partition_wrapper); - Ok(Arc::new(fs)) - } - Some(FilesystemType::Ext4) => { - info!( - "Creating ext4 filesystem for partition '{}'", - partition.name - ); - // Create a partition wrapper - let partition_wrapper = - crate::dev::Partition::new(disk, partition.starting_lba, partition.ending_lba); - let fs = crate::fs::ext4fs::Ext4FileSystem::from_partition(partition_wrapper); - Ok(Arc::new(fs)) - } - Some(FilesystemType::Unknown) | None => { - warn!("Unknown filesystem type for partition '{}'", partition.name); - Err(AxError::Unsupported) - } - } -} - -/// Read filesystem UUID directly from disk without mounting -/// This reads the UUID from the filesystem superblock -fn read_filesystem_uuid_simple( - disk: &mut Disk, - starting_lba: u64, - filesystem_type: &FilesystemType, -) -> Option { - match filesystem_type { - FilesystemType::Ext4 => read_ext4_uuid(disk, starting_lba), - FilesystemType::Fat => read_fat32_uuid(disk, starting_lba), - _ => None, - } -} - -/// Read UUID from ext4 filesystem superblock -fn read_ext4_uuid(disk: &mut Disk, starting_lba: u64) -> Option { - // Ext4 superblock is at offset 1024 bytes from the start of the partition - let superblock_offset = starting_lba * 512 + 1024; - - // Set position to superblock - disk.set_position(superblock_offset); - - // Read the superblock (ext4 superblock is 1024 bytes) - let mut superblock_data = vec![0u8; 1024]; - let mut total_read = 0; - - // Read in chunks since read_one might not read all at once - while total_read < 1024 { - match disk.read_one(&mut superblock_data[total_read..]) { - Ok(0) => break, // EOF - Ok(n) => total_read += n, - Err(_) => return None, - } - } - - // UUID is at offset 0x68 (104) in the superblock, 16 bytes long - if superblock_data.len() >= 120 { - let uuid_bytes = &superblock_data[104..120]; - - // Convert UUID bytes to string format (8-4-4-4-12) - // ext4 stores UUID as little-endian for the first 3 fields and big-endian for the last 2 fields - let uuid_str = format!( - "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:\ - 02x}{:02x}{:02x}", - uuid_bytes[0], - uuid_bytes[1], - uuid_bytes[2], - uuid_bytes[3], // Little endian for first 4 bytes - uuid_bytes[4], - uuid_bytes[5], // Little endian for next 2 bytes - uuid_bytes[6], - uuid_bytes[7], // Little endian for next 2 bytes - uuid_bytes[8], - uuid_bytes[9], // Big endian for next 2 bytes - uuid_bytes[10], - uuid_bytes[11], - uuid_bytes[12], - uuid_bytes[13], - uuid_bytes[14], - uuid_bytes[15] // Big endian for last 6 bytes - ); - - Some(uuid_str) - } else { - None - } -} - -/// Read UUID from FAT32 filesystem -fn read_fat32_uuid(disk: &mut Disk, starting_lba: u64) -> Option { - // FAT32 boot sector is at the start of the partition - let boot_sector_offset = starting_lba * 512; - - // Set position to boot sector - disk.set_position(boot_sector_offset); - - // Read the boot sector (512 bytes) - let mut boot_sector = vec![0u8; 512]; - let mut total_read = 0; - - // Read in chunks since read_one might not read all at once - while total_read < 512 { - match disk.read_one(&mut boot_sector[total_read..]) { - Ok(0) => break, // EOF - Ok(n) => total_read += n, - Err(_) => return None, - } - } - - // FAT32 doesn't have a standard UUID like ext4, but it has a Volume ID - // Volume ID is at offset 0x43 (67) in the boot sector, 4 bytes long - if boot_sector.len() >= 71 { - let volume_id_bytes = &boot_sector[67..71]; - - // Format as 8-character hex string - let volume_id_str = format!( - "{:02x}{:02x}{:02x}{:02x}", - volume_id_bytes[3], - volume_id_bytes[2], - volume_id_bytes[1], - volume_id_bytes[0] // Little endian - ); - - Some(volume_id_str) - } else { - None - } -} diff --git a/os/arceos/modules/axfs/src/root.rs b/os/arceos/modules/axfs/src/root.rs deleted file mode 100644 index 5ba0e364d9..0000000000 --- a/os/arceos/modules/axfs/src/root.rs +++ /dev/null @@ -1,569 +0,0 @@ -// Copyright 2025 The Axvisor Team -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Root directory of the filesystem -//! -//! TODO: it doesn't work very well if the mount points have containment relationships. - -use alloc::{ - borrow::ToOwned, - collections::BTreeMap, - format, - string::{String, ToString}, - sync::Arc, - vec::Vec, -}; - -use ax_errno::{AxError, AxResult}; -use ax_fs_vfs::{VfsDirEntry, VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps, VfsResult}; -use ax_kspin::SpinNoIrq as Mutex; -use ax_lazyinit::LazyInit; - -use crate::{ - api::FileType, - mounts, - partition::{FilesystemType, PartitionInfo, create_filesystem_for_partition}, -}; - -static CURRENT_DIR_PATH: Mutex = Mutex::new(String::new()); -static CURRENT_DIR: LazyInit> = LazyInit::new(); - -struct MountPoint { - path: String, - fs: Arc, -} - -pub struct RootDirectory { - main_fs: Arc, - mounts: Vec, -} - -static ROOT_DIR: LazyInit> = LazyInit::new(); - -impl MountPoint { - pub fn new(path: String, fs: Arc) -> Self { - Self { path, fs } - } -} - -impl Drop for MountPoint { - fn drop(&mut self) { - self.fs.umount().ok(); - } -} - -impl RootDirectory { - pub const fn new(main_fs: Arc) -> Self { - Self { - main_fs, - mounts: Vec::new(), - } - } - - pub fn mount(&mut self, path: &str, fs: Arc) -> AxResult { - if path == "/" { - return Err(AxError::InvalidInput); - } - if !path.starts_with('/') { - return Err(AxError::InvalidInput); - } - if self.mounts.iter().any(|mp| mp.path == path) { - return Err(AxError::InvalidInput); - } - // create the mount point in the main filesystem if it does not exist - self.main_fs.root_dir().create(path, FileType::Dir)?; - fs.mount(path, self.main_fs.root_dir().lookup(path)?)?; - self.mounts.push(MountPoint::new(path.to_owned(), fs)); - Ok(()) - } - - pub fn _umount(&mut self, path: &str) { - self.mounts.retain(|mp| mp.path != path); - } - - pub fn contains(&self, path: &str) -> bool { - self.mounts.iter().any(|mp| mp.path == path) - } - - /// Normalize path by trimming leading '/' and handling './' prefix - fn normalize_path<'a>(&self, path: &'a str) -> &'a str { - let path = path.trim_matches('/'); - if let Some(rest) = path.strip_prefix("./") { - rest - } else { - path - } - } - - /// Find the best matching mount point for the given path - /// Returns (filesystem, remaining_path) if a match is found - fn find_best_mount<'a>(&self, path: &'a str) -> Option<(Arc, &'a str)> { - let mut best_match = None; - let mut max_len = 0; - - for (i, mp) in self.mounts.iter().enumerate() { - // Skip the first '/' in mount path for comparison - let mount_path = &mp.path[1..]; - - if path.starts_with(mount_path) && mp.path.len() - 1 > max_len { - max_len = mp.path.len() - 1; - best_match = Some(i); - } - } - - if let Some(idx) = best_match { - let rest_path = &path[max_len..]; - Some((self.mounts[idx].fs.clone(), rest_path)) - } else { - None - } - } -} - -impl VfsNodeOps for RootDirectory { - ax_fs_vfs::impl_vfs_dir_default! {} - - fn get_attr(&self) -> VfsResult { - self.main_fs.root_dir().get_attr() - } - - fn lookup(self: Arc, path: &str) -> VfsResult { - let normalized_path = self.normalize_path(path); - if let Some((mount_fs, rest_path)) = self.find_best_mount(normalized_path) { - mount_fs.root_dir().lookup(rest_path) - } else { - self.main_fs.root_dir().lookup(normalized_path) - } - } - - fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { - let normalized_path = self.normalize_path(path); - if let Some((mount_fs, rest_path)) = self.find_best_mount(normalized_path) { - if rest_path.is_empty() { - Ok(()) - } else { - mount_fs.root_dir().create(rest_path, ty) - } - } else { - self.main_fs.root_dir().create(normalized_path, ty) - } - } - - fn remove(&self, path: &str) -> VfsResult { - let normalized_path = self.normalize_path(path); - if let Some((mount_fs, rest_path)) = self.find_best_mount(normalized_path) { - if rest_path.is_empty() { - Err(ax_fs_vfs::VfsError::PermissionDenied) - } else { - mount_fs.root_dir().remove(rest_path) - } - } else { - self.main_fs.root_dir().remove(normalized_path) - } - } - - fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { - let mut all_entries = Vec::new(); - - // Add mount points - for mp in &self.mounts { - let name = &mp.path[1..]; - all_entries.push((name.to_string(), VfsNodeType::Dir)); - } - - // Add from main_fs - let mut main_dirents = Vec::with_capacity(64); - for _ in 0..64 { - main_dirents.push(VfsDirEntry::default()); - } - let main_count = self.main_fs.root_dir().read_dir(0, &mut main_dirents)?; - for dirent in main_dirents.iter().take(main_count) { - let name_bytes = dirent.name_as_bytes(); - if let Ok(name_str) = core::str::from_utf8(name_bytes) - && !name_str.is_empty() - { - let ty = dirent.entry_type(); - all_entries.push((name_str.to_string(), ty)); - } - } - - // Unique - let mut unique = BTreeMap::new(); - for (name, ty) in all_entries { - unique.insert(name, ty); - } - - let unique_vec: Vec<_> = unique.into_iter().collect(); - let mut count = 0; - for (name, ty) in unique_vec.iter().skip(start_idx) { - if count >= dirents.len() { - break; - } - dirents[count] = VfsDirEntry::new(name, *ty); - count += 1; - } - Ok(count) - } -} - -pub(crate) fn init_rootfs_with_ramfs() { - info!("Initializing root filesystem with ramfs"); - let main_fs = mounts::ramfs(); - let root_dir = RootDirectory::new(main_fs); - mount_virtual_fs(root_dir); -} - -/// Find and create root filesystem from partitions -fn find_root_filesystem( - disk: &Arc, - partitions: &[PartitionInfo], - root_partition_index: Option, -) -> (Option>, Option) { - // Try to use the specified partition index first - if let Some(index) = root_partition_index - && let Some((fs, idx)) = try_use_specified_partition(disk, partitions, index) - { - return (Some(fs), Some(idx)); - } - - // Fall back to first partition with supported filesystem - if let Some((fs, idx)) = find_first_supported_partition(disk, partitions) { - return (Some(fs), Some(idx)); - } - - (None, None) -} - -/// Try to use the specified partition as root filesystem -fn try_use_specified_partition( - disk: &Arc, - partitions: &[PartitionInfo], - index: usize, -) -> Option<(Arc, usize)> { - if index >= partitions.len() { - warn!( - "Specified partition index {} is out of range (total partitions: {})", - index, - partitions.len() - ); - return None; - } - - let partition = &partitions[index]; - if partition.filesystem_type.is_none() { - warn!( - "Specified partition '{}' has no supported filesystem", - partition.name - ); - return None; - } - - match create_filesystem_for_partition((**disk).clone(), partition) { - Ok(fs) => { - info!( - "Using specified partition '{}' ({:?}) as root filesystem", - partition.name, - partition.filesystem_type.unwrap_or(FilesystemType::Unknown) - ); - Some((fs, index)) - } - Err(e) => { - warn!( - "Failed to create filesystem for specified partition '{}': {:?}", - partition.name, e - ); - None - } - } -} - -/// Find the first partition with a supported filesystem -fn find_first_supported_partition( - disk: &Arc, - partitions: &[PartitionInfo], -) -> Option<(Arc, usize)> { - for (i, partition) in partitions.iter().enumerate() { - if partition.filesystem_type.is_some() { - match create_filesystem_for_partition((**disk).clone(), partition) { - Ok(fs) => { - info!( - "Using partition '{}' ({:?}) as root filesystem", - partition.name, - partition.filesystem_type.unwrap_or(FilesystemType::Unknown) - ); - return Some((fs, i)); - } - Err(e) => { - warn!( - "Failed to create filesystem for partition '{}': {:?}", - partition.name, e - ); - } - } - } - } - None -} - -/// Mount additional partitions (non-root partitions) -fn mount_additional_partitions( - disk: &Arc, - root_dir: &mut RootDirectory, - partitions: &[PartitionInfo], - root_partition_index: Option, -) { - // Create /boot directory first if it doesn't exist - if let Err(e) = root_dir.main_fs.root_dir().create("/boot", FileType::Dir) { - warn!("Failed to create /boot directory: {:?}", e); - } - - // Mount all non-root partitions - for (i, partition) in partitions.iter().enumerate() { - // Skip root partition - if Some(i) == root_partition_index { - continue; - } - - // Only mount partitions with supported filesystems - if partition.filesystem_type.is_some() { - mount_single_partition(disk, root_dir, partition); - } - } -} - -/// Mount a single partition -fn mount_single_partition( - disk: &Arc, - root_dir: &mut RootDirectory, - partition: &PartitionInfo, -) { - match create_filesystem_for_partition((**disk).clone(), partition) { - Ok(fs) => { - // Determine mount path based on partition name - let mount_path = if partition.name.to_lowercase().contains("boot") { - String::from("/boot") - } else { - format!("/{}", partition.name) - }; - - info!( - "Mounting partition '{}' at '{}'", - partition.name, mount_path - ); - - // Create mount point directory in root filesystem - if let Err(e) = root_dir - .main_fs - .root_dir() - .create(&mount_path, FileType::Dir) - { - warn!("Failed to create mount point '{}': {:?}", mount_path, e); - return; - } - - // Mount filesystem - if let Err(e) = root_dir.mount(&mount_path, fs) { - warn!( - "Failed to mount partition '{}' at '{}': {:?}", - partition.name, mount_path, e - ); - } - } - Err(e) => { - warn!( - "Failed to create filesystem for partition '{}': {:?}", - partition.name, e - ); - } - } -} - -/// Initialize root filesystem with dynamic partition detection and specified root partition -pub(crate) fn init_rootfs_with_partitions( - disk: Arc, - partitions: Vec, - root_partition_index: Option, -) -> bool { - info!( - "Initializing root filesystem with {} partitions", - partitions.len() - ); - - // Find and create the root filesystem - let (main_fs, actual_root_partition_index) = - find_root_filesystem(&disk, &partitions, root_partition_index); - - // If no supported filesystem found, fall back to ramfs - let main_fs = match main_fs { - Some(fs) => fs, - None => { - warn!("No supported filesystem found in partitions, mount ramfs as rootfs"); - mounts::ramfs() - } - }; - - let mut root_dir = RootDirectory::new(main_fs); - - // Mount additional partitions - mount_additional_partitions( - &disk, - &mut root_dir, - &partitions, - actual_root_partition_index, - ); - - mount_virtual_fs(root_dir); - true -} - -pub fn mount_virtual_fs(mut root_dir: RootDirectory) { - // Mount virtual filesystems - if let Err(e) = root_dir - .mount("/proc", mounts::procfs().unwrap()) - .and_then(|_| root_dir.mount("/sys", mounts::sysfs().unwrap())) - { - panic!("Failed to mount virtual filesystems: {:?}", e); - } - - // Initialize global state - let root_dir = Arc::new(root_dir); - ROOT_DIR.init_once(root_dir.clone()); - CURRENT_DIR.init_once(Mutex::new(ROOT_DIR.clone())); - *CURRENT_DIR_PATH.lock() = "/".into(); -} - -fn parent_node_of(dir: Option<&VfsNodeRef>, path: &str) -> VfsNodeRef { - if path.starts_with('/') { - ROOT_DIR.clone() - } else { - dir.cloned().unwrap_or_else(|| CURRENT_DIR.lock().clone()) - } -} - -pub(crate) fn absolute_path(path: &str) -> AxResult { - if path.starts_with('/') { - Ok(ax_fs_vfs::path::canonicalize(path)) - } else { - let path = CURRENT_DIR_PATH.lock().clone() + path; - Ok(ax_fs_vfs::path::canonicalize(&path)) - } -} - -pub(crate) fn lookup(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { - if path.is_empty() { - return Err(AxError::NotFound); - } - let node = parent_node_of(dir, path).lookup(path)?; - if path.ends_with('/') && !node.get_attr()?.is_dir() { - Err(AxError::NotADirectory) - } else { - Ok(node) - } -} - -pub(crate) fn create_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { - if path.is_empty() { - return Err(AxError::NotFound); - } else if path.ends_with('/') { - return Err(AxError::NotADirectory); - } - let parent = parent_node_of(dir, path); - parent.create(path, VfsNodeType::File)?; - parent.lookup(path) -} - -pub(crate) fn create_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { - match lookup(dir, path) { - Ok(_) => Err(AxError::AlreadyExists), - Err(AxError::NotFound) => Ok(parent_node_of(dir, path).create(path, VfsNodeType::Dir)?), - Err(e) => Err(e), - } -} - -pub(crate) fn remove_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { - let node = lookup(dir, path)?; - let attr = node.get_attr()?; - if attr.is_dir() { - Err(AxError::IsADirectory) - } else if !attr.perm().owner_writable() { - Err(AxError::PermissionDenied) - } else { - Ok(parent_node_of(dir, path).remove(path)?) - } -} - -pub(crate) fn remove_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { - if path.is_empty() { - return Err(AxError::NotFound); - } - let path_check = path.trim_matches('/'); - if path_check.is_empty() { - return Err(AxError::DirectoryNotEmpty); // rm -d '/' - } else if path_check == "." - || path_check == ".." - || path_check.ends_with("/.") - || path_check.ends_with("/..") - { - return Err(AxError::InvalidInput); - } - if ROOT_DIR.contains(&absolute_path(path)?) { - return Err(AxError::PermissionDenied); - } - - let node = lookup(dir, path)?; - let attr = node.get_attr()?; - if !attr.is_dir() { - Err(AxError::NotADirectory) - } else if !attr.perm().owner_writable() { - Err(AxError::PermissionDenied) - } else { - Ok(parent_node_of(dir, path).remove(path)?) - } -} - -pub(crate) fn current_dir() -> AxResult { - Ok(CURRENT_DIR_PATH.lock().clone()) -} - -pub(crate) fn set_current_dir(path: &str) -> AxResult { - let mut abs_path = absolute_path(path)?; - if !abs_path.ends_with('/') { - abs_path += "/"; - } - if abs_path == "/" { - *CURRENT_DIR.lock() = ROOT_DIR.clone(); - *CURRENT_DIR_PATH.lock() = "/".into(); - return Ok(()); - } - - let node = lookup(None, &abs_path)?; - let attr = node.get_attr()?; - if !attr.is_dir() { - Err(AxError::NotADirectory) - } else if !attr.perm().owner_executable() { - Err(AxError::PermissionDenied) - } else { - *CURRENT_DIR.lock() = node; - *CURRENT_DIR_PATH.lock() = abs_path; - Ok(()) - } -} - -pub(crate) fn rename(old: &str, new: &str) -> AxResult { - if parent_node_of(None, new).lookup(new).is_ok() { - warn!("dst file already exist, now remove it"); - remove_file(None, new)?; - } - parent_node_of(None, old).rename(old, new) -} diff --git a/os/arceos/modules/axnet-ng/Cargo.toml b/os/arceos/modules/axnet-ng/Cargo.toml index fbec6699e5..166297b2e7 100644 --- a/os/arceos/modules/axnet-ng/Cargo.toml +++ b/os/arceos/modules/axnet-ng/Cargo.toml @@ -15,8 +15,8 @@ async-channel = { version = "2.5", default-features = false } async-trait = "0.1" ax-config = { workspace = true } ax-errno = { workspace = true } -ax-fs-ng = { workspace = true } -axfs-ng-vfs = { workspace = true } +ax-fs = { workspace = true } +ax-fs-vfs = { workspace = true } ax-hal = { workspace = true } ax-io = { workspace = true } ax-kspin = { workspace = true } diff --git a/os/arceos/modules/axnet-ng/src/unix/mod.rs b/os/arceos/modules/axnet-ng/src/unix/mod.rs index d2a0165a3a..0eaf50e55b 100644 --- a/os/arceos/modules/axnet-ng/src/unix/mod.rs +++ b/os/arceos/modules/axnet-ng/src/unix/mod.rs @@ -6,11 +6,11 @@ use core::task::Context; use async_trait::async_trait; use ax_errno::{AxError, AxResult}; -use ax_fs_ng::{FS_CONTEXT, OpenOptions}; +use ax_fs::{FS_CONTEXT, OpenOptions}; +use ax_fs_vfs::NodeType; use ax_io::{IoBuf, Read, Write}; use ax_sync::Mutex; use ax_task::future::{block_on, poll_io}; -use axfs_ng_vfs::NodeType; use axpoll::{IoEvents, Pollable}; use enum_dispatch::enum_dispatch; use hashbrown::HashMap; diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 88da7cad43..6e97df9ab8 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -33,14 +33,6 @@ fs = [ "dep:axklib", "ax-driver/block", ] -fs-ng = [ - "dep:ax-errno", - "dep:ax-fs-ng", - "dep:rd-block", - "dep:spin", - "dep:axklib", - "ax-driver/block", -] input = ["dep:ax-input", "ax-driver/input"] net = ["dep:ax-net", "ax-driver/net"] net-ng = ["dep:ax-net-ng", "dep:rd-net", "dep:spin", "dep:axklib", "ax-driver/net"] @@ -54,7 +46,6 @@ ax-display = { workspace = true, optional = true } ax-driver = { workspace = true } ax-errno = { workspace = true, optional = true } ax-fs = { workspace = true, optional = true } -ax-fs-ng = { workspace = true, optional = true } ax-hal = { workspace = true } ax-input = { workspace = true, optional = true } ax-ipi = { workspace = true, optional = true } diff --git a/os/arceos/modules/axruntime/src/devices.rs b/os/arceos/modules/axruntime/src/devices.rs index f7bfe6643f..bee05ef9ef 100644 --- a/os/arceos/modules/axruntime/src/devices.rs +++ b/os/arceos/modules/axruntime/src/devices.rs @@ -4,7 +4,7 @@ pub(crate) fn probe_all_devices() { .unwrap_or_else(|err| panic!("failed to probe platform devices: {err:?}")); } -#[cfg(all(feature = "fs", not(feature = "fs-ng"), feature = "plat-dyn"))] +#[cfg(all(feature = "fs", feature = "plat-dyn"))] pub(crate) fn take_dyn_fs_block_devices() -> alloc::vec::Vec> { #[cfg(target_os = "none")] @@ -22,7 +22,7 @@ pub(crate) fn take_dyn_fs_block_devices() alloc::vec::Vec::new() } -#[cfg(all(feature = "fs", not(feature = "fs-ng"), not(feature = "plat-dyn")))] +#[cfg(all(feature = "fs", not(feature = "plat-dyn")))] pub(crate) fn take_static_fs_block_devices() -> alloc::vec::Vec> { ax_driver::block::take_block_devices() @@ -34,36 +34,6 @@ pub(crate) fn take_static_fs_block_devices() .collect() } -#[cfg(all(feature = "fs-ng", feature = "plat-dyn"))] -pub(crate) fn take_dyn_fs_ng_block_devices() --> alloc::vec::Vec> { - #[cfg(target_os = "none")] - { - ax_driver::block::take_block_devices() - .into_iter() - .map(|dev| { - alloc::boxed::Box::new(FsBlockDevice(dev)) - as alloc::boxed::Box - }) - .collect() - } - - #[cfg(not(target_os = "none"))] - alloc::vec::Vec::new() -} - -#[cfg(all(feature = "fs-ng", not(feature = "plat-dyn")))] -pub(crate) fn take_static_fs_ng_block_devices() --> alloc::vec::Vec> { - ax_driver::block::take_block_devices() - .into_iter() - .map(|dev| { - alloc::boxed::Box::new(FsBlockDevice(dev)) - as alloc::boxed::Box - }) - .collect() -} - #[cfg(all(feature = "display", feature = "plat-dyn"))] pub(crate) fn init_dyn_display() { #[cfg(target_os = "none")] @@ -228,17 +198,10 @@ fn take_dyn_net_ng_drivers() -> alloc::vec::Vec &str { self.0.name() @@ -264,30 +227,3 @@ impl ax_fs::FsBlockDevice for FsBlockDevice { self.0.flush() } } - -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] -impl ax_fs_ng::FsBlockDevice for FsBlockDevice { - fn name(&self) -> &str { - self.0.name() - } - - fn num_blocks(&self) -> u64 { - self.0.num_blocks() - } - - fn block_size(&self) -> usize { - self.0.block_size() - } - - fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> ax_errno::AxResult { - self.0.read_block(block_id, buf) - } - - fn write_block(&mut self, block_id: u64, buf: &[u8]) -> ax_errno::AxResult { - self.0.write_block(block_id, buf) - } - - fn flush(&mut self) -> ax_errno::AxResult { - self.0.flush() - } -} diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index c0c2426d23..c69a27b1a2 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -55,8 +55,8 @@ mod registers; pub use self::mp::rust_main_secondary; #[cfg(any( - all(any(feature = "fs", feature = "fs-ng"), not(feature = "plat-dyn")), - all(any(feature = "fs", feature = "fs-ng"), feature = "plat-dyn"), + all(feature = "fs", not(feature = "plat-dyn")), + all(feature = "fs", feature = "plat-dyn"), feature = "net", feature = "net-ng", feature = "display", @@ -274,14 +274,7 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { devices::probe_all_devices(); cfg_if::cfg_if! { - if #[cfg(all(feature = "fs-ng", feature = "plat-dyn"))] { - ax_fs_ng::init_filesystems( - devices::take_dyn_fs_ng_block_devices(), - ax_hal::dtb::get_chosen_bootargs(), - ); - } else if #[cfg(all(feature = "fs-ng", not(feature = "plat-dyn")))] { - ax_fs_ng::init_filesystems(devices::take_static_fs_ng_block_devices(), None); - } else if #[cfg(all(feature = "fs", feature = "plat-dyn"))] { + if #[cfg(all(feature = "fs", feature = "plat-dyn"))] { ax_fs::init_filesystems( devices::take_dyn_fs_block_devices(), ax_hal::dtb::get_chosen_bootargs(), diff --git a/os/arceos/ulib/arceos-rust/lib/Cargo.toml b/os/arceos/ulib/arceos-rust/lib/Cargo.toml index be7f862865..29cd75b044 100644 --- a/os/arceos/ulib/arceos-rust/lib/Cargo.toml +++ b/os/arceos/ulib/arceos-rust/lib/Cargo.toml @@ -61,7 +61,7 @@ stack-guard-page = ["multitask", "paging", "ax-feat/stack-guard-page"] # File system fd = ["ax-posix-api/fd"] -fs = ["ax-api/fs", "ax-posix-api/fs", "ax-feat/fs", "ax-driver/pci", "ax-driver/virtio-blk"] +fs = ["ax-api/fs", "ax-posix-api/fs", "ax-feat/fs-ext4", "ax-driver/pci", "ax-driver/virtio-blk"] # Networking dns = [] diff --git a/os/arceos/ulib/axstd/Cargo.toml b/os/arceos/ulib/axstd/Cargo.toml index ce212c099f..7d22dbeb01 100644 --- a/os/arceos/ulib/axstd/Cargo.toml +++ b/os/arceos/ulib/axstd/Cargo.toml @@ -60,8 +60,8 @@ stack-guard-page = ["multitask", "paging", "ax-feat/stack-guard-page"] # File system fs = ["ax-api/fs", "ax-feat/fs"] -ext4fs = ["ax-api/fs", "ax-feat/fs"] -fatfs = ["ax-api/fs", "ax-feat/fs"] +ext4fs = ["fs", "ax-feat/fs-ext4"] +fatfs = ["fs", "ax-feat/fs-fat"] # Networking net = ["ax-api/net", "ax-feat/net"] diff --git a/os/arceos/ulib/axstd/src/fs/dir.rs b/os/arceos/ulib/axstd/src/fs/dir.rs index 53c6feb3c2..b07c40fa2f 100644 --- a/os/arceos/ulib/axstd/src/fs/dir.rs +++ b/os/arceos/ulib/axstd/src/fs/dir.rs @@ -37,8 +37,7 @@ impl<'a> ReadDir<'a> { opts.read(true); let inner = api::ax_open_dir(path, &opts)?; - const EMPTY: api::AxDirEntry = api::AxDirEntry::default(); - let dirent_buf = [EMPTY; 31]; + let dirent_buf = core::array::from_fn(|_| api::AxDirEntry::default()); Ok(ReadDir { path, inner, diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index aeb1ab5756..cfad42568c 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -35,7 +35,6 @@ path = "xtask/src/main.rs" [features] default = [] ept-level-4 = [] -fs = ["ax-std/fs"] vmx = ["axvm/vmx"] svm = ["axvm/svm"] sstc = ["riscv_vcpu/sstc"] @@ -67,7 +66,8 @@ ax-timer-list = { workspace = true } hashbrown = "0.14" # System dependent modules provided by ArceOS. -ax-std = { workspace = true, features = ["paging", "irq", "multitask", "task-ext", "smp", "hv"] } +ax-std = { workspace = true, features = ["paging", "irq", "multitask", "task-ext", "smp", "hv", "fs"] } +ax-feat = { workspace = true, features = ["fs-ext4", "fs-fat"] } ax-hal = { workspace = true, features = ["paging", "irq", "smp", "hv", "axvisor-linker"] } # System dependent modules provided by ArceOS-Hypervisor (bare-metal only) axaddrspace = { workspace = true } diff --git a/os/axvisor/src/shell/command/base.rs b/os/axvisor/src/shell/command/base.rs index 6e302b7479..d61bd38b44 100644 --- a/os/axvisor/src/shell/command/base.rs +++ b/os/axvisor/src/shell/command/base.rs @@ -13,16 +13,13 @@ // limitations under the License. use std::collections::BTreeMap; -#[cfg(feature = "fs")] use std::fs::{self, File, FileType}; -#[cfg(feature = "fs")] use std::io::{self, Read, Write}; use std::println; use std::string::{String, ToString}; use crate::shell::command::{CommandNode, FlagDef, ParsedCommand}; -#[cfg(feature = "fs")] macro_rules! print_err { ($cmd: literal, $msg: expr) => { println!("{}: {}", $cmd, $msg); @@ -33,7 +30,6 @@ macro_rules! print_err { } // Helper function: split whitespace -#[cfg(feature = "fs")] fn split_whitespace(s: &str) -> (&str, &str) { let s = s.trim(); if let Some(pos) = s.find(char::is_whitespace) { @@ -44,7 +40,6 @@ fn split_whitespace(s: &str) -> (&str, &str) { } } -#[cfg(feature = "fs")] fn do_ls(cmd: &ParsedCommand) { let args = &cmd.positional_args; let show_long = cmd.flags.get("long").unwrap_or(&false); @@ -111,7 +106,6 @@ fn do_ls(cmd: &ParsedCommand) { } } -#[cfg(feature = "fs")] fn do_cat(cmd: &ParsedCommand) { let args = &cmd.positional_args; @@ -140,7 +134,6 @@ fn do_cat(cmd: &ParsedCommand) { } } -#[cfg(feature = "fs")] fn do_echo(cmd: &ParsedCommand) { let args = &cmd.positional_args; let no_newline = cmd.flags.get("no-newline").unwrap_or(&false); @@ -181,7 +174,6 @@ fn do_echo(cmd: &ParsedCommand) { } } -#[cfg(feature = "fs")] fn do_mkdir(cmd: &ParsedCommand) { let args = &cmd.positional_args; let create_parents = cmd.flags.get("parents").unwrap_or(&false); @@ -206,7 +198,6 @@ fn do_mkdir(cmd: &ParsedCommand) { } } -#[cfg(feature = "fs")] fn do_rm(cmd: &ParsedCommand) { let args = &cmd.positional_args; let rm_dir = cmd.flags.get("dir").unwrap_or(&false); @@ -250,7 +241,6 @@ fn do_rm(cmd: &ParsedCommand) { } // Implementation of recursively deleting directories (manual recursion) -#[cfg(feature = "fs")] fn remove_dir_recursive(path: &str, _force: bool) -> io::Result<()> { // Read directory contents let entries = fs::read_dir(path)?; @@ -274,7 +264,6 @@ fn remove_dir_recursive(path: &str, _force: bool) -> io::Result<()> { fs::remove_dir(path) } -#[cfg(feature = "fs")] fn do_cd(cmd: &ParsedCommand) { let args = &cmd.positional_args; @@ -292,7 +281,6 @@ fn do_cd(cmd: &ParsedCommand) { } } -#[cfg(feature = "fs")] fn do_pwd(cmd: &ParsedCommand) { let _logical = cmd.flags.get("logical").unwrap_or(&false); @@ -373,7 +361,6 @@ fn do_log(cmd: &ParsedCommand) { println!("Log level set to: {:?}", log::max_level()); } -#[cfg(feature = "fs")] fn do_mv(cmd: &ParsedCommand) { let args = &cmd.positional_args; @@ -462,7 +449,6 @@ fn do_mv(cmd: &ParsedCommand) { } // Helper function to move file or directory (handles cross-filesystem moves) -#[cfg(feature = "fs")] fn move_file_or_dir(source: &str, dest: &str) -> io::Result<()> { // Try simple rename first (works within same filesystem) match fs::rename(source, dest) { @@ -485,7 +471,6 @@ fn move_file_or_dir(source: &str, dest: &str) -> io::Result<()> { } } -#[cfg(feature = "fs")] fn do_touch(cmd: &ParsedCommand) { let args = &cmd.positional_args; @@ -501,7 +486,6 @@ fn do_touch(cmd: &ParsedCommand) { } } -#[cfg(feature = "fs")] fn do_cp(cmd: &ParsedCommand) { let args = &cmd.positional_args; let recursive = cmd.flags.get("recursive").unwrap_or(&false); @@ -539,7 +523,6 @@ fn do_cp(cmd: &ParsedCommand) { } // Manually implement file copy -#[cfg(feature = "fs")] fn copy_file(src: &str, dst: &str) -> io::Result<()> { let mut src_file = File::open(src)?; let mut dst_file = File::create(dst)?; @@ -556,7 +539,6 @@ fn copy_file(src: &str, dst: &str) -> io::Result<()> { } // Recursively copy directory -#[cfg(feature = "fs")] fn copy_dir_recursive(src: &str, dst: &str) -> io::Result<()> { // Create target directory fs::create_dir(dst)?; @@ -580,7 +562,6 @@ fn copy_dir_recursive(src: &str, dst: &str) -> io::Result<()> { Ok(()) } -#[cfg(feature = "fs")] fn file_type_to_char(ty: FileType) -> char { if ty.is_char_device() { 'c' @@ -602,7 +583,6 @@ fn file_type_to_char(ty: FileType) -> char { } #[rustfmt::skip] -#[cfg(feature = "fs")] const fn file_perm_to_rwx(mode: u32) -> [u8; 9] { let mut perm = [b'-'; 9]; macro_rules! set { @@ -621,7 +601,6 @@ const fn file_perm_to_rwx(mode: u32) -> [u8; 9] { pub fn build_base_cmd(tree: &mut BTreeMap) { // ls Command - #[cfg(feature = "fs")] tree.insert( "ls".to_string(), CommandNode::new("List directory contents") @@ -640,7 +619,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ); // cat Command - #[cfg(feature = "fs")] tree.insert( "cat".to_string(), CommandNode::new("Display file contents") @@ -649,7 +627,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ); // echo Command - #[cfg(feature = "fs")] tree.insert( "echo".to_string(), CommandNode::new("Display text") @@ -663,7 +640,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ); // mkdir Command - #[cfg(feature = "fs")] tree.insert( "mkdir".to_string(), CommandNode::new("Create directories") @@ -677,7 +653,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ); // rm Command - #[cfg(feature = "fs")] tree.insert( "rm".to_string(), CommandNode::new("Remove files and directories") @@ -701,7 +676,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ); // cd Command - #[cfg(feature = "fs")] tree.insert( "cd".to_string(), CommandNode::new("Change directory") @@ -710,7 +684,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ); // pwd Command - #[cfg(feature = "fs")] tree.insert( "pwd".to_string(), CommandNode::new("Print working directory") @@ -763,7 +736,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ); // touch Command - #[cfg(feature = "fs")] tree.insert( "touch".to_string(), CommandNode::new("Create empty files") @@ -772,7 +744,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ); // cp Command - #[cfg(feature = "fs")] tree.insert( "cp".to_string(), CommandNode::new("Copy files") @@ -786,7 +757,6 @@ pub fn build_base_cmd(tree: &mut BTreeMap) { ); // mv Command - #[cfg(feature = "fs")] tree.insert( "mv".to_string(), CommandNode::new("Move/rename files") diff --git a/os/axvisor/src/shell/command/mod.rs b/os/axvisor/src/shell/command/mod.rs index b6be05f55c..367898b00a 100644 --- a/os/axvisor/src/shell/command/mod.rs +++ b/os/axvisor/src/shell/command/mod.rs @@ -480,10 +480,7 @@ pub fn show_help(command_path: &[String]) -> Result<(), ParseError> { } pub fn print_prompt() { - #[cfg(feature = "fs")] print!("axvisor:{}$ ", std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - print!("axvisor:$ "); std::io::stdout().flush().unwrap(); } diff --git a/os/axvisor/src/shell/command/vm.rs b/os/axvisor/src/shell/command/vm.rs index 8114067854..283f320f1e 100644 --- a/os/axvisor/src/shell/command/vm.rs +++ b/os/axvisor/src/shell/command/vm.rs @@ -21,7 +21,6 @@ use std::{ use ax_hal::time::busy_wait; use axvm::VMStatus; -#[cfg(feature = "fs")] use std::fs::read_to_string; use crate::{ @@ -122,7 +121,6 @@ fn vm_help(_cmd: &ParsedCommand) { println!("Use 'vm --help' for more information on a specific command."); } -#[cfg(feature = "fs")] fn vm_create(cmd: &ParsedCommand) { let args = &cmd.positional_args; @@ -173,7 +171,6 @@ fn vm_create(cmd: &ParsedCommand) { } } -#[cfg(feature = "fs")] fn vm_start(cmd: &ParsedCommand) { let args = &cmd.positional_args; let detach = cmd.flags.get("detach").unwrap_or(&false); @@ -781,7 +778,6 @@ fn delete_vm_by_id(vm_id: usize, keep_data: bool) { println!("✓ VM[{}] deletion completed", vm_id); } -#[cfg(feature = "fs")] fn vm_list_simple() { let vms = vm_list::get_vm_list(); println!("ID NAME STATE VCPU MEMORY"); @@ -1273,7 +1269,6 @@ fn show_vm_full_details(vm_id: usize) { /// Build the VM command tree and register it. pub fn build_vm_cmd(tree: &mut BTreeMap) { - #[cfg(feature = "fs")] let create_cmd = CommandNode::new("Create a new virtual machine") .with_handler(vm_create) .with_usage("vm create [OPTIONS] ...") @@ -1298,7 +1293,6 @@ pub fn build_vm_cmd(tree: &mut BTreeMap) { .with_long("force"), ); - #[cfg(feature = "fs")] let start_cmd = CommandNode::new("Start a virtual machine") .with_handler(vm_start) .with_usage("vm start [OPTIONS] [VM_ID...]") @@ -1392,7 +1386,6 @@ pub fn build_vm_cmd(tree: &mut BTreeMap) { CommandNode::new("Show VM help").with_handler(vm_help), ); - #[cfg(feature = "fs")] { vm_node = vm_node .add_subcommand("create", create_cmd) diff --git a/os/axvisor/src/shell/mod.rs b/os/axvisor/src/shell/mod.rs index 49aac346a2..1041362878 100644 --- a/os/axvisor/src/shell/mod.rs +++ b/os/axvisor/src/shell/mod.rs @@ -51,8 +51,6 @@ pub fn console_init() { println!("Welcome to AxVisor Shell!"); println!("Type 'help' to see available commands"); println!("Use UP/DOWN arrows to navigate command history"); - #[cfg(not(feature = "fs"))] - println!("Note: Running with limited features (filesystem support disabled)."); println!(); print_prompt(); @@ -105,10 +103,7 @@ pub fn console_init() { let current_content = std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - #[cfg(feature = "fs")] let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor); } } @@ -131,10 +126,7 @@ pub fn console_init() { let current_content = std::str::from_utf8(&buf[..line_len]).unwrap_or(""); - #[cfg(feature = "fs")] let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); clear_line_and_redraw(&mut stdout, &prompt, current_content, cursor); } } @@ -161,10 +153,7 @@ pub fn console_init() { buf[..copy_len].copy_from_slice(&cmd_bytes[..copy_len]); cursor = copy_len; line_len = copy_len; - #[cfg(feature = "fs")] let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); clear_line_and_redraw(&mut stdout, &prompt, prev_cmd, cursor); } input_state = InputState::Normal; @@ -182,11 +171,8 @@ pub fn console_init() { cursor = copy_len; line_len = copy_len; - #[cfg(feature = "fs")] let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); clear_line_and_redraw(&mut stdout, &prompt, next_cmd, cursor); } None => { @@ -194,11 +180,8 @@ pub fn console_init() { buf[..line_len].fill(0); cursor = 0; line_len = 0; - #[cfg(feature = "fs")] let prompt = format!("axvisor:{}$ ", &std::env::current_dir().unwrap()); - #[cfg(not(feature = "fs"))] - let prompt = "axvisor:$ ".to_string(); clear_line_and_redraw(&mut stdout, &prompt, "", cursor); } } diff --git a/os/axvisor/src/vmm/config.rs b/os/axvisor/src/vmm/config.rs index 4dc0c2c27c..9f8c36249d 100644 --- a/os/axvisor/src/vmm/config.rs +++ b/os/axvisor/src/vmm/config.rs @@ -42,7 +42,6 @@ pub mod config { } /// Read VM configs from filesystem - #[cfg(feature = "fs")] pub fn filesystem_vm_configs() -> Vec { use ax_std::fs; use ax_std::io::{BufReader, Read}; @@ -122,12 +121,6 @@ pub mod config { configs } - /// Fallback function for when "fs" feature is not enabled - #[cfg(not(feature = "fs"))] - pub fn filesystem_vm_configs() -> Vec { - Vec::new() - } - include!(concat!(env!("OUT_DIR"), "/vm_configs.rs")); } @@ -157,7 +150,7 @@ pub fn init_guest_vms() { init_dtb_cache(); } - // First try to get configs from filesystem if fs feature is enabled + // First try to get configs from filesystem. let mut gvm_raw_configs = config::filesystem_vm_configs(); // If no filesystem configs found, fallback to static configs diff --git a/os/axvisor/src/vmm/fdt/mod.rs b/os/axvisor/src/vmm/fdt/mod.rs index 7bff3d0525..9189735a2a 100644 --- a/os/axvisor/src/vmm/fdt/mod.rs +++ b/os/axvisor/src/vmm/fdt/mod.rs @@ -132,7 +132,6 @@ pub fn get_developer_provided_dtb( return Some(dtb.to_vec()); } } - #[cfg(feature = "fs")] Some("fs") => { use ax_errno::ax_err_type; use std::io::{BufReader, Read}; diff --git a/os/axvisor/src/vmm/images/mod.rs b/os/axvisor/src/vmm/images/mod.rs index 75b7284e6f..0024ea52f1 100644 --- a/os/axvisor/src/vmm/images/mod.rs +++ b/os/axvisor/src/vmm/images/mod.rs @@ -30,14 +30,13 @@ mod x86_boot; pub fn get_image_header(config: &AxVMCrateConfig) -> Option { match config.kernel.image_location.as_deref() { Some("memory") => with_memory_image(config, linux::Header::parse), - #[cfg(feature = "fs")] Some("fs") => { let read_size = linux::Header::hdr_size(); let data = fs::kernal_read(config, read_size).ok()?; linux::Header::parse(&data) } _ => unimplemented!( - "Check your \"image_location\" in config.toml, \"memory\" and \"fs\" are supported,\n NOTE: \"fs\" feature should be enabled if you want to load images from filesystem. (APP_FEATURES=fs)" + "Check your \"image_location\" in config.toml; \"memory\" and \"fs\" are supported." ), } } @@ -92,10 +91,9 @@ impl ImageLoader { match self.config.kernel.image_location.as_deref() { Some("memory") => self.load_vm_images_from_memory(), - #[cfg(feature = "fs")] Some("fs") => fs::load_vm_images_from_filesystem(self), _ => unimplemented!( - "Check your \"image_location\" in config.toml, \"memory\" and \"fs\" are supported,\n NOTE: \"fs\" feature should be enabled if you want to load images from filesystem. (APP_FEATURES=fs)" + "Check your \"image_location\" in config.toml; \"memory\" and \"fs\" are supported." ), } } @@ -245,7 +243,6 @@ impl ImageLoader { load_vm_image_from_memory(ramdisk, load_gpa, self.vm.clone()) } - #[cfg(feature = "fs")] fn load_ramdisk_from_filesystem(&self, ramdisk_path: &str) -> AxResult { let load_gpa = self .vm @@ -316,7 +313,6 @@ pub fn load_vm_image_from_memory( Ok(()) } -#[cfg(feature = "fs")] pub mod fs { use super::*; use ax_errno::{AxResult, ax_err, ax_err_type}; diff --git a/test-suit/arceos/rust/fs/shell/Cargo.toml b/test-suit/arceos/rust/fs/shell/Cargo.toml index 793ac84397..6de667030c 100644 --- a/test-suit/arceos/rust/fs/shell/Cargo.toml +++ b/test-suit/arceos/rust/fs/shell/Cargo.toml @@ -17,4 +17,4 @@ ax-fs-ramfs = { workspace = true, optional = true } ax-crate-interface = { workspace = true, optional = true } ax-driver.workspace = true ax-hal.workspace = true -ax-std = { workspace = true, features = ["alloc", "fs"], optional = true } +ax-std = { workspace = true, features = ["alloc", "fatfs"], optional = true } diff --git a/test-suit/arceos/rust/task/lockdep/Cargo.toml b/test-suit/arceos/rust/task/lockdep/Cargo.toml index 333e4de282..a049fc4676 100644 --- a/test-suit/arceos/rust/task/lockdep/Cargo.toml +++ b/test-suit/arceos/rust/task/lockdep/Cargo.toml @@ -7,7 +7,7 @@ description = "A regression test for ArceOS lockdep lock-order detection" publish = false [features] -ax-std = ["dep:ax-std", "dep:ax-kspin", "dep:axfs-ng-vfs"] +ax-std = ["dep:ax-std", "dep:ax-kspin", "dep:ax-fs-vfs"] lockdep = ["ax-std", "ax-std/lockdep"] [dependencies] @@ -15,4 +15,4 @@ ax-driver.workspace = true ax-hal.workspace = true ax-std = { workspace = true, features = ["alloc", "multitask"], optional = true } ax-kspin = { workspace = true, optional = true } -axfs-ng-vfs = { workspace = true, optional = true } +ax-fs-vfs = { workspace = true, optional = true } diff --git a/test-suit/arceos/rust/task/lockdep/README.md b/test-suit/arceos/rust/task/lockdep/README.md index db6364400f..6c74d34ba5 100644 --- a/test-suit/arceos/rust/task/lockdep/README.md +++ b/test-suit/arceos/rust/task/lockdep/README.md @@ -12,7 +12,7 @@ This test app exercises lock order inversion detection for ArceOS lockdep. - `mixed-two-task`: two-task spin->mutex then mutex->spin - `mixed-ms-single`: single-task mutex->spin then spin->mutex - `mixed-ms-two-task`: two-task mutex->spin then spin->mutex -- `vfs-cache-single`: single-task `axfs-ng-vfs` dentry user-data/cache ABBA +- `vfs-cache-single`: single-task `ax-fs-vfs` dentry user-data/cache ABBA ## Test modes diff --git a/test-suit/arceos/rust/task/lockdep/src/main.rs b/test-suit/arceos/rust/task/lockdep/src/main.rs index b7ffd199c1..d6ae179ba4 100644 --- a/test-suit/arceos/rust/task/lockdep/src/main.rs +++ b/test-suit/arceos/rust/task/lockdep/src/main.rs @@ -36,7 +36,7 @@ use std::{ use ax_kspin::SpinRaw; #[cfg(feature = "ax-std")] -use axfs_ng_vfs::{ +use ax_fs_vfs::{ DeviceId, DirEntry, DirEntrySink, DirNode, DirNodeOps, FilesystemOps, Metadata, MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, Reference, StatFs, VfsError, VfsResult, WeakDirEntry, @@ -54,7 +54,7 @@ const WAIT_UNTIL_RETRY_LIMIT: usize = 10_000_000; // - mixed-two-task: two-task spin->mutex then mutex->spin // - mixed-ms-single: single-task mutex->spin then spin->mutex // - mixed-ms-two-task: two-task mutex->spin then spin->mutex -// - vfs-cache-single: single-task axfs-ng-vfs dentry cache ABBA +// - vfs-cache-single: single-task ax-fs-vfs dentry cache ABBA #[cfg(feature = "ax-std")] fn lockdep_case() -> &'static str { match option_env!("LOCKDEP_CASE") { diff --git a/test-suit/axvisor/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml index 5ad4b8a556..a70dc2f213 100644 --- a/test-suit/axvisor/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/board-orangepi-5-plus/build-aarch64-unknown-none-softfloat.toml @@ -2,7 +2,6 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ "sdmmc", "rockchip-soc", - "fs", ] log = "Info" plat_dyn = true diff --git a/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml index 85565cb686..7dddccbb2b 100644 --- a/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/qemu/build-aarch64-unknown-none-softfloat.toml @@ -3,7 +3,6 @@ features = [ "ax-hal/plat-dyn", "ept-level-4", "ax-driver/virtio-blk", - "fs", ] log = "Info" plat_dyn = true diff --git a/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml index d16951f9f4..4dadc9adb6 100644 --- a/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/qemu/build-loongarch64-unknown-none-softfloat.toml @@ -3,7 +3,6 @@ features = [ "ax-driver/virtio-blk", "ax-driver/pci", "ept-level-4", - "fs", ] log = "Info" max_cpu_num = 1 diff --git a/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml b/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml index 4e9112e70c..ee96e6ed5f 100644 --- a/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/axvisor/normal/qemu/build-riscv64gc-unknown-none-elf.toml @@ -4,7 +4,6 @@ features = [ "ax-driver/fdt", "ax-driver/virtio-blk", "ept-level-4", - "fs", "sstc" ] log = "Info" diff --git a/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml b/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml index fccc9c223a..7a905166ab 100644 --- a/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml +++ b/test-suit/axvisor/normal/qemu/build-x86_64-unknown-none.toml @@ -3,7 +3,6 @@ features = [ "ax-driver/virtio-blk", "ax-driver/pci", "ept-level-4", - "fs", "vmx", ] log = "Info" diff --git a/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml b/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml index 832d68593f..d3335b8ae0 100644 --- a/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml +++ b/test-suit/axvisor/svm/qemu/build-x86_64-unknown-none.toml @@ -3,7 +3,6 @@ features = [ "ax-driver/virtio-blk", "ax-driver/pci", "ept-level-4", - "fs", "svm", ] log = "Info" From 16b70721138ecb27cb05a73719c7cf98e6266f79 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Mon, 25 May 2026 16:40:54 +0800 Subject: [PATCH 02/15] fix: correct package name from axfs-ng-vfs to ax-fs-vfs in std_crates.csv --- scripts/test/std_crates.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test/std_crates.csv b/scripts/test/std_crates.csv index 9193eed899..3e6eb935ca 100644 --- a/scripts/test/std_crates.csv +++ b/scripts/test/std_crates.csv @@ -9,7 +9,7 @@ axbacktrace axdevice axdevice_base ax-errno -axfs-ng-vfs +ax-fs-vfs axhvc ax-io axklib From 7b4324e81ca6f6cdbc87a05d4a10ddb03637c3fc Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Mon, 25 May 2026 17:08:59 +0800 Subject: [PATCH 03/15] Refactor filesystem handling and remove unused features - Updated Cargo.toml to streamline filesystem dependencies by removing redundant entries and introducing new features for ext4 and fat filesystems. - Modified board configuration files to eliminate the "fs" feature where it was no longer necessary. - Adjusted tests and build scripts to reflect the removal of the "fs" feature, ensuring compatibility with the new filesystem structure. - Introduced a new `fs_policy.rs` file to manage filesystem detection and initialization, enhancing the overall organization and clarity of the filesystem handling logic. --- Cargo.lock | 1 - components/axfs_crates/axfs_vfs/src/mount.rs | 203 +---- .../docs/mount-umount2-linux-compat.md | 5 +- .../src/pseudofs/lockdep-tmpfs-analysis.md | 87 +- os/StarryOS/kernel/src/syscall/fs/mount.rs | 12 +- os/arceos/api/axfeat/Cargo.toml | 6 +- os/arceos/modules/axfs/src/fs_policy.rs | 758 +++++++++++++++++ os/arceos/modules/axfs/src/highlevel/fs.rs | 3 +- os/arceos/modules/axfs/src/lib.rs | 767 +----------------- os/arceos/modules/axruntime/Cargo.toml | 12 +- os/axvisor/configs/board/orangepi-5-plus.toml | 1 - os/axvisor/configs/board/phytiumpi.toml | 1 - os/axvisor/configs/board/qemu-aarch64.toml | 1 - .../configs/board/qemu-loongarch64.toml | 1 - os/axvisor/configs/board/qemu-riscv64.toml | 1 - os/axvisor/configs/board/qemu-x86_64.toml | 1 - os/axvisor/configs/board/roc-rk3568-pc.toml | 1 - scripts/axbuild/src/axvisor/board.rs | 4 +- scripts/axbuild/src/axvisor/build.rs | 5 +- scripts/axbuild/src/axvisor/build/x86.rs | 4 +- scripts/axbuild/src/axvisor/config.rs | 2 +- scripts/repo/repos.csv | 1 - 22 files changed, 820 insertions(+), 1057 deletions(-) create mode 100644 os/arceos/modules/axfs/src/fs_policy.rs diff --git a/Cargo.lock b/Cargo.lock index a2a639d4b8..49f19e0783 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1531,7 +1531,6 @@ dependencies = [ "cfg-if", "chrono", "indoc", - "rd-block", "rd-net", "rdrive", "spin", diff --git a/components/axfs_crates/axfs_vfs/src/mount.rs b/components/axfs_crates/axfs_vfs/src/mount.rs index 475910dd56..868498ca62 100644 --- a/components/axfs_crates/axfs_vfs/src/mount.rs +++ b/components/axfs_crates/axfs_vfs/src/mount.rs @@ -3,7 +3,6 @@ use alloc::{ string::String, sync::{Arc, Weak}, vec, - vec::Vec, }; use core::{ iter, mem, @@ -14,7 +13,6 @@ use core::{ use axpoll::{IoEvents, Pollable}; use hashbrown::HashMap; use inherit_methods_macro::inherit_methods; -use log::warn; use crate::{ DirEntry, DirEntrySink, Filesystem, FilesystemOps, Metadata, MetadataUpdate, Mutex, MutexGuard, @@ -24,14 +22,6 @@ use crate::{ static DEVICE_COUNTER: AtomicU64 = AtomicU64::new(1); -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum PropagationType { - Private, - Shared, - Slave, - Unbindable, -} - #[derive(Debug)] pub struct Mountpoint { /// Root dir entry in the mountpoint. @@ -46,14 +36,8 @@ pub struct Mountpoint { readonly: AtomicBool, /// Expire mark for umount2(MNT_EXPIRE). expired: AtomicBool, - /// Mount propagation type. - propagation: Mutex, - /// Other shared peers in the same propagation group. - peers: Mutex>>, - /// Slave mounts that receive propagation events from this shared mount. - slaves: Mutex>>, - /// Shared masters that this slave receives propagation events from. - masters: Mutex>>, + /// Bind mounts cannot recursively copy this mount when set. + unbindable: AtomicBool, } impl Mountpoint { @@ -69,10 +53,7 @@ impl Mountpoint { device, readonly: AtomicBool::new(false), expired: AtomicBool::new(false), - propagation: Mutex::new(PropagationType::Private), - peers: Mutex::default(), - slaves: Mutex::default(), - masters: Mutex::default(), + unbindable: AtomicBool::new(false), }) } @@ -88,7 +69,7 @@ impl Mountpoint { Self::new(fs, None) } - fn bind(source: &Location, location_in_parent: Location, recursive: bool) -> Arc { + fn bind(source: &Location, location_in_parent: Location) -> Arc { let result = Self::new_with_root( source.entry.clone(), Some(location_in_parent), @@ -97,21 +78,6 @@ impl Mountpoint { result .readonly .store(source.mountpoint.is_readonly(), Ordering::Release); - if recursive { - let mut children_to_bind: Vec<_> = source - .mountpoint - .children - .lock() - .iter() - .map(|(key, child)| (key.clone(), child.clone())) - .collect(); - children_to_bind - .retain(|(_, child)| child.upgrade().is_none_or(|child| !child.is_unbindable())); - let mut result_children = result.children.lock(); - for (key, child) in children_to_bind { - result_children.insert(key, child); - } - } result } @@ -206,156 +172,12 @@ impl Mountpoint { self.expired.store(false, Ordering::Release); } - fn propagation(&self) -> PropagationType { - *self.propagation.lock() - } - - pub fn is_shared(&self) -> bool { - self.propagation() == PropagationType::Shared - } - - pub fn is_slave(&self) -> bool { - self.propagation() == PropagationType::Slave - } - pub fn is_unbindable(&self) -> bool { - self.propagation() == PropagationType::Unbindable - } - - fn remove_from_shared_group(self: &Arc) { - let peers: Vec<_> = self.peers.lock().iter().filter_map(Weak::upgrade).collect(); - for peer in peers { - peer.peers.lock().retain(|candidate| { - candidate - .upgrade() - .is_some_and(|mp| !Arc::ptr_eq(&mp, self)) - }); - } - self.peers.lock().clear(); - } - - fn remove_from_masters(self: &Arc) { - let masters: Vec<_> = self - .masters - .lock() - .iter() - .filter_map(Weak::upgrade) - .collect(); - for master in masters { - master.slaves.lock().retain(|candidate| { - candidate - .upgrade() - .is_some_and(|mp| !Arc::ptr_eq(&mp, self)) - }); - } - self.masters.lock().clear(); - } - - pub fn set_shared(self: &Arc) { - self.remove_from_shared_group(); - self.remove_from_masters(); - *self.propagation.lock() = PropagationType::Shared; - } - - pub fn set_private(self: &Arc) { - self.remove_from_shared_group(); - self.remove_from_masters(); - *self.propagation.lock() = PropagationType::Private; - } - - pub fn set_slave(self: &Arc) { - let mut masters = Vec::new(); - if self.is_shared() { - masters.extend(self.peers.lock().iter().filter_map(Weak::upgrade)); - } - - self.remove_from_shared_group(); - self.remove_from_masters(); - *self.propagation.lock() = PropagationType::Slave; - for master in masters { - master.slaves.lock().push(Arc::downgrade(self)); - self.masters.lock().push(Arc::downgrade(&master)); - } + self.unbindable.load(Ordering::Acquire) } pub fn set_unbindable(self: &Arc) { - self.set_private(); - *self.propagation.lock() = PropagationType::Unbindable; - } - - pub fn join_shared_group(self: &Arc, source: &Arc) { - let mut group = vec![source.clone()]; - group.extend(source.peers.lock().iter().filter_map(Weak::upgrade)); - - self.set_shared(); - for member in group { - if Arc::ptr_eq(&member, self) { - continue; - } - member.peers.lock().push(Arc::downgrade(self)); - self.peers.lock().push(Arc::downgrade(&member)); - } - } - - fn attach_child(parent: &Arc, location: Location, child: &Arc) -> VfsResult<()> { - *location.entry.as_dir()?.mountpoint.lock() = Some(child.clone()); - parent - .children - .lock() - .insert(location.entry.key(), Arc::downgrade(child)); - Ok(()) - } - - fn propagate_new_child( - source_parent: &Arc, - source_location: &Location, - child: &Arc, - ) -> VfsResult<()> { - let peers: Vec<_> = source_parent - .peers - .lock() - .iter() - .filter_map(Weak::upgrade) - .collect(); - let slaves: Vec<_> = source_parent - .slaves - .lock() - .iter() - .filter_map(Weak::upgrade) - .collect(); - let mut path_components = vec![]; - let mut current = source_location.clone(); - while !current.is_root_of_mount() { - path_components.push(current.name().into_owned()); - current = current.parent().ok_or(VfsError::InvalidInput)?; - } - path_components.reverse(); - - for target_parent in peers.into_iter().chain(slaves) { - let mut location = target_parent.root_location(); - for component in &path_components { - location = location.lookup_no_follow(component)?; - } - let inserted_key = location.entry.key(); - Self::attach_child(&target_parent, location, child)?; - let mut resolved = target_parent.root_location(); - for component in &path_components { - resolved = resolved.lookup_no_follow(component)?; - } - if !Arc::ptr_eq(resolved.mountpoint(), child) { - warn!( - "mount propagation mismatch path={:?} inserted_key={:?} resolved_key={:?} \ - resolved_is_root={} resolved_mp_device={} replicated_device={}", - path_components, - inserted_key, - resolved.entry.key(), - resolved.is_root_of_mount(), - resolved.mountpoint().device(), - child.device(), - ); - } - } - Ok(()) + self.unbindable.store(true, Ordering::Release); } pub fn move_to(self: &Arc, new_location: &Location) -> VfsResult<()> { @@ -642,7 +464,6 @@ impl Location { pub fn mount(&self, fs: &Filesystem) -> VfsResult> { let result = Mountpoint::new(fs, Some(self.clone())); - let should_propagate = self.mountpoint.is_shared(); { let mut mountpoint = self.entry.as_dir()?.mountpoint.lock(); if mountpoint.is_some() { @@ -654,13 +475,10 @@ impl Location { .children .lock() .insert(self.entry.key(), Arc::downgrade(&result)); - if should_propagate { - Mountpoint::propagate_new_child(self.mountpoint(), self, &result)?; - } Ok(result) } - pub fn bind_mount(&self, source: &Self, recursive: bool) -> VfsResult> { + pub fn bind_mount(&self, source: &Self) -> VfsResult> { if source.mountpoint().is_unbindable() { return Err(VfsError::InvalidInput); } @@ -669,12 +487,7 @@ impl Location { if mountpoint.is_some() { return Err(VfsError::ResourceBusy); } - let result = Mountpoint::bind(source, self.clone(), recursive); - if source.mountpoint().is_shared() { - result.join_shared_group(source.mountpoint()); - } else if source.mountpoint().is_slave() { - result.set_slave(); - } + let result = Mountpoint::bind(source, self.clone()); *mountpoint = Some(result.clone()); self.mountpoint .children diff --git a/os/StarryOS/docs/mount-umount2-linux-compat.md b/os/StarryOS/docs/mount-umount2-linux-compat.md index 6d1f7c808b..13cce95c43 100644 --- a/os/StarryOS/docs/mount-umount2-linux-compat.md +++ b/os/StarryOS/docs/mount-umount2-linux-compat.md @@ -315,7 +315,7 @@ if propagation != 0 { - `flags & !allowed != 0` 对应 Linux 的“propagation type flags 只能和 `MS_REC` / `MS_SILENT` 共存” - 这里当前只是让 StarryOS 具备“不要误挂新 fs”的语义边界,真实传播机制仍是后续工作 -## 5.3 `axfs-ng-vfs::Mountpoint`:把 mountpoint 当成 mount 语义状态承载体 +## 5.3 `ax-fs-vfs::Mountpoint`:把 mountpoint 当成 mount 语义状态承载体 本次把几个 Linux mount 语义相关的状态放到了 `Mountpoint` 上: @@ -327,12 +327,13 @@ pub struct Mountpoint { device: u64, readonly: AtomicBool, expired: AtomicBool, + unbindable: AtomicBool, } ``` 对应代码位置: -- `components/axfs-ng-vfs/src/mount.rs` +- `components/axfs_crates/axfs_vfs/src/mount.rs` ### 为什么放在 `Mountpoint` 上 diff --git a/os/StarryOS/kernel/src/pseudofs/lockdep-tmpfs-analysis.md b/os/StarryOS/kernel/src/pseudofs/lockdep-tmpfs-analysis.md index efb40682be..91e389ddc1 100644 --- a/os/StarryOS/kernel/src/pseudofs/lockdep-tmpfs-analysis.md +++ b/os/StarryOS/kernel/src/pseudofs/lockdep-tmpfs-analysis.md @@ -358,49 +358,27 @@ Current status: - the lockdep cleanup should proceed separately from this possible `test-shm-deadlock` runtime race. -## Follow-up: FAT32/VFS lockdep visibility gap +## Follow-up: FAT32/VFS lock-order coverage -There is another possible filesystem lock-order issue that is not currently -covered by lockdep. - -The relevant FAT32 implementation uses the project-local kernel spin lock: +The previous VFS lockdep visibility gap has been closed in the unified FS +stack: ```text -os/arceos/modules/axfs-ng/src/fs/fat/fs.rs: - use ax_kspin::{SpinNoPreempt as Mutex, SpinNoPreemptGuard as MutexGuard}; +components/axfs_crates/axfs_vfs/src/lib.rs: + use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; ``` -So the FAT filesystem lock is visible to lockdep when `ax-kspin/lockdep` is -enabled. - -However, the VFS layer still imports the third-party `spin` crate directly: +FAT also uses the project-local kernel spin lock: ```text -components/axfs-ng-vfs/src/lib.rs: - use spin::{Mutex, MutexGuard}; +os/arceos/modules/axfs/src/fs/fat/fs.rs: + use ax_kspin::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; ``` -That dependency was already present when `axfs-ng-vfs` was imported as a -subtree: - -```text -components/axfs-ng-vfs/Cargo.toml: - spin = { version = "0.10", default-features = false, features = ["mutex"] } -``` +This means VFS cache locks and FAT filesystem locks are both visible to +lockdep when `ax-kspin/lockdep` is enabled. -`ax-kspin` is related but not identical to this external `spin` crate. Its -`BaseSpinLock` is explicitly based on `spin::Mutex`, but it is a separate -project-local implementation that adds kernel guard semantics and, with the -current lockdep work, lockdep acquire/release hooks. - -This creates a lockdep blind spot: - -- `ax_kspin::SpinNoPreempt` locks are visible to lockdep; -- `spin::Mutex` locks in `axfs-ng-vfs` are not visible to lockdep; -- any dependency edge involving a VFS `spin::Mutex` therefore cannot be - recorded. - -The suspected FAT32/VFS ordering is: +The remaining ordering that should be covered by a targeted FAT32 case is: ```text DirNode.cache lock -> FAT filesystem lock @@ -426,42 +404,11 @@ FatDirNode::read_dir() -> DirNode.cache lock ``` -This is a real ABBA-shaped ordering risk, not a subclass problem like the tmpfs -parent/child entry lock report. The reason current lockdep may not report it is -that one side of the pair, `DirNode.cache`, is outside the lockdep-visible lock -set. - Current implication: -- a missing lockdep report does not prove the FAT32/VFS ordering is safe; -- the current lockdep coverage is incomplete for `axfs-ng-vfs` internals; -- FAT32-specific testing may also be absent from the normal Starry QEMU path, - so the code path might not be exercised even if all locks were visible. - -Future work should consider migrating suitable `axfs-ng-vfs` internal locks -from third-party `spin::Mutex` to `ax_kspin`. - -That migration should not be treated as a mechanical rename. The lock type must -match the context: - -- `SpinNoPreempt` is probably the first candidate for VFS cache locks if they - are only used in task context; -- `SpinNoIrq` may be needed only for locks that can be taken from IRQ-enabled - contexts where interrupt-side reentry is possible; -- `SpinRaw` should remain reserved for contexts that already guarantee the - necessary preemption/IRQ state externally. - -Before changing the VFS lock type, the code paths that access `DirNode.cache` -and `DirEntry` user data should be checked for: - -- use from interrupt context; -- use while holding block-device or filesystem implementation locks; -- nested VFS operations through callbacks such as directory-entry sinks; -- layout or dependency impact on `axfs-ng-vfs`, which is a component crate and - not only a StarryOS-local module. - -After such a migration, the FAT32/VFS ordering should be retested with a -targeted FAT32 case. If lockdep then reports the suspected ABBA, the fix should -be a real ordering change, not a subclass annotation: either avoid holding the -VFS cache lock across filesystem backend callbacks, or establish a single -stable order between VFS cache locks and filesystem implementation locks. +- a missing lockdep report is now more meaningful because both lock classes are + visible; +- FAT32-specific testing may still be absent from the normal Starry QEMU path, + so the code path should be exercised by a targeted FAT32 case; +- if lockdep reports this ABBA pattern, the fix should be a real ordering + change, not a subclass annotation. diff --git a/os/StarryOS/kernel/src/syscall/fs/mount.rs b/os/StarryOS/kernel/src/syscall/fs/mount.rs index 71639e4ee6..1f4f840320 100644 --- a/os/StarryOS/kernel/src/syscall/fs/mount.rs +++ b/os/StarryOS/kernel/src/syscall/fs/mount.rs @@ -85,17 +85,13 @@ pub fn sys_mount( return Err(AxError::InvalidInput); } + let _ = (MS_SHARED, MS_PRIVATE, MS_SLAVE); let target = FS_CONTEXT.lock().resolve(target)?; if !target.is_root_of_mount() { return Err(AxError::InvalidInput); } - let mountpoint = target.mountpoint().clone(); - match propagation { - MS_SHARED => mountpoint.set_shared(), - MS_PRIVATE => mountpoint.set_private(), - MS_SLAVE => mountpoint.set_slave(), - MS_UNBINDABLE => mountpoint.set_unbindable(), - _ => {} + if propagation == MS_UNBINDABLE { + target.mountpoint().set_unbindable(); } return Ok(0); } @@ -123,7 +119,7 @@ pub fn sys_mount( let ctx = FS_CONTEXT.lock(); let source = ctx.resolve(source)?; let target = ctx.resolve(target)?; - let mp = target.bind_mount(&source, (flags & MS_REC) != 0)?; + let mp = target.bind_mount(&source)?; if (flags & MS_RDONLY) != 0 { mp.set_readonly(true); } diff --git a/os/arceos/api/axfeat/Cargo.toml b/os/arceos/api/axfeat/Cargo.toml index cb84601a9d..c54b7733aa 100644 --- a/os/arceos/api/axfeat/Cargo.toml +++ b/os/arceos/api/axfeat/Cargo.toml @@ -81,8 +81,10 @@ fs = [ "dep:ax-fs", "ax-runtime/fs", ] # TODO: try to remove "paging" -fs-ext4 = ["fs", "ax-fs/ext4"] -fs-fat = ["fs", "ax-fs/fat"] +fs-ext4 = ["fs", "ax-fs/ext4", "ax-runtime/fs-ext4"] +fs-fat = ["fs", "ax-fs/fat", "ax-runtime/fs-fat"] +fs-devfs = ["fs", "ax-fs/devfs"] +fs-ramfs = ["fs", "ax-fs/ramfs"] fs-times = ["fs", "ax-fs/times"] # Networking diff --git a/os/arceos/modules/axfs/src/fs_policy.rs b/os/arceos/modules/axfs/src/fs_policy.rs new file mode 100644 index 0000000000..9ce8273f71 --- /dev/null +++ b/os/arceos/modules/axfs/src/fs_policy.rs @@ -0,0 +1,758 @@ +use alloc::{ + boxed::Box, + format, + string::{String, ToString}, + vec::Vec, +}; + +use rd_block_volume::{BlockVolume, DiskId, PartitionTableKind as VolumeTableKind, scan_volumes}; +use spin::Once; + +use crate::{ + BlockRegion, FilesystemKind, FsBlockDevice, FsContext, SharedBlockDevice, VolumeReader, + init_root_filesystem, new_filesystem_from_dyn, new_filesystem_from_dyn_by_kind, +}; + +#[derive(Debug, Default)] +struct RootSpec { + disk_index: Option, + partition_index: Option, + partuuid: Option, + partlabel: Option, +} + +struct RootCandidate { + disk_index: usize, + partition: Option, + filesystem: Option, +} + +#[derive(Clone)] +struct DiscoveredDisk { + disk_index: usize, + dev: SharedBlockDevice, + raw_filesystem: Option, + partitions: Vec, +} + +#[derive(Clone)] +struct DetectedPartition { + info: PartitionInfo, + filesystem: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +struct PartitionInfo { + index: usize, + table_kind: PartitionTableKind, + region: BlockRegion, + name: Option, + part_uuid: Option, + bootable: bool, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum PartitionTableKind { + Raw, + Gpt, + Mbr, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct RootSelection { + disk_index: usize, + partition_index: Option, + filesystem: Option, +} + +/// A filesystem-bearing region discovered from the available block devices. +#[derive(Clone, Debug)] +pub struct DiscoveredFilesystem { + /// Zero-based physical block device index. + pub disk_index: usize, + /// Zero-based partition index, or `None` for a raw whole-disk filesystem. + pub partition_index: Option, + /// Block range occupied by this filesystem candidate. + pub region: BlockRegion, + /// Detected filesystem kind, if the region is recognizable. + pub filesystem: Option, + /// Partition label/name, when available. + pub name: Option, + /// Partition UUID, when available. + pub part_uuid: Option, + /// Whether an MBR partition is marked bootable. + pub bootable: bool, + /// Whether this region was selected as the root filesystem. + pub is_root: bool, +} + +static DISCOVERED_DISKS: Once> = Once::new(); +static ROOT_SELECTION: Once = Once::new(); + +impl RootCandidate { + fn selection(&self) -> RootSelection { + RootSelection { + disk_index: self.disk_index, + partition_index: self.partition.as_ref().map(|part| part.info.index), + filesystem: self.filesystem, + } + } + + fn description(&self) -> String { + if let Some(partition) = &self.partition { + let name = partition.info.name.as_deref().unwrap_or(""); + let fs = partition + .filesystem + .map(filesystem_name) + .unwrap_or("unknown"); + format!( + "disk{} partition {} ({}, fs={}, lba {}..{})", + self.disk_index, + partition.info.index + 1, + name, + fs, + partition.info.region.start_lba, + partition.info.region.end_lba + ) + } else { + let fs = self.filesystem.map(filesystem_name).unwrap_or("unknown"); + format!("disk{} raw device (fs={})", self.disk_index, fs) + } + } +} + +pub fn init_filesystems(block_devs: Vec>, bootargs: Option<&str>) { + info!("Select root filesystem..."); + + let root_spec = parse_root_spec(bootargs); + let mut disks = collect_disks(block_devs); + let candidates = collect_root_candidates(&disks); + let selection = select_root_candidate(&candidates, &root_spec) + .unwrap_or_else(|| panic!("failed to determine root device from available block devices")); + let selected_disk_pos = disks + .iter() + .position(|disk| disk.disk_index == selection.disk_index) + .unwrap_or_else(|| panic!("selected root disk disappeared during initialization")); + DISCOVERED_DISKS.call_once(|| disks.clone()); + ROOT_SELECTION.call_once(|| selection); + let selected = disks.swap_remove(selected_disk_pos); + let (description, region) = { + let selected_partition_info = selection.partition_index.and_then(|part_index| { + selected + .partitions + .iter() + .find(|partition| partition.info.index == part_index) + }); + ( + describe_selection( + selected.disk_index, + selected_partition_info, + selection.filesystem, + ), + selected_partition_info + .map_or_else(|| full_region(&selected.dev), |part| part.info.region), + ) + }; + info!(" selected root device: {}", description); + + let fs = match selection.filesystem { + Some(kind) => new_filesystem_from_dyn_by_kind(Box::new(selected.dev.clone()), region, kind), + None => new_filesystem_from_dyn(Box::new(selected.dev.clone()), region), + } + .unwrap_or_else(|err| { + panic!( + "failed to initialize filesystem on {}: {err:?}", + description + ) + }); + init_root_filesystem(fs); +} + +/// Returns the block filesystems discovered during runtime initialization. +/// +/// The root filesystem is mounted automatically. Other regions can be mounted +/// by OS policy code with [`mount_discovered_filesystem`]. +pub fn discovered_filesystems() -> Vec { + let Some(disks) = DISCOVERED_DISKS.get() else { + return Vec::new(); + }; + let root_selection = ROOT_SELECTION.get().copied(); + + let mut filesystems = Vec::new(); + for disk in disks { + if disk.partitions.is_empty() { + let selection = RootSelection { + disk_index: disk.disk_index, + partition_index: None, + filesystem: disk.raw_filesystem, + }; + filesystems.push(DiscoveredFilesystem { + disk_index: disk.disk_index, + partition_index: None, + region: full_region(&disk.dev), + filesystem: disk.raw_filesystem, + name: None, + part_uuid: None, + bootable: false, + is_root: root_selection == Some(selection), + }); + continue; + } + + for partition in &disk.partitions { + let selection = RootSelection { + disk_index: disk.disk_index, + partition_index: Some(partition.info.index), + filesystem: partition.filesystem, + }; + filesystems.push(DiscoveredFilesystem { + disk_index: disk.disk_index, + partition_index: Some(partition.info.index), + region: partition.info.region, + filesystem: partition.filesystem, + name: partition.info.name.clone(), + part_uuid: partition.info.part_uuid.clone(), + bootable: partition.info.bootable, + is_root: root_selection == Some(selection), + }); + } + } + filesystems +} + +/// Mounts a discovered non-root filesystem at an existing VFS location. +pub fn mount_discovered_filesystem( + ctx: &FsContext, + disk_index: usize, + partition_index: Option, + target: impl AsRef, +) -> ax_fs_vfs::VfsResult<()> { + let (dev, region, kind) = discovered_region(disk_index, partition_index)?; + let selection = RootSelection { + disk_index, + partition_index, + filesystem: Some(kind), + }; + if ROOT_SELECTION.get().copied() == Some(selection) { + return Err(ax_fs_vfs::VfsError::ResourceBusy); + } + + let fs = new_filesystem_from_dyn_by_kind(Box::new(dev), region, kind)?; + let target = ctx.resolve(target)?; + target.mount(&fs).map(|_| ()) +} + +fn collect_disks(block_devs: Vec>) -> Vec { + let mut disks = Vec::new(); + + for (disk_index, dev) in block_devs.into_iter().enumerate() { + let device_name = dev.name().to_string(); + let dev = SharedBlockDevice::new(dev); + let mut scan_dev = dev.clone(); + let mut reader = VolumeReader::new(&mut scan_dev); + match scan_volumes(&mut reader, DiskId(disk_index as u64)) { + Ok(volumes) => { + let raw_region = volumes + .iter() + .find(|volume| volume.table_kind == VolumeTableKind::Raw) + .map(region_from_volume) + .unwrap_or_else(|| full_region(&dev)); + let partitions: Vec = volumes + .into_iter() + .filter(|volume| volume.table_kind != VolumeTableKind::Raw) + .map(|volume| { + let partition = partition_info_from_volume(&volume); + let mut detect_dev = dev.clone(); + let filesystem = detect_filesystem(&mut detect_dev, partition.region); + info!( + " partition {} name={:?} fs={:?} lba {}..{}", + partition.index + 1, + partition.name, + filesystem, + partition.region.start_lba, + partition.region.end_lba + ); + DetectedPartition { + info: partition, + filesystem, + } + }) + .collect(); + let raw_filesystem = if partitions.is_empty() { + let mut detect_dev = dev.clone(); + let raw_fs = detect_filesystem(&mut detect_dev, raw_region); + info!(" raw device fs={:?}", raw_fs); + raw_fs + } else { + None + }; + if let Some(first) = partitions.first() { + info!( + " block device {} ({}) has {:?} partition table with {} partitions", + disk_index, + device_name, + first.info.table_kind, + partitions.len() + ); + } else { + info!( + " block device {} ({}) has no usable partition table; treating the whole \ + disk as a candidate", + disk_index, device_name + ); + } + disks.push(DiscoveredDisk { + disk_index, + dev, + raw_filesystem, + partitions, + }); + } + Err(err) => { + warn!( + " failed to scan partitions on block device {} ({}): {err:?}", + disk_index, device_name + ); + } + } + } + + disks +} + +fn partition_info_from_volume(volume: &BlockVolume) -> PartitionInfo { + PartitionInfo { + index: volume + .partition_id + .0 + .checked_sub(1) + .map(|index| index as usize) + .unwrap_or(0), + table_kind: table_kind_from_volume(volume.table_kind), + region: region_from_volume(volume), + name: volume.partlabel.as_ref().map(|label| label.0.clone()), + part_uuid: volume.partuuid.as_ref().map(|uuid| uuid.0.clone()), + bootable: volume.bootable, + } +} + +fn region_from_volume(volume: &BlockVolume) -> BlockRegion { + BlockRegion::new(volume.region.start_block, volume.region.num_blocks) +} + +fn table_kind_from_volume(kind: VolumeTableKind) -> PartitionTableKind { + match kind { + VolumeTableKind::Raw => PartitionTableKind::Raw, + VolumeTableKind::Gpt => PartitionTableKind::Gpt, + VolumeTableKind::Mbr => PartitionTableKind::Mbr, + } +} + +fn collect_root_candidates(disks: &[DiscoveredDisk]) -> Vec { + let mut candidates = Vec::new(); + + for disk in disks { + if disk.partitions.is_empty() { + candidates.push(RootCandidate { + disk_index: disk.disk_index, + partition: None, + filesystem: disk.raw_filesystem, + }); + continue; + } + + for partition in &disk.partitions { + candidates.push(RootCandidate { + disk_index: disk.disk_index, + filesystem: partition.filesystem, + partition: Some(partition.clone()), + }); + } + } + + candidates +} + +fn select_root_candidate(candidates: &[RootCandidate], spec: &RootSpec) -> Option { + if let Some(index) = select_explicit_root(candidates, spec) { + return Some(index); + } + + select_default_root(candidates) +} + +fn select_explicit_root(candidates: &[RootCandidate], spec: &RootSpec) -> Option { + for candidate in candidates { + if let Some(partition) = candidate.partition.as_ref() { + if let Some(partuuid) = &spec.partuuid + && partition + .info + .part_uuid + .as_ref() + .is_some_and(|candidate_uuid| candidate_uuid.eq_ignore_ascii_case(partuuid)) + { + info!(" matched root by PARTUUID on {}", candidate.description()); + return Some(candidate.selection()); + } + + if let Some(partlabel) = &spec.partlabel + && partition.info.name.as_deref() == Some(partlabel.as_str()) + { + info!(" matched root by PARTLABEL on {}", candidate.description()); + return Some(candidate.selection()); + } + } + + if let Some(disk_index) = spec.disk_index + && candidate.disk_index == disk_index + { + match (spec.partition_index, &candidate.partition) { + (Some(partition_index), Some(partition)) + if partition.info.index == partition_index => + { + info!( + " matched root by device path on {}", + candidate.description() + ); + return Some(candidate.selection()); + } + (None, None) => { + info!( + " matched root by raw device path on {}", + candidate.description() + ); + return Some(candidate.selection()); + } + _ => {} + } + } + } + + if spec.disk_index.is_some() || spec.partuuid.is_some() || spec.partlabel.is_some() { + panic!("configured root device was not found in discovered block devices"); + } + + None +} + +fn select_default_root(candidates: &[RootCandidate]) -> Option { + let rootfs_matches: Vec<_> = candidates + .iter() + .filter(|candidate| { + candidate + .partition + .as_ref() + .and_then(|part| part.info.name.as_deref()) + == Some("rootfs") + && is_supported_filesystem(candidate.filesystem) + }) + .map(RootCandidate::selection) + .collect(); + if rootfs_matches.len() == 1 { + info!(" falling back to PARTLABEL=rootfs"); + return rootfs_matches.into_iter().next(); + } + if rootfs_matches.len() > 1 { + panic!("multiple partitions are labeled 'rootfs'; specify root= explicitly"); + } + + let partition_matches: Vec<_> = candidates + .iter() + .filter(|candidate| { + candidate + .partition + .as_ref() + .is_some_and(is_default_root_partition) + }) + .map(RootCandidate::selection) + .collect(); + if partition_matches.len() == 1 { + info!(" only one supported filesystem partition is available; using it as root"); + return partition_matches.into_iter().next(); + } + + let raw_matches: Vec<_> = candidates + .iter() + .filter(|candidate| candidate.partition.is_none()) + .map(RootCandidate::selection) + .collect(); + if partition_matches.is_empty() && raw_matches.len() == 1 { + info!(" only one raw block device is available; using it as root"); + return raw_matches.into_iter().next(); + } + + None +} + +fn is_default_root_partition(partition: &DetectedPartition) -> bool { + if !is_supported_filesystem(partition.filesystem) { + return false; + } + + match partition.info.table_kind { + PartitionTableKind::Mbr => { + #[cfg(feature = "ext4")] + if partition.filesystem == Some(FilesystemKind::Ext4) { + return partition.info.bootable; + } + #[cfg(feature = "fat")] + if partition.filesystem == Some(FilesystemKind::Fat) { + return true; + } + false + } + PartitionTableKind::Gpt | PartitionTableKind::Raw => true, + } +} + +fn discovered_region( + disk_index: usize, + partition_index: Option, +) -> ax_fs_vfs::VfsResult<(SharedBlockDevice, BlockRegion, FilesystemKind)> { + let disks = DISCOVERED_DISKS + .get() + .ok_or(ax_fs_vfs::VfsError::NotFound)?; + let disk = disks + .iter() + .find(|disk| disk.disk_index == disk_index) + .ok_or(ax_fs_vfs::VfsError::NotFound)?; + + if let Some(partition_index) = partition_index { + let partition = disk + .partitions + .iter() + .find(|partition| partition.info.index == partition_index) + .ok_or(ax_fs_vfs::VfsError::NotFound)?; + let kind = partition + .filesystem + .ok_or(ax_fs_vfs::VfsError::Unsupported)?; + return Ok((disk.dev.clone(), partition.info.region, kind)); + } + + if !disk.partitions.is_empty() { + return Err(ax_fs_vfs::VfsError::InvalidInput); + } + let kind = disk + .raw_filesystem + .ok_or(ax_fs_vfs::VfsError::Unsupported)?; + Ok((disk.dev.clone(), full_region(&disk.dev), kind)) +} + +const fn is_supported_filesystem(fs: Option) -> bool { + match fs { + #[cfg(feature = "ext4")] + Some(FilesystemKind::Ext4) => true, + #[cfg(feature = "fat")] + Some(FilesystemKind::Fat) => true, + _ => false, + } +} + +fn describe_selection( + disk_index: usize, + partition: Option<&DetectedPartition>, + filesystem: Option, +) -> String { + if let Some(partition) = partition { + let name = partition.info.name.as_deref().unwrap_or(""); + let fs = partition + .filesystem + .map(filesystem_name) + .unwrap_or("unknown"); + format!( + "disk{} partition {} ({}, fs={}, lba {}..{})", + disk_index, + partition.info.index + 1, + name, + fs, + partition.info.region.start_lba, + partition.info.region.end_lba + ) + } else { + let fs = filesystem.map(filesystem_name).unwrap_or("unknown"); + format!("disk{} raw device (fs={})", disk_index, fs) + } +} + +fn parse_root_spec(bootargs: Option<&str>) -> RootSpec { + let mut spec = RootSpec::default(); + + if let Some(bootargs) = bootargs + && let Some(root_arg) = bootargs + .split_whitespace() + .find(|arg| arg.starts_with("root=")) + { + let root_value = root_arg.strip_prefix("root=").unwrap_or(""); + spec = match root_value { + value if value.starts_with("/dev/mmcblk") => parse_mmcblk_path(value), + value if value.starts_with("/dev/sd") => parse_sd_path(value), + value if value.starts_with("PARTUUID=") => RootSpec { + partuuid: Some(value.strip_prefix("PARTUUID=").unwrap_or("").to_uppercase()), + ..RootSpec::default() + }, + value if value.starts_with("PARTLABEL=") => RootSpec { + partlabel: Some(value.strip_prefix("PARTLABEL=").unwrap_or("").to_string()), + ..RootSpec::default() + }, + _ => RootSpec::default(), + }; + } + + spec +} + +fn parse_mmcblk_path(path: &str) -> RootSpec { + if let Some(remaining) = path.strip_prefix("/dev/mmcblk") { + if let Some(p_pos) = remaining.find('p') { + let disk = remaining[..p_pos].parse::().ok(); + let part = remaining[p_pos + 1..] + .parse::() + .ok() + .and_then(|part| part.checked_sub(1)); + return RootSpec { + disk_index: disk, + partition_index: part, + ..RootSpec::default() + }; + } + + if let Ok(disk) = remaining.parse::() { + return RootSpec { + disk_index: Some(disk), + ..RootSpec::default() + }; + } + } + + RootSpec::default() +} + +fn parse_sd_path(path: &str) -> RootSpec { + if let Some(remaining) = path.strip_prefix("/dev/sd") { + let bytes = remaining.as_bytes(); + if bytes.is_empty() { + return RootSpec::default(); + } + + let disk_index = bytes[0] + .is_ascii_lowercase() + .then(|| usize::from(bytes[0] - b'a')); + let partition_index = if bytes.len() > 1 { + core::str::from_utf8(&bytes[1..]) + .ok() + .and_then(|part| part.parse::().ok()) + .and_then(|part| part.checked_sub(1)) + } else { + None + }; + return RootSpec { + disk_index, + partition_index, + ..RootSpec::default() + }; + } + + RootSpec::default() +} + +fn detect_filesystem(dev: &mut impl FsBlockDevice, region: BlockRegion) -> Option { + #[cfg(not(any(feature = "ext4", feature = "fat")))] + let _ = (&mut *dev, region); + + #[cfg(feature = "ext4")] + if region_has_ext4(dev, region) { + return Some(FilesystemKind::Ext4); + } + + #[cfg(feature = "fat")] + if region_has_fat(dev, region) { + return Some(FilesystemKind::Fat); + } + + None +} + +#[cfg(feature = "ext4")] +fn region_has_ext4(dev: &mut impl FsBlockDevice, region: BlockRegion) -> bool { + const EXT4_SUPERBLOCK_OFFSET: usize = 1024; + const EXT4_MAGIC_OFFSET: usize = 0x38; + const EXT4_MAGIC: u16 = 0xEF53; + region_has_magic_u16( + dev, + region, + EXT4_SUPERBLOCK_OFFSET + EXT4_MAGIC_OFFSET, + EXT4_MAGIC, + ) +} + +#[cfg(feature = "fat")] +fn region_has_fat(dev: &mut impl FsBlockDevice, region: BlockRegion) -> bool { + const FAT16_MAGIC: &[u8; 5] = b"FAT16"; + const FAT32_MAGIC: &[u8; 5] = b"FAT32"; + let start_lba = region.start_lba; + let visible_blocks = region.num_blocks(); + if visible_blocks == 0 { + return false; + } + + let block_size = dev.block_size(); + if block_size < 512 { + return false; + } + + let mut buf = alloc::vec![0u8; block_size]; + if dev.read_block(start_lba, &mut buf).is_err() { + return false; + } + + buf.get(510..512) == Some([0x55, 0xAA].as_slice()) + && (buf.get(54..59) == Some(FAT16_MAGIC.as_slice()) + || buf.get(82..87) == Some(FAT32_MAGIC.as_slice())) +} + +#[cfg(feature = "ext4")] +fn region_has_magic_u16( + dev: &mut impl FsBlockDevice, + region: BlockRegion, + byte_offset: usize, + magic: u16, +) -> bool { + let block_size = dev.block_size(); + if block_size == 0 { + return false; + } + + let start_lba = region.start_lba; + let visible_blocks = region.num_blocks(); + let block_index = byte_offset / block_size; + let within_block = byte_offset % block_size; + if visible_blocks == 0 || within_block + 2 > block_size { + return false; + } + + let Some(block_index_u64) = u64::try_from(block_index).ok() else { + return false; + }; + let Some(end_lba) = start_lba.checked_add(visible_blocks) else { + return false; + }; + let block_id = match start_lba.checked_add(block_index_u64) { + Some(block_id) if block_id < end_lba => block_id, + _ => return false, + }; + + let mut buf = alloc::vec![0u8; block_size]; + if dev.read_block(block_id, &mut buf).is_err() { + return false; + } + + u16::from_le_bytes([buf[within_block], buf[within_block + 1]]) == magic +} + +fn full_region(dev: &impl FsBlockDevice) -> BlockRegion { + BlockRegion::from_num_blocks(dev.num_blocks()) +} + +fn filesystem_name(kind: FilesystemKind) -> &'static str { + kind.name() +} diff --git a/os/arceos/modules/axfs/src/highlevel/fs.rs b/os/arceos/modules/axfs/src/highlevel/fs.rs index 374675cf4c..d012cc1aae 100644 --- a/os/arceos/modules/axfs/src/highlevel/fs.rs +++ b/os/arceos/modules/axfs/src/highlevel/fs.rs @@ -20,7 +20,8 @@ use super::File; /// Maximum number of symlinks that will be followed during path resolution. pub const SYMLINKS_MAX: usize = 40; -/// Global root filesystem context, initialized once during [`init_filesystems`](crate::init_filesystems). +/// Global root filesystem context, initialized once during +/// [`init_root_filesystem`](crate::init_root_filesystem). pub static ROOT_FS_CONTEXT: Once = Once::new(); /// Registry of all live `FsContext` instances (weak references). diff --git a/os/arceos/modules/axfs/src/lib.rs b/os/arceos/modules/axfs/src/lib.rs index 040af1330b..e34c11a535 100644 --- a/os/arceos/modules/axfs/src/lib.rs +++ b/os/arceos/modules/axfs/src/lib.rs @@ -9,9 +9,11 @@ //! - Primary filesystem API: [`File`], [`OpenOptions`], [`FsContext`], and //! [`FS_CONTEXT`] are the shared entry points used by ArceOS, StarryOS, and //! Axvisor-facing library code. -//! - Runtime glue: [`init_filesystems`] consumes block devices from -//! `ax-driver`, selects the root filesystem, and initializes the global root -//! context. +//! - Filesystem construction: [`new_filesystem_from_dyn`] and +//! [`new_filesystem_from_dyn_by_kind`] create mountable filesystems from +//! caller-provided block devices. +//! - Runtime filesystem initialization: [`init_filesystems`] scans block +//! devices, selects a root filesystem, and initializes [`FS_CONTEXT`]. #![cfg_attr(all(not(test), not(doc)), no_std)] #![allow(clippy::new_ret_no_self)] @@ -21,775 +23,34 @@ extern crate alloc; #[macro_use] extern crate log; -use alloc::{ - boxed::Box, - format, - string::{String, ToString}, - vec::Vec, -}; - -use ax_fs_vfs::{VfsError, VfsResult, path::Path}; -use fs::filesystem_name; -use rd_block_volume::{BlockVolume, DiskId, PartitionTableKind as VolumeTableKind, scan_volumes}; -use spin::Once; - -use crate::block::{SharedBlockDevice, VolumeReader}; +use ax_fs_vfs::Filesystem; mod block; mod fs; +mod fs_policy; mod highlevel; #[cfg(feature = "devfs")] pub use ax_fs_devfs as devfs; #[cfg(feature = "ramfs")] pub use ax_fs_ramfs as ramfs; -pub use block::{BlockRegion, FsBlockDevice}; +pub use block::{BlockRegion, FsBlockDevice, SharedBlockDevice, VolumeReader}; /// Create a filesystem from a dynamic (boxed) block device. pub use fs::{ FilesystemKind, new_from_dyn as new_filesystem_from_dyn, new_from_dyn_by_kind as new_filesystem_from_dyn_by_kind, }; +pub use fs_policy::{ + DiscoveredFilesystem, discovered_filesystems, init_filesystems, mount_discovered_filesystem, +}; pub use highlevel::*; -#[derive(Debug, Default)] -struct RootSpec { - disk_index: Option, - partition_index: Option, - partuuid: Option, - partlabel: Option, -} - -struct RootCandidate { - disk_index: usize, - partition: Option, - filesystem: Option, -} - -#[derive(Clone)] -struct DiscoveredDisk { - disk_index: usize, - dev: SharedAxBlockDevice, - raw_filesystem: Option, - partitions: Vec, -} - -#[derive(Clone)] -struct DetectedPartition { - info: PartitionInfo, - filesystem: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq)] -struct PartitionInfo { - index: usize, - table_kind: PartitionTableKind, - region: BlockRegion, - name: Option, - part_uuid: Option, - bootable: bool, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum PartitionTableKind { - Raw, - Gpt, - Mbr, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -struct RootSelection { - disk_index: usize, - partition_index: Option, - filesystem: Option, -} - -type SharedAxBlockDevice = SharedBlockDevice; - -static DISCOVERED_DISKS: Once> = Once::new(); -static ROOT_SELECTION: Once = Once::new(); - -/// A filesystem-bearing region discovered from the available block devices. -#[derive(Clone, Debug)] -pub struct DiscoveredFilesystem { - /// Zero-based physical block device index. - pub disk_index: usize, - /// Zero-based partition index, or `None` for a raw whole-disk filesystem. - pub partition_index: Option, - /// Block range occupied by this filesystem candidate. - pub region: BlockRegion, - /// Detected filesystem kind, if the region is recognizable. - pub filesystem: Option, - /// Partition label/name, when available. - pub name: Option, - /// Partition UUID, when available. - pub part_uuid: Option, - /// Whether an MBR partition is marked bootable. - pub bootable: bool, - /// Whether this region was selected as the root filesystem. - pub is_root: bool, -} - -impl RootCandidate { - fn selection(&self) -> RootSelection { - RootSelection { - disk_index: self.disk_index, - partition_index: self.partition.as_ref().map(|part| part.info.index), - filesystem: self.filesystem, - } - } - - fn description(&self) -> String { - if let Some(partition) = &self.partition { - let name = partition.info.name.as_deref().unwrap_or(""); - let fs = partition - .filesystem - .map(filesystem_name) - .unwrap_or("unknown"); - format!( - "disk{} partition {} ({}, fs={}, lba {}..{})", - self.disk_index, - partition.info.index + 1, - name, - fs, - partition.info.region.start_lba, - partition.info.region.end_lba - ) - } else { - let fs = self.filesystem.map(filesystem_name).unwrap_or("unknown"); - format!("disk{} raw device (fs={})", self.disk_index, fs) - } - } -} - -/// Initializes the filesystem subsystem by selecting a root device from the -/// available block devices and optional boot arguments. -pub fn init_filesystems(block_devs: Vec>, bootargs: Option<&str>) { +/// Initializes the global root filesystem context from an already constructed +/// filesystem. +pub fn init_root_filesystem(fs: Filesystem) { info!("Initialize filesystem subsystem..."); - - let root_spec = parse_root_spec(bootargs); - let mut disks = collect_disks(block_devs); - let candidates = collect_root_candidates(&disks); - let selection = select_root_candidate(&candidates, &root_spec) - .unwrap_or_else(|| panic!("failed to determine root device from available block devices")); - let selected_disk_pos = disks - .iter() - .position(|disk| disk.disk_index == selection.disk_index) - .unwrap_or_else(|| panic!("selected root disk disappeared during initialization")); - DISCOVERED_DISKS.call_once(|| disks.clone()); - ROOT_SELECTION.call_once(|| selection); - let selected = disks.swap_remove(selected_disk_pos); - let (description, region) = { - let selected_partition_info = selection.partition_index.and_then(|part_index| { - selected - .partitions - .iter() - .find(|partition| partition.info.index == part_index) - }); - ( - describe_selection( - selected.disk_index, - selected_partition_info, - selection.filesystem, - ), - selected_partition_info - .map_or_else(|| full_region(&selected.dev), |part| part.info.region), - ) - }; - info!(" selected root device: {}", description); - - let fs = match selection.filesystem { - Some(kind) => fs::new_by_kind_from_dyn(Box::new(selected.dev.clone()), region, kind), - None => fs::new_default_from_dyn(Box::new(selected.dev.clone()), region), - } - .unwrap_or_else(|err| { - panic!( - "failed to initialize filesystem on {}: {err:?}", - description - ) - }); info!(" filesystem type: {:?}", fs.name()); - let mp = ax_fs_vfs::Mountpoint::new_root(&fs); let root = mp.root_location(); ROOT_FS_CONTEXT.call_once(|| FsContext::new(root)); } - -fn collect_disks(block_devs: Vec>) -> Vec { - let mut disks = Vec::new(); - - for (disk_index, dev) in block_devs.into_iter().enumerate() { - let device_name = dev.name().to_string(); - let dev = SharedBlockDevice::new(dev); - let mut scan_dev = dev.clone(); - let mut reader = VolumeReader::new(&mut scan_dev); - match scan_volumes(&mut reader, DiskId(disk_index as u64)) { - Ok(volumes) => { - let raw_region = volumes - .iter() - .find(|volume| volume.table_kind == VolumeTableKind::Raw) - .map(region_from_volume) - .unwrap_or_else(|| full_region(&dev)); - let partitions: Vec = volumes - .into_iter() - .filter(|volume| volume.table_kind != VolumeTableKind::Raw) - .map(|volume| { - let partition = partition_info_from_volume(&volume); - let mut detect_dev = dev.clone(); - let filesystem = detect_filesystem(&mut detect_dev, partition.region); - info!( - " partition {} name={:?} fs={:?} lba {}..{}", - partition.index + 1, - partition.name, - filesystem, - partition.region.start_lba, - partition.region.end_lba - ); - DetectedPartition { - info: partition, - filesystem, - } - }) - .collect(); - let raw_filesystem = if partitions.is_empty() { - let mut detect_dev = dev.clone(); - let raw_fs = detect_filesystem(&mut detect_dev, raw_region); - info!(" raw device fs={:?}", raw_fs); - raw_fs - } else { - None - }; - if let Some(first) = partitions.first() { - info!( - " block device {} ({}) has {:?} partition table with {} partitions", - disk_index, - device_name, - first.info.table_kind, - partitions.len() - ); - } else { - info!( - " block device {} ({}) has no usable partition table; treating the whole \ - disk as a candidate", - disk_index, device_name - ); - } - disks.push(DiscoveredDisk { - disk_index, - dev, - raw_filesystem, - partitions, - }); - } - Err(err) => { - warn!( - " failed to scan partitions on block device {} ({}): {err:?}", - disk_index, device_name - ); - } - } - } - - disks -} - -fn partition_info_from_volume(volume: &BlockVolume) -> PartitionInfo { - PartitionInfo { - index: volume - .partition_id - .0 - .checked_sub(1) - .map(|index| index as usize) - .unwrap_or(0), - table_kind: table_kind_from_volume(volume.table_kind), - region: region_from_volume(volume), - name: volume.partlabel.as_ref().map(|label| label.0.clone()), - part_uuid: volume.partuuid.as_ref().map(|uuid| uuid.0.clone()), - bootable: volume.bootable, - } -} - -fn region_from_volume(volume: &BlockVolume) -> BlockRegion { - BlockRegion::new(volume.region.start_block, volume.region.num_blocks) -} - -fn table_kind_from_volume(kind: VolumeTableKind) -> PartitionTableKind { - match kind { - VolumeTableKind::Raw => PartitionTableKind::Raw, - VolumeTableKind::Gpt => PartitionTableKind::Gpt, - VolumeTableKind::Mbr => PartitionTableKind::Mbr, - } -} - -fn collect_root_candidates(disks: &[DiscoveredDisk]) -> Vec { - let mut candidates = Vec::new(); - - for disk in disks { - if disk.partitions.is_empty() { - candidates.push(RootCandidate { - disk_index: disk.disk_index, - partition: None, - filesystem: disk.raw_filesystem, - }); - continue; - } - - for partition in &disk.partitions { - candidates.push(RootCandidate { - disk_index: disk.disk_index, - filesystem: partition.filesystem, - partition: Some(partition.clone()), - }); - } - } - - candidates -} - -fn select_root_candidate(candidates: &[RootCandidate], spec: &RootSpec) -> Option { - if let Some(index) = select_explicit_root(candidates, spec) { - return Some(index); - } - - select_default_root(candidates) -} - -fn select_explicit_root(candidates: &[RootCandidate], spec: &RootSpec) -> Option { - for candidate in candidates { - if let Some(partition) = candidate.partition.as_ref() { - if let Some(partuuid) = &spec.partuuid - && partition - .info - .part_uuid - .as_ref() - .is_some_and(|candidate_uuid| candidate_uuid.eq_ignore_ascii_case(partuuid)) - { - info!(" matched root by PARTUUID on {}", candidate.description()); - return Some(candidate.selection()); - } - - if let Some(partlabel) = &spec.partlabel - && partition.info.name.as_deref() == Some(partlabel.as_str()) - { - info!(" matched root by PARTLABEL on {}", candidate.description()); - return Some(candidate.selection()); - } - } - - if let Some(disk_index) = spec.disk_index - && candidate.disk_index == disk_index - { - match (spec.partition_index, &candidate.partition) { - (Some(partition_index), Some(partition)) - if partition.info.index == partition_index => - { - info!( - " matched root by device path on {}", - candidate.description() - ); - return Some(candidate.selection()); - } - (None, None) => { - info!( - " matched root by raw device path on {}", - candidate.description() - ); - return Some(candidate.selection()); - } - _ => {} - } - } - } - - if spec.disk_index.is_some() || spec.partuuid.is_some() || spec.partlabel.is_some() { - panic!("configured root device was not found in discovered block devices"); - } - - None -} - -fn select_default_root(candidates: &[RootCandidate]) -> Option { - let rootfs_matches: Vec<_> = candidates - .iter() - .filter(|candidate| { - candidate - .partition - .as_ref() - .and_then(|part| part.info.name.as_deref()) - == Some("rootfs") - && is_supported_filesystem(candidate.filesystem) - }) - .map(RootCandidate::selection) - .collect(); - if rootfs_matches.len() == 1 { - info!(" falling back to PARTLABEL=rootfs"); - return rootfs_matches.into_iter().next(); - } - if rootfs_matches.len() > 1 { - panic!("multiple partitions are labeled 'rootfs'; specify root= explicitly"); - } - - let partition_matches: Vec<_> = candidates - .iter() - .filter(|candidate| { - candidate - .partition - .as_ref() - .is_some_and(is_default_root_partition) - }) - .map(RootCandidate::selection) - .collect(); - if partition_matches.len() == 1 { - info!(" only one supported filesystem partition is available; using it as root"); - return partition_matches.into_iter().next(); - } - - let raw_matches: Vec<_> = candidates - .iter() - .filter(|candidate| candidate.partition.is_none()) - .map(RootCandidate::selection) - .collect(); - if partition_matches.is_empty() && raw_matches.len() == 1 { - info!(" only one raw block device is available; using it as root"); - return raw_matches.into_iter().next(); - } - - None -} - -fn is_default_root_partition(partition: &DetectedPartition) -> bool { - if !is_supported_filesystem(partition.filesystem) { - return false; - } - - match partition.info.table_kind { - PartitionTableKind::Mbr => { - #[cfg(feature = "ext4")] - if partition.filesystem == Some(FilesystemKind::Ext4) { - return partition.info.bootable; - } - #[cfg(feature = "fat")] - if partition.filesystem == Some(FilesystemKind::Fat) { - return true; - } - false - } - PartitionTableKind::Gpt | PartitionTableKind::Raw => true, - } -} - -/// Returns the block filesystems discovered during [`init_filesystems`]. -/// -/// The root filesystem is mounted automatically. Other regions are reported -/// here and can be mounted by policy code with -/// [`mount_discovered_filesystem`]. -pub fn discovered_filesystems() -> Vec { - let Some(disks) = DISCOVERED_DISKS.get() else { - return Vec::new(); - }; - let root_selection = ROOT_SELECTION.get().copied(); - - let mut filesystems = Vec::new(); - for disk in disks { - if disk.partitions.is_empty() { - let selection = RootSelection { - disk_index: disk.disk_index, - partition_index: None, - filesystem: disk.raw_filesystem, - }; - filesystems.push(DiscoveredFilesystem { - disk_index: disk.disk_index, - partition_index: None, - region: full_region(&disk.dev), - filesystem: disk.raw_filesystem, - name: None, - part_uuid: None, - bootable: false, - is_root: root_selection == Some(selection), - }); - continue; - } - - for partition in &disk.partitions { - let selection = RootSelection { - disk_index: disk.disk_index, - partition_index: Some(partition.info.index), - filesystem: partition.filesystem, - }; - filesystems.push(DiscoveredFilesystem { - disk_index: disk.disk_index, - partition_index: Some(partition.info.index), - region: partition.info.region, - filesystem: partition.filesystem, - name: partition.info.name.clone(), - part_uuid: partition.info.part_uuid.clone(), - bootable: partition.info.bootable, - is_root: root_selection == Some(selection), - }); - } - } - filesystems -} - -/// Mounts a discovered non-root filesystem at an existing VFS location. -pub fn mount_discovered_filesystem( - ctx: &FsContext, - disk_index: usize, - partition_index: Option, - target: impl AsRef, -) -> VfsResult<()> { - let (dev, region, kind) = discovered_region(disk_index, partition_index)?; - let selection = RootSelection { - disk_index, - partition_index, - filesystem: Some(kind), - }; - if ROOT_SELECTION.get().copied() == Some(selection) { - return Err(VfsError::ResourceBusy); - } - - let fs = fs::new_by_kind_from_dyn(Box::new(dev), region, kind)?; - let target = ctx.resolve(target)?; - target.mount(&fs).map(|_| ()) -} - -fn discovered_region( - disk_index: usize, - partition_index: Option, -) -> VfsResult<(SharedAxBlockDevice, BlockRegion, FilesystemKind)> { - let disks = DISCOVERED_DISKS.get().ok_or(VfsError::NotFound)?; - let disk = disks - .iter() - .find(|disk| disk.disk_index == disk_index) - .ok_or(VfsError::NotFound)?; - - if let Some(partition_index) = partition_index { - let partition = disk - .partitions - .iter() - .find(|partition| partition.info.index == partition_index) - .ok_or(VfsError::NotFound)?; - let kind = partition.filesystem.ok_or(VfsError::Unsupported)?; - return Ok((disk.dev.clone(), partition.info.region, kind)); - } - - if !disk.partitions.is_empty() { - return Err(VfsError::InvalidInput); - } - let kind = disk.raw_filesystem.ok_or(VfsError::Unsupported)?; - Ok((disk.dev.clone(), full_region(&disk.dev), kind)) -} - -const fn is_supported_filesystem(fs: Option) -> bool { - match fs { - #[cfg(feature = "ext4")] - Some(FilesystemKind::Ext4) => true, - #[cfg(feature = "fat")] - Some(FilesystemKind::Fat) => true, - _ => false, - } -} - -fn describe_selection( - disk_index: usize, - partition: Option<&DetectedPartition>, - filesystem: Option, -) -> String { - if let Some(partition) = partition { - let name = partition.info.name.as_deref().unwrap_or(""); - let fs = partition - .filesystem - .map(filesystem_name) - .unwrap_or("unknown"); - format!( - "disk{} partition {} ({}, fs={}, lba {}..{})", - disk_index, - partition.info.index + 1, - name, - fs, - partition.info.region.start_lba, - partition.info.region.end_lba - ) - } else { - let fs = filesystem.map(filesystem_name).unwrap_or("unknown"); - format!("disk{} raw device (fs={})", disk_index, fs) - } -} - -fn parse_root_spec(bootargs: Option<&str>) -> RootSpec { - let mut spec = RootSpec::default(); - - if let Some(bootargs) = bootargs - && let Some(root_arg) = bootargs - .split_whitespace() - .find(|arg| arg.starts_with("root=")) - { - let root_value = root_arg.strip_prefix("root=").unwrap_or(""); - spec = match root_value { - value if value.starts_with("/dev/mmcblk") => parse_mmcblk_path(value), - value if value.starts_with("/dev/sd") => parse_sd_path(value), - value if value.starts_with("PARTUUID=") => RootSpec { - partuuid: Some(value.strip_prefix("PARTUUID=").unwrap_or("").to_uppercase()), - ..RootSpec::default() - }, - value if value.starts_with("PARTLABEL=") => RootSpec { - partlabel: Some(value.strip_prefix("PARTLABEL=").unwrap_or("").to_string()), - ..RootSpec::default() - }, - _ => RootSpec::default(), - }; - } - - spec -} - -fn parse_mmcblk_path(path: &str) -> RootSpec { - if let Some(remaining) = path.strip_prefix("/dev/mmcblk") { - if let Some(p_pos) = remaining.find('p') { - let disk = remaining[..p_pos].parse::().ok(); - let part = remaining[p_pos + 1..] - .parse::() - .ok() - .and_then(|part| part.checked_sub(1)); - return RootSpec { - disk_index: disk, - partition_index: part, - ..RootSpec::default() - }; - } - - if let Ok(disk) = remaining.parse::() { - return RootSpec { - disk_index: Some(disk), - ..RootSpec::default() - }; - } - } - - RootSpec::default() -} - -fn parse_sd_path(path: &str) -> RootSpec { - if let Some(remaining) = path.strip_prefix("/dev/sd") { - let bytes = remaining.as_bytes(); - if bytes.is_empty() { - return RootSpec::default(); - } - - let disk_index = bytes[0] - .is_ascii_lowercase() - .then(|| usize::from(bytes[0] - b'a')); - let partition_index = if bytes.len() > 1 { - core::str::from_utf8(&bytes[1..]) - .ok() - .and_then(|part| part.parse::().ok()) - .and_then(|part| part.checked_sub(1)) - } else { - None - }; - return RootSpec { - disk_index, - partition_index, - ..RootSpec::default() - }; - } - - RootSpec::default() -} - -fn detect_filesystem(dev: &mut impl FsBlockDevice, region: BlockRegion) -> Option { - #[cfg(not(any(feature = "ext4", feature = "fat")))] - let _ = (&mut *dev, region); - - #[cfg(feature = "ext4")] - if region_has_ext4(dev, region) { - return Some(FilesystemKind::Ext4); - } - - #[cfg(feature = "fat")] - if region_has_fat(dev, region) { - return Some(FilesystemKind::Fat); - } - - None -} - -#[cfg(feature = "ext4")] -fn region_has_ext4(dev: &mut impl FsBlockDevice, region: BlockRegion) -> bool { - const EXT4_SUPERBLOCK_OFFSET: usize = 1024; - const EXT4_MAGIC_OFFSET: usize = 0x38; - const EXT4_MAGIC: u16 = 0xEF53; - region_has_magic_u16( - dev, - region, - EXT4_SUPERBLOCK_OFFSET + EXT4_MAGIC_OFFSET, - EXT4_MAGIC, - ) -} - -#[cfg(feature = "fat")] -fn region_has_fat(dev: &mut impl FsBlockDevice, region: BlockRegion) -> bool { - const FAT16_MAGIC: &[u8; 5] = b"FAT16"; - const FAT32_MAGIC: &[u8; 5] = b"FAT32"; - let start_lba = region.start_lba; - let visible_blocks = region.num_blocks(); - if visible_blocks == 0 { - return false; - } - - let block_size = dev.block_size(); - if block_size < 512 { - return false; - } - - let mut buf = alloc::vec![0u8; block_size]; - if dev.read_block(start_lba, &mut buf).is_err() { - return false; - } - - buf.get(510..512) == Some([0x55, 0xAA].as_slice()) - && (buf.get(54..59) == Some(FAT16_MAGIC.as_slice()) - || buf.get(82..87) == Some(FAT32_MAGIC.as_slice())) -} - -#[cfg(feature = "ext4")] -fn region_has_magic_u16( - dev: &mut impl FsBlockDevice, - region: BlockRegion, - byte_offset: usize, - magic: u16, -) -> bool { - let block_size = dev.block_size(); - if block_size == 0 { - return false; - } - - let start_lba = region.start_lba; - let visible_blocks = region.num_blocks(); - let block_index = byte_offset / block_size; - let within_block = byte_offset % block_size; - if visible_blocks == 0 || within_block + 2 > block_size { - return false; - } - - let Some(block_index_u64) = u64::try_from(block_index).ok() else { - return false; - }; - let Some(end_lba) = start_lba.checked_add(visible_blocks) else { - return false; - }; - let block_id = match start_lba.checked_add(block_index_u64) { - Some(block_id) if block_id < end_lba => block_id, - _ => return false, - }; - - let mut buf = alloc::vec![0u8; block_size]; - if dev.read_block(block_id, &mut buf).is_err() { - return false; - } - - u16::from_le_bytes([buf[within_block], buf[within_block + 1]]) == magic -} - -fn full_region(dev: &impl FsBlockDevice) -> BlockRegion { - BlockRegion::from_num_blocks(dev.num_blocks()) -} diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 6e97df9ab8..5c03a7957f 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -25,14 +25,9 @@ tls = ["ax-hal/tls", "ax-task?/tls"] plat-dyn = ["ax-hal/plat-dyn", "paging", "buddy-slab"] display = ["dep:ax-display", "ax-driver/display"] -fs = [ - "dep:ax-errno", - "dep:ax-fs", - "dep:rd-block", - "dep:spin", - "dep:axklib", - "ax-driver/block", -] +fs = ["dep:ax-errno", "dep:ax-fs", "ax-driver/block"] +fs-ext4 = ["fs", "ax-fs/ext4"] +fs-fat = ["fs", "ax-fs/fat"] input = ["dep:ax-input", "ax-driver/input"] net = ["dep:ax-net", "ax-driver/net"] net-ng = ["dep:ax-net-ng", "dep:rd-net", "dep:spin", "dep:axklib", "ax-driver/net"] @@ -65,7 +60,6 @@ ax-crate-interface = { workspace = true } ax-ctor-bare = { workspace = true } indoc = "2" ax-percpu = { workspace = true, optional = true } -rd-block = { workspace = true, optional = true } rd-net = { workspace = true, optional = true } rdrive.workspace = true spin = { workspace = true, optional = true } diff --git a/os/axvisor/configs/board/orangepi-5-plus.toml b/os/axvisor/configs/board/orangepi-5-plus.toml index e8f449b079..432d3ba133 100644 --- a/os/axvisor/configs/board/orangepi-5-plus.toml +++ b/os/axvisor/configs/board/orangepi-5-plus.toml @@ -2,7 +2,6 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ "sdmmc", "rockchip-soc", - "fs", ] log = "Info" plat_dyn = true diff --git a/os/axvisor/configs/board/phytiumpi.toml b/os/axvisor/configs/board/phytiumpi.toml index e8dca5f660..d0ea656712 100644 --- a/os/axvisor/configs/board/phytiumpi.toml +++ b/os/axvisor/configs/board/phytiumpi.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "fs", "sdmmc", ] log = "Info" diff --git a/os/axvisor/configs/board/qemu-aarch64.toml b/os/axvisor/configs/board/qemu-aarch64.toml index 317e9aae7c..e2eec7502e 100644 --- a/os/axvisor/configs/board/qemu-aarch64.toml +++ b/os/axvisor/configs/board/qemu-aarch64.toml @@ -3,7 +3,6 @@ features = [ "ax-hal/plat-dyn", "ept-level-4", "ax-driver/virtio-blk", - "fs", ] log = "Info" plat_dyn = true diff --git a/os/axvisor/configs/board/qemu-loongarch64.toml b/os/axvisor/configs/board/qemu-loongarch64.toml index b7945d66d0..99cf9bfd26 100644 --- a/os/axvisor/configs/board/qemu-loongarch64.toml +++ b/os/axvisor/configs/board/qemu-loongarch64.toml @@ -4,7 +4,6 @@ features = [ "ax-driver/virtio-blk", "ax-driver/pci", "ept-level-4", - "fs", ] log = "Info" max_cpu_num = 1 diff --git a/os/axvisor/configs/board/qemu-riscv64.toml b/os/axvisor/configs/board/qemu-riscv64.toml index da0062ef6c..ff3458a252 100644 --- a/os/axvisor/configs/board/qemu-riscv64.toml +++ b/os/axvisor/configs/board/qemu-riscv64.toml @@ -4,7 +4,6 @@ features = [ "ax-driver/fdt", "ax-driver/virtio-blk", "ept-level-4", - "fs", "sstc" ] log = "Info" diff --git a/os/axvisor/configs/board/qemu-x86_64.toml b/os/axvisor/configs/board/qemu-x86_64.toml index 64e99f9093..dd672cd628 100644 --- a/os/axvisor/configs/board/qemu-x86_64.toml +++ b/os/axvisor/configs/board/qemu-x86_64.toml @@ -4,7 +4,6 @@ features = [ "ax-driver/virtio-blk", "ax-driver/pci", "ept-level-4", - "fs", ] log = "Info" target = "x86_64-unknown-none" diff --git a/os/axvisor/configs/board/roc-rk3568-pc.toml b/os/axvisor/configs/board/roc-rk3568-pc.toml index e8dca5f660..d0ea656712 100644 --- a/os/axvisor/configs/board/roc-rk3568-pc.toml +++ b/os/axvisor/configs/board/roc-rk3568-pc.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "fs", "sdmmc", ] log = "Info" diff --git a/scripts/axbuild/src/axvisor/board.rs b/scripts/axbuild/src/axvisor/board.rs index 7f03fdb8c9..ad6ee3ef8d 100644 --- a/scripts/axbuild/src/axvisor/board.rs +++ b/scripts/axbuild/src/axvisor/board.rs @@ -98,7 +98,7 @@ mod tests { r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "aarch64-unknown-none-softfloat" -features = ["fs"] +features = [] log = "Info" "#, ); @@ -128,7 +128,7 @@ log = "Info" r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "aarch64-unknown-none-softfloat" -features = ["fs", "ax-driver/rockchip-sdhci"] +features = ["ax-driver/rockchip-sdhci"] log = "Info" plat_dyn = true "#, diff --git a/scripts/axbuild/src/axvisor/build.rs b/scripts/axbuild/src/axvisor/build.rs index f09c126e01..6bf7a82b7f 100644 --- a/scripts/axbuild/src/axvisor/build.rs +++ b/scripts/axbuild/src/axvisor/build.rs @@ -426,7 +426,7 @@ vm_configs = [] &config_path, r#" env = {} -features = ["fs", "ept-level-4"] +features = ["ept-level-4"] log = "Info" plat_dyn = true "#, @@ -515,7 +515,7 @@ vm_configs = [] r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "x86_64-unknown-none" -features = ["ax-hal/x86-qemu-q35", "ept-level-4", "fs", "vmx"] +features = ["ax-hal/x86-qemu-q35", "ept-level-4", "vmx"] log = "Info" vm_configs = [] "#, @@ -542,7 +542,6 @@ vm_configs = [] fs::read_to_string(board_path).unwrap() ); assert!(cargo.features.contains(&"ept-level-4".to_string())); - assert!(cargo.features.contains(&"fs".to_string())); assert!(cargo.features.contains(&"vmx".to_string())); assert!(!cargo.features.contains(&"ax-std/plat-dyn".to_string())); assert!(!cargo.features.contains(&"ax-std/defplat".to_string())); diff --git a/scripts/axbuild/src/axvisor/build/x86.rs b/scripts/axbuild/src/axvisor/build/x86.rs index a9de92bd61..0ee98827cb 100644 --- a/scripts/axbuild/src/axvisor/build/x86.rs +++ b/scripts/axbuild/src/axvisor/build/x86.rs @@ -92,7 +92,7 @@ mod tests { #[test] fn backend_auto_selects_vmx_when_missing() { - let mut features = vec!["ept-level-4".to_string(), "fs".to_string()]; + let mut features = vec!["ept-level-4".to_string()]; normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Vmx)).unwrap(); @@ -102,7 +102,7 @@ mod tests { #[test] fn backend_auto_selects_svm_when_missing() { - let mut features = vec!["ept-level-4".to_string(), "fs".to_string()]; + let mut features = vec!["ept-level-4".to_string()]; normalize_backend_features_with(&mut features, || Ok(VirtualizationBackend::Svm)).unwrap(); diff --git a/scripts/axbuild/src/axvisor/config.rs b/scripts/axbuild/src/axvisor/config.rs index 2144c8fa2c..0187c6b0fb 100644 --- a/scripts/axbuild/src/axvisor/config.rs +++ b/scripts/axbuild/src/axvisor/config.rs @@ -79,7 +79,7 @@ mod tests { r#" env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } target = "aarch64-unknown-none-softfloat" -features = ["fs", "ax-driver/rockchip-sdhci"] +features = ["ax-driver/rockchip-sdhci"] log = "Info" plat_dyn = true vm_configs = [] diff --git a/scripts/repo/repos.csv b/scripts/repo/repos.csv index 6db251cec6..d16d926efb 100644 --- a/scripts/repo/repos.csv +++ b/scripts/repo/repos.csv @@ -47,7 +47,6 @@ https://github.com/arceos-org/riscv_plic,,drivers/intc/riscv_plic,ArceOS, https://github.com/rcore-os/bitmap-allocator,,components/bitmap-allocator,rCore, https://github.com/Starry-OS/axbacktrace,,components/axbacktrace,Starry, https://github.com/Starry-OS/axpoll,,components/axpoll,Starry, -https://github.com/Starry-OS/axfs-ng-vfs,,components/axfs-ng-vfs,Starry, https://github.com/Starry-OS/rsext4,,components/rsext4,Starry, https://github.com/Starry-OS/scope-local,,components/scope-local,Starry, https://github.com/Starry-OS/starry-process,,components/starry-process,Starry, From 914bd0518057339c686e5de4757d3891cc6b4d3f Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Mon, 25 May 2026 17:35:31 +0800 Subject: [PATCH 04/15] Implement filesystem discovery and selection policy - Added a new module `discovery.rs` to handle the discovery of filesystems on block devices. - Introduced `RootSelectionPolicy` to define rules for selecting the root filesystem when no explicit boot arguments are provided. - Updated `lib.rs` to include the new discovery module and its functionalities. - Modified `Cargo.toml` to enable filesystem features by default. - Enhanced the initialization process for filesystems to support automatic detection and selection based on the defined policy. --- .../axfs/src/{fs_policy.rs => discovery.rs} | 110 +++++++++++++----- os/arceos/modules/axfs/src/lib.rs | 9 +- os/axvisor/Cargo.toml | 7 +- 3 files changed, 91 insertions(+), 35 deletions(-) rename os/arceos/modules/axfs/src/{fs_policy.rs => discovery.rs} (87%) diff --git a/os/arceos/modules/axfs/src/fs_policy.rs b/os/arceos/modules/axfs/src/discovery.rs similarity index 87% rename from os/arceos/modules/axfs/src/fs_policy.rs rename to os/arceos/modules/axfs/src/discovery.rs index 9ce8273f71..e8f0856485 100644 --- a/os/arceos/modules/axfs/src/fs_policy.rs +++ b/os/arceos/modules/axfs/src/discovery.rs @@ -21,6 +21,23 @@ struct RootSpec { partlabel: Option, } +/// Policy used when no explicit `root=` boot argument selects a filesystem. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RootSelectionPolicy { + /// Prefer a uniquely labeled partition before generic fallbacks. + pub preferred_partlabel: Option, + /// Allow a unique supported partition to be selected as root. + pub allow_single_supported_partition: bool, + /// Allow a unique raw whole-disk filesystem to be selected as root. + pub allow_single_raw_device: bool, + /// Require an MBR ext4 partition to carry the bootable flag before it can + /// be selected by the generic single-partition fallback. + pub require_mbr_ext4_bootable: bool, + /// Allow an MBR FAT partition to be selected by the generic + /// single-partition fallback without requiring the bootable flag. + pub allow_mbr_fat_without_bootable: bool, +} + struct RootCandidate { disk_index: usize, partition: Option, @@ -89,6 +106,18 @@ pub struct DiscoveredFilesystem { static DISCOVERED_DISKS: Once> = Once::new(); static ROOT_SELECTION: Once = Once::new(); +impl Default for RootSelectionPolicy { + fn default() -> Self { + Self { + preferred_partlabel: Some("rootfs".to_string()), + allow_single_supported_partition: true, + allow_single_raw_device: true, + require_mbr_ext4_bootable: true, + allow_mbr_fat_without_bootable: true, + } + } +} + impl RootCandidate { fn selection(&self) -> RootSelection { RootSelection { @@ -121,13 +150,24 @@ impl RootCandidate { } } +/// Initializes the filesystem subsystem by selecting a root device from the +/// available block devices and optional boot arguments. pub fn init_filesystems(block_devs: Vec>, bootargs: Option<&str>) { + init_filesystems_with_policy(block_devs, bootargs, &RootSelectionPolicy::default()) +} + +/// Initializes the filesystem subsystem with an explicit root selection policy. +pub fn init_filesystems_with_policy( + block_devs: Vec>, + bootargs: Option<&str>, + policy: &RootSelectionPolicy, +) { info!("Select root filesystem..."); let root_spec = parse_root_spec(bootargs); let mut disks = collect_disks(block_devs); let candidates = collect_root_candidates(&disks); - let selection = select_root_candidate(&candidates, &root_spec) + let selection = select_root_candidate(&candidates, &root_spec, policy) .unwrap_or_else(|| panic!("failed to determine root device from available block devices")); let selected_disk_pos = disks .iter() @@ -373,12 +413,16 @@ fn collect_root_candidates(disks: &[DiscoveredDisk]) -> Vec { candidates } -fn select_root_candidate(candidates: &[RootCandidate], spec: &RootSpec) -> Option { +fn select_root_candidate( + candidates: &[RootCandidate], + spec: &RootSpec, + policy: &RootSelectionPolicy, +) -> Option { if let Some(index) = select_explicit_root(candidates, spec) { return Some(index); } - select_default_root(candidates) + select_default_root(candidates, policy) } fn select_explicit_root(candidates: &[RootCandidate], spec: &RootSpec) -> Option { @@ -435,25 +479,32 @@ fn select_explicit_root(candidates: &[RootCandidate], spec: &RootSpec) -> Option None } -fn select_default_root(candidates: &[RootCandidate]) -> Option { - let rootfs_matches: Vec<_> = candidates - .iter() - .filter(|candidate| { - candidate - .partition - .as_ref() - .and_then(|part| part.info.name.as_deref()) - == Some("rootfs") - && is_supported_filesystem(candidate.filesystem) - }) - .map(RootCandidate::selection) - .collect(); - if rootfs_matches.len() == 1 { - info!(" falling back to PARTLABEL=rootfs"); - return rootfs_matches.into_iter().next(); - } - if rootfs_matches.len() > 1 { - panic!("multiple partitions are labeled 'rootfs'; specify root= explicitly"); +fn select_default_root( + candidates: &[RootCandidate], + policy: &RootSelectionPolicy, +) -> Option { + if let Some(preferred_partlabel) = policy.preferred_partlabel.as_deref() { + let labeled_matches: Vec<_> = candidates + .iter() + .filter(|candidate| { + candidate + .partition + .as_ref() + .and_then(|part| part.info.name.as_deref()) + == Some(preferred_partlabel) + && is_supported_filesystem(candidate.filesystem) + }) + .map(RootCandidate::selection) + .collect(); + if labeled_matches.len() == 1 { + info!(" falling back to PARTLABEL={preferred_partlabel}"); + return labeled_matches.into_iter().next(); + } + if labeled_matches.len() > 1 { + panic!( + "multiple partitions are labeled '{preferred_partlabel}'; specify root= explicitly" + ); + } } let partition_matches: Vec<_> = candidates @@ -462,11 +513,11 @@ fn select_default_root(candidates: &[RootCandidate]) -> Option { candidate .partition .as_ref() - .is_some_and(is_default_root_partition) + .is_some_and(|partition| is_default_root_partition(partition, policy)) }) .map(RootCandidate::selection) .collect(); - if partition_matches.len() == 1 { + if policy.allow_single_supported_partition && partition_matches.len() == 1 { info!(" only one supported filesystem partition is available; using it as root"); return partition_matches.into_iter().next(); } @@ -476,7 +527,7 @@ fn select_default_root(candidates: &[RootCandidate]) -> Option { .filter(|candidate| candidate.partition.is_none()) .map(RootCandidate::selection) .collect(); - if partition_matches.is_empty() && raw_matches.len() == 1 { + if policy.allow_single_raw_device && partition_matches.is_empty() && raw_matches.len() == 1 { info!(" only one raw block device is available; using it as root"); return raw_matches.into_iter().next(); } @@ -484,7 +535,10 @@ fn select_default_root(candidates: &[RootCandidate]) -> Option { None } -fn is_default_root_partition(partition: &DetectedPartition) -> bool { +fn is_default_root_partition(partition: &DetectedPartition, policy: &RootSelectionPolicy) -> bool { + #[cfg(not(any(feature = "ext4", feature = "fat")))] + let _ = policy; + if !is_supported_filesystem(partition.filesystem) { return false; } @@ -493,11 +547,11 @@ fn is_default_root_partition(partition: &DetectedPartition) -> bool { PartitionTableKind::Mbr => { #[cfg(feature = "ext4")] if partition.filesystem == Some(FilesystemKind::Ext4) { - return partition.info.bootable; + return !policy.require_mbr_ext4_bootable || partition.info.bootable; } #[cfg(feature = "fat")] if partition.filesystem == Some(FilesystemKind::Fat) { - return true; + return policy.allow_mbr_fat_without_bootable || partition.info.bootable; } false } diff --git a/os/arceos/modules/axfs/src/lib.rs b/os/arceos/modules/axfs/src/lib.rs index e34c11a535..50de806f80 100644 --- a/os/arceos/modules/axfs/src/lib.rs +++ b/os/arceos/modules/axfs/src/lib.rs @@ -26,8 +26,8 @@ extern crate log; use ax_fs_vfs::Filesystem; mod block; +mod discovery; mod fs; -mod fs_policy; mod highlevel; #[cfg(feature = "devfs")] @@ -35,14 +35,15 @@ pub use ax_fs_devfs as devfs; #[cfg(feature = "ramfs")] pub use ax_fs_ramfs as ramfs; pub use block::{BlockRegion, FsBlockDevice, SharedBlockDevice, VolumeReader}; +pub use discovery::{ + DiscoveredFilesystem, RootSelectionPolicy, discovered_filesystems, init_filesystems, + init_filesystems_with_policy, mount_discovered_filesystem, +}; /// Create a filesystem from a dynamic (boxed) block device. pub use fs::{ FilesystemKind, new_from_dyn as new_filesystem_from_dyn, new_from_dyn_by_kind as new_filesystem_from_dyn_by_kind, }; -pub use fs_policy::{ - DiscoveredFilesystem, discovered_filesystems, init_filesystems, mount_discovered_filesystem, -}; pub use highlevel::*; /// Initializes the global root filesystem context from an already constructed diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index cfad42568c..5bf6f10f26 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -33,8 +33,9 @@ name = "xtask" path = "xtask/src/main.rs" [features] -default = [] +default = ["fs-default"] ept-level-4 = [] +fs-default = ["ax-std/fs", "ax-feat/fs-ext4", "ax-feat/fs-fat"] vmx = ["axvm/vmx"] svm = ["axvm/svm"] sstc = ["riscv_vcpu/sstc"] @@ -66,8 +67,8 @@ ax-timer-list = { workspace = true } hashbrown = "0.14" # System dependent modules provided by ArceOS. -ax-std = { workspace = true, features = ["paging", "irq", "multitask", "task-ext", "smp", "hv", "fs"] } -ax-feat = { workspace = true, features = ["fs-ext4", "fs-fat"] } +ax-std = { workspace = true, features = ["paging", "irq", "multitask", "task-ext", "smp", "hv"] } +ax-feat = { workspace = true } ax-hal = { workspace = true, features = ["paging", "irq", "smp", "hv", "axvisor-linker"] } # System dependent modules provided by ArceOS-Hypervisor (bare-metal only) axaddrspace = { workspace = true } From 571d827dcc72c33b9cfbd14b9889e25b9fb72f8c Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 08:35:43 +0800 Subject: [PATCH 05/15] feat: add default boot arguments and enhance filesystem discovery logic --- .../axplat-aarch64-bsta1000b/build.rs | 2 + .../axplat-aarch64-phytium-pi/build.rs | 2 + .../axplat-aarch64-qemu-virt/build.rs | 2 + .../platforms/axplat-aarch64-raspi/build.rs | 2 + .../axplat-loongarch64-qemu-virt/build.rs | 2 + .../axplat-riscv64-qemu-virt/build.rs | 2 + .../axplat-riscv64-sg2002/axconfig.toml | 1 - .../platforms/axplat-riscv64-sg2002/build.rs | 2 + .../platforms/axplat-x86-pc/build.rs | 2 + os/arceos/configs/defconfig.toml | 3 + os/arceos/configs/dummy.toml | 2 + os/arceos/modules/axconfig/build.rs | 2 + os/arceos/modules/axconfig/dummy.toml | 2 + os/arceos/modules/axfs/src/discovery.rs | 139 ++++++++---------- os/arceos/modules/axruntime/src/lib.rs | 5 +- os/arceos/ulib/arceos-rust/defconfig.toml | 3 + .../build-riscv64gc-unknown-none-elf.toml | 1 + 17 files changed, 92 insertions(+), 82 deletions(-) diff --git a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/build.rs b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/build.rs index 12b902da8a..6844a8db63 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/build.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-bsta1000b/build.rs @@ -2,5 +2,7 @@ fn main() { println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { println!("cargo:rerun-if-changed={config_path}"); + } else { + println!("cargo:rerun-if-changed=axconfig.toml"); } } diff --git a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/build.rs b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/build.rs index 12b902da8a..6844a8db63 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/build.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-phytium-pi/build.rs @@ -2,5 +2,7 @@ fn main() { println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { println!("cargo:rerun-if-changed={config_path}"); + } else { + println!("cargo:rerun-if-changed=axconfig.toml"); } } diff --git a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/build.rs b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/build.rs index 12b902da8a..6844a8db63 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/build.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-qemu-virt/build.rs @@ -2,5 +2,7 @@ fn main() { println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { println!("cargo:rerun-if-changed={config_path}"); + } else { + println!("cargo:rerun-if-changed=axconfig.toml"); } } diff --git a/components/axplat_crates/platforms/axplat-aarch64-raspi/build.rs b/components/axplat_crates/platforms/axplat-aarch64-raspi/build.rs index 12b902da8a..6844a8db63 100644 --- a/components/axplat_crates/platforms/axplat-aarch64-raspi/build.rs +++ b/components/axplat_crates/platforms/axplat-aarch64-raspi/build.rs @@ -2,5 +2,7 @@ fn main() { println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { println!("cargo:rerun-if-changed={config_path}"); + } else { + println!("cargo:rerun-if-changed=axconfig.toml"); } } diff --git a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/build.rs b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/build.rs index 12b902da8a..6844a8db63 100644 --- a/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/build.rs +++ b/components/axplat_crates/platforms/axplat-loongarch64-qemu-virt/build.rs @@ -2,5 +2,7 @@ fn main() { println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { println!("cargo:rerun-if-changed={config_path}"); + } else { + println!("cargo:rerun-if-changed=axconfig.toml"); } } diff --git a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/build.rs b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/build.rs index 12b902da8a..6844a8db63 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/build.rs +++ b/components/axplat_crates/platforms/axplat-riscv64-qemu-virt/build.rs @@ -2,5 +2,7 @@ fn main() { println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { println!("cargo:rerun-if-changed={config_path}"); + } else { + println!("cargo:rerun-if-changed=axconfig.toml"); } } diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/axconfig.toml b/components/axplat_crates/platforms/axplat-riscv64-sg2002/axconfig.toml index 8aab2aaf35..1778654485 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/axconfig.toml +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/axconfig.toml @@ -81,4 +81,3 @@ uart-irq = 0x2c # uint syscon-paddr = 0x0300_0000 # uint cvsd-paddr = 0x0431_0000 # uint root-partition-name = "rootfs" # str -bootargs = "root=/dev/mmcblk0p2" # str diff --git a/components/axplat_crates/platforms/axplat-riscv64-sg2002/build.rs b/components/axplat_crates/platforms/axplat-riscv64-sg2002/build.rs index 12b902da8a..6844a8db63 100644 --- a/components/axplat_crates/platforms/axplat-riscv64-sg2002/build.rs +++ b/components/axplat_crates/platforms/axplat-riscv64-sg2002/build.rs @@ -2,5 +2,7 @@ fn main() { println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { println!("cargo:rerun-if-changed={config_path}"); + } else { + println!("cargo:rerun-if-changed=axconfig.toml"); } } diff --git a/components/axplat_crates/platforms/axplat-x86-pc/build.rs b/components/axplat_crates/platforms/axplat-x86-pc/build.rs index 12b902da8a..6844a8db63 100644 --- a/components/axplat_crates/platforms/axplat-x86-pc/build.rs +++ b/components/axplat_crates/platforms/axplat-x86-pc/build.rs @@ -2,5 +2,7 @@ fn main() { println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { println!("cargo:rerun-if-changed={config_path}"); + } else { + println!("cargo:rerun-if-changed=axconfig.toml"); } } diff --git a/os/arceos/configs/defconfig.toml b/os/arceos/configs/defconfig.toml index b5f5141118..f294eb57f0 100644 --- a/os/arceos/configs/defconfig.toml +++ b/os/arceos/configs/defconfig.toml @@ -1,6 +1,9 @@ # Stack size of each task. task-stack-size = 0x40000 # uint +# Kernel boot arguments. +bootargs = "" # str + # Number of timer ticks per second (Hz). A timer tick may contain several timer # interrupts. ticks-per-sec = 100 # uint diff --git a/os/arceos/configs/dummy.toml b/os/arceos/configs/dummy.toml index bc0b556917..53ef995dca 100644 --- a/os/arceos/configs/dummy.toml +++ b/os/arceos/configs/dummy.toml @@ -4,6 +4,8 @@ arch = "unknown" # str platform = "dummy" # str # Platform package. package = "dummy" # str +# Kernel boot arguments. +bootargs = "" # str # Stack size of each task. task-stack-size = 0x40000 # uint # Number of timer ticks per second (Hz). A timer tick may contain several timer diff --git a/os/arceos/modules/axconfig/build.rs b/os/arceos/modules/axconfig/build.rs index d074a6c122..169cc97948 100644 --- a/os/arceos/modules/axconfig/build.rs +++ b/os/arceos/modules/axconfig/build.rs @@ -16,5 +16,7 @@ fn main() { println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); if let Ok(config_path) = std::env::var("AX_CONFIG_PATH") { println!("cargo:rerun-if-changed={config_path}"); + } else { + println!("cargo:rerun-if-changed=dummy.toml"); } } diff --git a/os/arceos/modules/axconfig/dummy.toml b/os/arceos/modules/axconfig/dummy.toml index bc0b556917..53ef995dca 100644 --- a/os/arceos/modules/axconfig/dummy.toml +++ b/os/arceos/modules/axconfig/dummy.toml @@ -4,6 +4,8 @@ arch = "unknown" # str platform = "dummy" # str # Platform package. package = "dummy" # str +# Kernel boot arguments. +bootargs = "" # str # Stack size of each task. task-stack-size = 0x40000 # uint # Number of timer ticks per second (Hz). A timer tick may contain several timer diff --git a/os/arceos/modules/axfs/src/discovery.rs b/os/arceos/modules/axfs/src/discovery.rs index e8f0856485..60d7a5feda 100644 --- a/os/arceos/modules/axfs/src/discovery.rs +++ b/os/arceos/modules/axfs/src/discovery.rs @@ -30,12 +30,6 @@ pub struct RootSelectionPolicy { pub allow_single_supported_partition: bool, /// Allow a unique raw whole-disk filesystem to be selected as root. pub allow_single_raw_device: bool, - /// Require an MBR ext4 partition to carry the bootable flag before it can - /// be selected by the generic single-partition fallback. - pub require_mbr_ext4_bootable: bool, - /// Allow an MBR FAT partition to be selected by the generic - /// single-partition fallback without requiring the bootable flag. - pub allow_mbr_fat_without_bootable: bool, } struct RootCandidate { @@ -112,8 +106,6 @@ impl Default for RootSelectionPolicy { preferred_partlabel: Some("rootfs".to_string()), allow_single_supported_partition: true, allow_single_raw_device: true, - require_mbr_ext4_bootable: true, - allow_mbr_fat_without_bootable: true, } } } @@ -292,55 +284,8 @@ fn collect_disks(block_devs: Vec>) -> Vec let mut reader = VolumeReader::new(&mut scan_dev); match scan_volumes(&mut reader, DiskId(disk_index as u64)) { Ok(volumes) => { - let raw_region = volumes - .iter() - .find(|volume| volume.table_kind == VolumeTableKind::Raw) - .map(region_from_volume) - .unwrap_or_else(|| full_region(&dev)); - let partitions: Vec = volumes - .into_iter() - .filter(|volume| volume.table_kind != VolumeTableKind::Raw) - .map(|volume| { - let partition = partition_info_from_volume(&volume); - let mut detect_dev = dev.clone(); - let filesystem = detect_filesystem(&mut detect_dev, partition.region); - info!( - " partition {} name={:?} fs={:?} lba {}..{}", - partition.index + 1, - partition.name, - filesystem, - partition.region.start_lba, - partition.region.end_lba - ); - DetectedPartition { - info: partition, - filesystem, - } - }) - .collect(); - let raw_filesystem = if partitions.is_empty() { - let mut detect_dev = dev.clone(); - let raw_fs = detect_filesystem(&mut detect_dev, raw_region); - info!(" raw device fs={:?}", raw_fs); - raw_fs - } else { - None - }; - if let Some(first) = partitions.first() { - info!( - " block device {} ({}) has {:?} partition table with {} partitions", - disk_index, - device_name, - first.info.table_kind, - partitions.len() - ); - } else { - info!( - " block device {} ({}) has no usable partition table; treating the whole \ - disk as a candidate", - disk_index, device_name - ); - } + let (partitions, raw_filesystem) = collect_partitions(&dev, volumes); + log_disk(disk_index, &device_name, raw_filesystem, &partitions); disks.push(DiscoveredDisk { disk_index, dev, @@ -360,6 +305,60 @@ fn collect_disks(block_devs: Vec>) -> Vec disks } +fn collect_partitions( + dev: &SharedBlockDevice, + volumes: Vec, +) -> (Vec, Option) { + let mut partitions = Vec::new(); + let mut raw_filesystem = None; + for volume in volumes { + if volume.table_kind == VolumeTableKind::Raw { + let region = region_from_volume(&volume); + let mut detect_dev = dev.clone(); + raw_filesystem = detect_filesystem(&mut detect_dev, region); + info!(" raw device fs={:?}", raw_filesystem); + continue; + } + + let info = partition_info_from_volume(&volume); + let mut detect_dev = dev.clone(); + let filesystem = detect_filesystem(&mut detect_dev, info.region); + info!( + " partition {} name={:?} fs={:?} lba {}..{}", + info.index + 1, + info.name, + filesystem, + info.region.start_lba, + info.region.end_lba + ); + partitions.push(DetectedPartition { info, filesystem }); + } + + (partitions, raw_filesystem) +} + +fn log_disk( + disk_index: usize, + device_name: &str, + raw_filesystem: Option, + partitions: &[DetectedPartition], +) { + if let Some(first) = partitions.first() { + info!( + " block device {} ({}) has {:?} partition table with {} partitions", + disk_index, + device_name, + first.info.table_kind, + partitions.len() + ); + } else { + info!( + " block device {} ({}) has no usable partition table; raw fs={:?}", + disk_index, device_name, raw_filesystem + ); + } +} + fn partition_info_from_volume(volume: &BlockVolume) -> PartitionInfo { PartitionInfo { index: volume @@ -513,7 +512,7 @@ fn select_default_root( candidate .partition .as_ref() - .is_some_and(|partition| is_default_root_partition(partition, policy)) + .is_some_and(is_default_root_partition) }) .map(RootCandidate::selection) .collect(); @@ -535,28 +534,8 @@ fn select_default_root( None } -fn is_default_root_partition(partition: &DetectedPartition, policy: &RootSelectionPolicy) -> bool { - #[cfg(not(any(feature = "ext4", feature = "fat")))] - let _ = policy; - - if !is_supported_filesystem(partition.filesystem) { - return false; - } - - match partition.info.table_kind { - PartitionTableKind::Mbr => { - #[cfg(feature = "ext4")] - if partition.filesystem == Some(FilesystemKind::Ext4) { - return !policy.require_mbr_ext4_bootable || partition.info.bootable; - } - #[cfg(feature = "fat")] - if partition.filesystem == Some(FilesystemKind::Fat) { - return policy.allow_mbr_fat_without_bootable || partition.info.bootable; - } - false - } - PartitionTableKind::Gpt | PartitionTableKind::Raw => true, - } +fn is_default_root_partition(partition: &DetectedPartition) -> bool { + is_supported_filesystem(partition.filesystem) } fn discovered_region( diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index c69a27b1a2..7c313ef7f3 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -280,7 +280,10 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { ax_hal::dtb::get_chosen_bootargs(), ); } else if #[cfg(all(feature = "fs", not(feature = "plat-dyn")))] { - ax_fs::init_filesystems(devices::take_static_fs_block_devices(), None); + ax_fs::init_filesystems( + devices::take_static_fs_block_devices(), + Some(ax_config::BOOTARGS), + ); } } diff --git a/os/arceos/ulib/arceos-rust/defconfig.toml b/os/arceos/ulib/arceos-rust/defconfig.toml index b5f5141118..f294eb57f0 100644 --- a/os/arceos/ulib/arceos-rust/defconfig.toml +++ b/os/arceos/ulib/arceos-rust/defconfig.toml @@ -1,6 +1,9 @@ # Stack size of each task. task-stack-size = 0x40000 # uint +# Kernel boot arguments. +bootargs = "" # str + # Number of timer ticks per second (Hz). A timer tick may contain several timer # interrupts. ticks-per-sec = 100 # uint diff --git a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml index 8bcd941406..e58daface9 100644 --- a/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml +++ b/test-suit/starryos/normal/board-licheerv-nano-sg2002/build-riscv64gc-unknown-none-elf.toml @@ -4,4 +4,5 @@ env = {} features = ["sg2002", "myplat", "ax-driver/cvsd"] log = "Info" max_cpu_num = 1 +axconfig_overrides = ["bootargs=\"root=/dev/mmcblk0p2\""] plat_dyn = false From 586dea4acaf60e53efc0d1236cd4228df461715b Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 08:51:05 +0800 Subject: [PATCH 06/15] fix: update boot arguments retrieval in filesystem initialization --- os/arceos/modules/axruntime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index 7c313ef7f3..8b322b9dd9 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -282,7 +282,7 @@ pub fn rust_main(cpu_id: usize, arg: usize) -> ! { } else if #[cfg(all(feature = "fs", not(feature = "plat-dyn")))] { ax_fs::init_filesystems( devices::take_static_fs_block_devices(), - Some(ax_config::BOOTARGS), + ax_hal::dtb::get_chosen_bootargs(), ); } } From 408565d19e488c6e3bb6e87a6ffd6297448b6dd6 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 09:10:57 +0800 Subject: [PATCH 07/15] feat: enhance mount propagation handling and add readonly checks in file operations --- components/axfs_crates/axfs_vfs/src/mount.rs | 203 ++++++++++++++++++- os/StarryOS/kernel/src/syscall/fs/mount.rs | 22 +- os/arceos/modules/axfs/src/highlevel/file.rs | 11 + 3 files changed, 219 insertions(+), 17 deletions(-) diff --git a/components/axfs_crates/axfs_vfs/src/mount.rs b/components/axfs_crates/axfs_vfs/src/mount.rs index 868498ca62..475910dd56 100644 --- a/components/axfs_crates/axfs_vfs/src/mount.rs +++ b/components/axfs_crates/axfs_vfs/src/mount.rs @@ -3,6 +3,7 @@ use alloc::{ string::String, sync::{Arc, Weak}, vec, + vec::Vec, }; use core::{ iter, mem, @@ -13,6 +14,7 @@ use core::{ use axpoll::{IoEvents, Pollable}; use hashbrown::HashMap; use inherit_methods_macro::inherit_methods; +use log::warn; use crate::{ DirEntry, DirEntrySink, Filesystem, FilesystemOps, Metadata, MetadataUpdate, Mutex, MutexGuard, @@ -22,6 +24,14 @@ use crate::{ static DEVICE_COUNTER: AtomicU64 = AtomicU64::new(1); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum PropagationType { + Private, + Shared, + Slave, + Unbindable, +} + #[derive(Debug)] pub struct Mountpoint { /// Root dir entry in the mountpoint. @@ -36,8 +46,14 @@ pub struct Mountpoint { readonly: AtomicBool, /// Expire mark for umount2(MNT_EXPIRE). expired: AtomicBool, - /// Bind mounts cannot recursively copy this mount when set. - unbindable: AtomicBool, + /// Mount propagation type. + propagation: Mutex, + /// Other shared peers in the same propagation group. + peers: Mutex>>, + /// Slave mounts that receive propagation events from this shared mount. + slaves: Mutex>>, + /// Shared masters that this slave receives propagation events from. + masters: Mutex>>, } impl Mountpoint { @@ -53,7 +69,10 @@ impl Mountpoint { device, readonly: AtomicBool::new(false), expired: AtomicBool::new(false), - unbindable: AtomicBool::new(false), + propagation: Mutex::new(PropagationType::Private), + peers: Mutex::default(), + slaves: Mutex::default(), + masters: Mutex::default(), }) } @@ -69,7 +88,7 @@ impl Mountpoint { Self::new(fs, None) } - fn bind(source: &Location, location_in_parent: Location) -> Arc { + fn bind(source: &Location, location_in_parent: Location, recursive: bool) -> Arc { let result = Self::new_with_root( source.entry.clone(), Some(location_in_parent), @@ -78,6 +97,21 @@ impl Mountpoint { result .readonly .store(source.mountpoint.is_readonly(), Ordering::Release); + if recursive { + let mut children_to_bind: Vec<_> = source + .mountpoint + .children + .lock() + .iter() + .map(|(key, child)| (key.clone(), child.clone())) + .collect(); + children_to_bind + .retain(|(_, child)| child.upgrade().is_none_or(|child| !child.is_unbindable())); + let mut result_children = result.children.lock(); + for (key, child) in children_to_bind { + result_children.insert(key, child); + } + } result } @@ -172,12 +206,156 @@ impl Mountpoint { self.expired.store(false, Ordering::Release); } + fn propagation(&self) -> PropagationType { + *self.propagation.lock() + } + + pub fn is_shared(&self) -> bool { + self.propagation() == PropagationType::Shared + } + + pub fn is_slave(&self) -> bool { + self.propagation() == PropagationType::Slave + } + pub fn is_unbindable(&self) -> bool { - self.unbindable.load(Ordering::Acquire) + self.propagation() == PropagationType::Unbindable + } + + fn remove_from_shared_group(self: &Arc) { + let peers: Vec<_> = self.peers.lock().iter().filter_map(Weak::upgrade).collect(); + for peer in peers { + peer.peers.lock().retain(|candidate| { + candidate + .upgrade() + .is_some_and(|mp| !Arc::ptr_eq(&mp, self)) + }); + } + self.peers.lock().clear(); + } + + fn remove_from_masters(self: &Arc) { + let masters: Vec<_> = self + .masters + .lock() + .iter() + .filter_map(Weak::upgrade) + .collect(); + for master in masters { + master.slaves.lock().retain(|candidate| { + candidate + .upgrade() + .is_some_and(|mp| !Arc::ptr_eq(&mp, self)) + }); + } + self.masters.lock().clear(); + } + + pub fn set_shared(self: &Arc) { + self.remove_from_shared_group(); + self.remove_from_masters(); + *self.propagation.lock() = PropagationType::Shared; + } + + pub fn set_private(self: &Arc) { + self.remove_from_shared_group(); + self.remove_from_masters(); + *self.propagation.lock() = PropagationType::Private; + } + + pub fn set_slave(self: &Arc) { + let mut masters = Vec::new(); + if self.is_shared() { + masters.extend(self.peers.lock().iter().filter_map(Weak::upgrade)); + } + + self.remove_from_shared_group(); + self.remove_from_masters(); + *self.propagation.lock() = PropagationType::Slave; + for master in masters { + master.slaves.lock().push(Arc::downgrade(self)); + self.masters.lock().push(Arc::downgrade(&master)); + } } pub fn set_unbindable(self: &Arc) { - self.unbindable.store(true, Ordering::Release); + self.set_private(); + *self.propagation.lock() = PropagationType::Unbindable; + } + + pub fn join_shared_group(self: &Arc, source: &Arc) { + let mut group = vec![source.clone()]; + group.extend(source.peers.lock().iter().filter_map(Weak::upgrade)); + + self.set_shared(); + for member in group { + if Arc::ptr_eq(&member, self) { + continue; + } + member.peers.lock().push(Arc::downgrade(self)); + self.peers.lock().push(Arc::downgrade(&member)); + } + } + + fn attach_child(parent: &Arc, location: Location, child: &Arc) -> VfsResult<()> { + *location.entry.as_dir()?.mountpoint.lock() = Some(child.clone()); + parent + .children + .lock() + .insert(location.entry.key(), Arc::downgrade(child)); + Ok(()) + } + + fn propagate_new_child( + source_parent: &Arc, + source_location: &Location, + child: &Arc, + ) -> VfsResult<()> { + let peers: Vec<_> = source_parent + .peers + .lock() + .iter() + .filter_map(Weak::upgrade) + .collect(); + let slaves: Vec<_> = source_parent + .slaves + .lock() + .iter() + .filter_map(Weak::upgrade) + .collect(); + let mut path_components = vec![]; + let mut current = source_location.clone(); + while !current.is_root_of_mount() { + path_components.push(current.name().into_owned()); + current = current.parent().ok_or(VfsError::InvalidInput)?; + } + path_components.reverse(); + + for target_parent in peers.into_iter().chain(slaves) { + let mut location = target_parent.root_location(); + for component in &path_components { + location = location.lookup_no_follow(component)?; + } + let inserted_key = location.entry.key(); + Self::attach_child(&target_parent, location, child)?; + let mut resolved = target_parent.root_location(); + for component in &path_components { + resolved = resolved.lookup_no_follow(component)?; + } + if !Arc::ptr_eq(resolved.mountpoint(), child) { + warn!( + "mount propagation mismatch path={:?} inserted_key={:?} resolved_key={:?} \ + resolved_is_root={} resolved_mp_device={} replicated_device={}", + path_components, + inserted_key, + resolved.entry.key(), + resolved.is_root_of_mount(), + resolved.mountpoint().device(), + child.device(), + ); + } + } + Ok(()) } pub fn move_to(self: &Arc, new_location: &Location) -> VfsResult<()> { @@ -464,6 +642,7 @@ impl Location { pub fn mount(&self, fs: &Filesystem) -> VfsResult> { let result = Mountpoint::new(fs, Some(self.clone())); + let should_propagate = self.mountpoint.is_shared(); { let mut mountpoint = self.entry.as_dir()?.mountpoint.lock(); if mountpoint.is_some() { @@ -475,10 +654,13 @@ impl Location { .children .lock() .insert(self.entry.key(), Arc::downgrade(&result)); + if should_propagate { + Mountpoint::propagate_new_child(self.mountpoint(), self, &result)?; + } Ok(result) } - pub fn bind_mount(&self, source: &Self) -> VfsResult> { + pub fn bind_mount(&self, source: &Self, recursive: bool) -> VfsResult> { if source.mountpoint().is_unbindable() { return Err(VfsError::InvalidInput); } @@ -487,7 +669,12 @@ impl Location { if mountpoint.is_some() { return Err(VfsError::ResourceBusy); } - let result = Mountpoint::bind(source, self.clone()); + let result = Mountpoint::bind(source, self.clone(), recursive); + if source.mountpoint().is_shared() { + result.join_shared_group(source.mountpoint()); + } else if source.mountpoint().is_slave() { + result.set_slave(); + } *mountpoint = Some(result.clone()); self.mountpoint .children diff --git a/os/StarryOS/kernel/src/syscall/fs/mount.rs b/os/StarryOS/kernel/src/syscall/fs/mount.rs index 1f4f840320..28a236296a 100644 --- a/os/StarryOS/kernel/src/syscall/fs/mount.rs +++ b/os/StarryOS/kernel/src/syscall/fs/mount.rs @@ -85,13 +85,17 @@ pub fn sys_mount( return Err(AxError::InvalidInput); } - let _ = (MS_SHARED, MS_PRIVATE, MS_SLAVE); let target = FS_CONTEXT.lock().resolve(target)?; if !target.is_root_of_mount() { return Err(AxError::InvalidInput); } - if propagation == MS_UNBINDABLE { - target.mountpoint().set_unbindable(); + let mountpoint = target.mountpoint().clone(); + match propagation { + MS_SHARED => mountpoint.set_shared(), + MS_PRIVATE => mountpoint.set_private(), + MS_SLAVE => mountpoint.set_slave(), + MS_UNBINDABLE => mountpoint.set_unbindable(), + _ => {} } return Ok(0); } @@ -119,7 +123,7 @@ pub fn sys_mount( let ctx = FS_CONTEXT.lock(); let source = ctx.resolve(source)?; let target = ctx.resolve(target)?; - let mp = target.bind_mount(&source)?; + let mp = target.bind_mount(&source, (flags & MS_REC) != 0)?; if (flags & MS_RDONLY) != 0 { mp.set_readonly(true); } @@ -178,11 +182,11 @@ fn mount_ext4(source: &str, target: &str, readonly: bool) -> AxResult<()> { let num_blocks = block_dev.num_blocks(); let region = ax_fs::BlockRegion::from_num_blocks(num_blocks); - // Create ext4 filesystem from the dynamic block device - let fs = ax_fs::new_filesystem_from_dyn(block_dev, region).map_err(|e| { - warn!("mount_ext4: failed to create ext4 filesystem: {:?}", e); - AxError::Io - })?; + let fs = ax_fs::new_filesystem_from_dyn_by_kind(block_dev, region, ax_fs::FilesystemKind::Ext4) + .map_err(|e| { + warn!("mount_ext4: failed to create ext4 filesystem: {:?}", e); + AxError::Io + })?; // Mount at the target location let target_loc = ctx.resolve(target)?; diff --git a/os/arceos/modules/axfs/src/highlevel/file.rs b/os/arceos/modules/axfs/src/highlevel/file.rs index a14182ae65..f041e61b3d 100644 --- a/os/arceos/modules/axfs/src/highlevel/file.rs +++ b/os/arceos/modules/axfs/src/highlevel/file.rs @@ -209,6 +209,12 @@ impl OpenOptions { return Err(VfsError::IsADirectory); } + if loc.is_readonly() + && (flags.intersects(FileFlags::WRITE | FileFlags::APPEND) || self.truncate) + { + return Err(VfsError::ReadOnlyFilesystem); + } + if self.directory { loc.check_is_dir()?; } @@ -1094,6 +1100,11 @@ impl File { /// Checks that the file has the required `flags` and returns the backend. pub fn access(&self, flags: FileFlags) -> VfsResult<&FileBackend> { if self.flags().contains(flags) && !self.is_path() { + if self.inner.location().is_readonly() + && flags.intersects(FileFlags::WRITE | FileFlags::APPEND) + { + return Err(VfsError::ReadOnlyFilesystem); + } Ok(&self.inner) } else { Err(VfsError::BadFileDescriptor) From 68b5892e3167f017e6a3e95c708cf009fd78149e Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 09:15:19 +0800 Subject: [PATCH 08/15] feat: update filesystem feature dependencies and improve filesystem handling --- os/arceos/api/arceos_posix_api/src/imp/fs.rs | 38 ++++++++------------ os/arceos/ulib/arceos-rust/lib/Cargo.toml | 2 +- os/arceos/ulib/axstd/Cargo.toml | 11 ++++-- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/os/arceos/api/arceos_posix_api/src/imp/fs.rs b/os/arceos/api/arceos_posix_api/src/imp/fs.rs index 7520b4c870..c1dc451848 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/fs.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/fs.rs @@ -5,7 +5,7 @@ use core::mem::size_of; use ax_errno::{LinuxError, LinuxResult}; use ax_fs::{OpenOptions, ReadDir}; -use ax_fs_vfs::{Metadata, NodeType}; +use ax_fs_vfs::{Location, Metadata, NodeType}; use ax_io::{PollState, SeekFrom, prelude::*}; use ax_sync::Mutex; @@ -17,6 +17,7 @@ pub struct File { } pub struct Directory { + loc: Location, inner: Mutex, } @@ -170,18 +171,16 @@ fn file_type_to_d_type(ty: NodeType) -> u8 { } fn metadata_to_stat(metadata: Metadata) -> ctypes::stat { - let ty = metadata.node_type as u8; - let perm = metadata.mode.bits() as u32; - let st_mode = ((ty as u32) << 12) | perm; + let st_mode = ((metadata.node_type as u32) << 12) | metadata.mode.bits() as u32; ctypes::stat { - st_ino: 1, - st_nlink: 1, + st_ino: metadata.inode, + st_nlink: metadata.nlink as _, st_mode, - st_uid: 1000, - st_gid: 1000, + st_uid: metadata.uid, + st_gid: metadata.gid, st_size: metadata.size as _, st_blocks: metadata.blocks as _, - st_blksize: 512, + st_blksize: metadata.block_size as _, ..Default::default() } } @@ -206,8 +205,9 @@ impl File { } impl Directory { - fn new(inner: ReadDir) -> Self { + fn new(loc: Location, inner: ReadDir) -> Self { Self { + loc, inner: Mutex::new(inner), } } @@ -263,18 +263,7 @@ impl FileLike for Directory { } fn stat(&self) -> LinuxResult { - let st_mode = 0o040755; - Ok(ctypes::stat { - st_ino: 1, - st_nlink: 1, - st_mode, - st_uid: 1000, - st_gid: 1000, - st_size: 0, - st_blocks: 0, - st_blksize: 512, - ..Default::default() - }) + Ok(metadata_to_stat(self.loc.metadata()?)) } fn into_any(self: Arc) -> Arc { @@ -338,8 +327,9 @@ pub fn sys_open(filename: *const c_char, flags: c_int, mode: ctypes::mode_t) -> let mut options = options; options.directory(true); let ctx = ax_fs::FS_CONTEXT.lock(); - let dir = options.open(&ctx, filename)?.into_dir()?; - Directory::new(ReadDir::new(dir)).add_to_fd_table() + let loc = options.open(&ctx, filename)?.into_dir()?; + let dir = ReadDir::new(loc.clone()); + Directory::new(loc, dir).add_to_fd_table() } else { let ctx = ax_fs::FS_CONTEXT.lock(); let file = options.open(&ctx, filename)?.into_file()?; diff --git a/os/arceos/ulib/arceos-rust/lib/Cargo.toml b/os/arceos/ulib/arceos-rust/lib/Cargo.toml index 29cd75b044..de8b7b4a2e 100644 --- a/os/arceos/ulib/arceos-rust/lib/Cargo.toml +++ b/os/arceos/ulib/arceos-rust/lib/Cargo.toml @@ -61,7 +61,7 @@ stack-guard-page = ["multitask", "paging", "ax-feat/stack-guard-page"] # File system fd = ["ax-posix-api/fd"] -fs = ["ax-api/fs", "ax-posix-api/fs", "ax-feat/fs-ext4", "ax-driver/pci", "ax-driver/virtio-blk"] +fs = ["ax-api/fs", "ax-posix-api/fs", "ax-feat/fs-fat", "ax-driver/pci", "ax-driver/virtio-blk"] # Networking dns = [] diff --git a/os/arceos/ulib/axstd/Cargo.toml b/os/arceos/ulib/axstd/Cargo.toml index 7d22dbeb01..4739d8deba 100644 --- a/os/arceos/ulib/axstd/Cargo.toml +++ b/os/arceos/ulib/axstd/Cargo.toml @@ -59,9 +59,14 @@ sched-cfs = ["ax-feat/sched-cfs"] stack-guard-page = ["multitask", "paging", "ax-feat/stack-guard-page"] # File system -fs = ["ax-api/fs", "ax-feat/fs"] -ext4fs = ["fs", "ax-feat/fs-ext4"] -fatfs = ["fs", "ax-feat/fs-fat"] +# Keep `fs` as the legacy default filesystem bundle. Use `fs-api` when only +# the neutral filesystem API is needed. +fs = ["fatfs"] +ext4fs = ["fs-api", "ax-feat/fs-ext4"] +fatfs = ["fs-api", "ax-feat/fs-fat"] +devfs = ["fs-api", "ax-feat/fs-devfs"] +ramfs = ["fs-api", "ax-feat/fs-ramfs"] +fs-api = ["ax-api/fs"] # Networking net = ["ax-api/net", "ax-feat/net"] From a5f40b0a264a31016161672aa1b498abc0b29185 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 09:26:02 +0800 Subject: [PATCH 09/15] fix: improve error handling in directory reading and entry writing --- components/axfs_crates/axfs_vfs/src/mount.rs | 5 +- os/arceos/api/arceos_api/src/imp/fs.rs | 21 ++++---- os/arceos/api/arceos_posix_api/src/imp/fs.rs | 50 +++++++++++++------- os/arceos/modules/axfs/src/fs/fat/util.rs | 30 ++++++------ os/arceos/ulib/axstd/src/env.rs | 8 ++-- os/arceos/ulib/axstd/src/lib.rs | 2 +- 6 files changed, 69 insertions(+), 47 deletions(-) diff --git a/components/axfs_crates/axfs_vfs/src/mount.rs b/components/axfs_crates/axfs_vfs/src/mount.rs index 475910dd56..981971cb3e 100644 --- a/components/axfs_crates/axfs_vfs/src/mount.rs +++ b/components/axfs_crates/axfs_vfs/src/mount.rs @@ -708,13 +708,14 @@ impl Location { self.mountpoint.clear_expired(); self.entry.as_dir()?.forget(); - if let Some(parent_loc) = self.mountpoint.location.lock().as_ref() { + let parent_loc = self.mountpoint.location.lock().clone(); + if let Some(parent_loc) = parent_loc { + *parent_loc.entry.as_dir()?.mountpoint.lock() = None; parent_loc .mountpoint .children .lock() .remove(&parent_loc.entry.key()); - *parent_loc.entry.as_dir()?.mountpoint.lock() = None; } Ok(()) } diff --git a/os/arceos/api/arceos_api/src/imp/fs.rs b/os/arceos/api/arceos_api/src/imp/fs.rs index 37a95a4c26..b02da522a9 100644 --- a/os/arceos/api/arceos_api/src/imp/fs.rs +++ b/os/arceos/api/arceos_api/src/imp/fs.rs @@ -168,14 +168,17 @@ pub fn ax_file_attr(file: &AxFileHandle) -> AxResult { pub fn ax_read_dir(dir: &mut AxDirHandle, dirents: &mut [AxDirEntry]) -> AxResult { let mut count = 0; for slot in dirents { - let Some(entry) = dir.0.next().transpose()? else { - break; - }; - *slot = AxDirEntry { - name: entry.name, - entry_type: entry.node_type, - }; - count += 1; + match dir.0.next() { + Some(Ok(entry)) => { + *slot = AxDirEntry { + name: entry.name, + entry_type: entry.node_type, + }; + count += 1; + } + Some(Err(err)) if count == 0 => return Err(err), + Some(Err(_)) | None => break, + } } Ok(count) } @@ -183,7 +186,7 @@ pub fn ax_read_dir(dir: &mut AxDirHandle, dirents: &mut [AxDirEntry]) -> AxResul pub fn ax_create_dir(path: &str) -> AxResult { ax_fs::FS_CONTEXT .lock() - .create_dir(path, NodePermission::default()) + .create_dir(path, NodePermission::from_bits_truncate(0o755)) .map(|_| ()) } diff --git a/os/arceos/api/arceos_posix_api/src/imp/fs.rs b/os/arceos/api/arceos_posix_api/src/imp/fs.rs index c1dc451848..f1653bb1bf 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/fs.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/fs.rs @@ -360,17 +360,25 @@ pub unsafe fn sys_getdents64(fd: c_int, buf: *mut u8, len: usize) -> ctypes::ssi let out = unsafe { core::slice::from_raw_parts_mut(buf, len) }; let mut dir_buf = DirBuffer::new(out); - for entry in dir.by_ref() { - let entry = entry?; - let d_type = file_type_to_d_type(entry.node_type); - // Linux style: d_ino, d_off both present - if !dir_buf.write_entry( - entry.ino, - entry.offset as i64, - d_type, - entry.name.as_bytes(), - ) { - return Ok(dir_buf.used_len() as ctypes::ssize_t); + loop { + match dir.next() { + Some(Ok(entry)) => { + let d_type = file_type_to_d_type(entry.node_type); + // Linux style: d_ino, d_off both present + if !dir_buf.write_entry( + entry.ino, + entry.offset as i64, + d_type, + entry.name.as_bytes(), + ) { + return Ok(dir_buf.used_len() as ctypes::ssize_t); + } + } + Some(Err(err)) if dir_buf.used_len() == 0 => return Err(err.into()), + Some(Err(_)) => { + return Ok(dir_buf.used_len() as ctypes::ssize_t); + } + None => break, } } @@ -411,12 +419,20 @@ pub unsafe fn sys_getdents64(fd: c_int, buf: *mut u8, len: usize) -> ctypes::ssi let out = unsafe { core::slice::from_raw_parts_mut(buf, len) }; let mut dir_buf = HermitDirBuffer::new(out); - for entry in dir.by_ref() { - let entry = entry?; - let d_type = file_type_to_d_type(entry.node_type); - // Hermit style: only d_ino and d_type, d_off is not meaningful - if !dir_buf.write_entry(entry.ino, d_type, entry.name.as_bytes()) { - return Ok(dir_buf.used_len() as ctypes::ssize_t); + loop { + match dir.next() { + Some(Ok(entry)) => { + let d_type = file_type_to_d_type(entry.node_type); + // Hermit style: only d_ino and d_type, d_off is not meaningful + if !dir_buf.write_entry(entry.ino, d_type, entry.name.as_bytes()) { + return Ok(dir_buf.used_len() as ctypes::ssize_t); + } + } + Some(Err(err)) if dir_buf.used_len() == 0 => return Err(err.into()), + Some(Err(_)) => { + return Ok(dir_buf.used_len() as ctypes::ssize_t); + } + None => break, } } diff --git a/os/arceos/modules/axfs/src/fs/fat/util.rs b/os/arceos/modules/axfs/src/fs/fat/util.rs index 8afba28e9c..e227cc13dd 100644 --- a/os/arceos/modules/axfs/src/fs/fat/util.rs +++ b/os/arceos/modules/axfs/src/fs/fat/util.rs @@ -5,21 +5,23 @@ use chrono::{DateTime, Datelike, NaiveDate, TimeZone, Timelike, Utc}; use super::{ff, fs::FatFilesystemInner}; -pub fn dos_to_unix(date: fatfs::DateTime) -> Duration { +pub fn dos_to_unix(dos_time: fatfs::DateTime) -> Duration { // let date: NaiveDateTime = date.into(); - let date = NaiveDate::from_ymd_opt( - date.date.year as _, - date.date.month as _, - date.date.day as _, - ) - .unwrap() - .and_hms_milli_opt( - date.time.hour as _, - date.time.min as _, - date.time.sec as _, - date.time.millis as _, - ) - .unwrap(); + let Some(date) = NaiveDate::from_ymd_opt( + dos_time.date.year as _, + dos_time.date.month as _, + dos_time.date.day as _, + ) else { + return Duration::default(); + }; + let Some(date) = date.and_hms_milli_opt( + dos_time.time.hour as _, + dos_time.time.min as _, + dos_time.time.sec as _, + dos_time.time.millis as _, + ) else { + return Duration::default(); + }; let Some(datetime) = Utc.from_local_datetime(&date).single() else { return Duration::default(); }; diff --git a/os/arceos/ulib/axstd/src/env.rs b/os/arceos/ulib/axstd/src/env.rs index 4be5c036f2..f0cdbc306d 100644 --- a/os/arceos/ulib/axstd/src/env.rs +++ b/os/arceos/ulib/axstd/src/env.rs @@ -1,19 +1,19 @@ //! Inspection and manipulation of the process’s environment. -#[cfg(feature = "fs")] +#[cfg(any(feature = "fs", feature = "fs-api"))] extern crate alloc; -#[cfg(feature = "fs")] +#[cfg(any(feature = "fs", feature = "fs-api"))] use {crate::io, alloc::string::String}; /// Returns the current working directory as a [`String`]. -#[cfg(feature = "fs")] +#[cfg(any(feature = "fs", feature = "fs-api"))] pub fn current_dir() -> io::Result { ax_api::fs::ax_current_dir() } /// Changes the current working directory to the specified path. -#[cfg(feature = "fs")] +#[cfg(any(feature = "fs", feature = "fs-api"))] pub fn set_current_dir(path: &str) -> io::Result<()> { ax_api::fs::ax_set_current_dir(path) } diff --git a/os/arceos/ulib/axstd/src/lib.rs b/os/arceos/ulib/axstd/src/lib.rs index 54993ccbb5..5b5ff7acf6 100644 --- a/os/arceos/ulib/axstd/src/lib.rs +++ b/os/arceos/ulib/axstd/src/lib.rs @@ -60,7 +60,7 @@ pub mod sync; pub mod thread; pub mod time; -#[cfg(feature = "fs")] +#[cfg(any(feature = "fs", feature = "fs-api"))] pub mod fs; #[cfg(feature = "net")] pub mod net; From 193caa65d82e50861a765356cd997e0250404425 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 10:03:54 +0800 Subject: [PATCH 10/15] feat: enhance directory handling and improve mount propagation logic --- components/axfs_crates/axfs_ramfs/src/dir.rs | 24 +++- components/axfs_crates/axfs_vfs/src/mount.rs | 10 +- .../axfs_crates/axfs_vfs/src/node/dir.rs | 6 +- .../axfs_crates/axfs_vfs/src/node/mod.rs | 7 +- os/arceos/api/arceos_posix_api/src/imp/fs.rs | 129 ++++-------------- os/arceos/modules/axfs/src/highlevel/fs.rs | 7 + 6 files changed, 60 insertions(+), 123 deletions(-) diff --git a/components/axfs_crates/axfs_ramfs/src/dir.rs b/components/axfs_crates/axfs_ramfs/src/dir.rs index e34a2c3185..feec18526f 100644 --- a/components/axfs_crates/axfs_ramfs/src/dir.rs +++ b/components/axfs_crates/axfs_ramfs/src/dir.rs @@ -90,14 +90,20 @@ impl RamDirNode { }, reference, ); - old_dir.rebind_children_to(&rebound)?; Ok(rebound) } - fn rebind_children_to(&self, parent: &DirEntry) -> VfsResult<()> { - let mut children = self.children.lock(); - for (name, child) in children.iter_mut() { - *child = self.rebind_entry(child, Some(parent.clone()), name)?; + fn rebind_children_to(root: Arc, parent: &DirEntry) -> VfsResult<()> { + let mut stack = alloc::vec![(root, parent.clone())]; + while let Some((dir, parent)) = stack.pop() { + let mut children = dir.children.lock(); + for (name, child) in children.iter_mut() { + *child = dir.rebind_entry(child, Some(parent.clone()), name)?; + if child.is_dir() { + let child_dir = child.as_dir()?.downcast::()?; + stack.push((child_dir, child.clone())); + } + } } Ok(()) } @@ -242,6 +248,10 @@ impl DirNodeOps for RamDirNode { let entry = children.get(src_name).cloned().ok_or(VfsError::NotFound)?; Self::check_replace_target(&children, dst_name)?; let rebound = self.rebind_entry(&entry, self.this.lock().upgrade(), dst_name)?; + if rebound.is_dir() { + let dir = rebound.as_dir()?.downcast::()?; + Self::rebind_children_to(dir, &rebound)?; + } children.remove(src_name); children.insert(dst_name.into(), rebound); return Ok(()); @@ -253,6 +263,10 @@ impl DirNodeOps for RamDirNode { Self::check_replace_target(&dst_children, dst_name)?; } let rebound = dst.rebind_entry(&entry, dst.this.lock().upgrade(), dst_name)?; + if rebound.is_dir() { + let dir = rebound.as_dir()?.downcast::()?; + Self::rebind_children_to(dir, &rebound)?; + } self.children.lock().remove(src_name); dst.children.lock().insert(dst_name.into(), rebound); Ok(()) diff --git a/components/axfs_crates/axfs_vfs/src/mount.rs b/components/axfs_crates/axfs_vfs/src/mount.rs index 981971cb3e..99ac5c467d 100644 --- a/components/axfs_crates/axfs_vfs/src/mount.rs +++ b/components/axfs_crates/axfs_vfs/src/mount.rs @@ -336,13 +336,17 @@ impl Mountpoint { for component in &path_components { location = location.lookup_no_follow(component)?; } + if location.is_mountpoint() { + return Err(VfsError::ResourceBusy); + } + let replica = Self::bind(&child.root_location(), location.clone(), true); let inserted_key = location.entry.key(); - Self::attach_child(&target_parent, location, child)?; + Self::attach_child(&target_parent, location, &replica)?; let mut resolved = target_parent.root_location(); for component in &path_components { resolved = resolved.lookup_no_follow(component)?; } - if !Arc::ptr_eq(resolved.mountpoint(), child) { + if !Arc::ptr_eq(resolved.mountpoint(), &replica) { warn!( "mount propagation mismatch path={:?} inserted_key={:?} resolved_key={:?} \ resolved_is_root={} resolved_mp_device={} replicated_device={}", @@ -351,7 +355,7 @@ impl Mountpoint { resolved.entry.key(), resolved.is_root_of_mount(), resolved.mountpoint().device(), - child.device(), + replica.device(), ); } } diff --git a/components/axfs_crates/axfs_vfs/src/node/dir.rs b/components/axfs_crates/axfs_vfs/src/node/dir.rs index 09bae6ff7c..75bd04a0ef 100644 --- a/components/axfs_crates/axfs_vfs/src/node/dir.rs +++ b/components/axfs_crates/axfs_vfs/src/node/dir.rs @@ -351,9 +351,9 @@ impl DirNode { && let Ok(fresh_entry) = dst_dir.ops.lookup(dst_name) { *fresh_entry.user_data().deref_mut() = mem::take(entry.user_data().deref_mut()); - if let (Ok(src_dir), Ok(dst_dir)) = (entry.as_dir(), fresh_entry.as_dir()) { - *dst_dir.cache.lock().deref_mut() = mem::take(src_dir.cache.lock().deref_mut()); - *dst_dir.mountpoint.lock().deref_mut() = + if let (Ok(src_dir), Ok(fresh_dir)) = (entry.as_dir(), fresh_entry.as_dir()) { + *src_dir.cache.lock().deref_mut() = DirChildren::default(); + *fresh_dir.mountpoint.lock().deref_mut() = mem::take(src_dir.mountpoint.lock().deref_mut()); } dst_children_ref.insert(dst_name.to_owned(), fresh_entry); diff --git a/components/axfs_crates/axfs_vfs/src/node/mod.rs b/components/axfs_crates/axfs_vfs/src/node/mod.rs index c04790be54..f8e095d03d 100644 --- a/components/axfs_crates/axfs_vfs/src/node/mod.rs +++ b/components/axfs_crates/axfs_vfs/src/node/mod.rs @@ -269,12 +269,7 @@ impl DirEntry { node_fn: impl FnOnce(WeakDirEntry) -> DirNode, reference: Reference, ) -> Self { - Self(Arc::new_cyclic(|this| Inner { - node: Node::Dir(node_fn(WeakDirEntry(this.clone()))), - node_type: NodeType::Directory, - reference, - user_data: Mutex::new(TypeMap::default()), - })) + Self::new_dir(node_fn, reference) } pub fn metadata(&self) -> VfsResult { diff --git a/os/arceos/api/arceos_posix_api/src/imp/fs.rs b/os/arceos/api/arceos_posix_api/src/imp/fs.rs index f1653bb1bf..4ff00a2af1 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/fs.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/fs.rs @@ -1,7 +1,5 @@ use alloc::sync::Arc; use core::ffi::{c_char, c_int}; -#[cfg(not(feature = "use-hermit-types"))] -use core::mem::size_of; use ax_errno::{LinuxError, LinuxResult}; use ax_fs::{OpenOptions, ReadDir}; @@ -25,22 +23,11 @@ pub struct Directory { // Linux-style getdents64 implementation (for normal Linux targets) // ============================================================================ -#[cfg(not(feature = "use-hermit-types"))] -#[repr(C, packed)] -struct LinuxDirent64Head { - d_ino: u64, - d_off: i64, - d_reclen: u16, - d_type: u8, -} - -#[cfg(not(feature = "use-hermit-types"))] struct DirBuffer<'a> { buf: &'a mut [u8], offset: usize, } -#[cfg(not(feature = "use-hermit-types"))] impl<'a> DirBuffer<'a> { fn new(buf: &'a mut [u8]) -> Self { Self { buf, offset: 0 } @@ -55,7 +42,7 @@ impl<'a> DirBuffer<'a> { } fn write_entry(&mut self, d_ino: u64, d_off: i64, d_type: u8, name: &[u8]) -> bool { - const NAME_OFFSET: usize = size_of::(); + const NAME_OFFSET: usize = 19; let name_len = name.len().min(255); let reclen = (NAME_OFFSET + name_len + 1).next_multiple_of(8); @@ -65,14 +52,13 @@ impl<'a> DirBuffer<'a> { unsafe { let entry_ptr = self.buf.as_mut_ptr().add(self.offset); + entry_ptr.cast::().write_unaligned(d_ino); + entry_ptr.add(8).cast::().write_unaligned(d_off); entry_ptr - .cast::() - .write_unaligned(LinuxDirent64Head { - d_ino, - d_off, - d_reclen: reclen as _, - d_type, - }); + .add(16) + .cast::() + .write_unaligned(reclen as u16); + entry_ptr.add(18).write(d_type); let name_ptr = entry_ptr.add(NAME_OFFSET); name_ptr.copy_from_nonoverlapping(name.as_ptr(), name_len); @@ -84,90 +70,12 @@ impl<'a> DirBuffer<'a> { } } -// ============================================================================ -// Hermit-style getdents64 implementation (for hermit targets) -// ============================================================================ - -#[cfg(feature = "use-hermit-types")] -use core::mem; - -#[cfg(feature = "use-hermit-types")] -struct HermitDirBuffer<'a> { - buf: &'a mut [u8], - offset: usize, -} - -#[cfg(feature = "use-hermit-types")] -impl<'a> HermitDirBuffer<'a> { - fn new(buf: &'a mut [u8]) -> Self { - Self { buf, offset: 0 } - } - - fn used_len(&self) -> usize { - self.offset - } - - fn remaining_space(&self) -> usize { - self.buf.len().saturating_sub(self.offset) - } - - fn write_entry(&mut self, d_ino: u64, d_type: u8, name: &[u8]) -> bool { - // Hermit dirent64 structure layout: - // offset 0: d_ino (u64, 8 bytes) - // offset 8: d_off (i64, 8 bytes) - // offset 16: d_reclen (u16, 2 bytes) - // offset 18: d_type (u8, 1 byte) - // offset 19: d_name (variable-length null-terminated c_char array) - const NAME_OFFSET: usize = 19; - - let name_len = name.len().min(255); - // Total size: fixed header (19 bytes) + name + null terminator - let dirent_len = NAME_OFFSET + name_len + 1; - // Align to dirent64 struct alignment (8 bytes for u64) - let reclen = dirent_len.next_multiple_of(mem::align_of::()); - - if self.remaining_space() < reclen { - return false; - } - - unsafe { - let entry_ptr = self.buf.as_mut_ptr().add(self.offset); - - // Write fixed fields - let d_ino_ptr = entry_ptr.cast::(); - d_ino_ptr.write_unaligned(d_ino); - - let d_off_ptr = entry_ptr.add(8).cast::(); - d_off_ptr.write_unaligned(0); // d_off is not meaningful in Hermit - - let d_reclen_ptr = entry_ptr.add(16).cast::(); - d_reclen_ptr.write_unaligned(reclen as u16); - - let d_type_ptr = entry_ptr.add(18); - d_type_ptr.write(d_type); - - // Write d_name (starting at offset 19) - let name_ptr = entry_ptr.add(NAME_OFFSET); - name_ptr.copy_from_nonoverlapping(name.as_ptr(), name_len); - name_ptr.add(name_len).write(0); // null terminator - } - - self.offset += reclen; - true - } -} - // ============================================================================ // Common file type conversion // ============================================================================ fn file_type_to_d_type(ty: NodeType) -> u8 { - match ty { - NodeType::Directory => 4, // DT_DIR - NodeType::RegularFile => 8, // DT_REG - NodeType::Symlink => 10, // DT_LNK - _ => 0, // DT_UNKNOWN - } + ty as u8 } fn metadata_to_stat(metadata: Metadata) -> ctypes::stat { @@ -417,14 +325,14 @@ pub unsafe fn sys_getdents64(fd: c_int, buf: *mut u8, len: usize) -> ctypes::ssi let mut dir = dir.inner.lock(); let out = unsafe { core::slice::from_raw_parts_mut(buf, len) }; - let mut dir_buf = HermitDirBuffer::new(out); + let mut dir_buf = DirBuffer::new(out); loop { match dir.next() { Some(Ok(entry)) => { let d_type = file_type_to_d_type(entry.node_type); // Hermit style: only d_ino and d_type, d_off is not meaningful - if !dir_buf.write_entry(entry.ino, d_type, entry.name.as_bytes()) { + if !dir_buf.write_entry(entry.ino, 0, d_type, entry.name.as_bytes()) { return Ok(dir_buf.used_len() as ctypes::ssize_t); } } @@ -452,10 +360,19 @@ pub fn sys_lseek(fd: c_int, offset: ctypes::off_t, whence: c_int) -> ctypes::off 2 => SeekFrom::End(offset as _), _ => return Err(LinuxError::EINVAL), }; - let file = File::from_fd(fd)?; - let file = file.inner.lock(); - let off = (&*file).seek(pos)?; - Ok(off) + if let Ok(file) = File::from_fd(fd) { + let file = file.inner.lock(); + let off = (&*file).seek(pos)?; + return Ok(off); + } + let dir = Directory::from_fd(fd).map_err(|_| LinuxError::EBADF)?; + match pos { + SeekFrom::Start(0) => { + dir.inner.lock().rewind(); + Ok(0) + } + _ => Err(LinuxError::EINVAL), + } }) } diff --git a/os/arceos/modules/axfs/src/highlevel/fs.rs b/os/arceos/modules/axfs/src/highlevel/fs.rs index d012cc1aae..1ee026d618 100644 --- a/os/arceos/modules/axfs/src/highlevel/fs.rs +++ b/os/arceos/modules/axfs/src/highlevel/fs.rs @@ -479,6 +479,13 @@ impl ReadDir { pub fn location(&self) -> &Location { &self.dir } + + /// Resets the directory stream to the first entry. + pub fn rewind(&mut self) { + self.buf.clear(); + self.offset = 0; + self.ended = false; + } } impl Iterator for ReadDir { From a0647f3b29edd448f444a2313bdd1c25d03b42df Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 10:06:51 +0800 Subject: [PATCH 11/15] fix: correct spelling of inodetable_cache in multiple files --- components/rsext4/src/ext4/alloc.rs | 4 ++-- components/rsext4/src/ext4/fs.rs | 10 +++++----- components/rsext4/src/ext4/lookup.rs | 4 ++-- components/rsext4/src/ext4/mount.rs | 4 ++-- components/rsext4/src/ext4/sync.rs | 2 +- components/rsext4/src/hashtree/tests.rs | 2 +- os/arceos/modules/axfs/src/discovery.rs | 9 +++++++-- .../modules/axfs/src/fs/ext4/rsext4/fs.rs | 6 ++---- os/arceos/modules/axfs/src/highlevel/file.rs | 19 +++++++++---------- 9 files changed, 31 insertions(+), 29 deletions(-) diff --git a/components/rsext4/src/ext4/alloc.rs b/components/rsext4/src/ext4/alloc.rs index e79e667510..28ed7581c1 100644 --- a/components/rsext4/src/ext4/alloc.rs +++ b/components/rsext4/src/ext4/alloc.rs @@ -596,7 +596,7 @@ mod tests { block_allocator: BlockAllocator::new(&sb), inode_allocator: InodeAllocator::new(&sb), bitmap_cache: BitmapCache::create_default(), - inodetable_cahce: InodeCache::default(sb.s_inode_size), + inodetable_cache: InodeCache::default(sb.s_inode_size), datablock_cache: DataBlockCache::create_default(), root_inode: InodeNumber::new(2).unwrap(), group_count: 1, @@ -638,7 +638,7 @@ mod tests { block_allocator: BlockAllocator::new(&sb), inode_allocator: InodeAllocator::new(&sb), bitmap_cache: BitmapCache::create_default(), - inodetable_cahce: InodeCache::default(sb.s_inode_size), + inodetable_cache: InodeCache::default(sb.s_inode_size), datablock_cache: DataBlockCache::create_default(), root_inode: InodeNumber::new(2).unwrap(), group_count: 2, diff --git a/components/rsext4/src/ext4/fs.rs b/components/rsext4/src/ext4/fs.rs index 4a4b81e142..9be1584816 100644 --- a/components/rsext4/src/ext4/fs.rs +++ b/components/rsext4/src/ext4/fs.rs @@ -16,7 +16,7 @@ pub struct Ext4FileSystem { /// Bitmap cache with lazy loading and eviction. pub bitmap_cache: BitmapCache, /// Inode-table cache. - pub inodetable_cahce: InodeCache, + pub inodetable_cache: InodeCache, /// Data-block cache. pub datablock_cache: DataBlockCache, /// Root inode number, normally inode 2. @@ -158,7 +158,7 @@ impl Ext4FileSystem { .ok_or(Ext4Error::corrupted())? .inode_table(); - let (block_num, offset, _g) = self.inodetable_cahce.calc_inode_location( + let (block_num, offset, _g) = self.inodetable_cache.calc_inode_location( inode_num, self.superblock.s_inodes_per_group, AbsoluteBN::new(inode_table_start), @@ -176,7 +176,7 @@ impl Ext4FileSystem { } }; - self.inodetable_cahce + self.inodetable_cache .modify(block_dev, inode_num, block_num, offset, wrapped_f) } @@ -194,7 +194,7 @@ impl Ext4FileSystem { .ok_or(Ext4Error::corrupted())? .inode_table(); - let (block_num, offset, _g) = self.inodetable_cahce.calc_inode_location( + let (block_num, offset, _g) = self.inodetable_cache.calc_inode_location( inode_num, self.superblock.s_inodes_per_group, AbsoluteBN::new(inode_table_start), @@ -202,7 +202,7 @@ impl Ext4FileSystem { )?; let cached = self - .inodetable_cahce + .inodetable_cache .get_or_load(block_dev, inode_num, block_num, offset)?; Ok(cached.inode) } diff --git a/components/rsext4/src/ext4/lookup.rs b/components/rsext4/src/ext4/lookup.rs index 7a3b0c5d37..8459eeffc2 100644 --- a/components/rsext4/src/ext4/lookup.rs +++ b/components/rsext4/src/ext4/lookup.rs @@ -32,14 +32,14 @@ impl Ext4FileSystem { Some(desc) => AbsoluteBN::new(desc.inode_table()), None => return Err(Ext4Error::corrupted()), }; - let (block_num, offset, _group_idx) = self.inodetable_cahce.calc_inode_location( + let (block_num, offset, _group_idx) = self.inodetable_cache.calc_inode_location( self.root_inode, self.superblock.s_inodes_per_group, inode_table_start, BLOCK_SIZE, )?; let result = - self.inodetable_cahce + self.inodetable_cache .get_or_load(block_dev, self.root_inode, block_num, offset)?; debug!("Root inode i_mode: {}", result.inode.i_mode); debug!("Root inode detail: {:?}", result.inode); diff --git a/components/rsext4/src/ext4/mount.rs b/components/rsext4/src/ext4/mount.rs index 9ed878283d..7c41174077 100644 --- a/components/rsext4/src/ext4/mount.rs +++ b/components/rsext4/src/ext4/mount.rs @@ -30,7 +30,7 @@ impl Ext4FileSystem { self.block_allocator = BlockAllocator::new(&self.superblock); self.inode_allocator = InodeAllocator::new(&self.superblock); self.bitmap_cache = BitmapCache::create_default(); - self.inodetable_cahce = + self.inodetable_cache = InodeCache::new(INODE_CACHE_MAX, Self::inode_cache_size(&self.superblock)); self.datablock_cache = DataBlockCache::new(DATABLOCK_CACHE_MAX, BLOCK_SIZE); Ok(()) @@ -145,7 +145,7 @@ impl Ext4FileSystem { inode_allocator, bitmap_cache, root_inode: InodeNumber::new(2)?, - inodetable_cahce: inode_cache, + inodetable_cache: inode_cache, datablock_cache, group_count, mounted: true, diff --git a/components/rsext4/src/ext4/sync.rs b/components/rsext4/src/ext4/sync.rs index 1656c327d3..068a02f89b 100644 --- a/components/rsext4/src/ext4/sync.rs +++ b/components/rsext4/src/ext4/sync.rs @@ -12,7 +12,7 @@ impl Ext4FileSystem { ) -> Ext4Result<()> { info!("Syncing filesystem..."); self.datablock_cache.flush_all(block_dev)?; - self.inodetable_cahce.flush_all(block_dev)?; + self.inodetable_cache.flush_all(block_dev)?; self.bitmap_cache.flush_all(block_dev)?; self.sync_group_descriptors(block_dev)?; self.sync_superblock(block_dev)?; diff --git a/components/rsext4/src/hashtree/tests.rs b/components/rsext4/src/hashtree/tests.rs index e699dcb041..e01d91e6e2 100644 --- a/components/rsext4/src/hashtree/tests.rs +++ b/components/rsext4/src/hashtree/tests.rs @@ -114,7 +114,7 @@ fn create_test_fs() -> Ext4FileSystem { block_allocator: BlockAllocator::new(&superblock), inode_allocator: InodeAllocator::new(&superblock), bitmap_cache: BitmapCache::new(100), - inodetable_cahce: InodeCache::new(100, inode_size), + inodetable_cache: InodeCache::new(100, inode_size), datablock_cache: DataBlockCache::new(100, 4096), root_inode: InodeNumber::new(2).unwrap(), group_count: 1, diff --git a/os/arceos/modules/axfs/src/discovery.rs b/os/arceos/modules/axfs/src/discovery.rs index 60d7a5feda..fd7b846dfa 100644 --- a/os/arceos/modules/axfs/src/discovery.rs +++ b/os/arceos/modules/axfs/src/discovery.rs @@ -165,9 +165,14 @@ pub fn init_filesystems_with_policy( .iter() .position(|disk| disk.disk_index == selection.disk_index) .unwrap_or_else(|| panic!("selected root disk disappeared during initialization")); - DISCOVERED_DISKS.call_once(|| disks.clone()); + let selected = disks.remove(selected_disk_pos); + let discovered_disks = { + let mut discovered = disks; + discovered.insert(selected_disk_pos, selected.clone()); + discovered + }; + DISCOVERED_DISKS.call_once(|| discovered_disks); ROOT_SELECTION.call_once(|| selection); - let selected = disks.swap_remove(selected_disk_pos); let (description, region) = { let selected_partition_info = selection.partition_index.and_then(|part_index| { selected diff --git a/os/arceos/modules/axfs/src/fs/ext4/rsext4/fs.rs b/os/arceos/modules/axfs/src/fs/ext4/rsext4/fs.rs index e97162695e..6a368b659a 100644 --- a/os/arceos/modules/axfs/src/fs/ext4/rsext4/fs.rs +++ b/os/arceos/modules/axfs/src/fs/ext4/rsext4/fs.rs @@ -20,9 +20,7 @@ pub(crate) struct Ext4State { impl Ext4State { pub(crate) fn split(&mut self) -> (&mut rsext4::Ext4FileSystem, &mut Jbd2Dev) { - let fs = &mut self.fs as *mut _; - let dev = &mut self.dev as *mut _; - unsafe { (&mut *fs, &mut *dev) } + (&mut self.fs, &mut self.dev) } } @@ -87,7 +85,7 @@ impl Ext4Filesystem { let (fs, dev) = state.split(); fs.datablock_cache.flush_all(dev).map_err(into_vfs_err)?; fs.bitmap_cache.flush_all(dev).map_err(into_vfs_err)?; - fs.inodetable_cahce.flush_all(dev).map_err(into_vfs_err)?; + fs.inodetable_cache.flush_all(dev).map_err(into_vfs_err)?; // Mark the filesystem clean before writing the superblock so the // on-disk state reflects a clean sync / unmount. fs.superblock.s_state = Ext4Superblock::EXT4_VALID_FS; diff --git a/os/arceos/modules/axfs/src/highlevel/file.rs b/os/arceos/modules/axfs/src/highlevel/file.rs index f041e61b3d..99c8347d7e 100644 --- a/os/arceos/modules/axfs/src/highlevel/file.rs +++ b/os/arceos/modules/axfs/src/highlevel/file.rs @@ -390,7 +390,7 @@ impl OpenOptions { (false, true, true) => FileFlags::WRITE | FileFlags::APPEND, (true, true, true) => FileFlags::READ | FileFlags::WRITE | FileFlags::APPEND, (false, false, true) => FileFlags::APPEND, // RDONLY-equivalent + APPEND: pure status - (false, false, false) => return Err(VfsError::InvalidInput), + (false, false, false) => FileFlags::empty(), } | if self.path { FileFlags::PATH } else { @@ -399,7 +399,7 @@ impl OpenOptions { } pub(crate) fn is_valid(&self) -> bool { - if !self.read && !self.write && !self.append { + if !self.read && !self.write && !self.append && !self.path { return false; } // Linux multi-fs: RDONLY|TRUNC truncates the file (POSIX VERSIONS @@ -460,7 +460,7 @@ impl PageCache { impl Drop for PageCache { fn drop(&mut self) { if self.dirty { - warn!("dirty page dropped without flushing"); + error!("dirty page dropped without flushing"); } global_allocator().dealloc_pages(self.addr.as_usize(), 1, UsageKind::PageCache); } @@ -478,6 +478,8 @@ intrusive_adapter!(EvictListenerAdapter = Box: EvictListener { li struct CachedFileShared { page_cache: Mutex>, evict_listeners: Mutex>, + /// Serializes append-offset selection for all handles of the same inode. + append_lock: RwLock<()>, } impl CachedFileShared { @@ -485,6 +487,7 @@ impl CachedFileShared { Self { page_cache: Mutex::new(LruCache::new(NonZeroUsize::new(64).unwrap())), evict_listeners: Mutex::new(LinkedList::default()), + append_lock: RwLock::new(()), } } @@ -492,6 +495,7 @@ impl CachedFileShared { Self { page_cache: Mutex::new(LruCache::unbounded()), evict_listeners: Mutex::new(LinkedList::default()), + append_lock: RwLock::new(()), } } } @@ -501,9 +505,6 @@ pub struct CachedFile { inner: Location, shared: Arc, in_memory: bool, - /// Only one thread can append to the file at a time, while multiple writers - /// are permitted. - append_lock: RwLock<()>, } impl Clone for CachedFile { @@ -512,7 +513,6 @@ impl Clone for CachedFile { inner: self.inner.clone(), shared: self.shared.clone(), in_memory: self.in_memory, - append_lock: RwLock::new(()), } } } @@ -557,7 +557,6 @@ impl CachedFile { inner: location, shared, in_memory, - append_lock: RwLock::new(()), } } @@ -732,13 +731,13 @@ impl CachedFile { /// Writes `buf` to the file at `offset`. pub fn write_at(&self, buf: impl Read + IoBuf, offset: u64) -> VfsResult { - let _guard = self.append_lock.read(); + let _guard = self.shared.append_lock.read(); self.write_at_locked(buf, offset) } /// Appends `buf` to the end of the file. Returns `(bytes_written, new_end)`. pub fn append(&self, buf: impl Read + IoBuf) -> VfsResult<(usize, u64)> { - let _guard = self.append_lock.write(); + let _guard = self.shared.append_lock.write(); let file = self.inner.entry().as_file()?; let len = file.len()?; self.write_at_locked(buf, len) From 398d35db5e22c89c257080778e8fcd245b051a79 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 10:38:11 +0800 Subject: [PATCH 12/15] feat: enhance directory reading logic and improve entry handling in filesystem --- components/axfs_crates/axfs_devfs/src/dir.rs | 25 +++++++++++++-- components/axfs_crates/axfs_ramfs/src/dir.rs | 26 ++++++++++++++-- os/arceos/api/arceos_posix_api/src/imp/fs.rs | 32 +++++++++++++------- os/arceos/modules/axfs/src/highlevel/fs.rs | 9 +----- 4 files changed, 69 insertions(+), 23 deletions(-) diff --git a/components/axfs_crates/axfs_devfs/src/dir.rs b/components/axfs_crates/axfs_devfs/src/dir.rs index 09f75585e0..62c3a65f82 100644 --- a/components/axfs_crates/axfs_devfs/src/dir.rs +++ b/components/axfs_crates/axfs_devfs/src/dir.rs @@ -104,14 +104,35 @@ impl NodeOps for DevDirNode { impl DirNodeOps for DevDirNode { fn read_dir(&self, offset: u64, sink: &mut dyn DirEntrySink) -> VfsResult { let mut count = 0; + let mut off = offset; + + if off == 0 { + if !sink.accept(".", self.inode, NodeType::Directory, 1) { + return Ok(0); + } + count += 1; + off = 1; + } + if off == 1 { + let parent_inode = self + .this + .upgrade() + .and_then(|e| e.parent()) + .map_or(self.inode, |p| p.inode()); + if !sink.accept("..", parent_inode, NodeType::Directory, 2) { + return Ok(count); + } + count += 1; + off = 2; + } for (index, (name, entry)) in self .children .lock() .iter() .enumerate() - .skip(offset as usize) + .skip((off - 2) as usize) { - if !sink.accept(name, entry.inode(), entry.node_type(), (index + 1) as u64) { + if !sink.accept(name, entry.inode(), entry.node_type(), (index + 3) as u64) { break; } count += 1; diff --git a/components/axfs_crates/axfs_ramfs/src/dir.rs b/components/axfs_crates/axfs_ramfs/src/dir.rs index feec18526f..c0f8c2d1a1 100644 --- a/components/axfs_crates/axfs_ramfs/src/dir.rs +++ b/components/axfs_crates/axfs_ramfs/src/dir.rs @@ -170,14 +170,36 @@ impl NodeOps for RamDirNode { impl DirNodeOps for RamDirNode { fn read_dir(&self, offset: u64, sink: &mut dyn DirEntrySink) -> VfsResult { let mut count = 0; + let mut off = offset; + + if off == 0 { + if !sink.accept(".", self.inode, NodeType::Directory, 1) { + return Ok(0); + } + count += 1; + off = 1; + } + if off == 1 { + let parent_inode = self + .this + .lock() + .upgrade() + .and_then(|e| e.parent()) + .map_or(self.inode, |p| p.inode()); + if !sink.accept("..", parent_inode, NodeType::Directory, 2) { + return Ok(count); + } + count += 1; + off = 2; + } for (index, (name, entry)) in self .children .lock() .iter() .enumerate() - .skip(offset as usize) + .skip((off - 2) as usize) { - if !sink.accept(name, entry.inode(), entry.node_type(), (index + 1) as u64) { + if !sink.accept(name, entry.inode(), entry.node_type(), (index + 3) as u64) { break; } count += 1; diff --git a/os/arceos/api/arceos_posix_api/src/imp/fs.rs b/os/arceos/api/arceos_posix_api/src/imp/fs.rs index 4ff00a2af1..f0f6155ca1 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/fs.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/fs.rs @@ -23,6 +23,15 @@ pub struct Directory { // Linux-style getdents64 implementation (for normal Linux targets) // ============================================================================ +#[repr(C, packed)] +struct Dirent64Header { + d_ino: u64, + d_off: i64, + d_reclen: u16, + d_type: u8, + // d_name: [u8] follows at offset size_of::() +} + struct DirBuffer<'a> { buf: &'a mut [u8], offset: usize, @@ -42,7 +51,7 @@ impl<'a> DirBuffer<'a> { } fn write_entry(&mut self, d_ino: u64, d_off: i64, d_type: u8, name: &[u8]) -> bool { - const NAME_OFFSET: usize = 19; + const NAME_OFFSET: usize = core::mem::size_of::(); let name_len = name.len().min(255); let reclen = (NAME_OFFSET + name_len + 1).next_multiple_of(8); @@ -51,16 +60,17 @@ impl<'a> DirBuffer<'a> { } unsafe { - let entry_ptr = self.buf.as_mut_ptr().add(self.offset); - entry_ptr.cast::().write_unaligned(d_ino); - entry_ptr.add(8).cast::().write_unaligned(d_off); - entry_ptr - .add(16) - .cast::() - .write_unaligned(reclen as u16); - entry_ptr.add(18).write(d_type); - - let name_ptr = entry_ptr.add(NAME_OFFSET); + self.buf + .as_mut_ptr() + .add(self.offset) + .cast::() + .write_unaligned(Dirent64Header { + d_ino, + d_off, + d_reclen: reclen as u16, + d_type, + }); + let name_ptr = self.buf.as_mut_ptr().add(self.offset + NAME_OFFSET); name_ptr.copy_from_nonoverlapping(name.as_ptr(), name_len); name_ptr.add(name_len).write(0); } diff --git a/os/arceos/modules/axfs/src/highlevel/fs.rs b/os/arceos/modules/axfs/src/highlevel/fs.rs index 1ee026d618..54e69f5cad 100644 --- a/os/arceos/modules/axfs/src/highlevel/fs.rs +++ b/os/arceos/modules/axfs/src/highlevel/fs.rs @@ -290,17 +290,13 @@ impl FsContext { let entry = self.resolve_no_follow(path.as_ref())?; entry .parent() - .ok_or(VfsError::IsADirectory)? + .ok_or(VfsError::PermissionDenied)? .unlink(&entry.name(), false) } /// Removes a directory from the filesystem. pub fn remove_dir(&self, path: impl AsRef) -> VfsResult<()> { let entry = self.resolve_no_follow(path.as_ref())?; - let dir = entry.entry().as_dir()?; - if dir.has_children()? { - return Err(VfsError::DirectoryNotEmpty); - } entry .parent() .ok_or(VfsError::ResourceBusy)? @@ -359,9 +355,6 @@ impl FsContext { link_path: impl AsRef, ) -> VfsResult { let (dir, name) = self.resolve_nonexistent(link_path.as_ref())?; - if dir.lookup_no_follow(name).is_ok() { - return Err(VfsError::AlreadyExists); - } let symlink = dir.create(name, NodeType::Symlink, NodePermission::default())?; symlink.entry().as_file()?.set_symlink(target.as_ref())?; Ok(symlink) From c13cfa684f04671bd0dd20e304e5d40aa4a7ca1c Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 11:04:07 +0800 Subject: [PATCH 13/15] fix: update filesystem feature name to 'fs-default' in board configuration files --- os/axvisor/configs/board/phytiumpi.toml | 2 +- os/axvisor/configs/board/roc-rk3568-pc.toml | 2 +- .../board-phytiumpi/build-aarch64-unknown-none-softfloat.toml | 2 +- .../build-aarch64-unknown-none-softfloat.toml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/os/axvisor/configs/board/phytiumpi.toml b/os/axvisor/configs/board/phytiumpi.toml index 42a0ed4050..a8fb9f318f 100644 --- a/os/axvisor/configs/board/phytiumpi.toml +++ b/os/axvisor/configs/board/phytiumpi.toml @@ -1,6 +1,6 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "fs", + "fs-default", "phytium-mci", ] log = "Info" diff --git a/os/axvisor/configs/board/roc-rk3568-pc.toml b/os/axvisor/configs/board/roc-rk3568-pc.toml index 211950cdb6..6f2cd26d24 100644 --- a/os/axvisor/configs/board/roc-rk3568-pc.toml +++ b/os/axvisor/configs/board/roc-rk3568-pc.toml @@ -1,6 +1,6 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "fs", + "fs-default", "rockchip-sdhci", ] log = "Info" diff --git a/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml index c460cd1906..0964e25e40 100644 --- a/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,6 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "fs", + "fs-default", "phytium-mci", ] log = "Info" diff --git a/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml index 722f81a042..112e1c9ad3 100644 --- a/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml @@ -2,7 +2,7 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ "rockchip-sdhci", "rockchip-soc", - "fs", + "fs-default", ] log = "Info" plat_dyn = true From 49dc03ee34ea60b09d555bc7b6ec05c80aff26b5 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 11:21:12 +0800 Subject: [PATCH 14/15] fix: remove 'fs-default' feature from board configuration files for PhytiumPi and ROC-RK3568 --- os/axvisor/configs/board/phytiumpi.toml | 1 - os/axvisor/configs/board/roc-rk3568-pc.toml | 1 - .../board-phytiumpi/build-aarch64-unknown-none-softfloat.toml | 1 - .../build-aarch64-unknown-none-softfloat.toml | 1 - 4 files changed, 4 deletions(-) diff --git a/os/axvisor/configs/board/phytiumpi.toml b/os/axvisor/configs/board/phytiumpi.toml index a8fb9f318f..793dbd6415 100644 --- a/os/axvisor/configs/board/phytiumpi.toml +++ b/os/axvisor/configs/board/phytiumpi.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "fs-default", "phytium-mci", ] log = "Info" diff --git a/os/axvisor/configs/board/roc-rk3568-pc.toml b/os/axvisor/configs/board/roc-rk3568-pc.toml index 6f2cd26d24..be601d96cf 100644 --- a/os/axvisor/configs/board/roc-rk3568-pc.toml +++ b/os/axvisor/configs/board/roc-rk3568-pc.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "fs-default", "rockchip-sdhci", ] log = "Info" diff --git a/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml index 0964e25e40..c22220c549 100644 --- a/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/board-phytiumpi/build-aarch64-unknown-none-softfloat.toml @@ -1,6 +1,5 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ - "fs-default", "phytium-mci", ] log = "Info" diff --git a/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml b/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml index 112e1c9ad3..5ad598acad 100644 --- a/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml +++ b/test-suit/axvisor/normal/board-roc-rk3568-pc/build-aarch64-unknown-none-softfloat.toml @@ -2,7 +2,6 @@ env = { AX_IP = "10.0.2.15", AX_GW = "10.0.2.2" } features = [ "rockchip-sdhci", "rockchip-soc", - "fs-default", ] log = "Info" plat_dyn = true From a0814c459a2c037ae938b30cee56e5d5878bd177 Mon Sep 17 00:00:00 2001 From: ZCShou <72115@163.com> Date: Tue, 26 May 2026 12:09:00 +0800 Subject: [PATCH 15/15] feat: implement non-root filesystem mounting and region management --- os/arceos/modules/axfs/src/discovery.rs | 115 ++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 6 deletions(-) diff --git a/os/arceos/modules/axfs/src/discovery.rs b/os/arceos/modules/axfs/src/discovery.rs index fd7b846dfa..eba29eec48 100644 --- a/os/arceos/modules/axfs/src/discovery.rs +++ b/os/arceos/modules/axfs/src/discovery.rs @@ -5,6 +5,7 @@ use alloc::{ vec::Vec, }; +use ax_kspin::SpinNoIrq; use rd_block_volume::{BlockVolume, DiskId, PartitionTableKind as VolumeTableKind, scan_volumes}; use spin::Once; @@ -99,6 +100,13 @@ pub struct DiscoveredFilesystem { static DISCOVERED_DISKS: Once> = Once::new(); static ROOT_SELECTION: Once = Once::new(); +static MOUNTED_REGIONS: SpinNoIrq> = SpinNoIrq::new(Vec::new()); + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +struct RegionKey { + disk_index: usize, + partition_index: Option, +} impl Default for RootSelectionPolicy { fn default() -> Self { @@ -142,6 +150,15 @@ impl RootCandidate { } } +impl RootSelection { + const fn region_key(self) -> RegionKey { + RegionKey { + disk_index: self.disk_index, + partition_index: self.partition_index, + } + } +} + /// Initializes the filesystem subsystem by selecting a root device from the /// available block devices and optional boot arguments. pub fn init_filesystems(block_devs: Vec>, bootargs: Option<&str>) { @@ -203,6 +220,7 @@ pub fn init_filesystems_with_policy( ) }); init_root_filesystem(fs); + mount_default_non_root_filesystems(); } /// Returns the block filesystems discovered during runtime initialization. @@ -265,18 +283,103 @@ pub fn mount_discovered_filesystem( target: impl AsRef, ) -> ax_fs_vfs::VfsResult<()> { let (dev, region, kind) = discovered_region(disk_index, partition_index)?; - let selection = RootSelection { + let key = RegionKey { disk_index, partition_index, - filesystem: Some(kind), }; - if ROOT_SELECTION.get().copied() == Some(selection) { + if ROOT_SELECTION + .get() + .is_some_and(|selection| selection.region_key() == key) + { return Err(ax_fs_vfs::VfsError::ResourceBusy); } + reserve_mounted_region(key)?; + + let fs = match new_filesystem_from_dyn_by_kind(Box::new(dev), region, kind) { + Ok(fs) => fs, + Err(err) => { + unreserve_mounted_region(key); + return Err(err); + } + }; + let target = match ctx.resolve(target) { + Ok(target) => target, + Err(err) => { + unreserve_mounted_region(key); + return Err(err); + } + }; + if let Err(err) = target.mount(&fs) { + unreserve_mounted_region(key); + return Err(err); + } + Ok(()) +} + +fn reserve_mounted_region(key: RegionKey) -> ax_fs_vfs::VfsResult<()> { + let mut mounted = MOUNTED_REGIONS.lock(); + if mounted.contains(&key) { + return Err(ax_fs_vfs::VfsError::ResourceBusy); + } + mounted.push(key); + Ok(()) +} + +fn unreserve_mounted_region(key: RegionKey) { + MOUNTED_REGIONS.lock().retain(|mounted| *mounted != key); +} + +fn mount_default_non_root_filesystems() { + let Some(ctx) = crate::ROOT_FS_CONTEXT.get() else { + return; + }; + + for fs in discovered_filesystems() { + if fs.is_root || fs.filesystem.is_none() { + continue; + } + let Some(mount_path) = default_mount_path(&fs) else { + continue; + }; + match ctx.create_dir(mount_path.as_str(), ax_fs_vfs::NodePermission::default()) { + Ok(_) | Err(ax_fs_vfs::VfsError::AlreadyExists) => {} + Err(err) => { + warn!( + "failed to create default mount point {} for disk{} partition {:?}: {:?}", + mount_path, fs.disk_index, fs.partition_index, err + ); + continue; + } + } + + match mount_discovered_filesystem( + ctx, + fs.disk_index, + fs.partition_index, + mount_path.as_str(), + ) { + Ok(()) => info!( + " mounted non-root filesystem disk{} partition {:?} at {}", + fs.disk_index, fs.partition_index, mount_path + ), + Err(err) => warn!( + "failed to mount non-root filesystem disk{} partition {:?} at {}: {:?}", + fs.disk_index, fs.partition_index, mount_path, err + ), + } + } +} - let fs = new_filesystem_from_dyn_by_kind(Box::new(dev), region, kind)?; - let target = ctx.resolve(target)?; - target.mount(&fs).map(|_| ()) +fn default_mount_path(fs: &DiscoveredFilesystem) -> Option { + let name = fs.name.as_deref()?; + if name.is_empty() || name == "." || name == ".." || name.contains('/') { + return None; + } + if name.to_ascii_lowercase().contains("boot") { + Some("/boot".to_string()) + } else { + Some(format!("/{name}")) + } } fn collect_disks(block_devs: Vec>) -> Vec {