From 31bf9c5ba023996a0228fab515681bf25294f4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E7=9D=BF?= Date: Thu, 4 Jun 2026 09:35:03 +0800 Subject: [PATCH 01/34] feat(std): unify Rust std-aware builds --- .../std/qemu-smp1/arce_agent/Cargo.toml | 10 +- .../std/qemu-smp1/arce_agent/src/main.rs | 52 +- .../build-aarch64-unknown-none-softfloat.toml | 1 - ...ld-loongarch64-unknown-none-softfloat.toml | 1 - .../build-riscv64gc-unknown-none-elf.toml | 1 - .../qemu-smp1/build-x86_64-unknown-none.toml | 1 - .../std/qemu-smp1/helloworld/Cargo.toml | 11 +- .../std/qemu-smp1/helloworld/src/main.rs | 4 +- .../std/qemu-smp1/httpclient/Cargo.toml | 10 +- .../std/qemu-smp1/httpclient/src/main.rs | 4 +- .../std/qemu-smp1/httpserver/Cargo.toml | 11 +- .../std/qemu-smp1/httpserver/src/main.rs | 24 +- apps/arceos/std/qemu-smp1/io_test/Cargo.toml | 11 +- apps/arceos/std/qemu-smp1/io_test/src/main.rs | 8 +- .../std/qemu-smp1/thread_test/Cargo.toml | 11 +- .../std/qemu-smp1/thread_test/src/main.rs | 16 +- .../std/qemu-smp1/tokio_test/Cargo.toml | 9 +- .../std/qemu-smp1/tokio_test/src/main.rs | 4 +- components/axcpu/Cargo.toml | 3 + components/axcpu/src/x86_64/asm.rs | 10 +- components/axcpu/src/x86_64/uspace.rs | 7 +- components/kernel_guard/Cargo.toml | 3 + components/kernel_guard/src/arch/mod.rs | 5 +- components/kernel_guard/src/lib.rs | 4 +- components/percpu/percpu/Cargo.toml | 3 + components/percpu/percpu/src/custom/mod.rs | 6 +- components/percpu/percpu/src/imp.rs | 20 +- components/percpu/percpu/src/lib.rs | 2 +- components/scope-local/src/scope.rs | 49 +- components/scope-local/tests/scope_local.rs | 17 + components/someboot/Cargo.toml | 3 + components/someboot/build.rs | 6 +- components/someboot/src/arch/aarch64/link.ld | 20 +- .../someboot/src/arch/loongarch64/link.ld | 6 +- components/someboot/src/arch/riscv64/entry.rs | 40 + components/someboot/src/consts.rs | 2 +- components/someboot/src/lib.rs | 2 +- docs/docs/build/build.md | 2 +- drivers/ax-driver/Cargo.toml | 3 + drivers/ax-driver/build.rs | 11 +- drivers/ax-driver/src/net/binding.rs | 4 +- drivers/ax-driver/src/usb/mod.rs | 45 +- drivers/usb/usb-host/Cargo.toml | 5 +- drivers/usb/usb-host/build.rs | 7 +- drivers/usb/usb-host/src/lib.rs | 2 +- memory/page_table_multiarch/src/bits64.rs | 44 +- .../page_table_multiarch/tests/alloc_tests.rs | 42 + os/StarryOS/kernel/Cargo.toml | 15 +- os/StarryOS/kernel/src/kmod/kprint.rs | 1 + os/StarryOS/kernel/src/pseudofs/mod.rs | 12 +- os/StarryOS/kernel/src/pseudofs/sysfs.rs | 14 +- .../kernel/src/pseudofs/usbfs/manager.rs | 335 +- os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs | 39 +- os/StarryOS/kernel/src/syscall/fs/fd_ops.rs | 1 - os/StarryOS/starryos/Cargo.toml | 6 +- os/StarryOS/starryos/src/main.rs | 9 +- os/arceos/api/arceos_posix_api/build.rs | 13 +- .../arceos_posix_api/src/imp/pthread/mutex.rs | 145 +- os/arceos/api/arceos_posix_api/src/lib.rs | 3 +- os/arceos/doc/std_support_readme.md | 154 +- os/arceos/examples/std/arce_agent/Cargo.toml | 10 +- os/arceos/examples/std/arce_agent/src/main.rs | 10 +- os/arceos/examples/std/config.template.toml | 8 +- os/arceos/examples/std/helloworld/Cargo.toml | 11 +- os/arceos/examples/std/helloworld/src/main.rs | 4 +- os/arceos/examples/std/httpclient/Cargo.toml | 10 +- os/arceos/examples/std/httpclient/src/main.rs | 4 +- os/arceos/examples/std/httpserver/Cargo.toml | 11 +- os/arceos/examples/std/httpserver/src/main.rs | 4 +- os/arceos/examples/std/io_test/Cargo.toml | 11 +- os/arceos/examples/std/io_test/src/main.rs | 4 +- os/arceos/examples/std/thread_test/Cargo.toml | 11 +- .../examples/std/thread_test/src/main.rs | 8 +- os/arceos/examples/std/tokio_test/Cargo.toml | 9 +- os/arceos/examples/std/tokio_test/src/main.rs | 4 +- os/arceos/modules/axalloc/build.rs | 1 + os/arceos/modules/axhal/Cargo.toml | 7 +- os/arceos/modules/axhal/build.rs | 16 +- os/arceos/modules/axhal/src/platform.rs | 4 +- os/arceos/modules/axruntime/Cargo.toml | 3 + os/arceos/modules/axruntime/src/block/mod.rs | 93 +- os/arceos/modules/axruntime/src/devices.rs | 30 +- os/arceos/modules/axruntime/src/lib.rs | 2 +- os/arceos/modules/axtask/Cargo.toml | 3 + os/arceos/modules/axtask/src/run_queue.rs | 6 +- os/arceos/ulib/arceos-rust/CHANGELOG.md | 33 - os/arceos/ulib/arceos-rust/Cargo.toml | 69 - os/arceos/ulib/arceos-rust/build.rs | 697 ---- os/arceos/ulib/arceos-rust/defconfig.toml | 6 - os/arceos/ulib/arceos-rust/lib/CHANGELOG.md | 43 - os/arceos/ulib/arceos-rust/lib/Cargo.toml | 91 - .../ulib/arceos-rust/lib/src/interface/fs.rs | 63 - .../ulib/arceos-rust/lib/src/interface/io.rs | 69 - .../ulib/arceos-rust/lib/src/interface/mem.rs | 36 - .../ulib/arceos-rust/lib/src/interface/mod.rs | 12 - .../ulib/arceos-rust/lib/src/interface/net.rs | 283 -- .../arceos-rust/lib/src/interface/sync.rs | 106 - .../arceos-rust/lib/src/interface/task.rs | 21 - .../arceos-rust/lib/src/interface/thread.rs | 85 - .../arceos-rust/lib/src/interface/util.rs | 63 - os/arceos/ulib/arceos-rust/lib/src/lib.rs | 35 - os/arceos/ulib/arceos-rust/lib/src/syscall.rs | 0 os/arceos/ulib/arceos-rust/src/lib.rs | 1 - os/arceos/ulib/axlibc/c/pthread.c | 7 - os/arceos/ulib/axlibc/src/pthread.rs | 12 + os/arceos/ulib/axstd/Cargo.toml | 70 +- os/arceos/ulib/axstd/src/lib.rs | 2 +- os/arceos/ulib/axstd/src/os.rs | 3 + os/arceos/ulib/axstd/src/os/libc_compat.rs | 3245 +++++++++++++++++ os/axvisor/Cargo.toml | 7 +- os/axvisor/src/main.rs | 11 +- os/axvisor/src/shell/command/base.rs | 19 +- os/axvisor/src/shell/command/mod.rs | 2 +- platforms/axplat-dyn/Cargo.toml | 3 + platforms/axplat-dyn/src/lib.rs | 2 +- platforms/somehal/Cargo.toml | 3 + platforms/somehal/src/lib.rs | 2 +- scripts/axbuild/src/arceos/build.rs | 87 +- scripts/axbuild/src/arceos/cbuild.rs | 2 +- scripts/axbuild/src/axvisor/build.rs | 31 +- scripts/axbuild/src/backtrace.rs | 8 +- scripts/axbuild/src/build.rs | 1551 ++++++-- scripts/axbuild/src/clippy.rs | 130 +- scripts/axbuild/src/starry/board.rs | 4 +- scripts/axbuild/src/starry/build.rs | 38 +- scripts/axbuild/src/starry/test.rs | 6 +- .../std/aarch64-unknown-linux-musl.json | 45 + .../std/loongarch64-unknown-linux-musl.json | 37 + .../std/pie/aarch64-unknown-linux-musl.json | 47 + .../std/pie/riscv64gc-unknown-linux-musl.json | 43 + .../std/riscv64gc-unknown-linux-musl.json | 42 + .../std/x86_64-unknown-linux-musl.json | 46 + virtualization/axaddrspace/src/npt/mod.rs | 9 + 133 files changed, 6180 insertions(+), 2640 deletions(-) delete mode 100644 os/arceos/ulib/arceos-rust/CHANGELOG.md delete mode 100644 os/arceos/ulib/arceos-rust/Cargo.toml delete mode 100644 os/arceos/ulib/arceos-rust/build.rs delete mode 100644 os/arceos/ulib/arceos-rust/defconfig.toml delete mode 100644 os/arceos/ulib/arceos-rust/lib/CHANGELOG.md delete mode 100644 os/arceos/ulib/arceos-rust/lib/Cargo.toml delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/interface/fs.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/interface/io.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/interface/mem.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/interface/mod.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/interface/net.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/interface/sync.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/interface/task.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/interface/thread.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/interface/util.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/lib.rs delete mode 100644 os/arceos/ulib/arceos-rust/lib/src/syscall.rs delete mode 100644 os/arceos/ulib/arceos-rust/src/lib.rs create mode 100644 os/arceos/ulib/axstd/src/os/libc_compat.rs create mode 100644 scripts/targets/std/aarch64-unknown-linux-musl.json create mode 100644 scripts/targets/std/loongarch64-unknown-linux-musl.json create mode 100644 scripts/targets/std/pie/aarch64-unknown-linux-musl.json create mode 100644 scripts/targets/std/pie/riscv64gc-unknown-linux-musl.json create mode 100644 scripts/targets/std/riscv64gc-unknown-linux-musl.json create mode 100644 scripts/targets/std/x86_64-unknown-linux-musl.json diff --git a/apps/arceos/std/qemu-smp1/arce_agent/Cargo.toml b/apps/arceos/std/qemu-smp1/arce_agent/Cargo.toml index 6808916e85..3b421a5d69 100644 --- a/apps/arceos/std/qemu-smp1/arce_agent/Cargo.toml +++ b/apps/arceos/std/qemu-smp1/arce_agent/Cargo.toml @@ -4,7 +4,13 @@ version = "0.2.0" edition.workspace = true publish = false +[features] +default = [] +arceos = ["dep:ax-std"] + [dependencies] +ax-std = { workspace = true, optional = true } + # minreq: minimal HTTP client, no TLS (we use a local HTTP proxy for HTTPS APIs) minreq = { version = "2", default-features = false } @@ -15,5 +21,5 @@ serde_json = "1" log = "0.4" env_logger = "0.11" -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["log-level-warn"] } +[package.metadata.axstd] +features = ["log-level-warn"] diff --git a/apps/arceos/std/qemu-smp1/arce_agent/src/main.rs b/apps/arceos/std/qemu-smp1/arce_agent/src/main.rs index 7a013f2c59..b85a7d9afe 100644 --- a/apps/arceos/std/qemu-smp1/arce_agent/src/main.rs +++ b/apps/arceos/std/qemu-smp1/arce_agent/src/main.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "arceos")] +use ax_std as _; + // ArceAgent v0.2: Interactive AI Agent on ArceOS Unikernel // // Architecture: @@ -9,54 +12,51 @@ // Uses minreq for HTTP (no TLS — requires a local HTTP proxy). // Compiles identically on Linux (for development) and ArceOS (for deployment). -#[cfg(target_os = "hermit")] -use arceos_rust as _; - -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] mod base64; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] mod context; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] mod hal; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] mod lineedit; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] mod llm; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] mod memory; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] mod tools; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] use context::ContextManager; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] use hal::HalInterface; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] use lineedit::LineEditor; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] use llm::{ChatMessage, LlmClient}; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] use log::{debug, error, info}; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] use memory::Memory; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] use tools::{dispatch, tool_catalog}; // --------------------------------------------------------------------------- // Configuration // --------------------------------------------------------------------------- -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const API_BASE: &str = "http://localhost:8080/v1"; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const MODEL: &str = "qwen3.5-plus-thinking"; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const MAX_CONTEXT_MESSAGES: usize = 40; /// Max tool-call rounds per user turn (prevents infinite loops). -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const MAX_TOOL_ROUNDS: usize = 10; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const SYSTEM_PROMPT: &str = r#"你是 ArceAgent,一个运行在 ArceOS Unikernel 上的智能机器人助手。 ## 你的身体 @@ -86,7 +86,7 @@ const SYSTEM_PROMPT: &str = r#"你是 ArceAgent,一个运行在 ArceOS Unikern - 这体现了"云端大脑 + 裸机身体"的架构:你的推理能力来自云端 LLM,执行能力来自本地硬件 - 请记住你是一个有物理实体的机器人,不是纯软件助手"#; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const STARTUP_PROMPT: &str = "你刚刚启动。请先用 read_memory \ 工具检查持久记忆中是否有需要注意的内容,然后根据记忆内容和当前状态,\ 向用户输出一句简短的欢迎/问候信息。如果记忆中有用户的名字或偏好,\ @@ -97,7 +97,7 @@ const STARTUP_PROMPT: &str = "你刚刚启动。请先用 read_memory \ // Sends context to LLM, executes any tool calls, prints final text response. // --------------------------------------------------------------------------- -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] fn run_llm_turn( llm: &LlmClient, ctx: &mut ContextManager, @@ -202,10 +202,10 @@ fn main() { println!("========================================"); println!(); - #[cfg(target_os = "hermit")] + #[cfg(feature = "arceos")] println!("ArceAgent smoke test ready"); - #[cfg(not(target_os = "hermit"))] + #[cfg(not(feature = "arceos"))] { // init logging (colors, log level) let mut builder = env_logger::Builder::new(); diff --git a/apps/arceos/std/qemu-smp1/build-aarch64-unknown-none-softfloat.toml b/apps/arceos/std/qemu-smp1/build-aarch64-unknown-none-softfloat.toml index 923c4b2e2c..d23dc6bf50 100644 --- a/apps/arceos/std/qemu-smp1/build-aarch64-unknown-none-softfloat.toml +++ b/apps/arceos/std/qemu-smp1/build-aarch64-unknown-none-softfloat.toml @@ -1,4 +1,3 @@ -std = true features = [] log = "Info" max_cpu_num = 1 diff --git a/apps/arceos/std/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml b/apps/arceos/std/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml index 34d8cf6299..3b060cfc2d 100644 --- a/apps/arceos/std/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml +++ b/apps/arceos/std/qemu-smp1/build-loongarch64-unknown-none-softfloat.toml @@ -1,4 +1,3 @@ -std = true features = [ "ax-hal/loongarch64-qemu-virt", ] diff --git a/apps/arceos/std/qemu-smp1/build-riscv64gc-unknown-none-elf.toml b/apps/arceos/std/qemu-smp1/build-riscv64gc-unknown-none-elf.toml index 923c4b2e2c..d23dc6bf50 100644 --- a/apps/arceos/std/qemu-smp1/build-riscv64gc-unknown-none-elf.toml +++ b/apps/arceos/std/qemu-smp1/build-riscv64gc-unknown-none-elf.toml @@ -1,4 +1,3 @@ -std = true features = [] log = "Info" max_cpu_num = 1 diff --git a/apps/arceos/std/qemu-smp1/build-x86_64-unknown-none.toml b/apps/arceos/std/qemu-smp1/build-x86_64-unknown-none.toml index 83fc38460d..ad417dfc24 100644 --- a/apps/arceos/std/qemu-smp1/build-x86_64-unknown-none.toml +++ b/apps/arceos/std/qemu-smp1/build-x86_64-unknown-none.toml @@ -1,4 +1,3 @@ -std = true features = [ ] log = "Info" diff --git a/apps/arceos/std/qemu-smp1/helloworld/Cargo.toml b/apps/arceos/std/qemu-smp1/helloworld/Cargo.toml index f0717f5dd7..6a27725123 100644 --- a/apps/arceos/std/qemu-smp1/helloworld/Cargo.toml +++ b/apps/arceos/std/qemu-smp1/helloworld/Cargo.toml @@ -5,7 +5,12 @@ edition.workspace = true authors = ["eternalcomet "] publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +arceos = ["dep:ax-std"] -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["log-level-debug"] } +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-debug"] diff --git a/apps/arceos/std/qemu-smp1/helloworld/src/main.rs b/apps/arceos/std/qemu-smp1/helloworld/src/main.rs index 63cc785704..dcc9e799ab 100644 --- a/apps/arceos/std/qemu-smp1/helloworld/src/main.rs +++ b/apps/arceos/std/qemu-smp1/helloworld/src/main.rs @@ -1,5 +1,5 @@ -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; fn main() { println!("Hello, world!"); diff --git a/apps/arceos/std/qemu-smp1/httpclient/Cargo.toml b/apps/arceos/std/qemu-smp1/httpclient/Cargo.toml index ce17173b57..a8c8d94cd2 100644 --- a/apps/arceos/std/qemu-smp1/httpclient/Cargo.toml +++ b/apps/arceos/std/qemu-smp1/httpclient/Cargo.toml @@ -9,7 +9,11 @@ publish = false [features] default = [] -dns = ["arceos-rust/dns"] +arceos = ["dep:ax-std", "ax-std/net"] +dns = [] -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["log-level-debug"] } +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-debug"] diff --git a/apps/arceos/std/qemu-smp1/httpclient/src/main.rs b/apps/arceos/std/qemu-smp1/httpclient/src/main.rs index b38c76d170..6c18cd5ff7 100644 --- a/apps/arceos/std/qemu-smp1/httpclient/src/main.rs +++ b/apps/arceos/std/qemu-smp1/httpclient/src/main.rs @@ -3,8 +3,8 @@ use std::{ net::{TcpStream, ToSocketAddrs}, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; #[cfg(feature = "dns")] const DEST: &str = "ident.me:80"; diff --git a/apps/arceos/std/qemu-smp1/httpserver/Cargo.toml b/apps/arceos/std/qemu-smp1/httpserver/Cargo.toml index 3706d9518a..087748e231 100644 --- a/apps/arceos/std/qemu-smp1/httpserver/Cargo.toml +++ b/apps/arceos/std/qemu-smp1/httpserver/Cargo.toml @@ -5,7 +5,12 @@ edition.workspace = true authors = ["Yuekai Jia "] publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +arceos = ["dep:ax-std"] -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["log-level-debug"] } +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-debug"] diff --git a/apps/arceos/std/qemu-smp1/httpserver/src/main.rs b/apps/arceos/std/qemu-smp1/httpserver/src/main.rs index 45c72f18c4..9fb55857e2 100644 --- a/apps/arceos/std/qemu-smp1/httpserver/src/main.rs +++ b/apps/arceos/std/qemu-smp1/httpserver/src/main.rs @@ -6,22 +6,22 @@ //! ab -n 5000 -c 20 http://X.X.X.X:5555/ //! ``` -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] use std::{ io::{self, prelude::*}, net::{TcpListener, TcpStream}, thread, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const LOCAL_IP: &str = "0.0.0.0"; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const LOCAL_PORT: u16 = 5555; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] #[rustfmt::skip] macro_rules! header { () => { @@ -35,7 +35,7 @@ Connection: close\r\n\ }; } -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const CONTENT: &str = r#" Hello, ArceOS @@ -52,7 +52,7 @@ const CONTENT: &str = r#" "#; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] macro_rules! info { ($($arg:tt)*) => { match option_env!("LOG") { @@ -64,7 +64,7 @@ macro_rules! info { }; } -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] fn http_server(mut stream: TcpStream) -> io::Result<()> { let mut buf = [0u8; 4096]; let _len = stream.read(&mut buf)?; @@ -75,7 +75,7 @@ fn http_server(mut stream: TcpStream) -> io::Result<()> { Ok(()) } -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] fn accept_loop() -> io::Result<()> { let listener = TcpListener::bind((LOCAL_IP, LOCAL_PORT))?; println!("listen on: http://{}/", listener.local_addr().unwrap()); @@ -98,8 +98,8 @@ fn accept_loop() -> io::Result<()> { fn main() { println!("Hello, ArceOS HTTP server!"); - #[cfg(target_os = "hermit")] + #[cfg(feature = "arceos")] println!("HTTP server smoke test ready"); - #[cfg(not(target_os = "hermit"))] + #[cfg(not(feature = "arceos"))] accept_loop().expect("test HTTP server failed"); } diff --git a/apps/arceos/std/qemu-smp1/io_test/Cargo.toml b/apps/arceos/std/qemu-smp1/io_test/Cargo.toml index 98a6c018d4..78b49cd62d 100644 --- a/apps/arceos/std/qemu-smp1/io_test/Cargo.toml +++ b/apps/arceos/std/qemu-smp1/io_test/Cargo.toml @@ -5,5 +5,12 @@ edition.workspace = true authors = ["eternalcomet "] publish = false -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["log-level-off"] } +[features] +default = [] +arceos = ["dep:ax-std", "ax-std/fs"] + +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-off"] diff --git a/apps/arceos/std/qemu-smp1/io_test/src/main.rs b/apps/arceos/std/qemu-smp1/io_test/src/main.rs index 72ffd8295a..27cf4877a5 100644 --- a/apps/arceos/std/qemu-smp1/io_test/src/main.rs +++ b/apps/arceos/std/qemu-smp1/io_test/src/main.rs @@ -6,8 +6,8 @@ use std::{ time::SystemTime, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; fn main() -> io::Result<()> { println!("=== Rust 文件读写功能测试 ===\n"); @@ -343,12 +343,12 @@ fn read_directory() -> io::Result<()> { } // 删除目录 - #[cfg(not(target_os = "hermit"))] + #[cfg(not(feature = "arceos"))] { fs::remove_dir_all("test_dir")?; println!(" 已删除 test_dir 目录"); } - #[cfg(target_os = "hermit")] + #[cfg(feature = "arceos")] println!(" Hermit 下跳过递归删除目录"); Ok(()) diff --git a/apps/arceos/std/qemu-smp1/thread_test/Cargo.toml b/apps/arceos/std/qemu-smp1/thread_test/Cargo.toml index 7a52b3c793..34d27d81a9 100644 --- a/apps/arceos/std/qemu-smp1/thread_test/Cargo.toml +++ b/apps/arceos/std/qemu-smp1/thread_test/Cargo.toml @@ -5,5 +5,12 @@ edition.workspace = true authors = ["eternalcomet "] publish = false -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["log-level-off"] } +[features] +default = [] +arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/irq"] + +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-off"] diff --git a/apps/arceos/std/qemu-smp1/thread_test/src/main.rs b/apps/arceos/std/qemu-smp1/thread_test/src/main.rs index ec42b91487..6cc1049c22 100644 --- a/apps/arceos/std/qemu-smp1/thread_test/src/main.rs +++ b/apps/arceos/std/qemu-smp1/thread_test/src/main.rs @@ -1,4 +1,4 @@ -#[cfg(not(all(target_os = "hermit", target_arch = "riscv64")))] +#[cfg(not(all(feature = "arceos", target_arch = "riscv64")))] use std::sync::atomic::{AtomicUsize, Ordering}; use std::{ sync::{Arc, Barrier, Mutex, mpsc}, @@ -6,8 +6,8 @@ use std::{ time::Duration, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; fn main() { println!("=== Rust 线程功能测试程序 ===\n"); @@ -31,9 +31,9 @@ fn main() { test_barrier_synchronization(); // 7. 线程恐慌处理 - #[cfg(not(target_os = "hermit"))] + #[cfg(not(feature = "arceos"))] test_thread_panic(); - #[cfg(target_os = "hermit")] + #[cfg(feature = "arceos")] println!("7. 线程异常处理:\nUnikernel 环境跳过会终止程序的异常捕获测试。\n"); println!("\n=== 所有测试完成 ==="); @@ -164,13 +164,13 @@ fn test_mutex_shared_data() { } // 5. 原子操作 -#[cfg(all(target_os = "hermit", target_arch = "riscv64"))] +#[cfg(all(feature = "arceos", target_arch = "riscv64"))] fn test_atomic_operations() { println!("5. 原子操作测试:"); println!("riscv64 Hermit 环境跳过高并发原子操作测试。\n"); } -#[cfg(not(all(target_os = "hermit", target_arch = "riscv64")))] +#[cfg(not(all(feature = "arceos", target_arch = "riscv64")))] fn test_atomic_operations() { println!("5. 原子操作测试:"); @@ -225,7 +225,7 @@ fn test_barrier_synchronization() { } // 7. 线程恐慌处理 -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] fn test_thread_panic() { println!("7. 线程恐慌处理:"); println!("Unikernel环境下使用panic_abort模式,不支持恐慌捕获,因此会导致程序终止。"); diff --git a/apps/arceos/std/qemu-smp1/tokio_test/Cargo.toml b/apps/arceos/std/qemu-smp1/tokio_test/Cargo.toml index fe4e9ab85b..3f4deb3aad 100644 --- a/apps/arceos/std/qemu-smp1/tokio_test/Cargo.toml +++ b/apps/arceos/std/qemu-smp1/tokio_test/Cargo.toml @@ -6,8 +6,13 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/irq"] + [dependencies] +ax-std = { workspace = true, optional = true } tokio = { version = "1", features = ["rt", "macros", "time", "sync"] } -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["log-level-off"] } +[package.metadata.axstd] +features = ["log-level-off"] diff --git a/apps/arceos/std/qemu-smp1/tokio_test/src/main.rs b/apps/arceos/std/qemu-smp1/tokio_test/src/main.rs index 12fd7dca51..17961f8549 100644 --- a/apps/arceos/std/qemu-smp1/tokio_test/src/main.rs +++ b/apps/arceos/std/qemu-smp1/tokio_test/src/main.rs @@ -3,8 +3,8 @@ use std::sync::{ atomic::{AtomicUsize, Ordering}, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; use tokio::{ sync::{Barrier, Mutex, mpsc}, time::{Duration, sleep}, diff --git a/components/axcpu/Cargo.toml b/components/axcpu/Cargo.toml index 5af06ef099..3bec01b7d0 100644 --- a/components/axcpu/Cargo.toml +++ b/components/axcpu/Cargo.toml @@ -56,6 +56,9 @@ ax-page-table-multiarch = { workspace = true } [lints.clippy] new_without_default = "allow" +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } + [package.metadata.docs.rs] all-features = true targets = [ diff --git a/components/axcpu/src/x86_64/asm.rs b/components/axcpu/src/x86_64/asm.rs index 92be733393..87966a9240 100644 --- a/components/axcpu/src/x86_64/asm.rs +++ b/components/axcpu/src/x86_64/asm.rs @@ -9,22 +9,22 @@ use x86_64::instructions::interrupts; /// Allows the current CPU to respond to interrupts. #[inline] pub fn enable_irqs() { - #[cfg(not(target_os = "none"))] + #[cfg(not(any(target_os = "none", arceos_std)))] { warn!("enable_irqs: not implemented"); } - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] interrupts::enable() } /// Makes the current CPU to ignore interrupts. #[inline] pub fn disable_irqs() { - #[cfg(not(target_os = "none"))] + #[cfg(not(any(target_os = "none", arceos_std)))] { warn!("disable_irqs: not implemented"); } - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] interrupts::disable() } @@ -39,7 +39,7 @@ pub fn irqs_enabled() -> bool { /// It must be called with interrupts enabled, otherwise it will never return. #[inline] pub fn wait_for_irqs() { - if cfg!(target_os = "none") { + if cfg!(any(target_os = "none", arceos_std)) { unsafe { asm!("hlt") } } else { core::hint::spin_loop() diff --git a/components/axcpu/src/x86_64/uspace.rs b/components/axcpu/src/x86_64/uspace.rs index c2e2329d36..27e6383938 100644 --- a/components/axcpu/src/x86_64/uspace.rs +++ b/components/axcpu/src/x86_64/uspace.rs @@ -95,13 +95,14 @@ impl UserContext { self.fs_base = read_thread_pointer() as _; unsafe { write_thread_pointer(kernel_fs_base) }; - let cr2 = Cr2::read().unwrap().as_u64() as usize; let vector = self.vector as u8; const PAGE_FAULT_VECTOR: u8 = ExceptionVector::Page as u8; let ret = match (vector, err_code_to_flags(self.error_code)) { - (PAGE_FAULT_VECTOR, Ok(flags)) => ReturnReason::PageFault(va!(cr2), flags), + (PAGE_FAULT_VECTOR, Ok(flags)) => { + ReturnReason::PageFault(va!(Cr2::read_raw() as usize), flags) + } (LEGACY_SYSCALL_VECTOR, _) => ReturnReason::Syscall, (IRQ_VECTOR_START..=IRQ_VECTOR_END, _) => { crate::trap::irq_handler(vector as _); @@ -110,7 +111,7 @@ impl UserContext { _ => ReturnReason::Exception(ExceptionInfo { vector, error_code: self.error_code, - cr2, + cr2: Cr2::read_raw() as usize, }), }; diff --git a/components/kernel_guard/Cargo.toml b/components/kernel_guard/Cargo.toml index 2cf444537d..1545034b18 100644 --- a/components/kernel_guard/Cargo.toml +++ b/components/kernel_guard/Cargo.toml @@ -16,3 +16,6 @@ default = [] [dependencies] cfg-if = "1.0" ax-crate-interface = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/components/kernel_guard/src/arch/mod.rs b/components/kernel_guard/src/arch/mod.rs index 5a320b2ed3..5074c01887 100644 --- a/components/kernel_guard/src/arch/mod.rs +++ b/components/kernel_guard/src/arch/mod.rs @@ -1,4 +1,7 @@ -#![cfg_attr(not(target_os = "none"), allow(dead_code, unused_imports))] +#![cfg_attr( + not(any(target_os = "none", arceos_std)), + allow(dead_code, unused_imports) +)] cfg_if::cfg_if! { if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { diff --git a/components/kernel_guard/src/lib.rs b/components/kernel_guard/src/lib.rs index c8b19c615e..e88c174a5c 100644 --- a/components/kernel_guard/src/lib.rs +++ b/components/kernel_guard/src/lib.rs @@ -89,7 +89,7 @@ pub struct NoOp; cfg_if::cfg_if! { // For user-mode std apps, we use the alias of [`NoOp`] for all guards, // since we can not disable IRQs or preemption in user-mode. - if #[cfg(any(target_os = "none", doc))] { + if #[cfg(any(target_os = "none", arceos_std, doc))] { /// A guard that disables/enables local IRQs around the critical section. pub struct IrqSave(usize); @@ -139,7 +139,7 @@ impl Drop for NoOp { fn drop(&mut self) {} } -#[cfg(any(target_os = "none", doc))] +#[cfg(any(target_os = "none", arceos_std, doc))] mod imp { use super::*; diff --git a/components/percpu/percpu/Cargo.toml b/components/percpu/percpu/Cargo.toml index 9740737faf..58348b47c2 100644 --- a/components/percpu/percpu/Cargo.toml +++ b/components/percpu/percpu/Cargo.toml @@ -39,3 +39,6 @@ x86 = "0.52" [target.'cfg(not(target_os = "none"))'.dependencies] spin = { workspace = true } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/components/percpu/percpu/src/custom/mod.rs b/components/percpu/percpu/src/custom/mod.rs index 4229c4dd5a..516d7e0d92 100644 --- a/components/percpu/percpu/src/custom/mod.rs +++ b/components/percpu/percpu/src/custom/mod.rs @@ -200,11 +200,11 @@ fn percpu_link_start() -> usize { /// Returns the number of areas initialized. If this function has been called /// before, it does nothing and returns 0. pub fn init() -> usize { - #[cfg(target_os = "linux")] + #[cfg(all(target_os = "linux", not(arceos_std)))] { _linux::init(4) } - #[cfg(not(target_os = "linux"))] + #[cfg(any(not(target_os = "linux"), arceos_std))] { 0 } @@ -237,7 +237,7 @@ pub fn percpu_area_size() -> usize { _percpu_load_end as *const () as usize - percpu_link_start() } -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", not(arceos_std)))] mod _linux { use std::sync::Mutex; diff --git a/components/percpu/percpu/src/imp.rs b/components/percpu/percpu/src/imp.rs index aca6343b2c..1a063a89a2 100644 --- a/components/percpu/percpu/src/imp.rs +++ b/components/percpu/percpu/src/imp.rs @@ -9,7 +9,7 @@ const fn align_up_64(val: usize) -> usize { (val + SIZE_64BIT - 1) & !(SIZE_64BIT - 1) } -#[cfg(not(target_os = "none"))] +#[cfg(not(any(target_os = "none", arceos_std)))] static PERCPU_AREA_BASE: spin::once::Once = spin::once::Once::new(); unsafe extern "C" { @@ -44,7 +44,7 @@ pub fn percpu_area_size() -> usize { /// if `cpu_id` is 0, it returns the base address of all per-CPU data areas. pub fn percpu_area_base(cpu_id: usize) -> usize { cfg_if::cfg_if! { - if #[cfg(target_os = "none")] { + if #[cfg(any(target_os = "none", arceos_std))] { let base = _percpu_start as *const () as usize; } else { let base = *PERCPU_AREA_BASE.get().unwrap(); @@ -85,7 +85,7 @@ pub fn init() -> usize { ) } - #[cfg(target_os = "linux")] + #[cfg(all(target_os = "linux", not(arceos_std)))] { // we not load the ax-percpu section in ELF, allocate them here. let total_size = _percpu_end as *const () as usize - _percpu_start as *const () as usize; @@ -98,7 +98,7 @@ pub fn init() -> usize { let num = percpu_area_num(); for i in 1..num { let secondary_base = percpu_area_base(i); - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] assert!(secondary_base + size <= _percpu_end as *const () as usize); // copy per-cpu data of the primary CPU to other CPUs. unsafe { @@ -116,9 +116,11 @@ pub fn read_percpu_reg() -> usize { unsafe { cfg_if::cfg_if! { if #[cfg(target_arch = "x86_64")] { - tp = if cfg!(target_os = "linux") { + tp = if cfg!(arceos_std) { + x86::msr::rdmsr(x86::msr::IA32_GS_BASE) as usize + } else if cfg!(target_os = "linux") { SELF_PTR.read_current_raw() - } else if cfg!(target_os = "none") { + } else if cfg!(any(target_os = "none", arceos_std)) { x86::msr::rdmsr(x86::msr::IA32_GS_BASE) as usize } else { unimplemented!() @@ -164,7 +166,9 @@ pub unsafe fn write_percpu_reg(tp: usize) { unsafe { cfg_if::cfg_if! { if #[cfg(target_arch = "x86_64")] { - if cfg!(target_os = "linux") { + if cfg!(arceos_std) { + x86::msr::wrmsr(x86::msr::IA32_GS_BASE, tp as u64); + } else if cfg!(target_os = "linux") { const ARCH_SET_GS: u32 = 0x1001; const SYS_ARCH_PRCTL: u32 = 158; core::arch::asm!( @@ -173,7 +177,7 @@ pub unsafe fn write_percpu_reg(tp: usize) { in("edi") ARCH_SET_GS, in("rsi") tp, ); - } else if cfg!(target_os = "none") { + } else if cfg!(any(target_os = "none", arceos_std)) { x86::msr::wrmsr(x86::msr::IA32_GS_BASE, tp as u64); } else { unimplemented!() diff --git a/components/percpu/percpu/src/lib.rs b/components/percpu/percpu/src/lib.rs index cf4129e6d6..d308ef4262 100644 --- a/components/percpu/percpu/src/lib.rs +++ b/components/percpu/percpu/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(any(target_os = "none", arceos_std), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] diff --git a/components/scope-local/src/scope.rs b/components/scope-local/src/scope.rs index 012a53f233..4b9c931650 100644 --- a/components/scope-local/src/scope.rs +++ b/components/scope-local/src/scope.rs @@ -1,7 +1,7 @@ use alloc::alloc::{alloc, dealloc, handle_alloc_error}; use core::{alloc::Layout, iter::zip, mem::MaybeUninit, ptr::NonNull}; -use spin::LazyLock; +use spin::{LazyLock, Once}; use crate::{ boxed::ItemBox, @@ -10,16 +10,19 @@ use crate::{ /// A scope is a collection of items. pub struct Scope { - // Not using [ItemBox] to save a `usize` because we know the length - ptr: NonNull, + ptr: NonNull, } unsafe impl Send for Scope {} unsafe impl Sync for Scope {} impl Scope { + fn len() -> usize { + Registry.len() + } + fn layout() -> Layout { - Layout::array::(Registry.len()).unwrap() + Layout::array::(Self::len()).unwrap() } /// Create a new namespace with all resources initialized as their default @@ -34,7 +37,7 @@ impl Scope { core::slice::from_raw_parts_mut(ptr.cast::>().as_ptr(), Registry.len()) }; for (item, d) in zip(&*Registry, slice) { - d.write(ItemBox::new(item)); + d.write(ItemSlot::new(item)); } Self { ptr } @@ -42,12 +45,12 @@ impl Scope { pub(crate) fn get(&self, item: &'static Item) -> &ItemBox { let index = item.index(); - unsafe { self.ptr.add(index).as_ref() } + unsafe { self.ptr.add(index).as_ref() }.get() } pub(crate) fn get_mut(&mut self, item: &'static Item) -> &mut ItemBox { let index = item.index(); - unsafe { self.ptr.add(index).as_mut() } + unsafe { self.ptr.add(index).as_mut() }.get_mut() } } @@ -59,7 +62,7 @@ impl Default for Scope { impl Drop for Scope { fn drop(&mut self) { - let ptr = NonNull::slice_from_raw_parts(self.ptr, Registry.len()); + let ptr = NonNull::slice_from_raw_parts(self.ptr, Self::len()); unsafe { ptr.drop_in_place(); dealloc(self.ptr.cast().as_ptr(), Self::layout()); @@ -67,6 +70,34 @@ impl Drop for Scope { } } +struct ItemSlot { + item: &'static Item, + value: Once, +} + +impl ItemSlot { + fn new(item: &'static Item) -> Self { + Self { + item, + value: Once::new(), + } + } + + fn get(&self) -> &ItemBox { + self.value.call_once(|| ItemBox::new(self.item)) + } + + fn get_mut(&mut self) -> &mut ItemBox { + if !self.value.is_completed() { + let item = self.item; + self.value.call_once(|| ItemBox::new(item)); + } + self.value + .get_mut() + .expect("scope-local item must be initialized") + } +} + static GLOBAL_SCOPE: LazyLock = LazyLock::new(Scope::new); #[ax_percpu::def_percpu] @@ -101,6 +132,6 @@ impl ActiveScope { let ptr = ACTIVE_SCOPE_PTR.read_current(); let ptr = NonNull::new(ptr as _).unwrap_or(GLOBAL_SCOPE.ptr); let index = item.index(); - unsafe { ptr.add(index).as_ref() } + unsafe { ptr.add(index).as_ref() }.get() } } diff --git a/components/scope-local/tests/scope_local.rs b/components/scope-local/tests/scope_local.rs index 4df86b5cdb..a89c3c2b67 100644 --- a/components/scope-local/tests/scope_local.rs +++ b/components/scope-local/tests/scope_local.rs @@ -11,6 +11,7 @@ use ctor::ctor; use scope_local::{ActiveScope, Scope, scope_local}; static TEST_LOCK: Mutex<()> = Mutex::new(()); +static UNUSED_INIT_COUNT: AtomicUsize = AtomicUsize::new(0); #[ctor] fn init_percpu() { @@ -31,6 +32,22 @@ fn scope_init() { assert_eq!(*DATA, 42); } +#[test] +fn scope_init_is_per_item_lazy() { + let _guard = TEST_LOCK.lock().unwrap(); + UNUSED_INIT_COUNT.store(0, Ordering::Relaxed); + scope_local! { + static DATA: usize = 42; + static UNUSED: usize = { + UNUSED_INIT_COUNT.fetch_add(1, Ordering::Relaxed); + 7 + }; + } + + assert_eq!(*DATA, 42); + assert_eq!(UNUSED_INIT_COUNT.load(Ordering::Relaxed), 0); +} + #[test] fn scope() { let _guard = TEST_LOCK.lock().unwrap(); diff --git a/components/someboot/Cargo.toml b/components/someboot/Cargo.toml index 0d3c0b9d5f..daa341199d 100644 --- a/components/someboot/Cargo.toml +++ b/components/someboot/Cargo.toml @@ -67,3 +67,6 @@ sbi-rt = { workspace = true, features = ["legacy"] } prettyplease = "0.2" quote = "1.0" syn = "2.0" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/components/someboot/build.rs b/components/someboot/build.rs index f50fcce366..352c5afe60 100644 --- a/components/someboot/build.rs +++ b/components/someboot/build.rs @@ -6,10 +6,14 @@ fn main() { println!("cargo::rustc-check-cfg=cfg(page_size_16k)"); println!("cargo::rustc-check-cfg=cfg(uspace)"); println!("cargo::rustc-check-cfg=cfg(hv)"); + println!("cargo::rustc-check-cfg=cfg(arceos_std)"); let target = std::env::var("TARGET").unwrap(); + let arceos_std = std::env::var_os("CARGO_CFG_ARCEOS_STD").is_some(); - if target.contains("windows") || target.contains("linux") || target.contains("darwin") { + if !arceos_std + && (target.contains("windows") || target.contains("linux") || target.contains("darwin")) + { return; } diff --git a/components/someboot/src/arch/aarch64/link.ld b/components/someboot/src/arch/aarch64/link.ld index b1c476f19d..552ff47739 100644 --- a/components/someboot/src/arch/aarch64/link.ld +++ b/components/someboot/src/arch/aarch64/link.ld @@ -13,8 +13,8 @@ OUTPUT_ARCH(aarch64) ENTRY(_head) PHDRS { text PT_LOAD FLAGS(7); /* RWX */ - note PT_NOTE FLAGS(4); /* R__ */ tls PT_TLS FLAGS(4); /* R__ */ + note PT_NOTE FLAGS(4); /* R__ */ } SECTIONS @@ -48,6 +48,8 @@ SECTIONS *(.init.data) *(.sdata .sdata.*) *(.got .got.*) + *(.got.plt .got.plt.*) + *(.igot .igot.*) . = ALIGN(64); __percpu_start = .; @@ -59,13 +61,18 @@ SECTIONS *(.rela .rela*) __rela_dyn_end = .; } + + .init_array : ALIGN(8) { + __init_array_start = .; + KEEP(*(SORT_BY_INIT_PRIORITY(.init_array.*))) + KEEP(*(.init_array)) + __init_array_end = .; + } .sdata : ALIGN(8) { *(.sdata) . = ALIGN(PAGE_SIZE); } - _edata = .; - __kernel_load_end = .; .tdata : { _stdata = .; @@ -73,6 +80,10 @@ SECTIONS _etdata = .; } :text :tls + _edata = .; + __kernel_load_end = .; + __bss_start = .; + .tbss : { _stbss = .; *(.tbss .tbss.*) @@ -82,7 +93,6 @@ SECTIONS } :text :tls . = ALIGN(8); - __bss_start = .; .sbss : { *(.dynsbss) *(.sbss) *(.scommon) } :text @@ -136,6 +146,6 @@ SECTIONS *(.dynamic .dynsym .dynstr .hash .gnu.hash) *(.gnu.attributes) *(.options) - *(.eh_frame) + *(.eh_frame .eh_frame_hdr) } } diff --git a/components/someboot/src/arch/loongarch64/link.ld b/components/someboot/src/arch/loongarch64/link.ld index 131f8b1008..7d73cca688 100644 --- a/components/someboot/src/arch/loongarch64/link.ld +++ b/components/someboot/src/arch/loongarch64/link.ld @@ -13,6 +13,7 @@ OUTPUT_ARCH(loongarch) ENTRY(kernel_entry) PHDRS { text PT_LOAD FLAGS(7); /* RWX */ + tls PT_TLS FLAGS(4); /* R__ */ note PT_NOTE FLAGS(4); /* R__ */ } @@ -92,7 +93,7 @@ SECTIONS _stdata = .; *(.tdata .tdata.*) _etdata = .; - } + } :text :tls .tbss : { _stbss = .; @@ -100,7 +101,7 @@ SECTIONS *(.init.bss) *(.tcommon) _etbss = .; - } + } :text :tls .sbss : { *(.dynsbss) *(.sbss) *(.scommon) @@ -158,4 +159,3 @@ SECTIONS *(.eh_frame) } } - diff --git a/components/someboot/src/arch/riscv64/entry.rs b/components/someboot/src/arch/riscv64/entry.rs index 5bdd8d94d0..3dfb91e636 100644 --- a/components/someboot/src/arch/riscv64/entry.rs +++ b/components/someboot/src/arch/riscv64/entry.rs @@ -8,6 +8,7 @@ const RISCV_LINUX_IMAGE_VERSION: usize = 0x0000_0002; const RISCV_LINUX_IMAGE_MAGIC: usize = 0x0056_4353_4952; const RISCV_LINUX_IMAGE_MAGIC2: usize = 0x0543_5352; +#[cfg(not(arceos_std))] #[unsafe(naked)] #[unsafe(no_mangle)] #[unsafe(link_section = ".head.text")] @@ -45,6 +46,45 @@ pub unsafe extern "C" fn _head() -> ! { ) } +#[cfg(arceos_std)] +#[unsafe(naked)] +#[unsafe(no_mangle)] +#[unsafe(link_section = ".head.text")] +pub unsafe extern "C" fn _head() -> ! { + naked_asm!( + ".option push", + ".option norvc", + // code0/code1 + "j 1f", + "nop", + ".option pop", + // text_offset + ".quad {text_offset}", + // image_size + ".quad __kernel_load_end - _head", + // flags + ".quad {flags}", + // version + reserved + ".word {version}", + ".word 0", + // reserved + ".quad 0", + // magic + magic2 + reserved + ".quad {magic}", + ".word {magic2}", + ".word 0", + "1:", + "lla t0, {kernel_entry}", + "jr t0", + text_offset = const RISCV_LINUX_IMAGE_TEXT_OFFSET, + flags = const RISCV_LINUX_IMAGE_FLAGS, + version = const RISCV_LINUX_IMAGE_VERSION, + magic = const RISCV_LINUX_IMAGE_MAGIC, + magic2 = const RISCV_LINUX_IMAGE_MAGIC2, + kernel_entry = sym kernel_entry, + ) +} + #[unsafe(naked)] pub unsafe extern "C" fn kernel_entry(_hart_id: usize, _fdt_addr: usize) -> ! { naked_asm!( diff --git a/components/someboot/src/consts.rs b/components/someboot/src/consts.rs index 00ccbad178..9feeb62f53 100644 --- a/components/someboot/src/consts.rs +++ b/components/someboot/src/consts.rs @@ -1,4 +1,4 @@ -#[cfg(target_os = "none")] +#[cfg(any(target_os = "none", arceos_std))] include!(concat!(env!("OUT_DIR"), "/defines.rs")); #[cfg(page_size_4k)] diff --git a/components/someboot/src/lib.rs b/components/someboot/src/lib.rs index c956b65b82..8d3236eba1 100644 --- a/components/someboot/src/lib.rs +++ b/components/someboot/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] #![no_main] -#![cfg(not(any(windows, unix)))] +#![cfg(any(not(any(windows, unix)), arceos_std))] #![cfg_attr(target_arch = "x86_64", feature(abi_x86_interrupt))] #[allow(unused_imports)] diff --git a/docs/docs/build/build.md b/docs/docs/build/build.md index 3ab9dbda2d..f76e5bea51 100644 --- a/docs/docs/build/build.md +++ b/docs/docs/build/build.md @@ -302,7 +302,7 @@ flowchart TD ### 编译期文件写入(write_if_changed) -axbuild 在生成构建辅助文件(如 ArceOS std 的 `.cargo/config.toml` 和 `loongarch64-unknown-hermit.json`)时使用 `write_if_changed` 模式:写入前先读取已有内容,内容相同时跳过写入。这避免了因时间戳更新导致 cargo 不必要的重建——cargo 的增量编译依赖文件 mtime 判断是否需要重新编译,`write_if_changed` 确保只有真正变化的配置才会触发重建。 +axbuild 在生成构建辅助文件(如 ArceOS std 的 `.cargo/config.toml`、fake libc 预构建脚本和 linker wrapper)时使用 `write_if_changed` 模式:写入前先读取已有内容,内容相同时跳过写入。这避免了因时间戳更新导致 cargo 不必要的重建——cargo 的增量编译依赖文件 mtime 判断是否需要重新编译,`write_if_changed` 确保只有真正变化的配置才会触发重建。 ### 环境变量作用域保护(EnvRestoreGuard) diff --git a/drivers/ax-driver/Cargo.toml b/drivers/ax-driver/Cargo.toml index 13c153d829..6d6eea8279 100644 --- a/drivers/ax-driver/Cargo.toml +++ b/drivers/ax-driver/Cargo.toml @@ -147,3 +147,6 @@ riscv_goldfish = "0.1" [target.'cfg(target_arch = "aarch64")'.dependencies] ax-arm-pl031.workspace = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/drivers/ax-driver/build.rs b/drivers/ax-driver/build.rs index 7dd7f5cb87..3f72ad47f3 100644 --- a/drivers/ax-driver/build.rs +++ b/drivers/ax-driver/build.rs @@ -40,6 +40,7 @@ fn main() { let has_plat_dyn = has_feature("plat-dyn"); let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default(); let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + let arceos_std = std::env::var_os("CARGO_CFG_ARCEOS_STD").is_some(); let target_has_cvsd = matches!(target_arch.as_str(), "riscv32" | "riscv64"); if has_plat_dyn { @@ -50,10 +51,16 @@ fn main() { if has_virtio_core || has_virtio_dev { enable_cfg_flag("virtio_dev"); } - if has_plat_dyn && target_os == "none" && has_any_feature(PCI_DYN_INTX_ROUTE_FEATURES) { + if has_plat_dyn + && (target_os == "none" || arceos_std) + && has_any_feature(PCI_DYN_INTX_ROUTE_FEATURES) + { enable_cfg_flag("pci_dyn_intx_route"); } - if has_plat_dyn && target_os == "none" && has_any_feature(PCI_DYN_ACPI_INTX_ROUTE_FEATURES) { + if has_plat_dyn + && (target_os == "none" || arceos_std) + && has_any_feature(PCI_DYN_ACPI_INTX_ROUTE_FEATURES) + { enable_cfg_flag("pci_dyn_acpi_intx_route"); } if has_any_feature(&["ahci", "bcm2835-sdhci"]) || (has_feature("cvsd") && target_has_cvsd) { diff --git a/drivers/ax-driver/src/net/binding.rs b/drivers/ax-driver/src/net/binding.rs index 8783bbe48b..228908c36e 100644 --- a/drivers/ax-driver/src/net/binding.rs +++ b/drivers/ax-driver/src/net/binding.rs @@ -49,7 +49,7 @@ pub fn pci_legacy_irq(endpoint: &EndpointRc) -> Option { || { #[cfg(all( plat_dyn, - target_os = "none", + any(target_os = "none", arceos_std), any( feature = "intel-net", feature = "ixgbe", @@ -76,7 +76,7 @@ pub fn pci_legacy_irq(endpoint: &EndpointRc) -> Option { || { #[cfg(all( plat_dyn, - target_os = "none", + any(target_os = "none", arceos_std), any( feature = "intel-net", feature = "ixgbe", diff --git a/drivers/ax-driver/src/usb/mod.rs b/drivers/ax-driver/src/usb/mod.rs index 801c57cd6a..ac0c2ab140 100644 --- a/drivers/ax-driver/src/usb/mod.rs +++ b/drivers/ax-driver/src/usb/mod.rs @@ -1,28 +1,23 @@ extern crate alloc; -#[cfg(target_os = "none")] use core::time::Duration; -#[cfg(target_os = "none")] use crab_usb::USBHost; -#[cfg(target_os = "none")] use dma_api::{DmaAllocHandle, DmaConstraints, DmaDirection, DmaError, DmaMapHandle, DmaOp}; use rdrive::DriverGeneric; -#[cfg(all(feature = "rockchip-dwc-xhci", target_os = "none"))] +#[cfg(feature = "rockchip-dwc-xhci")] mod dwc; -#[cfg(all(feature = "xhci-mmio", target_os = "none"))] +#[cfg(feature = "xhci-mmio")] mod xhci_mmio; -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(feature = "xhci-pci")] mod xhci_pci; pub type UsbHostDevice = rdrive::Device; pub type UsbHostDeviceGuard = rdrive::DeviceGuard; -#[cfg(target_os = "none")] struct UsbKernel; -#[cfg(target_os = "none")] impl DmaOp for UsbKernel { fn page_size(&self) -> usize { axklib::dma::op().page_size() @@ -119,36 +114,25 @@ impl DmaOp for UsbKernel { } } -#[cfg(target_os = "none")] impl crab_usb::KernelOp for UsbKernel { fn delay(&self, duration: Duration) { axklib::time::busy_wait(duration); } } -#[cfg(target_os = "none")] static USB_KERNEL: UsbKernel = UsbKernel; -#[cfg(target_os = "none")] pub fn usb_kernel() -> &'static dyn crab_usb::KernelOp { &USB_KERNEL } -#[cfg(target_os = "none")] pub struct PlatformUsbHost { name: &'static str, irq_num: Option, host: USBHost, } -#[cfg(not(target_os = "none"))] -pub struct PlatformUsbHost { - name: &'static str, - irq_num: Option, -} - impl PlatformUsbHost { - #[cfg(target_os = "none")] fn new(name: &'static str, host: USBHost, irq_num: Option) -> Self { Self { name, @@ -157,17 +141,10 @@ impl PlatformUsbHost { } } - #[cfg(not(target_os = "none"))] - fn new_stub(name: &'static str, irq_num: Option) -> Self { - Self { name, irq_num } - } - - #[cfg(target_os = "none")] pub fn host(&self) -> &USBHost { &self.host } - #[cfg(target_os = "none")] pub fn host_mut(&mut self) -> &mut USBHost { &mut self.host } @@ -184,26 +161,16 @@ impl DriverGeneric for PlatformUsbHost { } pub trait PlatformDeviceUsbHost { - #[cfg(target_os = "none")] fn register_usb_host(self, name: &'static str, host: USBHost, irq_num: Option); - - #[cfg(not(target_os = "none"))] - fn register_usb_host_stub(self, name: &'static str, irq_num: Option); } impl PlatformDeviceUsbHost for rdrive::PlatformDevice { - #[cfg(target_os = "none")] fn register_usb_host(self, name: &'static str, host: USBHost, irq_num: Option) { self.register(PlatformUsbHost::new(name, host, irq_num)); } - - #[cfg(not(target_os = "none"))] - fn register_usb_host_stub(self, name: &'static str, irq_num: Option) { - self.register(PlatformUsbHost::new_stub(name, irq_num)); - } } -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(feature = "xhci-pci")] pub(crate) fn align_up_4k(size: usize) -> usize { const MASK: usize = 0xfff; (size + MASK) & !MASK @@ -226,7 +193,7 @@ fn decode_irq_cells(specifier: &[u32]) -> Option { } } -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(feature = "xhci-pci")] fn pci_static_irq(endpoint: &rdrive::probe::pci::EndpointRc) -> Option { let interrupt_pin = endpoint.interrupt_pin(); if let Some(irq) = crate::pci::legacy_irq_for_endpoint(endpoint.address(), interrupt_pin) { @@ -236,7 +203,7 @@ fn pci_static_irq(endpoint: &rdrive::probe::pci::EndpointRc) -> Option { (line != 0 && line != u8::MAX).then_some(crate::pci::legacy_line_to_irq(line)) } -#[cfg(all(feature = "xhci-pci", target_os = "none"))] +#[cfg(feature = "xhci-pci")] pub(crate) fn pci_irq_or_error( endpoint: &rdrive::probe::pci::EndpointRc, ) -> Result { diff --git a/drivers/usb/usb-host/Cargo.toml b/drivers/usb/usb-host/Cargo.toml index 1b6821de0c..c70da9519f 100644 --- a/drivers/usb/usb-host/Cargo.toml +++ b/drivers/usb/usb-host/Cargo.toml @@ -12,7 +12,8 @@ version = "0.9.4" [features] aggressive_usb_reset = [] default = ["aggressive_usb_reset"] -libusb = ["libusb1-sys"] +libusb = ["umod"] +umod = ["libusb1-sys"] [dependencies] bitflags = "2.8" @@ -36,6 +37,4 @@ tock-registers.workspace = true trait-ffi = "0.2.4" usb-if = {workspace = true} xhci = "0.9" - -[target.'cfg(not(target_os = "none"))'.dependencies] libusb1-sys = {version = "0.7", optional = true} diff --git a/drivers/usb/usb-host/build.rs b/drivers/usb/usb-host/build.rs index f8cc9f1221..95eae89cc8 100644 --- a/drivers/usb/usb-host/build.rs +++ b/drivers/usb/usb-host/build.rs @@ -2,10 +2,9 @@ fn main() { println!("cargo::rustc-check-cfg=cfg(umod)"); println!("cargo::rustc-check-cfg=cfg(kmod)"); - let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); - if os == "none" { - println!("cargo::rustc-cfg=kmod"); - } else if std::env::var("CARGO_FEATURE_LIBUSB").is_ok() { + if std::env::var("CARGO_FEATURE_UMOD").is_ok() { println!("cargo::rustc-cfg=umod"); + } else { + println!("cargo::rustc-cfg=kmod"); } } diff --git a/drivers/usb/usb-host/src/lib.rs b/drivers/usb/usb-host/src/lib.rs index e06627c41a..db821b3fd0 100644 --- a/drivers/usb/usb-host/src/lib.rs +++ b/drivers/usb/usb-host/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(not(feature = "umod"), no_std)] extern crate alloc; #[macro_use] extern crate log; diff --git a/memory/page_table_multiarch/src/bits64.rs b/memory/page_table_multiarch/src/bits64.rs index c21b3d5884..ba0bef9379 100644 --- a/memory/page_table_multiarch/src/bits64.rs +++ b/memory/page_table_multiarch/src/bits64.rs @@ -32,6 +32,8 @@ const fn p1_index(vaddr: usize) -> usize { /// When the [`PageTable64`] itself is dropped. pub struct PageTable64 { root_paddr: PhysAddr, + root_frames: usize, + root_allocated_with_frames: bool, #[cfg(feature = "copy-from")] borrowed_entries: bitmaps::Bitmap, _phantom: PhantomData<(M, PTE, H)>, @@ -45,6 +47,29 @@ impl PageTable64 PagingResult { + if root_frames == 1 && root_align == PAGE_SIZE_4K { + return Self::try_new(); + } + let root_paddr = Self::alloc_root_table(root_frames, root_align)?; + Ok(Self { + root_paddr, + root_frames, + root_allocated_with_frames: true, #[cfg(feature = "copy-from")] borrowed_entries: bitmaps::Bitmap::new(), _phantom: PhantomData, @@ -118,6 +143,19 @@ impl PageTable64 PagingResult { + if root_frames == 0 || root_align < PAGE_SIZE_4K || !root_align.is_power_of_two() { + return Err(PagingError::NoMemory); + } + if let Some(paddr) = H::alloc_frames(root_frames, root_align) { + let ptr = H::phys_to_virt(paddr).as_mut_ptr(); + unsafe { core::ptr::write_bytes(ptr, 0, PAGE_SIZE_4K * root_frames) }; + Ok(paddr) + } else { + Err(PagingError::NoMemory) + } + } + fn table_of<'a>(&self, paddr: PhysAddr) -> &'a [PTE] { let ptr = H::phys_to_virt(paddr).as_ptr() as _; unsafe { core::slice::from_raw_parts(ptr, ENTRY_COUNT) } @@ -307,7 +345,11 @@ impl Drop for PageTable64< self.dealloc_tree(entry.paddr(), 1); } } - H::dealloc_frame(self.root_paddr()); + if self.root_allocated_with_frames { + H::dealloc_frames(self.root_paddr(), self.root_frames); + } else { + H::dealloc_frame(self.root_paddr()); + } } } diff --git a/memory/page_table_multiarch/tests/alloc_tests.rs b/memory/page_table_multiarch/tests/alloc_tests.rs index 01d073160f..01b2572b89 100644 --- a/memory/page_table_multiarch/tests/alloc_tests.rs +++ b/memory/page_table_multiarch/tests/alloc_tests.rs @@ -139,6 +139,34 @@ fn run_test_for, PTE: GenericPTE>() -> Pa Ok(()) } +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64", docsrs))] +fn run_aligned_root_test_for, PTE: GenericPTE>() +-> PagingResult<()> { + ALLOCATED.with_borrow_mut(|it| { + it.clear(); + }); + ALIGN.with_borrow_mut(|it| { + it.clear(); + }); + + let table = + PageTable64::>::try_new_with_root(4, 4096 * 4).unwrap(); + assert_eq!(table.root_paddr().as_usize() % (4096 * 4), 0); + assert_eq!( + ALIGN.with_borrow(|it| it.get(&table.root_paddr().as_usize()).copied()), + Some(4096 * 4) + ); + + drop(table); + assert_eq!( + ALLOCATED.with_borrow(|it| it.len()), + 0, + "Some frames were not deallocated" + ); + + Ok(()) +} + #[cfg(target_pointer_width = "32")] fn run_test_for_32bit, PTE: GenericPTE>() -> PagingResult<()> { @@ -223,6 +251,20 @@ fn test_dealloc_riscv() -> PagingResult<()> { Ok(()) } +#[test] +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64", docsrs))] +fn test_aligned_root_riscv() -> PagingResult<()> { + run_aligned_root_test_for::< + ax_page_table_multiarch::riscv::Sv39MetaData, + ax_page_table_entry::riscv::Rv64PTE, + >()?; + run_aligned_root_test_for::< + ax_page_table_multiarch::riscv::Sv48MetaData, + ax_page_table_entry::riscv::Rv64PTE, + >()?; + Ok(()) +} + #[test] #[cfg(any(target_arch = "aarch64", docsrs))] fn test_dealloc_aarch64() -> PagingResult<()> { diff --git a/os/StarryOS/kernel/Cargo.toml b/os/StarryOS/kernel/Cargo.toml index 4ee46e6912..ff63e63214 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -19,14 +19,10 @@ dev-log = [] ext4 = ["ax-fs/ext4"] input = ["dep:ax-input", "ax-feat/input"] memtrack = ["ax-feat/backtrace", "ax-alloc/tracking"] -rknpu = ["dep:ax-driver", "ax-driver/rknpu"] +rknpu = ["ax-driver/rknpu"] k230-kpu = ["ax-feat/xuantie-c9xx", "dep:axklib", "dep:k230-kpu"] plat-dyn = [ "ax-feat/plat-dyn", - "dep:ax-driver", - "ax-driver/usb", - "dep:crab-usb", - "dep:rdrive", ] vsock = ["ax-feat/vsock"] dynamic_debug = ["ax-feat/ipi"] @@ -68,7 +64,7 @@ ax-lazyinit.workspace = true ax-log.workspace = true ax-mm.workspace = true axnet = { version = "0.7.0", path = "../../arceos/modules/axnet-ng", package = "ax-net-ng" } -ax-driver = { workspace = true, optional = true } +ax-driver = { workspace = true, features = ["usb"] } axklib = { workspace = true, optional = true } axplat-dyn = { workspace = true, optional = true } ax-runtime.workspace = true @@ -77,8 +73,8 @@ ax-task.workspace = true ax-dma = { workspace = true, optional = true } sg2002-tpu = { workspace = true, optional = true } k230-kpu = { workspace = true, optional = true } -crab-usb = { workspace = true, optional = true } -rdrive = { workspace = true, optional = true } +crab-usb.workspace = true +rdrive.workspace = true axbacktrace = { workspace = true } ax-errno = { workspace = true } @@ -158,3 +154,6 @@ riscv.workspace = true [target.'cfg(not(any(target_arch = "aarch64", target_arch = "loongarch64")))'.dependencies] ax-page-table-multiarch = { workspace = true, features = ["copy-from"] } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/os/StarryOS/kernel/src/kmod/kprint.rs b/os/StarryOS/kernel/src/kmod/kprint.rs index 96bad85d73..3e3b4b140f 100644 --- a/os/StarryOS/kernel/src/kmod/kprint.rs +++ b/os/StarryOS/kernel/src/kmod/kprint.rs @@ -43,6 +43,7 @@ const LOG_LEVELS: &[&[u8; 2]] = &[ /// `memset` shim. C ABI; some modules call this directly rather than /// through the compiler's intrinsic. +#[cfg(not(arceos_std))] #[capi_fn] pub unsafe extern "C" fn memset(s: *mut core::ffi::c_void, c: c_int, n: usize) -> *mut c_char { let xs = s as *mut u8; diff --git a/os/StarryOS/kernel/src/pseudofs/mod.rs b/os/StarryOS/kernel/src/pseudofs/mod.rs index 650db27ebd..9add0a7983 100644 --- a/os/StarryOS/kernel/src/pseudofs/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/mod.rs @@ -11,7 +11,6 @@ mod fs; pub(crate) mod proc; mod sysfs; mod tmp; -#[cfg(feature = "plat-dyn")] pub(crate) mod usbfs; use alloc::{boxed::Box, sync::Arc}; @@ -83,8 +82,10 @@ pub fn mount_all() -> LinuxResult<()> { let fs = FS_CONTEXT.lock(); mount_at(&fs, "/dev", dev::new_devfs())?; - #[cfg(feature = "plat-dyn")] - mount_at(&fs, "/dev/bus/usb", usbfs::new_usbfs()?)?; + let usbfs = usbfs::new_usbfs()?; + if let Some(dev_usbfs) = usbfs { + mount_at(&fs, "/dev/bus/usb", dev_usbfs)?; + } let (shm_fs, shm_handle) = tmp::MemoryFs::new_with_handle(); mount_at(&fs, "/dev/shm", shm_fs)?; @@ -97,8 +98,9 @@ pub fn mount_all() -> LinuxResult<()> { mount_at(&fs, "/proc", proc::new_procfs())?; mount_at(&fs, "/sys", sysfs::new_sysfs())?; - #[cfg(feature = "plat-dyn")] - mount_at(&fs, "/sys/bus/usb", usbfs::new_bus_usb_sysfs())?; + if usbfs::has_manager() { + mount_at(&fs, "/sys/bus/usb", usbfs::new_bus_usb_sysfs())?; + } mount_at(&fs, "/sys/kernel/debug", debug::new_debugfs())?; diff --git a/os/StarryOS/kernel/src/pseudofs/sysfs.rs b/os/StarryOS/kernel/src/pseudofs/sysfs.rs index cbb9b84c5f..28a3803408 100644 --- a/os/StarryOS/kernel/src/pseudofs/sysfs.rs +++ b/os/StarryOS/kernel/src/pseudofs/sysfs.rs @@ -303,10 +303,11 @@ struct BusDir { impl SimpleDirOps for BusDir { fn child_names<'a>(&'a self) -> Box> + 'a> { - #[cfg(feature = "plat-dyn")] - let names: &'static [&'static str] = &["platform", "usb", "event_source"]; - #[cfg(not(feature = "plat-dyn"))] - let names: &'static [&'static str] = &["platform", "event_source"]; + let names: &'static [&'static str] = if crate::pseudofs::usbfs::has_manager() { + &["platform", "usb", "event_source"] + } else { + &["platform", "event_source"] + }; Box::new(names.iter().copied().map(Cow::Borrowed)) } @@ -314,11 +315,12 @@ impl SimpleDirOps for BusDir { let fs = self.fs.clone(); Ok(NodeOpsMux::Dir(match name { "platform" => SimpleDir::new_maker(fs.clone(), Arc::new(PlatformBusClassDir)), - #[cfg(feature = "plat-dyn")] - "usb" => SimpleDir::new_maker(fs.clone(), Arc::new(DirMapping::new())), "event_source" => { SimpleDir::new_maker(fs.clone(), Arc::new(EventSourceBusDir { fs: fs.clone() })) } + "usb" if crate::pseudofs::usbfs::has_manager() => { + SimpleDir::new_maker(fs.clone(), Arc::new(DirMapping::new())) + } _ => return Err(VfsError::NotFound), })) } diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/manager.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/manager.rs index a5113939e7..29deb52e42 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/manager.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/manager.rs @@ -387,64 +387,63 @@ impl UsbFsManager { self.usb_activity.event.listen() } + pub(super) fn has_hosts(&self) -> bool { + !self.state.lock().hosts.is_empty() + } + pub(super) fn refresh_dirty_hosts(&self) { - #[cfg(not(target_os = "none"))] - return; - #[cfg(target_os = "none")] - { - let pending_hosts = { - let mut state = self.state.lock(); - let open_hosts = state - .devices - .values() - .filter(|record| record.open_count > 0) - .map(|record| record.host_device_id) - .collect::>(); - let mut pending = Vec::new(); - for host in &mut state.hosts { - let irq_dirty = host.irq_num.map(irq::take_dirty).unwrap_or(false); - if open_hosts.contains(&host.device_id) { - host.needs_probe |= irq_dirty; - continue; - } - if host.needs_probe || irq_dirty { - host.needs_probe = false; - pending.push((host.device_id, host.bus_num)); - } + let pending_hosts = { + let mut state = self.state.lock(); + let open_hosts = state + .devices + .values() + .filter(|record| record.open_count > 0) + .map(|record| record.host_device_id) + .collect::>(); + let mut pending = Vec::new(); + for host in &mut state.hosts { + let irq_dirty = host.irq_num.map(irq::take_dirty).unwrap_or(false); + if open_hosts.contains(&host.device_id) { + host.needs_probe |= irq_dirty; + continue; } - pending - }; + if host.needs_probe || irq_dirty { + host.needs_probe = false; + pending.push((host.device_id, host.bus_num)); + } + } + pending + }; - for (device_id, bus_num) in pending_hosts { - let host = match rdrive::get::(device_id) { - Ok(host) => host, - Err(err) => { - warn!( - "usbfs: failed to reacquire USB host {:?}: {err:?}", - device_id - ); - continue; - } - }; + for (device_id, bus_num) in pending_hosts { + let host = match rdrive::get::(device_id) { + Ok(host) => host, + Err(err) => { + warn!( + "usbfs: failed to reacquire USB host {:?}: {err:?}", + device_id + ); + continue; + } + }; - let mut guard = match host.lock() { - Ok(guard) => guard, - Err(err) => { - warn!("usbfs: failed to lock USB host {:?}: {err:?}", device_id); - continue; - } - }; + let mut guard = match host.lock() { + Ok(guard) => guard, + Err(err) => { + warn!("usbfs: failed to lock USB host {:?}: {err:?}", device_id); + continue; + } + }; - let devices = match ax_task::future::block_on(guard.host_mut().probe_devices()) { - Ok(devices) => devices, - Err(err) => { - warn!("usbfs: refresh probe failed on bus {bus_num}: {err:?}"); - continue; - } - }; - drop(guard); - self.apply_probe_results(device_id, bus_num, devices); - } + let devices = match ax_task::future::block_on(guard.host_mut().probe_devices()) { + Ok(devices) => devices, + Err(err) => { + warn!("usbfs: refresh probe failed on bus {bus_num}: {err:?}"); + continue; + } + }; + drop(guard); + self.apply_probe_results(device_id, bus_num, devices); } } @@ -699,7 +698,6 @@ impl UsbFsManager { } } - #[cfg(target_os = "none")] fn open_device(&self, host_device_id: RDriveDeviceId, info: &DeviceInfo) -> AxResult { let host = rdrive::get::(host_device_id) .map_err(|_| AxError::NoSuchDevice)?; @@ -715,13 +713,6 @@ impl UsbFsManager { }) } - #[cfg(not(target_os = "none"))] - fn open_device(&self, host_device_id: RDriveDeviceId, info: &DeviceInfo) -> AxResult { - let _ = (host_device_id, info); - Err(AxError::Unsupported) - } - - #[cfg(target_os = "none")] fn refresh_host(&self, host_device_id: RDriveDeviceId, bus_num: u8) -> AxResult<()> { let host = rdrive::get::(host_device_id) .map_err(|_| AxError::NoSuchDevice)?; @@ -733,12 +724,6 @@ impl UsbFsManager { Ok(()) } - #[cfg(not(target_os = "none"))] - fn refresh_host(&self, host_device_id: RDriveDeviceId, bus_num: u8) -> AxResult<()> { - let _ = (host_device_id, bus_num); - Err(AxError::Unsupported) - } - fn snapshot_by_id(&self, stable_id: UsbStableId) -> AxResult { self.state .lock() @@ -1155,93 +1140,84 @@ pub(super) async fn usbfs_refresh_task(manager: Arc) { } pub(super) fn initialize_hosts(manager: &UsbFsManager) -> usize { - #[cfg(not(target_os = "none"))] - { - let _ = manager; - 0 - } - #[cfg(target_os = "none")] - { - let hosts = { - let state = manager.state.lock(); - state - .hosts - .iter() - .map(|host| (host.device_id, host.bus_num, host.irq_num)) - .collect::>() + let hosts = { + let state = manager.state.lock(); + state + .hosts + .iter() + .map(|host| (host.device_id, host.bus_num, host.irq_num)) + .collect::>() + }; + + let mut initialized = 0usize; + let mut failed_device_ids = Vec::new(); + + for (device_id, bus_num, irq_num) in hosts { + let host = match rdrive::get::(device_id) { + Ok(host) => host, + Err(err) => { + warn!( + "usbfs: failed to reacquire USB host {:?} for init: {err:?}", + device_id + ); + failed_device_ids.push((device_id, irq_num)); + continue; + } }; - let mut initialized = 0usize; - let mut failed_device_ids = Vec::new(); - - for (device_id, bus_num, irq_num) in hosts { - let host = match rdrive::get::(device_id) { - Ok(host) => host, - Err(err) => { - warn!( - "usbfs: failed to reacquire USB host {:?} for init: {err:?}", - device_id - ); - failed_device_ids.push((device_id, irq_num)); - continue; - } - }; - - let mut guard = match host.lock() { - Ok(guard) => guard, - Err(err) => { - warn!( - "usbfs: failed to lock USB host {:?} for init: {err:?}", - device_id - ); - failed_device_ids.push((device_id, irq_num)); - continue; - } - }; - - info!("usbfs: initializing host on bus {}", bus_num); - if let Err(err) = ax_task::future::block_on(guard.host_mut().init()) { - warn!("usbfs: failed to initialize USB host on bus {bus_num}: {err:?}"); + let mut guard = match host.lock() { + Ok(guard) => guard, + Err(err) => { + warn!( + "usbfs: failed to lock USB host {:?} for init: {err:?}", + device_id + ); failed_device_ids.push((device_id, irq_num)); continue; } + }; - let devices = match ax_task::future::block_on(guard.host_mut().probe_devices()) { - Ok(devices) => devices, - Err(err) => { - warn!("usbfs: initial probe failed on bus {bus_num}: {err:?}"); - failed_device_ids.push((device_id, irq_num)); - continue; - } - }; - - info!("usbfs: host on bus {} initialized", bus_num); - initialized += 1; + info!("usbfs: initializing host on bus {}", bus_num); + if let Err(err) = ax_task::future::block_on(guard.host_mut().init()) { + warn!("usbfs: failed to initialize USB host on bus {bus_num}: {err:?}"); + failed_device_ids.push((device_id, irq_num)); + continue; + } - if let Some(irq_num) = irq_num { - irq::bootstrap_irq(irq_num); + let devices = match ax_task::future::block_on(guard.host_mut().probe_devices()) { + Ok(devices) => devices, + Err(err) => { + warn!("usbfs: initial probe failed on bus {bus_num}: {err:?}"); + failed_device_ids.push((device_id, irq_num)); + continue; } - manager.apply_probe_results(device_id, bus_num, devices); - } + }; - if !failed_device_ids.is_empty() { - let mut state = manager.state.lock(); - state.hosts.retain(|host| { - !failed_device_ids - .iter() - .any(|(failed_device_id, _)| *failed_device_id == host.device_id) - }); + info!("usbfs: host on bus {} initialized", bus_num); + initialized += 1; + if let Some(irq_num) = irq_num { + irq::bootstrap_irq(irq_num); } + manager.apply_probe_results(device_id, bus_num, devices); + } - for (_, irq_num) in failed_device_ids { - if let Some(irq_num) = irq_num { - irq::free_irq(irq_num); - } - } + if !failed_device_ids.is_empty() { + let mut state = manager.state.lock(); + state.hosts.retain(|host| { + !failed_device_ids + .iter() + .any(|(failed_device_id, _)| *failed_device_id == host.device_id) + }); + } - info!("usbfs: {} host(s) ready", initialized); - initialized + for (_, irq_num) in failed_device_ids { + if let Some(irq_num) = irq_num { + irq::free_irq(irq_num); + } } + + info!("usbfs: {} host(s) ready", initialized); + initialized } fn map_transfer_error(err: TransferError) -> AxError { @@ -1313,54 +1289,47 @@ fn recipient_from_raw(raw: u8) -> Recipient { } pub(super) fn discover_hosts() -> (Vec, Vec) { - #[cfg(not(target_os = "none"))] - { - (Vec::new(), Vec::new()) - } - #[cfg(target_os = "none")] - { - let hosts = rdrive::get_list::(); - let mut initialized_hosts = Vec::new(); - let mut irq_slots = Vec::new(); - - for (index, host) in hosts.into_iter().enumerate() { - let device_id = host.descriptor().device_id(); - let bus_num = (index + 1) as u8; - info!("usbfs: preparing host {:?} as bus {}", device_id, bus_num); - - let mut guard = match host.lock() { - Ok(guard) => guard, - Err(err) => { - warn!("usbfs: failed to lock USB host {device_id:?}: {err:?}"); - continue; - } - }; - - let irq_num = guard.irq_num(); - info!("usbfs: creating event handler for bus {}", bus_num); - let event_handler: EventHandler = guard.host_mut().create_event_handler(); - drop(guard); - - if let Some(irq_num) = irq_num { - irq_slots.push(PendingUsbIrqSlot { - irq_num, - device_id, - bus_num, - handler: event_handler, - }); + let hosts = rdrive::get_list::(); + let mut initialized_hosts = Vec::new(); + let mut irq_slots = Vec::new(); + + for (index, host) in hosts.into_iter().enumerate() { + let device_id = host.descriptor().device_id(); + let bus_num = (index + 1) as u8; + info!("usbfs: preparing host {:?} as bus {}", device_id, bus_num); + + let mut guard = match host.lock() { + Ok(guard) => guard, + Err(err) => { + warn!("usbfs: failed to lock USB host {device_id:?}: {err:?}"); + continue; } + }; + + let irq_num = guard.irq_num(); + info!("usbfs: creating event handler for bus {}", bus_num); + let event_handler: EventHandler = guard.host_mut().create_event_handler(); + drop(guard); - initialized_hosts.push(UsbHostState { + if let Some(irq_num) = irq_num { + irq_slots.push(PendingUsbIrqSlot { + irq_num, device_id, bus_num, - irq_num, - needs_probe: true, - next_device_num: 1, - stable_id_to_device_num: BTreeMap::new(), + handler: event_handler, }); } - info!("usbfs: discovered {} USB host(s)", initialized_hosts.len()); - (initialized_hosts, irq_slots) + initialized_hosts.push(UsbHostState { + device_id, + bus_num, + irq_num, + needs_probe: true, + next_device_num: 1, + stable_id_to_device_num: BTreeMap::new(), + }); } + + info!("usbfs: discovered {} USB host(s)", initialized_hosts.len()); + (initialized_hosts, irq_slots) } diff --git a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs index 78f4a0d8b6..acb085c7d5 100644 --- a/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/usbfs/mod.rs @@ -1,5 +1,3 @@ -#![cfg_attr(not(target_os = "none"), allow(dead_code, unused_imports))] - mod descriptor; mod irq; mod manager; @@ -45,28 +43,39 @@ fn create_filesystem(manager: Arc) -> Filesystem { }) } -pub(crate) fn new_usbfs() -> LinuxResult { +pub(crate) fn new_usbfs() -> LinuxResult> { if let Some(manager) = manager() { - return Ok(create_filesystem(manager)); + return Ok(Some(create_filesystem(manager))); } info!("usbfs: initializing manager"); let (hosts, irq_slots) = manager::discover_hosts(); + if hosts.is_empty() { + info!("usbfs: no USB host found, skip mounting usbfs"); + return Ok(None); + } + let manager = Arc::new(UsbFsManager::new(hosts)); irq::init_globals(manager.clone(), irq_slots); - let should_spawn_refresh = manager::initialize_hosts(&manager) > 0; - - if should_spawn_refresh { - info!("usbfs: spawning refresh task"); - let refresh_manager = manager.clone(); - ax_task::spawn_with_name( - move || ax_task::future::block_on(manager::usbfs_refresh_task(refresh_manager.clone())), - "usbfs-refresh".to_owned(), - ); - manager.refresh_event.notify(1); + let initialized_hosts = manager::initialize_hosts(&manager) > 0; + if !initialized_hosts { + info!("usbfs: no USB host initialized, skip mounting usbfs"); + return Ok(None); } - Ok(create_filesystem(manager)) + info!("usbfs: spawning refresh task"); + let refresh_manager = manager.clone(); + ax_task::spawn_with_name( + move || ax_task::future::block_on(manager::usbfs_refresh_task(refresh_manager.clone())), + "usbfs-refresh".to_owned(), + ); + manager.refresh_event.notify(1); + + Ok(Some(create_filesystem(manager))) +} + +pub(crate) fn has_manager() -> bool { + manager().is_some_and(|manager| manager.has_hosts()) } pub(crate) fn new_bus_usb_sysfs() -> Filesystem { diff --git a/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs b/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs index 010a4b61e4..a8874fd355 100644 --- a/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs +++ b/os/StarryOS/kernel/src/syscall/fs/fd_ops.rs @@ -124,7 +124,6 @@ fn add_to_fd(result: OpenResult, flags: u32) -> AxResult { device.inner().open(true)?; } let inner = device.inner().as_any(); - #[cfg(feature = "plat-dyn")] if crate::pseudofs::usbfs::is_usbfs_device(inner) { let wrapped = crate::pseudofs::usbfs::open_usbfs_file(inner, file, flags)?; if flags & O_NONBLOCK != 0 { diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index a003d1812c..a54a74f1cb 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -57,12 +57,13 @@ path = "xtask/main.rs" [dependencies] ax-feat = { workspace = true, features = ["ext-ld", "fs-ng-times", "irq"] } +ax-std = { workspace = true, features = ["ext-ld", "fs", "multitask", "irq"] } ax-driver.workspace = true ax-hal.workspace = true axplat-dyn = { workspace = true, optional = true } starry-kernel = { workspace = true, features = ["dev-log", "ext4"] } -[target.'cfg(any(windows,unix))'.dependencies] +[target.'cfg(any(windows, all(unix, not(target_env = "musl"))))'.dependencies] anyhow = "1.0" axbuild = { workspace = true } clap = { version = "4.6", features = ["derive"] } @@ -71,3 +72,6 @@ tokio = { version = "1.0", features = ["full"] } [package.metadata.vendor-filter] all-features = true platforms = ["riscv64gc-unknown-none-elf", "loongarch64-unknown-none-softfloat"] + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/os/StarryOS/starryos/src/main.rs b/os/StarryOS/starryos/src/main.rs index 14a4a4723c..705220c41e 100644 --- a/os/StarryOS/starryos/src/main.rs +++ b/os/StarryOS/starryos/src/main.rs @@ -1,14 +1,17 @@ -#![no_std] -#![no_main] +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] #![doc = include_str!("../../README.md")] extern crate alloc; use alloc::{borrow::ToOwned, vec::Vec}; +#[cfg(arceos_std)] +use ax_std as _; + pub const CMDLINE: &[&str] = &["/bin/sh", "-c", include_str!("init.sh")]; -#[unsafe(no_mangle)] +#[cfg_attr(target_os = "none", unsafe(no_mangle))] fn main() { let args = CMDLINE .iter() diff --git a/os/arceos/api/arceos_posix_api/build.rs b/os/arceos/api/arceos_posix_api/build.rs index f135c51bca..95202db5f1 100644 --- a/os/arceos/api/arceos_posix_api/build.rs +++ b/os/arceos/api/arceos_posix_api/build.rs @@ -29,7 +29,7 @@ fn main() { } } - fn gen_pthread_mutex(out_file: &str) -> std::io::Result<()> { + fn gen_pthread_mutex(out_file: &std::path::Path) -> std::io::Result<()> { println!("cargo:rerun-if-env-changed=CARGO_FEATURE_MULTITASK"); println!("cargo:rerun-if-env-changed=CARGO_FEATURE_SMP"); println!("cargo:rerun-if-env-changed=CARGO_FEATURE_LOCKDEP"); @@ -59,7 +59,11 @@ typedef struct {{ Ok(()) } - fn gen_c_to_rust_bindings(in_file: &str, out_file: &std::path::Path) { + fn gen_c_to_rust_bindings( + in_file: &str, + generated_include_dir: &std::path::Path, + out_file: &std::path::Path, + ) { println!("cargo:rerun-if-changed={in_file}"); let allow_types = [ @@ -118,6 +122,7 @@ typedef struct {{ let target = std::env::var("TARGET").unwrap(); let mut builder = bindgen::Builder::default() .header(in_file) + .clang_arg(format!("-I{}", generated_include_dir.display())) .clang_arg("-I./../../ulib/axlibc/include") .parse_callbacks(Box::new(MyCallbacks)) .derive_default(true) @@ -154,8 +159,8 @@ typedef struct {{ let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); let out_file = out_dir.join("ctypes_gen.rs"); if axlibc_include.exists() { - gen_pthread_mutex(axlibc_include.join("ax_pthread_mutex.h").to_str().unwrap()).unwrap(); - gen_c_to_rust_bindings("ctypes.h", &out_file); + gen_pthread_mutex(&out_dir.join("ax_pthread_mutex.h")).unwrap(); + gen_c_to_rust_bindings("ctypes.h", &out_dir, &out_file); } else { // During `cargo publish` verification, the external headers are not // available. Use the pre-generated ctypes_gen.rs shipped in the crate. diff --git a/os/arceos/api/arceos_posix_api/src/imp/pthread/mutex.rs b/os/arceos/api/arceos_posix_api/src/imp/pthread/mutex.rs index f3494044ef..663608e117 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/pthread/mutex.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/pthread/mutex.rs @@ -1,21 +1,21 @@ -#[cfg(feature = "lockdep")] -use core::sync::atomic::{AtomicBool, Ordering}; +use alloc::{boxed::Box, collections::BTreeMap}; use core::{ ffi::c_int, - mem::{ManuallyDrop, size_of}, - ptr::NonNull, + mem, + ptr::{self, NonNull}, + sync::atomic::{AtomicBool, Ordering}, }; -use ax_errno::LinuxResult; +use ax_errno::{LinuxError, LinuxResult}; use ax_sync::Mutex; +use spin::LazyLock; use crate::{ctypes, utils::check_null_mut_ptr}; -const _: () = assert!(size_of::() == size_of::()); -#[cfg(feature = "lockdep")] -const STATIC_MUTEX_SENTINEL: i64 = -1; -#[cfg(feature = "lockdep")] +const STATIC_MUTEX_SENTINEL: usize = usize::MAX; static STATIC_MUTEX_INIT_LOCK: AtomicBool = AtomicBool::new(false); +static MUTEXES: LazyLock>>>> = + LazyLock::new(|| Mutex::new(BTreeMap::new())); #[repr(C)] pub struct PthreadMutex(Mutex<()>); @@ -26,17 +26,25 @@ impl PthreadMutex { } fn lock(&self) -> LinuxResult { - let _guard = ManuallyDrop::new(self.0.lock()); + mem::forget(self.0.lock()); Ok(()) } + fn try_lock(&self) -> LinuxResult { + if let Some(guard) = self.0.try_lock() { + mem::forget(guard); + Ok(()) + } else { + Err(LinuxError::EBUSY) + } + } + fn unlock(&self) -> LinuxResult { unsafe { self.0.force_unlock() }; Ok(()) } } -#[cfg(feature = "lockdep")] fn with_static_mutex_init_lock(f: impl FnOnce() -> R) -> R { while STATIC_MUTEX_INIT_LOCK .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) @@ -51,40 +59,76 @@ fn with_static_mutex_init_lock(f: impl FnOnce() -> R) -> R { result } -#[cfg(feature = "lockdep")] -fn ensure_mutex_initialized(mutex: NonNull) { - let words = unsafe { - core::slice::from_raw_parts_mut( - mutex.as_ptr().cast::(), - size_of::() / size_of::(), - ) - }; - if words.first().copied() != Some(STATIC_MUTEX_SENTINEL) { - return; +#[derive(Clone, Copy)] +struct ForceSendSync(T); + +unsafe impl Send for ForceSendSync {} +unsafe impl Sync for ForceSendSync {} + +fn mutex_key(mutex: NonNull) -> usize { + mutex.as_ptr() as usize +} + +fn read_mutex_handle(mutex: NonNull) -> usize { + unsafe { ptr::read_unaligned(mutex.as_ptr().cast::()) } +} + +fn write_mutex_handle(mutex: NonNull, handle: usize) { + unsafe { ptr::write_unaligned(mutex.as_ptr().cast::(), handle) } +} + +fn create_mutex() -> NonNull { + let ptr = Box::into_raw(Box::new(PthreadMutex::new())); + NonNull::new(ptr).expect("Box::into_raw never returns null") +} + +fn ensure_mutex_initialized(mutex: NonNull) -> NonNull { + let handle = read_mutex_handle(mutex); + if handle != 0 && handle != STATIC_MUTEX_SENTINEL { + return NonNull::new(handle as *mut PthreadMutex).expect("stored pthread mutex handle"); } with_static_mutex_init_lock(|| { - if words.first().copied() == Some(STATIC_MUTEX_SENTINEL) { - unsafe { - mutex - .cast::() - .as_ptr() - .write(PthreadMutex::new()); - } + let handle = read_mutex_handle(mutex); + if handle != 0 && handle != STATIC_MUTEX_SENTINEL { + return NonNull::new(handle as *mut PthreadMutex).expect("stored pthread mutex handle"); } - }); + + let inner = create_mutex(); + let handle = inner.as_ptr() as usize; + write_mutex_handle(mutex, handle); + MUTEXES + .lock() + .insert(mutex_key(mutex), ForceSendSync(inner)); + inner + }) } fn lock_mutex(mutex: NonNull) -> LinuxResult { - #[cfg(feature = "lockdep")] - ensure_mutex_initialized(mutex); - unsafe { mutex.cast::().as_ref().lock() } + unsafe { ensure_mutex_initialized(mutex).as_ref().lock() } +} + +fn try_lock_mutex(mutex: NonNull) -> LinuxResult { + unsafe { ensure_mutex_initialized(mutex).as_ref().try_lock() } } fn unlock_mutex(mutex: NonNull) -> LinuxResult { - #[cfg(feature = "lockdep")] - ensure_mutex_initialized(mutex); - unsafe { mutex.cast::().as_ref().unlock() } + unsafe { ensure_mutex_initialized(mutex).as_ref().unlock() } +} + +fn destroy_mutex(mutex: NonNull) -> LinuxResult { + let handle = read_mutex_handle(mutex); + if handle == 0 || handle == STATIC_MUTEX_SENTINEL { + return Ok(()); + } + + if MUTEXES.lock().remove(&mutex_key(mutex)).is_some() { + unsafe { drop(Box::from_raw(handle as *mut PthreadMutex)) }; + write_mutex_handle(mutex, 0); + Ok(()) + } else { + Err(LinuxError::EINVAL) + } } /// Initialize a mutex. @@ -95,9 +139,12 @@ pub fn sys_pthread_mutex_init( debug!("sys_pthread_mutex_init <= {:#x}", mutex as usize); syscall_body!(sys_pthread_mutex_init, { check_null_mut_ptr(mutex)?; - unsafe { - mutex.cast::().write(PthreadMutex::new()); - } + let mutex = NonNull::new(mutex).expect("mutex pointer was checked for null"); + let inner = create_mutex(); + write_mutex_handle(mutex, inner.as_ptr() as usize); + MUTEXES + .lock() + .insert(mutex_key(mutex), ForceSendSync(inner)); Ok(0) }) } @@ -113,6 +160,17 @@ pub fn sys_pthread_mutex_lock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { }) } +/// Try locking the given mutex. +pub fn sys_pthread_mutex_trylock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + debug!("sys_pthread_mutex_trylock <= {:#x}", mutex as usize); + syscall_body!(sys_pthread_mutex_trylock, { + check_null_mut_ptr(mutex)?; + let mutex = NonNull::new(mutex).expect("mutex pointer was checked for null"); + try_lock_mutex(mutex)?; + Ok(0) + }) +} + /// Unlock the given mutex. pub fn sys_pthread_mutex_unlock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { debug!("sys_pthread_mutex_unlock <= {:#x}", mutex as usize); @@ -123,3 +181,14 @@ pub fn sys_pthread_mutex_unlock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { Ok(0) }) } + +/// Destroy the given mutex. +pub fn sys_pthread_mutex_destroy(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + debug!("sys_pthread_mutex_destroy <= {:#x}", mutex as usize); + syscall_body!(sys_pthread_mutex_destroy, { + check_null_mut_ptr(mutex)?; + let mutex = NonNull::new(mutex).expect("mutex pointer was checked for null"); + destroy_mutex(mutex)?; + Ok(0) + }) +} diff --git a/os/arceos/api/arceos_posix_api/src/lib.rs b/os/arceos/api/arceos_posix_api/src/lib.rs index d62be320d0..c74e5cbacd 100644 --- a/os/arceos/api/arceos_posix_api/src/lib.rs +++ b/os/arceos/api/arceos_posix_api/src/lib.rs @@ -61,7 +61,8 @@ pub use imp::net::{ pub use imp::pipe::sys_pipe; #[cfg(feature = "multitask")] pub use imp::pthread::mutex::{ - sys_pthread_mutex_init, sys_pthread_mutex_lock, sys_pthread_mutex_unlock, + sys_pthread_mutex_destroy, sys_pthread_mutex_init, sys_pthread_mutex_lock, + sys_pthread_mutex_trylock, sys_pthread_mutex_unlock, }; #[cfg(feature = "multitask")] pub use imp::pthread::{sys_pthread_create, sys_pthread_exit, sys_pthread_join, sys_pthread_self}; diff --git a/os/arceos/doc/std_support_readme.md b/os/arceos/doc/std_support_readme.md index c3468cd4e7..bc0661b326 100644 --- a/os/arceos/doc/std_support_readme.md +++ b/os/arceos/doc/std_support_readme.md @@ -1,130 +1,98 @@ # ArceOS Rust std support -`arceos-rust` lets Rust applications use `std` on bare metal (Hermit target) with ArceOS as runtime/kernel support. +ArceOS std applications are built with Cargo's std-aware build mode. The app +uses Rust's upstream `std` for a built-in `*-unknown-linux-musl` target, while +the application enables its `arceos` feature to depend on `ax-std` directly, so +`app + ax-std + axruntime` are compiled in one Cargo dependency graph. The fake +`libc.a` only satisfies the fixed library name requested by the compiler; the +real libc/syscall compatibility symbols come from `ax-std`. -Examples are under `examples/std`. +This path does not require changes to `rust-lang/rust`, and it does not use a +Hermit target. ## Run the std examples -### Disk image preparation - -Examples expect `disk.img` in `examples/std` directory. Both ext4 and FAT32 filesystems are supported. - -Recommended commands: - -```bash -dd if=/dev/zero of=disk.img bs=1M count=64 -# or truncate: -# truncate -s 64M disk.img -mkfs.ext4 -F disk.img -# or FAT32: -# mkfs.vfat -F 32 disk.img -``` +Examples are under `examples/std`. -### Run examples with cargo +Use `axbuild`/`xtask` instead of running Cargo in the example directory +directly. `axbuild` maps the ArceOS bare-metal target to the matching built-in +linux-musl target, creates empty fake `libc.a`/`libunwind.a` placeholders, +injects `--cfg arceos_std`, and installs the linker wrapper. -Run from each example directory, for example: +Example: ```bash -cd examples/std/helloworld -cargo run --target riscv64gc-unknown-hermit +cargo xtask arceos test qemu \ + --target x86_64-unknown-none \ + --test-group rust \ + --test-case helloworld ``` -Supported targets used by examples: +The std build path uses: -- `x86_64-unknown-hermit` -- `riscv64gc-unknown-hermit` -- `aarch64-unknown-hermit` -- `loongarch64-unknown-hermit` uses a custom target JSON (`--target ../loongarch64-unknown-hermit.json -Zjson-target-spec`), see below +- `-Z build-std=std,panic_abort` +- `-Z build-std-features=` +- `panic = "abort"` +- `--cfg arceos_std` +- empty fake `libc.a` +- empty fake `libunwind.a` -`examples/std/*/.cargo/config.toml` already provides: +## Select ArceOS features -- linker args (`-no-pie`, `-Tlinker.x`) -- per-arch QEMU runner -- `build-std` settings for build standard library - -## Port an existing Rust project - -### 1) Add dependency +An app declares the ArceOS-side features it needs behind its app-local +`arceos` feature. Without this feature, the same app remains an ordinary Rust +`std` app and does not depend on `ax-std`. ```toml -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["log-level-off"] } -``` - -Then enable features based on your app: - -- `fs` for file system -- `net` for networking -- `multitask`/`irq` if needed - -For other available features, see document for `arceos-rust`. - -### 2) Import runtime shim in `main.rs` - -```rust -#[cfg(target_os = "hermit")] -use arceos_rust as _; -``` - -### 3) Add `.cargo/config.toml` +[features] +default = [] +arceos = ["dep:ax-std", "ax-std/fs", "ax-std/net", "ax-std/multitask", "ax-std/irq"] -Use template: - -```bash -mkdir -p .cargo -cp /examples/std/config.template.toml .cargo/config.toml +[dependencies] +ax-std = { workspace = true, optional = true } ``` -Then adjust runner args for your enabled features (network/disk), as described in template comments. +Keep logging and normal Cargo features for app-local choices. For example: -### 4) Build and run +```toml +[features] +default = [] +dns = [] -```bash -cargo run --target -unknown-hermit +[package.metadata.axstd] +features = ["log-level-debug"] ``` -`/ +dd if=/dev/zero of=disk.img bs=1M count=64 +mkfs.ext4 -F disk.img ``` -Run with JSON target: +or: ```bash -cargo run --target ./loongarch64-unknown-hermit.json -Zjson-target-spec +truncate -s 64M disk.img +mkfs.vfat -F 32 disk.img ``` - -If you keep `target.loongarch64-unknown-hermit` in `.cargo/config.toml`, it applies only when target triple name matches exactly. For JSON-path target usage, use `target."cfg(...)"`/manual command line, or keep a dedicated local config for LoongArch. diff --git a/os/arceos/examples/std/arce_agent/Cargo.toml b/os/arceos/examples/std/arce_agent/Cargo.toml index 62ca2f6640..06f5798e41 100644 --- a/os/arceos/examples/std/arce_agent/Cargo.toml +++ b/os/arceos/examples/std/arce_agent/Cargo.toml @@ -4,7 +4,13 @@ version = "0.2.0" edition.workspace = true publish = false +[features] +default = [] +arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/net", "ax-std/fs"] + [dependencies] +ax-std = { workspace = true, optional = true } + # minreq: minimal HTTP client, no TLS (we use a local HTTP proxy for HTTPS APIs) minreq = { version = "2", default-features = false } @@ -15,5 +21,5 @@ serde_json = "1" log = "0.4" env_logger = "0.11" -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["multitask", "net", "fs", "log-level-warn"] } +[package.metadata.axstd] +features = ["log-level-warn"] diff --git a/os/arceos/examples/std/arce_agent/src/main.rs b/os/arceos/examples/std/arce_agent/src/main.rs index 33e40040b4..17354e8cf9 100644 --- a/os/arceos/examples/std/arce_agent/src/main.rs +++ b/os/arceos/examples/std/arce_agent/src/main.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "arceos")] +use ax_std as _; + // ArceAgent v0.2: Interactive AI Agent on ArceOS Unikernel // // Architecture: @@ -9,9 +12,6 @@ // Uses minreq for HTTP (no TLS — requires a local HTTP proxy). // Compiles identically on Linux (for development) and ArceOS (for deployment). -#[cfg(target_os = "hermit")] -use arceos_rust as _; - mod base64; mod context; mod hal; @@ -32,9 +32,9 @@ use tools::{dispatch, tool_catalog}; // Configuration // --------------------------------------------------------------------------- -#[cfg(target_os = "hermit")] +#[cfg(feature = "arceos")] const API_BASE: &str = "http://10.0.2.2:8080/v1"; -#[cfg(not(target_os = "hermit"))] +#[cfg(not(feature = "arceos"))] const API_BASE: &str = "http://localhost:8080/v1"; const MODEL: &str = "qwen3.5-plus-thinking"; const MAX_CONTEXT_MESSAGES: usize = 40; diff --git a/os/arceos/examples/std/config.template.toml b/os/arceos/examples/std/config.template.toml index e677660cbd..1e35c8b48f 100644 --- a/os/arceos/examples/std/config.template.toml +++ b/os/arceos/examples/std/config.template.toml @@ -35,7 +35,7 @@ # Linux: -cpu host -accel kvm # macOS: -cpu host -accel hvf -[target.riscv64gc-unknown-hermit] +[target.riscv64gc-unknown-linux-musl] rustflags = [ "-C", "link-arg=-no-pie", "-C", "link-arg=-Tlinker.x" @@ -54,7 +54,7 @@ runner = [ "-kernel", ] -[target.x86_64-unknown-hermit] +[target.x86_64-unknown-linux-musl] rustflags = [ "-C", "link-arg=-no-pie", "-C", "link-arg=-Tlinker.x" @@ -73,7 +73,7 @@ runner = [ "-kernel", ] -[target.aarch64-unknown-hermit] +[target.aarch64-unknown-linux-musl] rustflags = [ "-C", "link-arg=-no-pie", "-C", "link-arg=-Tlinker.x" @@ -93,7 +93,7 @@ runner = [ "-kernel", ] -[target.loongarch64-unknown-hermit] +[target.loongarch64-unknown-linux-musl] rustflags = [ "-C", "link-arg=-no-pie", "-C", "link-arg=-Tlinker.x" diff --git a/os/arceos/examples/std/helloworld/Cargo.toml b/os/arceos/examples/std/helloworld/Cargo.toml index b6591fff8f..7e899a6d56 100644 --- a/os/arceos/examples/std/helloworld/Cargo.toml +++ b/os/arceos/examples/std/helloworld/Cargo.toml @@ -5,7 +5,12 @@ edition.workspace = true authors = ["eternalcomet "] publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +arceos = ["dep:ax-std"] -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["log-level-debug"] } +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-debug"] diff --git a/os/arceos/examples/std/helloworld/src/main.rs b/os/arceos/examples/std/helloworld/src/main.rs index 63cc785704..dcc9e799ab 100644 --- a/os/arceos/examples/std/helloworld/src/main.rs +++ b/os/arceos/examples/std/helloworld/src/main.rs @@ -1,5 +1,5 @@ -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; fn main() { println!("Hello, world!"); diff --git a/os/arceos/examples/std/httpclient/Cargo.toml b/os/arceos/examples/std/httpclient/Cargo.toml index 227d7f7545..8c725bc68e 100644 --- a/os/arceos/examples/std/httpclient/Cargo.toml +++ b/os/arceos/examples/std/httpclient/Cargo.toml @@ -9,7 +9,11 @@ publish = false [features] default = [] -dns = ["arceos-rust/dns"] +arceos = ["dep:ax-std", "ax-std/net"] +dns = [] -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["net", "log-level-debug"] } +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-debug"] diff --git a/os/arceos/examples/std/httpclient/src/main.rs b/os/arceos/examples/std/httpclient/src/main.rs index 5cf8700242..021c091042 100644 --- a/os/arceos/examples/std/httpclient/src/main.rs +++ b/os/arceos/examples/std/httpclient/src/main.rs @@ -3,8 +3,8 @@ use std::{ net::{TcpStream, ToSocketAddrs}, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; #[cfg(feature = "dns")] const DEST: &str = "ident.me:80"; diff --git a/os/arceos/examples/std/httpserver/Cargo.toml b/os/arceos/examples/std/httpserver/Cargo.toml index 1cd8617def..c104ddd856 100644 --- a/os/arceos/examples/std/httpserver/Cargo.toml +++ b/os/arceos/examples/std/httpserver/Cargo.toml @@ -5,7 +5,12 @@ edition.workspace = true authors = ["Yuekai Jia "] publish = false -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/net"] -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["multitask", "net", "log-level-debug"] } +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-debug"] diff --git a/os/arceos/examples/std/httpserver/src/main.rs b/os/arceos/examples/std/httpserver/src/main.rs index a53e585bbd..b5836dd6a3 100644 --- a/os/arceos/examples/std/httpserver/src/main.rs +++ b/os/arceos/examples/std/httpserver/src/main.rs @@ -12,8 +12,8 @@ use std::{ thread, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; const LOCAL_IP: &str = "0.0.0.0"; const LOCAL_PORT: u16 = 5555; diff --git a/os/arceos/examples/std/io_test/Cargo.toml b/os/arceos/examples/std/io_test/Cargo.toml index 93658f614c..baf9a6c235 100644 --- a/os/arceos/examples/std/io_test/Cargo.toml +++ b/os/arceos/examples/std/io_test/Cargo.toml @@ -5,5 +5,12 @@ edition.workspace = true authors = ["eternalcomet "] publish = false -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["fs", "log-level-off"] } +[features] +default = [] +arceos = ["dep:ax-std", "ax-std/fs"] + +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-off"] diff --git a/os/arceos/examples/std/io_test/src/main.rs b/os/arceos/examples/std/io_test/src/main.rs index 9f62c7ea24..96900f00b4 100644 --- a/os/arceos/examples/std/io_test/src/main.rs +++ b/os/arceos/examples/std/io_test/src/main.rs @@ -6,8 +6,8 @@ use std::{ time::SystemTime, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; fn main() -> io::Result<()> { println!("=== Rust 文件读写功能测试 ===\n"); diff --git a/os/arceos/examples/std/thread_test/Cargo.toml b/os/arceos/examples/std/thread_test/Cargo.toml index 3c0b0691bf..dc058c6a01 100644 --- a/os/arceos/examples/std/thread_test/Cargo.toml +++ b/os/arceos/examples/std/thread_test/Cargo.toml @@ -5,5 +5,12 @@ edition.workspace = true authors = ["eternalcomet "] publish = false -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["multitask", "log-level-off", "irq"] } +[features] +default = [] +arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/irq"] + +[dependencies] +ax-std = { workspace = true, optional = true } + +[package.metadata.axstd] +features = ["log-level-off"] diff --git a/os/arceos/examples/std/thread_test/src/main.rs b/os/arceos/examples/std/thread_test/src/main.rs index b915d6d527..c996df584a 100644 --- a/os/arceos/examples/std/thread_test/src/main.rs +++ b/os/arceos/examples/std/thread_test/src/main.rs @@ -8,8 +8,8 @@ use std::{ time::Duration, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; fn main() { println!("=== Rust 线程功能测试程序 ===\n"); @@ -33,7 +33,10 @@ fn main() { test_barrier_synchronization(); // 7. 线程恐慌处理 + #[cfg(not(feature = "arceos"))] test_thread_panic(); + #[cfg(feature = "arceos")] + println!("跳过线程恐慌处理测试(panic_abort 模式)\n"); println!("\n=== 所有测试完成 ==="); } @@ -215,6 +218,7 @@ fn test_barrier_synchronization() { } // 7. 线程恐慌处理 +#[cfg(not(feature = "arceos"))] fn test_thread_panic() { println!("7. 线程恐慌处理:"); println!("Unikernel环境下使用panic_abort模式,不支持恐慌捕获,因此会导致程序终止。"); diff --git a/os/arceos/examples/std/tokio_test/Cargo.toml b/os/arceos/examples/std/tokio_test/Cargo.toml index 007f2b0ff5..db79fe9814 100644 --- a/os/arceos/examples/std/tokio_test/Cargo.toml +++ b/os/arceos/examples/std/tokio_test/Cargo.toml @@ -6,8 +6,13 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +arceos = ["dep:ax-std", "ax-std/multitask", "ax-std/irq"] + [dependencies] +ax-std = { workspace = true, optional = true } tokio = { version = "1", features = ["rt", "macros", "time", "sync"] } -[target.'cfg(target_os = "hermit")'.dependencies] -arceos-rust = { workspace = true, default-features = true, features = ["multitask", "irq", "log-level-off"] } +[package.metadata.axstd] +features = ["log-level-off"] diff --git a/os/arceos/examples/std/tokio_test/src/main.rs b/os/arceos/examples/std/tokio_test/src/main.rs index 12fd7dca51..17961f8549 100644 --- a/os/arceos/examples/std/tokio_test/src/main.rs +++ b/os/arceos/examples/std/tokio_test/src/main.rs @@ -3,8 +3,8 @@ use std::sync::{ atomic::{AtomicUsize, Ordering}, }; -#[cfg(target_os = "hermit")] -use arceos_rust as _; +#[cfg(feature = "arceos")] +use ax_std as _; use tokio::{ sync::{Barrier, Mutex, mpsc}, time::{Duration, sleep}, diff --git a/os/arceos/modules/axalloc/build.rs b/os/arceos/modules/axalloc/build.rs index 9c1f5a466f..f955671892 100644 --- a/os/arceos/modules/axalloc/build.rs +++ b/os/arceos/modules/axalloc/build.rs @@ -1,4 +1,5 @@ fn main() { + println!("cargo:rustc-check-cfg=cfg(arceos_std)"); println!("cargo:rustc-check-cfg=cfg(tlsf)"); println!("cargo:rustc-check-cfg=cfg(buddy_slab)"); diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index 0c98cba5c1..f260d94e39 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -92,6 +92,7 @@ log.workspace = true ax-memory-addr.workspace = true ax-page-table-multiarch = { workspace = true, optional = true } ax-percpu = { workspace = true, features = ["non-zero-vma"] } +axplat-dyn = { workspace = true, default-features = false, optional = true } rdrive.workspace = true spin.workspace = true @@ -99,9 +100,6 @@ spin.workspace = true ax-plat-x86-pc = { workspace = true, optional = true } ax-plat-x86-qemu-q35 = { workspace = true, optional = true } -[target.'cfg(target_os = "none")'.dependencies] -axplat-dyn = { workspace = true, default-features = false, optional = true } - [target.'cfg(target_arch = "riscv64")'.dependencies] ax-plat-riscv64-sg2002 = { workspace = true, optional = true } ax-plat-riscv64-visionfive2 = { workspace = true, optional = true } @@ -112,3 +110,6 @@ ax-plat-loongarch64-qemu-virt = { workspace = true, optional = true } [build-dependencies] ax-config.workspace = true toml.workspace = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/os/arceos/modules/axhal/build.rs b/os/arceos/modules/axhal/build.rs index a6dde5ccfb..b5614a815f 100644 --- a/os/arceos/modules/axhal/build.rs +++ b/os/arceos/modules/axhal/build.rs @@ -54,6 +54,7 @@ const DEFAULT_PLATFORMS: &[(&str, &str)] = &[ fn main() { println!("cargo:rustc-check-cfg=cfg(plat_dyn)"); + println!("cargo:rustc-check-cfg=cfg(arceos_std)"); println!("cargo:rustc-check-cfg=cfg(ax_hal_any_platform_feature)"); println!("cargo:rerun-if-changed={LINKER_TEMPLATE_NAME}"); println!("cargo:rerun-if-env-changed=AX_CONFIG_PATH"); @@ -68,8 +69,9 @@ fn main() { let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); - let selected_platform = check_platform_features(&arch, &target_os); - gen_selected_platform(&arch, &target_os, selected_platform).unwrap(); + let bare_target = target_os == "none" || std::env::var_os("CARGO_CFG_ARCEOS_STD").is_some(); + let selected_platform = check_platform_features(&arch, bare_target); + gen_selected_platform(&arch, bare_target, selected_platform).unwrap(); let config = load_linker_config().unwrap(); @@ -85,7 +87,7 @@ fn main() { } } -fn check_platform_features(arch: &str, target_os: &str) -> Option<&'static PlatformFeature> { +fn check_platform_features(arch: &str, bare_target: bool) -> Option<&'static PlatformFeature> { let has_myplat = feature_enabled("myplat"); let enabled_platforms = PLATFORM_FEATURES .iter() @@ -121,7 +123,7 @@ fn check_platform_features(arch: &str, target_os: &str) -> Option<&'static Platf } } - if target_os == "none" { + if bare_target { for platform in &enabled_platforms { if let Some(target_arch) = platform.target_arch && arch != target_arch @@ -143,19 +145,19 @@ fn check_platform_features(arch: &str, target_os: &str) -> Option<&'static Platf fn gen_selected_platform( arch: &str, - target_os: &str, + bare_target: bool, platform: Option<&PlatformFeature>, ) -> Result<()> { let crate_name = if let Some(platform) = platform { if platform.feature == "plat-dyn" { - (target_os == "none").then_some(platform.crate_name) + bare_target.then_some(platform.crate_name) } else { platform .target_arch .is_some_and(|target_arch| target_arch == arch) .then_some(platform.crate_name) } - } else if target_os == "none" && feature_enabled("defplat") && !feature_enabled("myplat") { + } else if bare_target && feature_enabled("defplat") && !feature_enabled("myplat") { DEFAULT_PLATFORMS .iter() .find_map(|(target_arch, crate_name)| (*target_arch == arch).then_some(*crate_name)) diff --git a/os/arceos/modules/axhal/src/platform.rs b/os/arceos/modules/axhal/src/platform.rs index 5361ec8acf..e92636b427 100644 --- a/os/arceos/modules/axhal/src/platform.rs +++ b/os/arceos/modules/axhal/src/platform.rs @@ -2,7 +2,7 @@ include!(concat!(env!("OUT_DIR"), "/selected_platform.rs")); #[cfg(all( - target_os = "none", + any(target_os = "none", arceos_std), not(test), not(feature = "myplat"), not(feature = "defplat"), @@ -16,7 +16,7 @@ mod dummy; #[cfg(all( not(test), - not(target_os = "none"), + not(any(target_os = "none", arceos_std)), not(feature = "defplat"), not(ax_hal_any_platform_feature) ))] diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 62f5211481..019d79e535 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -91,3 +91,6 @@ indoc = "2" rd-net = { workspace = true, optional = true } rdrive.workspace = true spin = {workspace = true, optional = true} + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/os/arceos/modules/axruntime/src/block/mod.rs b/os/arceos/modules/axruntime/src/block/mod.rs index 5bb5f943e0..6032c02a3f 100644 --- a/os/arceos/modules/axruntime/src/block/mod.rs +++ b/os/arceos/modules/axruntime/src/block/mod.rs @@ -1,4 +1,7 @@ -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) +))] use alloc::vec::Vec; #[cfg(all( feature = "irq", @@ -6,17 +9,26 @@ use alloc::vec::Vec; all( feature = "fs", not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) + all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) + ) ) ))] use core::ptr::NonNull; -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) +))] mod root; #[cfg(any( - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")), + all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) + ), test ))] pub(crate) mod volume; @@ -27,9 +39,12 @@ pub(crate) mod volume; all( feature = "fs", not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) + all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) + ) ) ))] struct BlockIrqState { @@ -43,9 +58,12 @@ struct BlockIrqState { all( feature = "fs", not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) + all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) + ) ) ))] pub(crate) struct BlockIrqRegistration { @@ -58,9 +76,12 @@ pub(crate) struct BlockIrqRegistration { all( feature = "fs", not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) + all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) + ) ) ))] pub(crate) type BlockIrqRegistration = (); @@ -71,9 +92,12 @@ pub(crate) type BlockIrqRegistration = (); all( feature = "fs", not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) + all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) + ) ) ))] unsafe fn handle_block_irq( @@ -91,9 +115,12 @@ unsafe fn handle_block_irq( all( feature = "fs", not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) + all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) + ) ) ))] impl Drop for BlockIrqState { @@ -106,13 +133,19 @@ impl Drop for BlockIrqState { } } -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) +))] struct FsNgBlockDevice { _irq: Option, block: ax_driver::block::Block, } -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) +))] impl FsNgBlockDevice { fn new(mut block: ax_driver::block::Block) -> Self { let irq = register_irq_handler(&mut block); @@ -120,7 +153,10 @@ impl FsNgBlockDevice { } } -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) +))] impl ax_fs_ng::FsBlockDevice for FsNgBlockDevice { fn name(&self) -> &str { self.block.name() @@ -151,9 +187,12 @@ impl ax_fs_ng::FsBlockDevice for FsNgBlockDevice { all( feature = "fs", not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) + all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) + ) ))] pub(crate) fn register_irq_handler( block: &mut ax_driver::block::Block, @@ -190,10 +229,10 @@ pub(crate) fn register_irq_handler( #[cfg(all(feature = "fs-ng", feature = "plat-dyn"))] pub(crate) fn init_dyn_fs_ng(bootargs: Option<&str>) { - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] init_fs_ng_from_blocks(take_block_devices(), bootargs); - #[cfg(not(target_os = "none"))] + #[cfg(not(any(target_os = "none", arceos_std)))] let _ = bootargs; } @@ -202,12 +241,18 @@ pub(crate) fn init_static_fs_ng() { init_fs_ng_from_blocks(take_block_devices(), None); } -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) +))] fn take_block_devices() -> Vec { ax_driver::block::take_block_devices() } -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(all( + feature = "fs-ng", + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) +))] fn init_fs_ng_from_blocks(blocks: Vec, bootargs: Option<&str>) { let block_devs = blocks.into_iter().map(|dev| { alloc::boxed::Box::new(FsNgBlockDevice::new(dev)) diff --git a/os/arceos/modules/axruntime/src/devices.rs b/os/arceos/modules/axruntime/src/devices.rs index c952817969..69fe9a2440 100644 --- a/os/arceos/modules/axruntime/src/devices.rs +++ b/os/arceos/modules/axruntime/src/devices.rs @@ -11,7 +11,7 @@ pub(crate) fn probe_all_devices() { #[cfg(all(feature = "fs", not(feature = "fs-ng"), feature = "plat-dyn"))] pub(crate) fn take_dyn_fs_block_devices() -> alloc::vec::Vec> { - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] { if !rdrive::is_initialized() { return alloc::vec::Vec::new(); @@ -26,7 +26,7 @@ pub(crate) fn take_dyn_fs_block_devices() .collect() } - #[cfg(not(target_os = "none"))] + #[cfg(not(any(target_os = "none", arceos_std)))] alloc::vec::Vec::new() } @@ -45,7 +45,7 @@ pub(crate) fn take_static_fs_block_devices() #[cfg(all(feature = "display", feature = "plat-dyn"))] pub(crate) fn init_dyn_display() { - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] { if !rdrive::is_initialized() { ax_display::init_display(core::iter::empty::()); @@ -62,7 +62,7 @@ pub(crate) fn init_dyn_display() { ax_display::init_display(devices); } - #[cfg(not(target_os = "none"))] + #[cfg(not(any(target_os = "none", arceos_std)))] ax_display::init_display(core::iter::empty::()); } @@ -81,7 +81,7 @@ pub(crate) fn init_static_display() { #[cfg(all(feature = "input", feature = "plat-dyn"))] pub(crate) fn init_dyn_input() { - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] { if !rdrive::is_initialized() { ax_input::init_input(core::iter::empty::()); @@ -94,7 +94,7 @@ pub(crate) fn init_dyn_input() { ax_input::init_input(devices); } - #[cfg(not(target_os = "none"))] + #[cfg(not(any(target_os = "none", arceos_std)))] ax_input::init_input(core::iter::empty::()); } @@ -155,7 +155,7 @@ pub(crate) fn take_static_net_ng_drivers() #[cfg(all(feature = "vsock", feature = "plat-dyn"))] pub(crate) fn init_dyn_vsock() { - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] { if !rdrive::is_initialized() { ax_net_ng::init_vsock(alloc::vec::Vec::new()); @@ -166,7 +166,7 @@ pub(crate) fn init_dyn_vsock() { ax_net_ng::init_vsock(devices); } - #[cfg(not(target_os = "none"))] + #[cfg(not(any(target_os = "none", arceos_std)))] ax_net_ng::init_vsock(alloc::vec::Vec::new()); } @@ -179,7 +179,7 @@ pub(crate) fn init_static_vsock() { #[cfg(all(feature = "net", not(feature = "net-ng"), feature = "plat-dyn"))] fn take_dyn_net_drivers() -> alloc::vec::Vec> { - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] { if !rdrive::is_initialized() { return alloc::vec::Vec::new(); @@ -197,13 +197,13 @@ fn take_dyn_net_drivers() -> alloc::vec::Vec alloc::vec::Vec> { - #[cfg(target_os = "none")] + #[cfg(any(target_os = "none", arceos_std))] { if !rdrive::is_initialized() { return alloc::vec::Vec::new(); @@ -221,14 +221,14 @@ fn take_dyn_net_ng_drivers() -> alloc::vec::Vec, @@ -238,7 +238,7 @@ struct FsBlockDevice { #[cfg(all( feature = "fs", not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) ))] impl FsBlockDevice { fn new(mut block: ax_driver::block::Block) -> Self { @@ -250,7 +250,7 @@ impl FsBlockDevice { #[cfg(all( feature = "fs", not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") + any(not(feature = "plat-dyn"), any(target_os = "none", arceos_std)) ))] impl ax_fs::FsBlockDevice for FsBlockDevice { fn name(&self) -> &str { diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index 2f318d72a9..a2f69ee803 100644 --- a/os/arceos/modules/axruntime/src/lib.rs +++ b/os/arceos/modules/axruntime/src/lib.rs @@ -38,7 +38,7 @@ extern crate ax_log; extern crate ax_driver as _; -#[cfg(all(target_os = "none", not(test)))] +#[cfg(all(target_os = "none", not(arceos_std), not(test)))] mod lang_items; #[cfg(feature = "smp")] diff --git a/os/arceos/modules/axtask/Cargo.toml b/os/arceos/modules/axtask/Cargo.toml index 5a743d5804..d18f14300d 100644 --- a/os/arceos/modules/axtask/Cargo.toml +++ b/os/arceos/modules/axtask/Cargo.toml @@ -82,3 +82,6 @@ spin = {workspace = true, optional = true} [target.'cfg(not(target_os = "none"))'.dev-dependencies] ax-hal = { workspace = true, features = ["fp-simd"] } + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/os/arceos/modules/axtask/src/run_queue.rs b/os/arceos/modules/axtask/src/run_queue.rs index 0d31ff3af4..859c3de267 100644 --- a/os/arceos/modules/axtask/src/run_queue.rs +++ b/os/arceos/modules/axtask/src/run_queue.rs @@ -56,13 +56,13 @@ static mut RUN_QUEUES: [MaybeUninit<&'static mut AxRunQueue>; ax_config::plat::M #[allow(clippy::declare_interior_mutable_const)] // It's ok because it's used only for initialization `RUN_QUEUES`. const ARRAY_REPEAT_VALUE: MaybeUninit<&'static mut AxRunQueue> = MaybeUninit::uninit(); -#[cfg(target_os = "none")] +#[cfg(any(target_os = "none", arceos_std))] fn main_task_stack() -> TaskStack { let (stack_ptr, stack_size) = ax_hal::mem::boot_stack_bounds(this_cpu_id()); TaskStack::borrowed(stack_ptr, stack_size, TASK_STACK_ALIGN) } -#[cfg(not(target_os = "none"))] +#[cfg(not(any(target_os = "none", arceos_std)))] fn main_task_stack() -> TaskStack { TaskStack::alloc(ax_config::TASK_STACK_SIZE) } @@ -668,7 +668,7 @@ impl AxRunQueue { fn switch_to(&mut self, prev_task: CurrentTask, next_task: AxTaskRef) { // Make sure that IRQs are disabled by kernel guard or other means. - #[cfg(all(target_os = "none", feature = "irq"))] // Note: irq is faked under unit tests. + #[cfg(all(any(target_os = "none", arceos_std), feature = "irq"))] // Note: irq is faked under unit tests. assert!( !ax_hal::asm::irqs_enabled(), "IRQs must be disabled during scheduling" diff --git a/os/arceos/ulib/arceos-rust/CHANGELOG.md b/os/arceos/ulib/arceos-rust/CHANGELOG.md deleted file mode 100644 index 2d898304a4..0000000000 --- a/os/arceos/ulib/arceos-rust/CHANGELOG.md +++ /dev/null @@ -1,33 +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.1.2](https://github.com/rcore-os/tgoskits/compare/arceos-rust-v0.1.1...arceos-rust-v0.1.2) - 2026-06-03 - -### Added - -- *(riscv64)* support dynamic platform on QEMU and SG2002 ([#961](https://github.com/rcore-os/tgoskits/pull/961)) - -### Fixed - -- *(repo)* migrate spin usage to ax-kspin ([#861](https://github.com/rcore-os/tgoskits/pull/861)) - -### Other - -- *(platform)* migrate riscv64 qemu to dynamic platform ([#1085](https://github.com/rcore-os/tgoskits/pull/1085)) -- *(platform)* remove static aarch64 platforms ([#1074](https://github.com/rcore-os/tgoskits/pull/1074)) -- *(linker)* layer platform runtime and final scripts ([#1075](https://github.com/rcore-os/tgoskits/pull/1075)) -- *(ci)* bump Rust toolchain to nightly-2026-05-28 and fix clippy ([#1027](https://github.com/rcore-os/tgoskits/pull/1027)) -- *(ax-alloc)* remove ax-allocator dependency, simplify to TLSF/buddy-slab backends ([#987](https://github.com/rcore-os/tgoskits/pull/987)) -- *(drivers)* split shared driver stack from ArceOS ([#831](https://github.com/rcore-os/tgoskits/pull/831)) - -## [0.1.1](https://github.com/rcore-os/tgoskits/compare/arceos-rust-v0.1.0...arceos-rust-v0.1.1) - 2026-05-15 - -### Other - -- release ([#641](https://github.com/rcore-os/tgoskits/pull/641)) diff --git a/os/arceos/ulib/arceos-rust/Cargo.toml b/os/arceos/ulib/arceos-rust/Cargo.toml deleted file mode 100644 index 78ef9fb184..0000000000 --- a/os/arceos/ulib/arceos-rust/Cargo.toml +++ /dev/null @@ -1,69 +0,0 @@ -[package] -name = "arceos-rust" -description = "Rust std support for ArceOS" -authors = [ - "eternalcomet " -] -version = "0.1.2" -edition.workspace = true -license.workspace = true -build = "build.rs" - -[lib] - -[build-dependencies] -walkdir = "2" - -[features] -# Default features are specified in `lib/Cargo.toml` -default = [] - -# Multicore -smp = [] - -# Floating point/SIMD -fp-simd = [] - -# Interrupts -irq = [] -ipi = [] - -# Custom or default platforms -myplat = [] -defplat = [] -plat-dyn = [] - -# Memory -alloc = [] -paging = [] -dma = [] -tls = [] - -# Multi-threading and scheduler -multitask = [] -lockdep = [] -sched-fifo = [] -sched-rr = [] -sched-cfs = [] - -# File system -fd = [] -fs = [] - -# Networking -net = [] -dns = [] - -# Display -display = [] - -# Real Time Clock (RTC) Driver. -rtc = [] - -# Logging -log-level-off = [] -log-level-error = [] -log-level-warn = [] -log-level-info = [] -log-level-debug = [] -log-level-trace = [] diff --git a/os/arceos/ulib/arceos-rust/build.rs b/os/arceos/ulib/arceos-rust/build.rs deleted file mode 100644 index 3bb05ab9c3..0000000000 --- a/os/arceos/ulib/arceos-rust/build.rs +++ /dev/null @@ -1,697 +0,0 @@ -// Package name of the `lib` directory -const LIB_NAME: &str = "arceos_rust_interface"; - -use std::{ - collections::BTreeSet, - env, fs, - path::{Path, PathBuf}, - process::Command, - time::UNIX_EPOCH, -}; - -fn main() { - println!( - "cargo:warning=Running build script for ArceOS rust library. Time: {:?}", - std::time::SystemTime::now() - ); - let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); - - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); - let lib_dir = manifest_dir.join("lib"); - let features = env::var("CARGO_CFG_FEATURE").unwrap_or_default(); - let passthrough_features = env::var("ARCEOS_RUST_FEATURES").unwrap_or_default(); - let feature_list = resolve_nested_features(&features, &passthrough_features); - - // generate configuration file - let config_path = get_config_path(&manifest_dir, &out_dir, has_dynamic_platform(&feature_list)); - if let Some(config_path) = &config_path { - println!("cargo:warning=config path: {}", config_path.display()); - } - // build the ArceOS library - let artifact_path = compile_project( - &lib_dir, - &out_dir, - config_path.as_deref(), - &feature_list, - get_log_level(&features), - ); - // rename symbols to avoid conflicts - let lib_file = artifact_path.join(format!("lib{}.a", LIB_NAME)); - - // Auto-generate rename list by extracting all ___rustc symbols - let rename_list = generate_rename_list(&lib_file); - rename_symbols(&lib_file, &rename_list); - emit_linker_script_search_paths(&artifact_path, has_dynamic_platform(&feature_list)); - println!("cargo:rustc-link-lib=static={}", LIB_NAME); - - // Trick: specify a non-existent path to always trigger a rebuild - // See https://doc.rust-lang.org/cargo/faq.html#why-is-cargo-rebuilding-my-code - println!("cargo:rerun-if-changed=always"); -} - -fn emit_linker_script_search_paths(artifact_path: &Path, plat_dyn: bool) { - println!("cargo:rustc-link-search=native={}", artifact_path.display()); - - if !plat_dyn { - // Use the final linker script generated by ax-runtime. - let linker_script_path = find_runtime_linker_script(artifact_path); - let linker_search_dirs = find_linker_search_dirs(artifact_path); - - println!( - "cargo:warning=Linker script path: {}", - linker_script_path.display() - ); - - for dir in linker_search_dirs { - println!("cargo:warning=Linker script search dir: {}", dir.display()); - println!("cargo:rustc-link-search=native={}", dir.display()); - } - return; - } - - let scripts = dynamic_linker_scripts(artifact_path); - for script in &scripts { - if !script.is_file() { - panic!( - "Linker script not found at {}. Expected from inner ArceOS build.", - script.display() - ); - } - println!("cargo:warning=Linker script path: {}", script.display()); - println!( - "cargo:rustc-link-search=native={}", - script.parent().unwrap().display() - ); - } -} - -fn dynamic_linker_scripts(artifact_path: &Path) -> Vec { - vec![ - find_runtime_linker_script(artifact_path), - required_build_output_file(artifact_path, "axplat.x"), - required_build_output_file(artifact_path, "link.x"), - required_build_output_file(artifact_path, "someboot.x"), - ] -} - -fn required_build_output_file(artifact_path: &Path, file_name: &str) -> PathBuf { - find_build_output_file(artifact_path, file_name).unwrap_or_else(|| { - panic!( - "Linker script {file_name} was not found under {}", - artifact_path.display() - ) - }) -} - -fn find_build_output_file(artifact_path: &Path, file_name: &str) -> Option { - let build_dir = artifact_path.join("build"); - walkdir::WalkDir::new(build_dir) - .max_depth(3) - .into_iter() - .filter_map(Result::ok) - .map(|entry| entry.into_path()) - .find(|path| { - path.is_file() && path.file_name().and_then(|name| name.to_str()) == Some(file_name) - }) -} - -fn get_config_path(manifest_dir: &Path, out_dir: &Path, plat_dyn: bool) -> Option { - if let Ok(path) = env::var("ARCEOS_RUST_CONFIG") - && !path.trim().is_empty() - { - return Some(PathBuf::from(path)); - } - - if plat_dyn { - return None; - } - - Some(generate_config(manifest_dir, out_dir)) -} - -fn generate_config(manifest_dir: &Path, out_dir: &Path) -> PathBuf { - let template = manifest_dir.join("defconfig.toml"); - let arch = get_arch(); - let platform = default_static_platform().unwrap_or_else(|| { - panic!("ARCEOS_RUST_PLATFORM_CONFIG is required for static {arch} std builds") - }); - let platform_config_path = get_platform_config_path(platform); - let out_config_path = out_dir.join("axconfig.toml"); - - let command = Command::new("axconfig-gen") - .arg(&template) - .arg(platform_config_path) - .arg("-w") - .arg(format!(r#"arch="{}""#, arch)) - .arg("-w") - .arg(format!(r#"platform="{}""#, platform)) - .arg("-o") - .arg(&out_config_path) - .status() - .expect("Failed to generate configuration file."); - - if !command.success() { - panic!("Failed to generate configuration file."); - } - - out_config_path -} - -fn get_platform_config_path(platform: &str) -> PathBuf { - if let Ok(path) = env::var("ARCEOS_RUST_PLATFORM_CONFIG") { - return PathBuf::from(path); - } - - // Fallback for direct upstream usage without axbuild. - let output = Command::new(cargo()) - .arg("axplat") - .arg("info") - .arg(format!("ax-plat-{}", platform)) - .arg("-c") - .output() - .expect("Failed to get platform config path."); - - if !output.status.success() { - println!( - "cargo:warning=axplat output: {} {}", - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - panic!("Failed to get platform config path."); - } - - PathBuf::from(String::from_utf8_lossy(&output.stdout).trim()) -} - -fn compile_project( - lib_dir: &PathBuf, - out_dir: &PathBuf, - config_path: Option<&Path>, - feature_list: &[String], - log_level: &str, -) -> PathBuf { - let profile = env::var("PROFILE").unwrap(); - let is_debug = profile == "debug"; - let arch = get_arch(); - let target = get_target(&arch); - let plat_dyn = has_dynamic_platform(feature_list); - let feature_list = feature_list.join(" "); - - let mut command = Command::new(cargo()); - command.env("AX_TARGET", target); - command.env("AX_MODE", profile); - if let Some(config_path) = config_path { - command.env("AX_CONFIG_PATH", config_path); - } - if plat_dyn { - append_inner_rustflag(&mut command, "-Crelocation-model=pic"); - } - command.env("AX_LOG", log_level); - if env::var("AX_IP").is_err() { - command.env("AX_IP", "10.0.2.15"); - } - if env::var("AX_GW").is_err() { - command.env("AX_GW", "10.0.2.2"); - } - command - .current_dir(lib_dir) - .arg("build") - .arg("--target-dir") - .arg(out_dir) - .arg("--target") - .arg(target) - .arg("--no-default-features"); - if !feature_list.is_empty() { - command.arg("--features").arg(feature_list); - } - if !is_debug { - command.arg("--release"); - } - println!( - "cargo:warning=FATURES are {}", - env::var("CARGO_CFG_FEATURE").unwrap_or("none".to_string()) - ); - println!("cargo:warning=command: {:?}", command); - - let status = command.status().expect("Failed to build ArceOS library."); - - if !status.success() { - panic!("Failed to build ArceOS library."); - } - - let build_type = if is_debug { "debug" } else { "release" }; - out_dir.join(target).join(build_type) -} - -fn find_runtime_linker_script(artifact_path: &Path) -> PathBuf { - latest_out_dir_with_script(&artifact_path.join("build"), "ax-runtime-", "linker.x") - .join("linker.x") -} - -fn find_linker_search_dirs(artifact_path: &Path) -> BTreeSet { - let build_dir = artifact_path.join("build"); - find_linker_search_dirs_for_owner(&build_dir, platform_linker_owner_prefix()) -} - -fn find_linker_search_dirs_for_owner( - build_dir: &Path, - platform_owner_prefix: &str, -) -> BTreeSet { - let mut dirs = BTreeSet::new(); - dirs.insert(latest_out_dir_with_script( - build_dir, - "ax-runtime-", - "linker.x", - )); - dirs.insert(latest_out_dir_with_script( - build_dir, - platform_owner_prefix, - "axplat.x", - )); - dirs -} - -fn latest_out_dir_with_script( - build_dir: &Path, - package_prefix: &str, - script_name: &str, -) -> PathBuf { - let mut candidates = Vec::new(); - let entries = fs::read_dir(build_dir) - .unwrap_or_else(|err| panic!("failed to read {}: {err}", build_dir.display())); - for entry in entries { - let path = entry - .unwrap_or_else(|err| { - panic!("failed to read entry under {}: {err}", build_dir.display()) - }) - .path(); - if path - .file_name() - .and_then(|name| name.to_str()) - .is_some_and(|name| name.starts_with(package_prefix)) - { - let out_dir = path.join("out"); - let script_path = out_dir.join(script_name); - if script_path.is_file() { - let modified = script_path - .metadata() - .and_then(|metadata| metadata.modified()) - .unwrap_or(UNIX_EPOCH); - candidates.push((modified, out_dir)); - } - } - } - - candidates.sort_by(|(left_time, left_path), (right_time, right_path)| { - right_time - .cmp(left_time) - .then_with(|| left_path.cmp(right_path)) - }); - candidates - .into_iter() - .map(|(_, path)| path) - .next() - .unwrap_or_else(|| { - panic!( - "Linker script {script_name} not found under {}. Expected from {package_prefix}* \ - build.", - build_dir - .join(format!("{package_prefix}*/out/{script_name}")) - .display() - ) - }) -} - -fn platform_linker_owner_prefix() -> &'static str { - platform_linker_owner_prefix_for(&selected_platform(), has_passthrough_feature("plat-dyn")) -} - -fn platform_linker_owner_prefix_for(platform: &str, is_dynamic_platform: bool) -> &'static str { - if is_dynamic_platform { - return "axplat-dyn-"; - } - - match platform { - "loongarch64-qemu-virt" => "ax-plat-loongarch64-qemu-virt-", - "x86-qemu-q35" => "ax-plat-x86-qemu-q35-", - _ => "ax-hal-", - } -} - -fn selected_platform() -> String { - passthrough_features() - .into_iter() - .find_map(|feature| { - feature - .strip_prefix("ax-hal/") - .filter(|platform| !matches!(*platform, "defplat" | "myplat" | "plat-dyn")) - .map(ToOwned::to_owned) - }) - .or_else(|| default_static_platform().map(ToOwned::to_owned)) - .unwrap_or_else(|| { - panic!( - "ARCEOS_RUST_FEATURES must include an explicit static platform feature for {}", - get_arch() - ) - }) -} - -fn has_passthrough_feature(feature_name: &str) -> bool { - passthrough_features() - .iter() - .any(|feature| feature == feature_name || feature.ends_with(&format!("/{feature_name}"))) -} - -fn passthrough_features() -> Vec { - env::var("ARCEOS_RUST_FEATURES") - .unwrap_or_default() - .split(|ch: char| ch == ',' || ch.is_whitespace()) - .map(str::trim) - .filter(|feature| !feature.is_empty()) - .map(map_nested_feature) - .collect() -} - -fn cargo() -> String { - env::var("CARGO").unwrap() -} - -fn append_inner_rustflag(command: &mut Command, flag: &str) { - if let Ok(existing) = env::var("CARGO_ENCODED_RUSTFLAGS") - && !existing.is_empty() - { - command.env("CARGO_ENCODED_RUSTFLAGS", format!("{existing}\x1f{flag}")); - return; - } - - if let Ok(existing) = env::var("RUSTFLAGS") - && !existing.is_empty() - { - command.env("RUSTFLAGS", format!("{existing} {flag}")); - return; - } - - command.env("CARGO_ENCODED_RUSTFLAGS", flag); -} - -fn generate_rename_list(lib_path: &Path) -> PathBuf { - // Use rust-nm to extract all symbols from the library - let nm_output = Command::new("rust-nm") - .arg(lib_path) - .output() - .expect("Failed to run rust-nm. Please ensure llvm-tools-preview is installed."); - - if !nm_output.status.success() { - panic!( - "rust-nm failed:\n{}", - String::from_utf8_lossy(&nm_output.stderr) - ); - } - - let symbols_output = String::from_utf8_lossy(&nm_output.stdout); - - // Extract all unique symbols containing "___rustc" and generate rename pairs - let mut rename_pairs = std::collections::HashSet::new(); - for line in symbols_output.lines() { - // nm output format:
- // Extract the last field (symbol name) - if let Some(symbol) = line.split_whitespace().last() - && symbol.contains("___rustc") - { - // Rename by appending "_1" suffix - rename_pairs.insert((symbol.to_string(), format!("{}_1", symbol))); - } - } - - // Write rename list to a temporary file - let rename_list_path = lib_path.parent().unwrap().join("symbol_rename_auto.txt"); - let mut file = std::fs::File::create(&rename_list_path) - .expect("Failed to create auto-generated rename list file"); - - use std::io::Write; - for (old_symbol, new_symbol) in &rename_pairs { - writeln!(file, "{} {}", old_symbol, new_symbol) - .expect("Failed to write to rename list file"); - } - - println!( - "cargo:warning=Auto-generated {} symbol rename rules", - rename_pairs.len() - ); - - rename_list_path -} - -fn rename_symbols(lib_path: &Path, rename_list: &Path) { - let output = Command::new("rust-objcopy") - .arg("--redefine-syms") - .arg(rename_list) - .arg(lib_path) - .output(); - - match output { - Ok(output) if output.status.success() => {} - Ok(output) => panic!( - "Failed to rename symbols with rust-objcopy (exit: {}).\nstdout:\n{}\nstderr:\n{}", - output - .status - .code() - .map_or_else(|| "signal".to_string(), |c| c.to_string()), - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr), - ), - Err(_) => panic!( - "Failed to run rust-objcopy. Please install required tools with:\n rustup component \ - add llvm-tools-preview\n cargo install cargo-binutils" - ), - } -} - -fn get_arch() -> String { - env::var("CARGO_CFG_TARGET_ARCH").unwrap() -} - -fn default_static_platform_for_arch(arch: &str) -> Option<&'static str> { - match arch { - "x86_64" => Some("x86-pc"), - "aarch64" => None, - "riscv64" => None, - "loongarch64" => Some("loongarch64-qemu-virt"), - _ => panic!("Unsupported architecture: {}", arch), - } -} - -fn get_target(arch: &str) -> &'static str { - match arch { - "x86_64" => "x86_64-unknown-none", - "aarch64" => "aarch64-unknown-none-softfloat", - "riscv64" => "riscv64gc-unknown-none-elf", - "loongarch64" => "loongarch64-unknown-none-softfloat", - _ => panic!("Unsupported architecture: {}", arch), - } -} - -fn default_static_platform() -> Option<&'static str> { - let arch = get_arch(); - default_static_platform_for_arch(&arch) -} - -fn get_log_level(feature_list: &str) -> &str { - let mut level = "off"; - for feature in feature_list.split(',') { - if let Some(stripped) = feature.strip_prefix("log-level-") { - level = stripped; - } - } - level -} - -fn map_nested_feature(feature: &str) -> String { - if let Some(rest) = feature.strip_prefix("ax_hal/") { - format!("ax-hal/{rest}") - } else if let Some(rest) = feature.strip_prefix("ax_driver/") { - format!("ax-driver/{rest}") - } else { - feature.to_string() - } -} - -fn resolve_nested_features(features: &str, passthrough_features: &str) -> Vec { - let mut feature_list = features - .split(',') - .chain(passthrough_features.split(',')) - .map(str::trim) - .filter(|feature| !feature.is_empty()) - .map(map_nested_feature) - .collect::>(); - - if !has_platform_feature(&feature_list) { - feature_list.push("defplat".to_string()); - } - feature_list.sort(); - feature_list.dedup(); - feature_list -} - -fn has_platform_feature(features: &[String]) -> bool { - features.iter().any(|feature| { - matches!( - feature.as_str(), - "default" | "defplat" | "myplat" | "plat-dyn" - ) || feature.starts_with("ax-hal/") - }) -} - -fn has_dynamic_platform(features: &[String]) -> bool { - features.iter().any(|feature| feature == "plat-dyn") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn single_feature_builds_with_default_platform() { - assert_eq!( - resolve_nested_features("alloc", ""), - vec!["alloc".to_string(), "defplat".to_string()] - ); - } - - #[test] - fn default_feature_keeps_inner_defaults() { - assert_eq!(resolve_nested_features("default", ""), vec!["default"]); - } - - #[test] - fn explicit_platform_is_preserved_without_defplat() { - assert_eq!( - resolve_nested_features("alloc", "ax_hal/x86-pc"), - vec!["alloc".to_string(), "ax-hal/x86-pc".to_string()] - ); - } - - #[test] - fn explicit_myplat_is_preserved_without_defplat() { - assert_eq!( - resolve_nested_features("alloc,myplat", ""), - vec!["alloc".to_string(), "myplat".to_string()] - ); - } - - #[test] - fn explicit_dynamic_platform_is_preserved_without_defplat() { - assert_eq!( - resolve_nested_features("alloc,plat-dyn", ""), - vec!["alloc".to_string(), "plat-dyn".to_string()] - ); - } - - #[test] - fn loongarch_platform_linker_is_platform_owned() { - assert_eq!( - platform_linker_owner_prefix_for("loongarch64-qemu-virt", false), - "ax-plat-loongarch64-qemu-virt-" - ); - } - - #[test] - fn loongarch_default_platform_linker_is_platform_owned() { - let platform = default_static_platform_for_arch("loongarch64").unwrap(); - - assert_eq!( - platform_linker_owner_prefix_for(platform, false), - "ax-plat-loongarch64-qemu-virt-" - ); - } - - #[test] - fn generic_static_platform_linker_is_axhal_owned() { - assert_eq!( - platform_linker_owner_prefix_for("aarch64-qemu-virt", false), - "ax-hal-" - ); - } - - #[test] - fn dynamic_platform_linker_is_axplat_dyn_owned() { - assert_eq!( - platform_linker_owner_prefix_for("loongarch64-qemu-virt", true), - "axplat-dyn-" - ); - } - - #[test] - fn static_aarch64_has_no_deleted_default_platform() { - assert_eq!(default_static_platform_for_arch("aarch64"), None); - } - - #[test] - fn inner_dynamic_build_uses_pic_codegen() { - let mut command = Command::new("cargo"); - - append_inner_rustflag(&mut command, "-Crelocation-model=pic"); - - assert_eq!( - command - .get_envs() - .find(|(key, _)| key == "CARGO_ENCODED_RUSTFLAGS"), - Some(( - std::ffi::OsStr::new("CARGO_ENCODED_RUSTFLAGS"), - Some(std::ffi::OsStr::new("-Crelocation-model=pic")) - )) - ); - } - - #[test] - fn linker_search_dirs_include_runtime_and_platform_out_dirs() { - let root = unique_temp_dir("arceos-rust-linker-search"); - let build_dir = root.join("build"); - let runtime_out = build_dir.join("ax-runtime-123/out"); - let platform_out = build_dir.join("ax-plat-loongarch64-qemu-virt-456/out"); - std::fs::create_dir_all(&runtime_out).unwrap(); - std::fs::create_dir_all(&platform_out).unwrap(); - std::fs::write(runtime_out.join("linker.x"), "").unwrap(); - std::fs::write(platform_out.join("axplat.x"), "").unwrap(); - - let dirs = find_linker_search_dirs_for_owner(&build_dir, "ax-plat-loongarch64-qemu-virt-"); - - assert!(dirs.contains(&runtime_out)); - assert!(dirs.contains(&platform_out)); - std::fs::remove_dir_all(root).unwrap(); - } - - #[test] - fn dynamic_linker_scripts_include_runtime_entrypoint_and_includes() { - let root = unique_temp_dir("arceos-rust-dyn-linker-search"); - let build_dir = root.join("build"); - let runtime_out = build_dir.join("ax-runtime-123/out"); - let axplat_out = build_dir.join("axplat-dyn-456/out"); - let somehal_out = build_dir.join("somehal-789/out"); - let someboot_out = build_dir.join("someboot-abc/out"); - std::fs::create_dir_all(&runtime_out).unwrap(); - std::fs::create_dir_all(&axplat_out).unwrap(); - std::fs::create_dir_all(&somehal_out).unwrap(); - std::fs::create_dir_all(&someboot_out).unwrap(); - std::fs::write(runtime_out.join("linker.x"), "").unwrap(); - std::fs::write(axplat_out.join("axplat.x"), "").unwrap(); - std::fs::write(somehal_out.join("link.x"), "").unwrap(); - std::fs::write(someboot_out.join("someboot.x"), "").unwrap(); - - let scripts = dynamic_linker_scripts(&root); - - assert_eq!(scripts[0], runtime_out.join("linker.x")); - assert!(scripts.contains(&axplat_out.join("axplat.x"))); - assert!(scripts.contains(&somehal_out.join("link.x"))); - assert!(scripts.contains(&someboot_out.join("someboot.x"))); - std::fs::remove_dir_all(root).unwrap(); - } - - fn unique_temp_dir(prefix: &str) -> PathBuf { - let nanos = std::time::SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_nanos(); - env::temp_dir().join(format!("{prefix}-{}-{nanos}", std::process::id())) - } -} diff --git a/os/arceos/ulib/arceos-rust/defconfig.toml b/os/arceos/ulib/arceos-rust/defconfig.toml deleted file mode 100644 index b5f5141118..0000000000 --- a/os/arceos/ulib/arceos-rust/defconfig.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Stack size of each task. -task-stack-size = 0x40000 # uint - -# 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/ulib/arceos-rust/lib/CHANGELOG.md b/os/arceos/ulib/arceos-rust/lib/CHANGELOG.md deleted file mode 100644 index d477cefd02..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/CHANGELOG.md +++ /dev/null @@ -1,43 +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] - -## [1.1.0](https://github.com/rcore-os/tgoskits/compare/arceos-rust-interface-v1.0.3...arceos-rust-interface-v1.1.0) - 2026-06-03 - -### Added - -- *(riscv64)* support dynamic platform on QEMU and SG2002 ([#961](https://github.com/rcore-os/tgoskits/pull/961)) -- *(axtask)* add task stack guard page support ([#811](https://github.com/rcore-os/tgoskits/pull/811)) - -### Fixed - -- *(repo)* migrate spin usage to ax-kspin ([#861](https://github.com/rcore-os/tgoskits/pull/861)) - -### Other - -- *(platform)* remove static aarch64 platforms ([#1074](https://github.com/rcore-os/tgoskits/pull/1074)) -- *(ax-alloc)* remove ax-allocator dependency, simplify to TLSF/buddy-slab backends ([#987](https://github.com/rcore-os/tgoskits/pull/987)) -- *(drivers)* split shared driver stack from ArceOS ([#831](https://github.com/rcore-os/tgoskits/pull/831)) - -## [1.0.3](https://github.com/rcore-os/tgoskits/compare/arceos-rust-interface-v1.0.2...arceos-rust-interface-v1.0.3) - 2026-05-22 - -### Other - -- updated the following local packages: ax-errno, ax-runtime, ax-feat, ax-io, ax-api, ax-posix-api - -## [1.0.2](https://github.com/rcore-os/tgoskits/compare/arceos-rust-interface-v1.0.1...arceos-rust-interface-v1.0.2) - 2026-05-19 - -### Other - -- updated the following local packages: ax-errno, ax-io, ax-runtime, ax-feat, ax-api, ax-posix-api - -## [1.0.1](https://github.com/rcore-os/tgoskits/compare/arceos-rust-interface-v1.0.0...arceos-rust-interface-v1.0.1) - 2026-05-15 - -### Other - -- updated the following local packages: ax-kspin, ax-io, ax-runtime, ax-feat, ax-api, ax-posix-api diff --git a/os/arceos/ulib/arceos-rust/lib/Cargo.toml b/os/arceos/ulib/arceos-rust/lib/Cargo.toml deleted file mode 100644 index eb69278edd..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/Cargo.toml +++ /dev/null @@ -1,91 +0,0 @@ -[package] -authors = [ - "eternalcomet ", -] -categories.workspace = true -description = "Arceos ABI definitions" -edition = "2024" -keywords.workspace = true -license.workspace = true -name = "arceos-rust-interface" -repository.workspace = true -version = "1.1.0" - -[lib] -crate-type = ["staticlib"] - -[features] -default = [ - "fp-simd", - "tls", - "defplat", - "fd", - "fs", - "net", - "irq", - "multitask", -] - -# Multicore -smp = ["ax-feat/smp", "ax-kspin/smp"] - -# Floating point/SIMD -fp-simd = ["ax-feat/fp-simd"] - -# Interrupts -ipi = ["ax-api/ipi", "ax-feat/ipi"] -irq = ["ax-api/irq", "ax-feat/irq"] - -# Custom or default platforms -defplat = ["ax-feat/defplat"] -myplat = ["ax-feat/myplat"] -plat-dyn = ["ax-feat/plat-dyn"] - -# Memory -alloc = ["ax-api/alloc", "ax-feat/alloc", "ax-io/alloc"] -dma = ["ax-api/dma", "ax-feat/dma"] -paging = ["ax-feat/paging"] -tls = ["ax-feat/tls"] - -# Multi-threading and scheduler -multitask = ["ax-api/multitask", "ax-feat/multitask", "irq"] -lockdep = ["ax-feat/lockdep"] -sched-cfs = ["ax-feat/sched-cfs"] -sched-fifo = ["ax-feat/sched-fifo"] -sched-rr = ["ax-feat/sched-rr"] -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/virtio-blk"] - -# Networking -dns = [] -net = ["ax-api/net", "ax-posix-api/net", "ax-posix-api/poll", "ax-feat/net", "ax-driver/virtio-net"] - -# Display -display = ["ax-api/display", "ax-feat/display"] - -# Real Time Clock (RTC) Driver. -rtc = ["ax-feat/rtc"] - -# Logging -log-level-debug = [] -log-level-error = [] -log-level-info = [] -log-level-off = [] -log-level-trace = [] -log-level-warn = [] - -[dependencies] -ax-api = {workspace = true, features = ["alloc"]} -ax-driver = {workspace = true} -ax-errno = {workspace = true} -ax-feat = {workspace = true} -ax-hal = {workspace = true} -ax-io = {workspace = true} -ax-kspin = {workspace = true} -ax-posix-api = {workspace = true, features = ["use-hermit-types"]} -ax-runtime = {workspace = true} -log = {workspace = true} -rand = {version = "0.9.2", default-features = false, features = ["small_rng"]} diff --git a/os/arceos/ulib/arceos-rust/lib/src/interface/fs.rs b/os/arceos/ulib/arceos-rust/lib/src/interface/fs.rs deleted file mode 100644 index cf3dd17613..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/interface/fs.rs +++ /dev/null @@ -1,63 +0,0 @@ -use core::ffi::c_char; - -use ax_errno::AxError; -use ax_posix_api::{ctypes::stat, utils::char_ptr_to_str}; -use log::info; - -use crate::err; - -#[unsafe(no_mangle)] -pub fn sys_stat(name: *const c_char, stat: *mut stat) -> i32 { - info!("called sys_stat"); - unsafe { ax_posix_api::sys_stat(name, stat as _) } -} - -#[unsafe(no_mangle)] -pub fn sys_lstat(name: *const c_char, stat: *mut stat) -> i32 { - info!("called sys_lstat"); - unsafe { ax_posix_api::sys_lstat(name, stat as _) as _ } -} - -#[unsafe(no_mangle)] -pub fn sys_fstat(fd: i32, stat: *mut stat) -> i32 { - info!("called sys_fstat"); - unsafe { ax_posix_api::sys_fstat(fd, stat as _) as _ } -} - -#[unsafe(no_mangle)] -pub fn sys_unlink(name: *const c_char) -> i32 { - info!("called sys_unlink"); - // get name as &str - let name = match char_ptr_to_str(name) { - Ok(s) => s, - Err(e) => return err(e), - }; - - if let Err(e) = ax_api::fs::ax_remove_file(name) { - if e != AxError::IsADirectory { - return err(e.into()); - } - if let Err(e) = ax_api::fs::ax_remove_dir(name) { - return err(e.into()); - } - } - 0 -} - -#[unsafe(no_mangle)] -pub fn sys_mkdir(name: *const c_char, _mode: u32) -> i32 { - let name = match char_ptr_to_str(name) { - Ok(s) => s, - Err(e) => return err(e), - }; - if let Err(e) = ax_api::fs::ax_create_dir(name) { - return err(e.into()); - } - 0 -} - -#[unsafe(no_mangle)] -pub fn sys_getdents64(fd: i32, buf: *mut u8, len: usize) -> isize { - info!("called sys_getdents64"); - unsafe { ax_posix_api::sys_getdents64(fd, buf, len) as _ } -} diff --git a/os/arceos/ulib/arceos-rust/lib/src/interface/io.rs b/os/arceos/ulib/arceos-rust/lib/src/interface/io.rs deleted file mode 100644 index 6e2d1483e7..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/interface/io.rs +++ /dev/null @@ -1,69 +0,0 @@ -#[cfg(feature = "fs")] -use core::ffi::c_char; -use core::sync::atomic::{AtomicI32, Ordering}; - -use ax_posix_api::ctypes::iovec; -use log::info; - -/// Global errno variable for the hermit ABI. -/// -/// Hermit std's `io::Error::last_os_error()` calls `sys_get_errno()` to -/// retrieve the last error. POSIX-style calls like `poll()` return -1 on -/// error and store the actual error code here. -pub(crate) static ERRNO: AtomicI32 = AtomicI32::new(0); - -/// Set the global errno value (used by POSIX-style wrappers like sys_poll). -#[inline] -#[cfg(feature = "net")] -pub(crate) fn set_errno(e: i32) { - ERRNO.store(e, Ordering::Relaxed); -} - -/// Get the last error number from the thread local storage. -/// -/// Called by hermit std's `io::Error::last_os_error()`. -#[unsafe(no_mangle)] -pub fn sys_get_errno() -> i32 { - let e = ERRNO.load(Ordering::Relaxed); - info!("called sys_get_errno => {}", e); - e -} - -#[unsafe(no_mangle)] -pub fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { - info!("called sys_write"); - ax_posix_api::sys_write(fd, buf as _, count) -} - -#[unsafe(no_mangle)] -pub fn sys_writev(fd: i32, iov: *const iovec, iovcnt: usize) -> isize { - info!("called sys_writev"); - unsafe { ax_posix_api::sys_writev(fd, iov, iovcnt as _) } -} - -#[cfg(feature = "fd")] -#[unsafe(no_mangle)] -pub fn sys_close(fd: i32) -> i32 { - info!("called sys_close"); - ax_posix_api::sys_close(fd) as _ -} - -#[unsafe(no_mangle)] -pub fn sys_read(fd: i32, buf: *mut u8, len: usize) -> isize { - info!("called sys_read"); - ax_posix_api::sys_read(fd, buf as _, len) -} - -#[cfg(feature = "fs")] -#[unsafe(no_mangle)] -pub fn sys_lseek(fd: i32, offset: isize, whence: i32) -> isize { - info!("called sys_lseek"); - ax_posix_api::sys_lseek(fd, offset as _, whence) as _ -} - -#[cfg(feature = "fs")] -#[unsafe(no_mangle)] -pub fn sys_open(name: *const c_char, flags: i32, mode: i32) -> i32 { - info!("called sys_open"); - ax_posix_api::sys_open(name, flags, mode as _) -} diff --git a/os/arceos/ulib/arceos-rust/lib/src/interface/mem.rs b/os/arceos/ulib/arceos-rust/lib/src/interface/mem.rs deleted file mode 100644 index 78ddfa76dd..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/interface/mem.rs +++ /dev/null @@ -1,36 +0,0 @@ -use core::alloc::Layout; - -use log::{error, info}; - -#[unsafe(no_mangle)] -pub fn sys_malloc(size: usize, align: usize) -> *mut u8 { - info!("called sys_malloc with size {} and align {}", size, align); - if let Ok(layout) = Layout::from_size_align(size, align) { - unsafe { alloc::alloc::alloc(layout) } - } else { - core::ptr::null_mut() - } -} - -#[unsafe(no_mangle)] -pub fn sys_free(ptr: *mut u8, size: usize, align: usize) { - info!("called sys_free"); - if let Ok(layout) = Layout::from_size_align(size, align) { - unsafe { alloc::alloc::dealloc(ptr, layout) } - } else { - error!( - "sys_free called with invalid layout: size {}, align {}", - size, align - ); - } -} - -#[unsafe(no_mangle)] -pub fn sys_realloc(ptr: *mut u8, size: usize, align: usize, new_size: usize) -> *mut u8 { - info!("called sys_realloc"); - if let Ok(layout) = Layout::from_size_align(size, align) { - unsafe { alloc::alloc::realloc(ptr, layout, new_size) } - } else { - core::ptr::null_mut() - } -} diff --git a/os/arceos/ulib/arceos-rust/lib/src/interface/mod.rs b/os/arceos/ulib/arceos-rust/lib/src/interface/mod.rs deleted file mode 100644 index 6e464489aa..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/interface/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[cfg(feature = "fs")] -mod fs; -mod io; -mod mem; -#[cfg(feature = "net")] -mod net; -#[cfg(feature = "multitask")] -mod sync; -mod task; -#[cfg(feature = "multitask")] -mod thread; -mod util; diff --git a/os/arceos/ulib/arceos-rust/lib/src/interface/net.rs b/os/arceos/ulib/arceos-rust/lib/src/interface/net.rs deleted file mode 100644 index a9bcc213cc..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/interface/net.rs +++ /dev/null @@ -1,283 +0,0 @@ -use core::{ - ffi::{c_char, c_int, c_void}, - mem::size_of, -}; - -use ax_posix_api::ctypes::{ - FIONBIO, IPPROTO_TCP, SO_ERROR, SO_KEEPALIVE, SO_LINGER, SO_RCVBUF, SO_RCVTIMEO, SO_REUSEADDR, - SO_SNDBUF, SO_SNDTIMEO, SOL_SOCKET, TCP_NODELAY, addrinfo, nfds_t, pollfd, sockaddr, socklen_t, - timeval, -}; -use log::info; - -use super::io::set_errno; - -#[unsafe(no_mangle)] -pub fn sys_freeaddrinfo(addr_info: *mut addrinfo) { - info!("[sys_freeaddrinfo] addr_info: {:p}", addr_info); - unsafe { ax_posix_api::sys_freeaddrinfo(addr_info) } -} - -#[unsafe(no_mangle)] -pub fn sys_getaddrinfo( - nodename: *const c_char, - servname: *const c_char, - hints: *const addrinfo, - res: *mut *mut addrinfo, -) -> i32 { - info!( - "[sys_getaddrinfo] nodename: {:?}, servname: {:?}", - nodename, servname - ); - let result = unsafe { ax_posix_api::sys_getaddrinfo(nodename, servname, hints, res) }; - // result is the length of the list - if result > 0 { - // hermit expected us to return 0 if success - 0 - } else { - -1 - } -} - -#[unsafe(no_mangle)] -pub fn sys_send(s: i32, mem: *const c_void, len: usize, flags: i32) -> isize { - info!("[sys_send] socket: {}, len: {}, flags: {}", s, len, flags); - ax_posix_api::sys_send(s, mem, len, flags) -} - -#[unsafe(no_mangle)] -pub fn sys_socket(domain: i32, type_: i32, protocol: i32) -> i32 { - info!( - "[sys_socket] domain: {}, type: {}, protocol: {}", - domain, type_, protocol - ); - ax_posix_api::sys_socket(domain, type_, protocol) -} - -#[unsafe(no_mangle)] -pub fn sys_connect(socket_fd: i32, name: *const sockaddr, namelen: socklen_t) -> i32 { - info!( - "[sys_connect] socket_fd: {}, namelen: {}", - socket_fd, namelen - ); - info!("name: {:?}", unsafe { *name }); - let ret = ax_posix_api::sys_connect(socket_fd, name, namelen); - // POSIX: non-blocking connect returns EINPROGRESS, not EAGAIN. - // ArceOS's TcpSocket::connect() returns WouldBlock (→ EAGAIN) when - // nonblocking, but hermit std's connect_timeout() expects EINPROGRESS. - if ret == -ax_errno::LinuxError::EAGAIN.code() { - -ax_errno::LinuxError::EINPROGRESS.code() - } else { - ret - } -} - -#[unsafe(no_mangle)] -pub fn sys_recv(socket: i32, buf: *mut u8, len: usize, flags: i32) -> isize { - info!( - "[sys_recv] socket: {}, len: {}, flags: {}", - socket, len, flags - ); - ax_posix_api::sys_recv(socket, buf as _, len, flags) -} - -#[unsafe(no_mangle)] -pub fn sys_listen(socket_fd: i32, backlog: i32) -> i32 { - info!( - "[sys_listen] socket_fd: {}, backlog: {}", - socket_fd, backlog - ); - ax_posix_api::sys_listen(socket_fd, backlog) -} - -#[unsafe(no_mangle)] -pub fn sys_bind(socket_fd: i32, name: *const sockaddr, namelen: socklen_t) -> i32 { - info!("[sys_bind] socket_fd: {}, namelen: {}", socket_fd, namelen); - ax_posix_api::sys_bind(socket_fd, name, namelen) -} - -#[unsafe(no_mangle)] -pub fn sys_getsockname(socket_fd: i32, name: *mut sockaddr, namelen: *mut socklen_t) -> i32 { - info!( - "[sys_getsockname] socket_fd: {}, namelen: {:p}", - socket_fd, namelen - ); - unsafe { ax_posix_api::sys_getsockname(socket_fd, name, namelen) } -} - -#[unsafe(no_mangle)] -pub fn sys_accept(socket_fd: i32, addr: *mut sockaddr, addrlen: *mut socklen_t) -> i32 { - info!( - "[sys_accept] socket_fd: {}, addrlen: {:p}", - socket_fd, addrlen - ); - unsafe { ax_posix_api::sys_accept(socket_fd, addr, addrlen) } -} - -#[unsafe(no_mangle)] -pub fn sys_setsockopt( - socket_fd: i32, - level: i32, - optname: i32, - optval: *const c_void, - optlen: socklen_t, -) -> i32 { - info!( - "[setsockopt] socket_fd: {}, level: {}, optname: {}, optlen: {}", - socket_fd, level, optname, optlen - ); - unsafe { ax_posix_api::sys_setsockopt(socket_fd, level, optname, optval, optlen) } -} - -#[unsafe(no_mangle)] -pub fn sys_getpeername(socket_fd: i32, addr: *mut sockaddr, addrlen: *mut socklen_t) -> i32 { - info!( - "[sys_getpeername] socket_fd: {}, addrlen: {:p}", - socket_fd, addrlen - ); - unsafe { ax_posix_api::sys_getpeername(socket_fd, addr, addrlen) } -} - -#[unsafe(no_mangle)] -pub fn sys_shutdown(socket_fd: i32, how: i32) -> i32 { - info!("[sys_shutdown] socket_fd: {}, how: {}", socket_fd, how); - ax_posix_api::sys_shutdown(socket_fd, how) -} - -/// Set I/O control options on a file descriptor. -/// -/// Currently only supports `FIONBIO` (set non-blocking mode), which is -/// used by hermit std's `Socket::set_nonblocking()`. -/// -/// Returns 0 on success, negative errno on error. -#[unsafe(no_mangle)] -pub fn sys_ioctl(fd: i32, cmd: i32, argp: *mut c_void) -> i32 { - info!("[sys_ioctl] fd: {}, cmd: {:#x}", fd, cmd); - if cmd == FIONBIO { - if argp.is_null() { - return -ax_errno::LinuxError::EFAULT.code(); - } - let val = unsafe { *(argp as *const c_int) }; - let nonblocking = val != 0; - info!("[sys_ioctl] FIONBIO: nonblocking={}", nonblocking); - match ax_posix_api::sys_fcntl( - fd, - ax_posix_api::ctypes::F_SETFL as c_int, - if nonblocking { - ax_posix_api::ctypes::O_NONBLOCK as usize - } else { - 0 - }, - ) { - r if r >= 0 => 0, - r => r, - } - } else { - info!("[sys_ioctl] unsupported cmd: {:#x}", cmd); - -ax_errno::LinuxError::EINVAL.code() - } -} - -/// Poll file descriptors for I/O readiness. -/// -/// Hermit std's `connect_timeout()` calls `poll()` with POSIX semantics: -/// returns the number of ready fds on success, 0 on timeout, or **-1** on -/// error (with the actual error code retrievable via `sys_get_errno`). -/// -/// The underlying `ax_posix_api::sys_poll` returns negative errno on -/// error (via `syscall_body!`), so we translate here. -#[unsafe(no_mangle)] -pub fn sys_poll(fds: *mut pollfd, nfds: nfds_t, timeout: i32) -> i32 { - info!( - "[sys_poll] fds: {:p}, nfds: {}, timeout: {}ms", - fds, nfds, timeout - ); - let ret = ax_posix_api::sys_poll(fds, nfds, timeout); - if ret < 0 { - // Convert from -errno to POSIX -1 + set errno - set_errno(-ret); - -1 - } else { - ret - } -} - -/// Get socket options. -/// -/// Used by hermit std for `timeout()` (SO_RCVTIMEO/SO_SNDTIMEO), -/// `take_error()` (SO_ERROR), `nodelay()` (TCP_NODELAY), and -/// `linger()` (SO_LINGER). -/// -/// Returns 0 on success, negative errno on error. -#[unsafe(no_mangle)] -pub fn sys_getsockopt( - socket_fd: i32, - level: i32, - optname: i32, - optval: *mut c_void, - optlen: *mut socklen_t, -) -> i32 { - info!( - "[sys_getsockopt] socket_fd: {}, level: {}, optname: {:#x}", - socket_fd, level, optname - ); - - if optval.is_null() || optlen.is_null() { - return -ax_errno::LinuxError::EFAULT.code(); - } - - // For most options we return sensible defaults since ArceOS's - // smoltcp-based network stack doesn't track all of these. - // - // Note: some hermit ABI constants (IPPROTO_TCP etc.) are u32 while - // the function parameters are i32, so we use if-else for clarity. - if level == SOL_SOCKET && optname == SO_ERROR { - // Return 0 (no pending error). - // Used by connect_timeout to check connection status. - let val: c_int = 0; - write_sockopt(optval, optlen, &val) - } else if level == SOL_SOCKET && (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) { - // Return zero timeval = no timeout set. - let tv = timeval { - tv_sec: 0, - tv_usec: 0, - }; - write_sockopt(optval, optlen, &tv) - } else if level == SOL_SOCKET && optname == SO_LINGER { - let val = ax_posix_api::ctypes::linger { - l_onoff: 0, - l_linger: 0, - }; - write_sockopt(optval, optlen, &val) - } else if level == SOL_SOCKET && (optname == SO_REUSEADDR || optname == SO_KEEPALIVE) { - let val: c_int = 0; - write_sockopt(optval, optlen, &val) - } else if level == SOL_SOCKET && (optname == SO_SNDBUF || optname == SO_RCVBUF) { - // Return a reasonable default buffer size - let val: c_int = 64 * 1024; // 64KB - write_sockopt(optval, optlen, &val) - } else if level == IPPROTO_TCP as i32 && optname == TCP_NODELAY { - // TCP_NODELAY: Nagle's algorithm. Default off (0). - let val: c_int = 0; - write_sockopt(optval, optlen, &val) - } else { - info!( - "[sys_getsockopt] unsupported: level={}, optname={:#x}", - level, optname - ); - -ax_errno::LinuxError::ENOPROTOOPT.code() - } -} - -/// Helper: write a value into the getsockopt output buffer. -fn write_sockopt(optval: *mut c_void, optlen: *mut socklen_t, val: &T) -> i32 { - let len = size_of::(); - unsafe { - if (*optlen as usize) < len { - return -ax_errno::LinuxError::EINVAL.code(); - } - core::ptr::copy_nonoverlapping(val as *const T as *const u8, optval as *mut u8, len); - *optlen = len as socklen_t; - } - 0 -} diff --git a/os/arceos/ulib/arceos-rust/lib/src/interface/sync.rs b/os/arceos/ulib/arceos-rust/lib/src/interface/sync.rs deleted file mode 100644 index e507389c75..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/interface/sync.rs +++ /dev/null @@ -1,106 +0,0 @@ -use alloc::{collections::BTreeMap, sync::Arc}; -use core::time::Duration; - -use ax_api::modules::{ax_hal::time::monotonic_time, ax_sync::Mutex, ax_task::WaitQueue}; -use ax_errno::LinuxError; -use ax_posix_api::ctypes::{FUTEX_RELATIVE_TIMEOUT, timespec}; -use log::{info, trace}; - -use crate::err; - -static FUTEX_TABLE: Mutex>> = Mutex::new(BTreeMap::new()); - -/// If the value at address matches the expected value, park the current thread until it is either -/// woken up with [`futex_wake`] (returns 0) or an optional timeout elapses (returns -ETIMEDOUT). -/// -/// Setting `timeout` to null means the function will only return if [`futex_wake`] is called. -/// Otherwise, `timeout` is interpreted as an absolute time measured with [`CLOCK_MONOTONIC`]. -/// If [`FUTEX_RELATIVE_TIMEOUT`] is set in `flags` the timeout is understood to be relative -/// to the current time. -/// -/// Returns -EINVAL if `address` is null, the timeout is negative or `flags` contains unknown values. -#[unsafe(no_mangle)] -pub fn sys_futex_wait( - address: *mut u32, - expected: u32, - timeout: *const timespec, - flags: u32, -) -> i32 { - if flags & !FUTEX_RELATIVE_TIMEOUT != 0 { - // other flags are not supported - return err(LinuxError::EINVAL); - } - let Some(value) = (unsafe { address.as_ref() }) else { - return err(LinuxError::EINVAL); - }; - if *value != expected { - return err(LinuxError::EAGAIN); - } - let wait_queue = { - let mut table = FUTEX_TABLE.lock(); - table - .entry(address as usize) - .or_insert_with(|| Arc::new(WaitQueue::new())) - .clone() - }; - trace!( - "futex wait on address {:p} with expected value {}", - address, expected - ); - if let Some(timeout) = unsafe { timeout.as_ref() } { - trace!("called sys_futex_wait with timeout: {:?}", timeout); - let timeout = Duration::new( - timeout.tv_sec.saturating_cast_unsigned(), - timeout.tv_nsec.saturating_cast_unsigned(), - ); - let duration = if flags & FUTEX_RELATIVE_TIMEOUT != 0 { - timeout - } else { - let now = monotonic_time(); - let Some(duration) = timeout.checked_sub(now) else { - return err(LinuxError::ETIMEDOUT); - }; - duration - }; - // relative timeout - if wait_queue.wait_timeout(duration) { - // timeout - return err(LinuxError::ETIMEDOUT); - } - } else { - trace!("called sys_futex_wait without timeout"); - wait_queue.wait(); - } - 0 -} - -/// Wake `count` threads waiting on the futex at `address`. Returns the number of threads -/// woken up (saturates to `i32::MAX`). If `count` is `i32::MAX`, wake up all matching -/// waiting threads. If `count` is negative or `address` is null, returns -EINVAL. -#[unsafe(no_mangle)] -pub fn sys_futex_wake(address: *mut u32, count: i32) -> i32 { - info!( - "called sys_futex_wake with address {:p} and count {}", - address, count - ); - if count < 0 { - return err(LinuxError::EINVAL); - } - let wait_queue = { - let table = FUTEX_TABLE.lock(); - match table.get(&(address as usize)) { - Some(queue) => queue.clone(), - None => return 0, - } - }; - let mut woken_count = 0; - for _ in 0..count { - if wait_queue.notify_one(true) { - woken_count += 1; - } else { - break; - } - } - trace!("futex woke {} threads", woken_count); - woken_count as _ -} diff --git a/os/arceos/ulib/arceos-rust/lib/src/interface/task.rs b/os/arceos/ulib/arceos-rust/lib/src/interface/task.rs deleted file mode 100644 index 857d5858bc..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/interface/task.rs +++ /dev/null @@ -1,21 +0,0 @@ -use ax_api::{sys::ax_terminate, task::ax_exit}; -use log::info; - -#[cfg(feature = "multitask")] -use crate::interface::thread::take_task; - -#[unsafe(no_mangle)] -pub fn sys_abort() -> ! { - info!("called sys_abort"); - #[cfg(feature = "multitask")] - take_task(ax_api::task::ax_current_task_id()); - ax_terminate() -} - -#[unsafe(no_mangle)] -pub fn sys_exit(code: i32) -> ! { - info!("called sys_exit with code {}", code); - #[cfg(feature = "multitask")] - take_task(ax_api::task::ax_current_task_id()); - ax_exit(code) -} diff --git a/os/arceos/ulib/arceos-rust/lib/src/interface/thread.rs b/os/arceos/ulib/arceos-rust/lib/src/interface/thread.rs deleted file mode 100644 index f3d42995c7..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/interface/thread.rs +++ /dev/null @@ -1,85 +0,0 @@ -use alloc::{collections::BTreeMap, format}; -use core::sync::atomic::{AtomicU64, Ordering}; - -use ax_api::{ - modules::ax_sync::Mutex, - task::{AxTaskHandle, ax_spawn, ax_wait_for_exit, ax_yield_now}, -}; -use ax_posix_api::ctypes::Tid; -use log::{info, warn}; - -const DEFAULT_STACK_SIZE: usize = ax_api::config::TASK_STACK_SIZE; - -static NEXT_TASK_NAME: AtomicU64 = AtomicU64::new(1); -static TASK_TABLE: Mutex> = Mutex::new(BTreeMap::new()); - -pub fn insert_task(handle: AxTaskHandle) -> u64 { - let tid = handle.id(); - TASK_TABLE.lock().insert(tid, handle); - tid -} - -pub fn take_task(tid: u64) -> Option { - TASK_TABLE.lock().remove(&tid) -} - -/// spawn a new thread with user-specified stack size -/// -/// spawn2() starts a new thread. The new thread starts execution -/// by invoking `func(usize)`; `arg` is passed as the argument -/// to `func`. `prio` defines the priority of the new thread, -/// which can be between `LOW_PRIO` and `HIGH_PRIO`. -/// `core_id` defines the core, where the thread is located. -/// A negative value give the operating system the possibility -/// to select the core by its own. -/// In contrast to spawn(), spawn2() is able to define the -/// stack size. -#[unsafe(no_mangle)] -pub fn sys_spawn2( - func: extern "C" fn(usize), - arg: usize, - _prio: u8, - stack_size: usize, - _core_id: isize, -) -> Tid { - let actual_stack = if stack_size == 0 { - DEFAULT_STACK_SIZE - } else { - stack_size - }; - let name = format!( - "hermit-thread-{}", - NEXT_TASK_NAME.fetch_add(1, Ordering::Relaxed) - ); - let handle = ax_spawn( - move || { - func(arg); - }, - name, - actual_stack, - ); - info!( - "called sys_spawn2: func={:?}, arg={}, stack_size={}", - func as *const (), arg, actual_stack - ); - info!("created new thread with tid {}", handle.id()); - insert_task(handle) as Tid -} - -/// Wait for a thread to finish. -#[unsafe(no_mangle)] -pub fn sys_join(tid: Tid) -> i32 { - match take_task(tid as u64) { - Some(handle) => ax_wait_for_exit(handle), - None => { - warn!("[sys_join] Unknown tid {}", tid); - -1 - } - } -} - -/// Yield the current thread. -#[unsafe(no_mangle)] -pub fn sys_yield() { - ax_yield_now(); -} diff --git a/os/arceos/ulib/arceos-rust/lib/src/interface/util.rs b/os/arceos/ulib/arceos-rust/lib/src/interface/util.rs deleted file mode 100644 index 940d43ec75..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/interface/util.rs +++ /dev/null @@ -1,63 +0,0 @@ -use core::time::Duration; - -use ax_api::modules::ax_hal::time::wall_time_nanos; -use ax_posix_api::ctypes::{clockid_t, timespec}; -use log::info; -use rand::{RngCore, SeedableRng, prelude::SmallRng}; - -/// Fill `len` bytes in `buf` with cryptographically secure random data. -/// -/// Returns either the number of bytes written to buf (a positive value) or -/// * `-EINVAL` if `flags` contains unknown flags. -/// * `-ENOSYS` if the system does not support random data generation. -#[unsafe(no_mangle)] -pub fn sys_read_entropy(buf: *mut u8, len: usize, _flags: u32) -> isize { - // flags are currently ignored - info!("called sys_read_entropy"); - let buffer = unsafe { core::slice::from_raw_parts_mut(buf, len) }; - let mut rng = SmallRng::seed_from_u64(wall_time_nanos()); - rng.fill_bytes(buffer); - len as isize -} - -#[unsafe(no_mangle)] -pub fn sys_clock_gettime(clockid: clockid_t, tp: *mut timespec) -> i32 { - info!( - "called sys_clock_gettime with clockid {}, tp {:p}", - clockid, tp - ); - unsafe { ax_posix_api::sys_clock_gettime(clockid, tp) } -} - -/// suspend execution for microsecond intervals -/// -/// The usleep() function suspends execution of the calling -/// thread for (at least) `usec` microseconds. -#[unsafe(no_mangle)] -pub fn sys_usleep(usec: u64) { - info!("called sys_usleep with {} usec", usec); - let duration = Duration::from_micros(usec); - #[cfg(feature = "multitask")] - ax_api::modules::ax_task::sleep(duration); - #[cfg(not(feature = "multitask"))] - ax_api::modules::ax_hal::time::busy_wait(duration); -} - -/// dummy implementation of futex wait -#[cfg(not(feature = "multitask"))] -#[unsafe(no_mangle)] -pub fn sys_futex_wait( - _address: *mut u32, - _expected: u32, - _timeout: *const timespec, - _flags: u32, -) -> i32 { - 0 -} - -/// dummy implementation of futex wake -#[cfg(not(feature = "multitask"))] -#[unsafe(no_mangle)] -pub fn sys_futex_wake(_address: *mut u32, _count: i32) -> i32 { - 0 -} diff --git a/os/arceos/ulib/arceos-rust/lib/src/lib.rs b/os/arceos/ulib/arceos-rust/lib/src/lib.rs deleted file mode 100644 index 17e00f28e5..0000000000 --- a/os/arceos/ulib/arceos-rust/lib/src/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![no_std] -#![allow(unused_features)] -#![feature(extern_item_impls)] -#![feature(integer_cast_extras)] - -extern crate alloc; -extern crate ax_driver as _; - -mod interface; - -use ax_api::modules::ax_log::{debug, info}; -use ax_runtime::ax_app_entry; - -unsafe extern "C" { - fn runtime_entry(argc: i32, argv: *const *const u8, env: *const *const u8) -> !; -} - -#[cfg(any(feature = "fs", feature = "multitask"))] -pub(crate) fn err(error: ax_errno::LinuxError) -> i32 { - -(error as i32) -} - -#[ax_app_entry] -pub fn app_entry() { - info!("Starting application..."); - // call the runtime entry point with zeroed arguments - const ARGC: i32 = 1; - const NAME: &[u8; 9] = b"app_name\0"; - let argv: [*const u8; 2] = [NAME.as_ptr(), core::ptr::null()]; - let env: [*const u8; 1] = [core::ptr::null()]; - debug!("address of runtime_entry: {:p}", runtime_entry as *const ()); - unsafe { - runtime_entry(ARGC, argv.as_ptr(), env.as_ptr()); - } -} diff --git a/os/arceos/ulib/arceos-rust/lib/src/syscall.rs b/os/arceos/ulib/arceos-rust/lib/src/syscall.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/os/arceos/ulib/arceos-rust/src/lib.rs b/os/arceos/ulib/arceos-rust/src/lib.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/os/arceos/ulib/arceos-rust/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/os/arceos/ulib/axlibc/c/pthread.c b/os/arceos/ulib/axlibc/c/pthread.c index 74fb1faf12..14fd4f7709 100644 --- a/os/arceos/ulib/axlibc/c/pthread.c +++ b/os/arceos/ulib/axlibc/c/pthread.c @@ -32,13 +32,6 @@ int pthread_cancel(pthread_t t) return 0; } -// TODO -int pthread_mutex_trylock(pthread_mutex_t *m) -{ - unimplemented(); - return 0; -} - // TODO int pthread_setname_np(pthread_t thread, const char *name) { diff --git a/os/arceos/ulib/axlibc/src/pthread.rs b/os/arceos/ulib/axlibc/src/pthread.rs index 6f6dcae58c..a4b238af19 100644 --- a/os/arceos/ulib/axlibc/src/pthread.rs +++ b/os/arceos/ulib/axlibc/src/pthread.rs @@ -54,8 +54,20 @@ pub unsafe extern "C" fn pthread_mutex_lock(mutex: *mut ctypes::pthread_mutex_t) e(api::sys_pthread_mutex_lock(mutex)) } +/// Try locking the given mutex. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_trylock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + e(api::sys_pthread_mutex_trylock(mutex)) +} + /// Unlock the given mutex. #[unsafe(no_mangle)] pub unsafe extern "C" fn pthread_mutex_unlock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { e(api::sys_pthread_mutex_unlock(mutex)) } + +/// Destroy the given mutex. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pthread_mutex_destroy(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + e(api::sys_pthread_mutex_destroy(mutex)) +} diff --git a/os/arceos/ulib/axstd/Cargo.toml b/os/arceos/ulib/axstd/Cargo.toml index 3db0ee9d51..041864b5be 100644 --- a/os/arceos/ulib/axstd/Cargo.toml +++ b/os/arceos/ulib/axstd/Cargo.toml @@ -14,11 +14,19 @@ authors = [ description = "ArceOS user library with an interface similar to rust std" license.workspace = true +[lib] +name = "ax_std" +crate-type = ["rlib", "staticlib"] + [features] -default = [] +default = ["alloc", "tls", "fd"] + +# Compatibility alias kept for existing manifests. The libc compatibility +# surface is now compiled by default when ax-std is linked. +std-compat = [] # Multicore -smp = ["ax-feat/smp", "ax-kspin/smp"] +smp = ["ax-feat/smp", "ax-kspin/smp", "ax-posix-api/smp"] # Floating point/SIMD fp-simd = ["ax-feat/fp-simd"] @@ -30,24 +38,27 @@ uspace = ["ax-feat/uspace"] hv = ["ax-feat/hv"] # Interrupts -irq = ["ax-api/irq", "ax-feat/irq"] +irq = ["ax-api/irq", "ax-feat/irq", "ax-posix-api/irq"] ipi = ["ax-api/ipi", "ax-feat/ipi"] # Custom or default platforms ext-ld = ["ax-feat/ext-ld"] -myplat = ["ax-feat/myplat"] -defplat = ["ax-feat/defplat"] -plat-dyn = ["ax-feat/plat-dyn"] +myplat = ["alloc", "tls", "fd", "ax-feat/myplat"] +defplat = ["alloc", "tls", "fd", "ax-feat/defplat"] +plat-dyn = ["alloc", "tls", "fd", "ax-feat/plat-dyn"] +x86-pc = ["alloc", "tls", "fd", "ax-hal/x86-pc"] +aarch64-qemu-virt = ["alloc", "tls", "fd"] +loongarch64-qemu-virt = ["alloc", "tls", "fd", "ax-hal/loongarch64-qemu-virt"] # Memory -alloc = ["ax-api/alloc", "ax-feat/alloc", "ax-io/alloc"] +alloc = ["ax-api/alloc", "ax-feat/alloc", "ax-io/alloc", "ax-posix-api/alloc"] paging = ["ax-feat/paging", "alloc"] dma = ["ax-api/dma", "ax-feat/dma"] tls = ["ax-feat/tls"] # Multi-threading and scheduler -multitask = ["ax-api/multitask", "ax-feat/multitask"] -lockdep = ["ax-feat/lockdep"] +multitask = ["ax-api/multitask", "ax-feat/multitask", "ax-posix-api/multitask"] +lockdep = ["ax-feat/lockdep", "ax-posix-api/lockdep"] task-ext = ["ax-feat/task-ext"] sched-fifo = ["ax-feat/sched-fifo"] sched-rr = ["ax-feat/sched-rr"] @@ -55,14 +66,38 @@ 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 = ["ax-api/fs", "ax-feat/fs"] -fatfs = ["ax-api/fs", "ax-feat/fs"] +fs = [ + "ax-api/fs", + "ax-feat/fs", + "ax-posix-api/fs", + "ax-driver/plat-static", + "ax-driver/virtio-blk", + "fd", +] +ext4fs = ["fs"] +fatfs = ["fs"] # Networking -net = ["ax-api/net", "ax-feat/net"] +net = [ + "ax-api/net", + "ax-feat/net", + "ax-posix-api/net", + "ax-driver/plat-static", + "ax-driver/virtio-net", + "fd", +] dns = [] +# libc compatibility surface used when ax-std is linked as fake libc for Rust std. +fd = ["ax-posix-api/fd", "ax-posix-api/poll"] + +# Driver feature forwards used by std-aware apps that depend on `axstd` directly. +virtio-blk = ["ax-driver/virtio-blk"] +virtio-net = ["ax-driver/virtio-net"] +virtio-gpu = ["ax-driver/virtio-gpu"] +virtio-input = ["ax-driver/virtio-input"] +virtio-socket = ["ax-driver/virtio-socket"] + # Display display = ["ax-api/display", "ax-feat/display"] @@ -78,10 +113,19 @@ dwarf = ["ax-feat/dwarf"] [dependencies] ax-api.workspace = true +ax-alloc.workspace = true +ax-driver.workspace = true ax-errno.workspace = true ax-feat.workspace = true +ax-hal.workspace = true ax-io.workspace = true ax-kspin.workspace = true ax-lazyinit.workspace = true +ax-posix-api.workspace = true +ax-runtime.workspace = true +libc.workspace = true lock_api.workspace = true spin.workspace = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/os/arceos/ulib/axstd/src/lib.rs b/os/arceos/ulib/axstd/src/lib.rs index 40830faf1e..e3fc92a4b3 100644 --- a/os/arceos/ulib/axstd/src/lib.rs +++ b/os/arceos/ulib/axstd/src/lib.rs @@ -37,8 +37,8 @@ #![cfg_attr(all(not(test), not(doc)), no_std)] #![cfg_attr(doc, feature(doc_cfg))] -#[cfg(feature = "alloc")] extern crate alloc; +extern crate ax_driver as _; #[cfg(feature = "alloc")] #[doc(no_inline)] diff --git a/os/arceos/ulib/axstd/src/os.rs b/os/arceos/ulib/axstd/src/os.rs index d954b42556..a61f0f6508 100644 --- a/os/arceos/ulib/axstd/src/os.rs +++ b/os/arceos/ulib/axstd/src/os.rs @@ -15,3 +15,6 @@ pub mod arceos { #[doc(no_inline)] pub use ax_api::modules; } + +#[cfg(arceos_std)] +pub mod libc_compat; diff --git a/os/arceos/ulib/axstd/src/os/libc_compat.rs b/os/arceos/ulib/axstd/src/os/libc_compat.rs new file mode 100644 index 0000000000..3a82ef0bf6 --- /dev/null +++ b/os/arceos/ulib/axstd/src/os/libc_compat.rs @@ -0,0 +1,3245 @@ +#[cfg(feature = "multitask")] +use alloc::boxed::Box; +#[cfg(feature = "fs")] +use alloc::string::{String, ToString}; +#[cfg(feature = "multitask")] +use alloc::sync::Arc; +use alloc::{collections::BTreeMap, vec::Vec}; +use core::{ + alloc::Layout, + ffi::{c_char, c_int, c_long, c_uint, c_void}, + mem::{align_of, size_of}, + ptr::{self, NonNull}, + sync::atomic::{AtomicBool, AtomicU64, Ordering}, +}; +#[cfg(feature = "multitask")] +use core::{sync::atomic::AtomicUsize, time::Duration}; + +use ax_errno::LinuxError; +use spin::{LazyLock, Mutex}; + +type SizeT = libc::size_t; +type SSizeT = libc::ssize_t; +type OffT = libc::off_t; +#[cfg(feature = "fs")] +type ModeT = libc::mode_t; +#[cfg(feature = "fs")] +type AxStat = ax_posix_api::ctypes::stat; + +const MALLOC_ALIGN: usize = size_of::() * 2; +const CTRL_BLK_ALIGN: usize = align_of::(); +const FUTEX_WAIT: c_int = 0; +const FUTEX_WAKE: c_int = 1; +const FUTEX_WAIT_BITSET: c_int = 9; +const FUTEX_WAKE_BITSET: c_int = 10; +const FUTEX_CMD_MASK: c_int = !(libc::FUTEX_PRIVATE_FLAG | libc::FUTEX_CLOCK_REALTIME); +#[cfg(feature = "multitask")] +const FUTEX_BITSET_MATCH_ANY: u32 = u32::MAX; +#[cfg(feature = "multitask")] +const MAX_PTHREAD_KEYS: usize = 1024; +#[cfg(feature = "multitask")] +const PTHREAD_DESTRUCTOR_ITERATIONS: usize = 4; +#[cfg(feature = "multitask")] +const PTHREAD_COND_SIZE: usize = 48; +#[cfg(feature = "multitask")] +const PTHREAD_ATTR_SIZE: usize = 56; +#[cfg(feature = "multitask")] +const PTHREAD_ATTR_STACK_SIZE_OFFSET: usize = 8; +#[cfg(feature = "multitask")] +const DEFAULT_STACK_SIZE: usize = 2 * 1024 * 1024; +#[cfg(feature = "fs")] +const LINUX_DIRENT64_NAME_OFFSET: usize = 19; +#[cfg(feature = "fs")] +const LIBC_DIRENT64_NAME_OFFSET: usize = 19; +#[cfg(feature = "fs")] +const LIBC_DIRENT64_SIZE: usize = 280; + +static FD_LAYER_READY: AtomicBool = AtomicBool::new(false); + +#[derive(Clone, Copy)] +struct CxaThreadDtor { + dtor: unsafe extern "C" fn(*mut c_void), + arg: *mut c_void, +} + +unsafe impl Send for CxaThreadDtor {} + +#[repr(C)] +#[derive(Clone, Copy)] +struct MemoryControlBlock { + size: usize, + allocation_size: usize, + allocation_align: usize, + base_offset: usize, +} + +#[cfg(feature = "fs")] +struct DirStream { + fd: c_int, + entries: Vec, + next: usize, + current: [u8; LIBC_DIRENT64_SIZE], +} + +#[cfg(feature = "fs")] +struct DirEntryBuf { + name: Vec, + d_type: u8, +} + +#[cfg(feature = "fs")] +struct FdPath { + path: String, + is_dir: bool, +} + +#[unsafe(no_mangle)] +#[allow(non_upper_case_globals)] +pub static mut errno: c_int = 0; + +fn set_errno(code: i32) { + unsafe { + errno = code; + } +} + +fn ok_or_errno(ret: c_int) -> c_int { + if ret < 0 { + set_errno(ret.wrapping_neg()); + -1 + } else { + ret + } +} + +fn ok_or_errno_isize(ret: isize) -> isize { + if ret < 0 { + set_errno(ret.wrapping_neg() as i32); + -1 + } else { + ret + } +} + +#[cfg(feature = "fs")] +fn ax_stat_to_libc_stat(src: AxStat) -> libc::stat { + let mut dst = unsafe { core::mem::zeroed::() }; + dst.st_dev = src.st_dev as _; + dst.st_ino = src.st_ino as _; + dst.st_mode = src.st_mode as _; + dst.st_nlink = src.st_nlink as _; + dst.st_uid = src.st_uid as _; + dst.st_gid = src.st_gid as _; + dst.st_rdev = src.st_rdev as _; + dst.st_size = src.st_size as _; + dst.st_blksize = src.st_blksize as _; + dst.st_blocks = src.st_blocks as _; + dst.st_atime = src.st_atime.tv_sec as _; + dst.st_atime_nsec = src.st_atime.tv_nsec as _; + dst.st_mtime = src.st_mtime.tv_sec as _; + dst.st_mtime_nsec = src.st_mtime.tv_nsec as _; + dst.st_ctime = src.st_ctime.tv_sec as _; + dst.st_ctime_nsec = src.st_ctime.tv_nsec as _; + dst +} + +#[cfg(feature = "fs")] +fn write_libc_stat(buf: *mut libc::stat, stat: AxStat) -> c_int { + if buf.is_null() { + set_errno(LinuxError::EFAULT.code()); + -1 + } else { + unsafe { buf.write(ax_stat_to_libc_stat(stat)) }; + 0 + } +} + +#[cfg(feature = "fs")] +fn stat_ret_to_libc(ret: c_int, stat: AxStat, buf: *mut libc::stat) -> c_int { + if ret < 0 { + ok_or_errno(ret) + } else { + write_libc_stat(buf, stat) + } +} + +fn fail(err: LinuxError) -> c_int { + set_errno(err as i32); + -1 +} + +fn is_stdio_fd(fd: c_int) -> bool { + matches!(fd, libc::STDOUT_FILENO | libc::STDERR_FILENO) +} + +fn early_stdio_write(fd: c_int, buf: *const c_void, count: SizeT) -> Option { + if !is_stdio_fd(fd) || FD_LAYER_READY.load(Ordering::Acquire) { + return None; + } + if count == 0 { + return Some(0); + } + if buf.is_null() && count != 0 { + set_errno(LinuxError::EFAULT as i32); + return Some(-1); + } + if count > isize::MAX as usize { + set_errno(LinuxError::EINVAL as i32); + return Some(-1); + } + + let bytes = unsafe { core::slice::from_raw_parts(buf.cast::(), count) }; + ax_hal::console::write_text_bytes(bytes); + Some(count as SSizeT) +} + +fn early_stdio_writev(fd: c_int, iov: *const libc::iovec, iocnt: c_int) -> Option { + if !is_stdio_fd(fd) || FD_LAYER_READY.load(Ordering::Acquire) { + return None; + } + if !(0..=1024).contains(&iocnt) { + set_errno(LinuxError::EINVAL as i32); + return Some(-1); + } + if iocnt == 0 { + return Some(0); + } + if iov.is_null() && iocnt != 0 { + set_errno(LinuxError::EFAULT as i32); + return Some(-1); + } + + let iovs = unsafe { core::slice::from_raw_parts(iov, iocnt as usize) }; + let mut written = 0usize; + for iov in iovs { + if iov.iov_len == 0 { + continue; + } + if iov.iov_base.is_null() { + set_errno(LinuxError::EFAULT as i32); + return Some(-1); + } + let Some(next) = written.checked_add(iov.iov_len) else { + set_errno(LinuxError::EINVAL as i32); + return Some(-1); + }; + if next > isize::MAX as usize { + set_errno(LinuxError::EINVAL as i32); + return Some(-1); + } + + let bytes = unsafe { core::slice::from_raw_parts(iov.iov_base.cast::(), iov.iov_len) }; + ax_hal::console::write_text_bytes(bytes); + written = next; + } + Some(written as SSizeT) +} + +fn align_up_checked(addr: usize, align: usize) -> Option { + debug_assert!(align.is_power_of_two()); + addr.checked_add(align - 1) + .map(|value| value & !(align - 1)) +} + +fn alloc_with_alignment(size: SizeT, alignment: usize) -> Result<*mut c_void, LinuxError> { + if !alignment.is_power_of_two() { + return Err(LinuxError::EINVAL); + } + + let size = size.max(1); + let alignment = alignment.max(MALLOC_ALIGN); + let Some(extra) = size_of::().checked_add(alignment - 1) else { + return Err(LinuxError::ENOMEM); + }; + let Some(total) = size.checked_add(extra) else { + return Err(LinuxError::ENOMEM); + }; + let layout = Layout::from_size_align(total, CTRL_BLK_ALIGN).map_err(|_| LinuxError::EINVAL)?; + let ptr = ax_alloc::global_allocator() + .alloc(layout) + .map_err(|_| LinuxError::ENOMEM)?; + + let base_addr = ptr.as_ptr() as usize; + let Some(data_start) = base_addr.checked_add(size_of::()) else { + ax_alloc::global_allocator().dealloc(ptr, layout); + return Err(LinuxError::ENOMEM); + }; + let Some(user_addr) = align_up_checked(data_start, alignment) else { + ax_alloc::global_allocator().dealloc(ptr, layout); + return Err(LinuxError::ENOMEM); + }; + let header_addr = user_addr - size_of::(); + unsafe { + (header_addr as *mut MemoryControlBlock).write(MemoryControlBlock { + size, + allocation_size: total, + allocation_align: CTRL_BLK_ALIGN, + base_offset: user_addr - base_addr, + }); + } + + Ok(user_addr as *mut c_void) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __errno_location() -> *mut c_int { + &raw mut errno +} + +/// # Safety +/// +/// Callers must pass a valid destructor function and argument according to the +/// Itanium C++ ABI used by Linux/musl. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __cxa_thread_atexit_impl( + dtor: unsafe extern "C" fn(*mut c_void), + arg: *mut c_void, + _dso: *mut c_void, +) -> c_int { + push_cxa_thread_dtor(CxaThreadDtor { dtor, arg }); + 0 +} + +#[cfg(not(feature = "multitask"))] +static CXA_THREAD_DTORS: Mutex> = Mutex::new(Vec::new()); + +#[cfg(not(feature = "multitask"))] +fn push_cxa_thread_dtor(record: CxaThreadDtor) { + CXA_THREAD_DTORS.lock().push(record); +} + +#[cfg(feature = "multitask")] +fn push_cxa_thread_dtor(record: CxaThreadDtor) { + pthread::push_cxa_thread_dtor(record); +} + +#[cfg(not(feature = "multitask"))] +fn run_cxa_thread_dtors() { + while let Some(record) = { CXA_THREAD_DTORS.lock().pop() } { + unsafe { (record.dtor)(record.arg) }; + } +} + +#[cfg(feature = "multitask")] +fn run_cxa_thread_dtors() { + pthread::run_cxa_thread_dtors(); +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strerror(e: c_int) -> *mut c_char { + #[allow(non_upper_case_globals)] + static mut STRERROR_BUF: [u8; 256] = [0; 256]; + + let err_str = if e == 0 { + "Success" + } else { + LinuxError::try_from(e) + .map(|err| err.as_str()) + .unwrap_or("Unknown error") + }; + unsafe { + let buf = &raw mut STRERROR_BUF; + (*buf).fill(0); + let bytes = err_str.as_bytes(); + let len = bytes.len().min((*buf).len() - 1); + (&mut (*buf))[..len].copy_from_slice(&bytes[..len]); + buf.cast::() + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn __xpg_strerror_r(errnum: c_int, buf: *mut c_char, buflen: SizeT) -> c_int { + if buf.is_null() || buflen == 0 { + return LinuxError::ERANGE as c_int; + } + let message = unsafe { strerror(errnum) }; + let mut len = unsafe { strlen(message) }; + if len >= buflen { + len = buflen - 1; + } + unsafe { + ptr::copy_nonoverlapping(message, buf, len); + *buf.add(len) = 0; + } + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getenv(_name: *const c_char) -> *mut c_char { + ptr::null_mut() +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn confstr(_name: c_int, _buf: *mut c_char, _len: SizeT) -> SizeT { + set_errno(LinuxError::EINVAL as i32); + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn malloc(size: SizeT) -> *mut c_void { + match alloc_with_alignment(size, MALLOC_ALIGN) { + Ok(ptr) => ptr, + Err(err) => { + set_errno(err as i32); + ptr::null_mut() + } + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn posix_memalign( + memptr: *mut *mut c_void, + alignment: SizeT, + size: SizeT, +) -> c_int { + if memptr.is_null() || !alignment.is_power_of_two() || alignment < size_of::<*mut c_void>() { + return LinuxError::EINVAL as c_int; + } + match alloc_with_alignment(size, alignment) { + Ok(ptr) => { + unsafe { + *memptr = ptr; + } + 0 + } + Err(err) => err as c_int, + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn calloc(nmemb: SizeT, size: SizeT) -> *mut c_void { + let Some(total) = nmemb.checked_mul(size) else { + set_errno(LinuxError::ENOMEM as i32); + return ptr::null_mut(); + }; + let ptr = unsafe { malloc(total) }; + if !ptr.is_null() { + unsafe { ptr::write_bytes(ptr, 0, total) }; + } + ptr +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn realloc(ptr: *mut c_void, size: SizeT) -> *mut c_void { + if ptr.is_null() { + return unsafe { malloc(size) }; + } + if size == 0 { + unsafe { free(ptr) }; + return ptr::null_mut(); + } + + let old_block = unsafe { ptr.cast::().sub(1) }; + let old_size = unsafe { (*old_block).size }; + let new_ptr = unsafe { malloc(size) }; + if !new_ptr.is_null() { + unsafe { + ptr::copy_nonoverlapping(ptr.cast::(), new_ptr.cast::(), old_size.min(size)); + free(ptr); + } + } + new_ptr +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn free(ptr: *mut c_void) { + if ptr.is_null() { + return; + } + let block = unsafe { ptr.cast::().sub(1) }; + let metadata = unsafe { *block }; + let base_addr = (ptr as usize).saturating_sub(metadata.base_offset); + let layout = + Layout::from_size_align(metadata.allocation_size, metadata.allocation_align).unwrap(); + let block = base_addr as *mut u8; + if let Some(block) = NonNull::new(block.cast::()) { + ax_alloc::global_allocator().dealloc(block, layout); + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memcpy(dst: *mut c_void, src: *const c_void, n: SizeT) -> *mut c_void { + let dst_u8 = dst.cast::(); + let src_u8 = src.cast::(); + for i in 0..n { + unsafe { *dst_u8.add(i) = *src_u8.add(i) }; + } + dst +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memmove(dst: *mut c_void, src: *const c_void, n: SizeT) -> *mut c_void { + let dst_u8 = dst.cast::(); + let src_u8 = src.cast::(); + let dst_addr = dst_u8 as usize; + let src_addr = src_u8 as usize; + if dst_addr <= src_addr || dst_addr >= src_addr.saturating_add(n) { + for i in 0..n { + unsafe { *dst_u8.add(i) = *src_u8.add(i) }; + } + } else { + for i in (0..n).rev() { + unsafe { *dst_u8.add(i) = *src_u8.add(i) }; + } + } + dst +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memset(dst: *mut c_void, value: c_int, n: SizeT) -> *mut c_void { + let dst_u8 = dst.cast::(); + for i in 0..n { + unsafe { *dst_u8.add(i) = value as u8 }; + } + dst +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn memcmp(left: *const c_void, right: *const c_void, n: SizeT) -> c_int { + let left = left.cast::(); + let right = right.cast::(); + for i in 0..n { + let lhs = unsafe { *left.add(i) }; + let rhs = unsafe { *right.add(i) }; + if lhs != rhs { + return lhs as c_int - rhs as c_int; + } + } + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn bcmp(left: *const c_void, right: *const c_void, n: SizeT) -> c_int { + unsafe { memcmp(left, right, n) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn strlen(s: *const c_char) -> SizeT { + if s.is_null() { + return 0; + } + let mut len = 0; + while unsafe { *s.add(len) } != 0 { + len += 1; + } + len +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn abort() -> ! { + ax_api::sys::ax_terminate(); +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn exit(exit_code: c_int) -> ! { + run_cxa_thread_dtors(); + ax_posix_api::sys_exit(exit_code) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _exit(exit_code: c_int) -> ! { + ax_posix_api::sys_exit(exit_code) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getpid() -> c_int { + ok_or_errno(ax_posix_api::sys_getpid()) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sched_yield() -> c_int { + ax_posix_api::sys_sched_yield() +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn pause() -> c_int { + fail(LinuxError::EINTR) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn gettid() -> libc::pid_t { + ax_posix_api::sys_getpid() as libc::pid_t +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sched_getaffinity( + _pid: libc::pid_t, + _cpusetsize: SizeT, + _mask: *mut libc::cpu_set_t, +) -> c_int { + fail(LinuxError::ENOSYS) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn read(fd: c_int, buf: *mut c_void, count: SizeT) -> SSizeT { + ok_or_errno_isize(ax_posix_api::sys_read(fd, buf, count) as isize) as SSizeT +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn write(fd: c_int, buf: *const c_void, count: SizeT) -> SSizeT { + if let Some(ret) = early_stdio_write(fd, buf, count) { + return ret; + } + ok_or_errno_isize(ax_posix_api::sys_write(fd, buf, count) as isize) as SSizeT +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn writev(fd: c_int, iov: *const libc::iovec, iocnt: c_int) -> SSizeT { + if let Some(ret) = early_stdio_writev(fd, iov, iocnt) { + return ret; + } + ok_or_errno_isize(unsafe { ax_posix_api::sys_writev(fd, iov.cast(), iocnt) } as isize) as SSizeT +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fd")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn close(fd: c_int) -> c_int { + #[cfg(feature = "fs")] + FD_PATHS.lock().remove(&fd); + ok_or_errno(ax_posix_api::sys_close(fd)) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(not(feature = "fd"))] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn close(_fd: c_int) -> c_int { + fail(LinuxError::ENOSYS) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fd")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dup(old_fd: c_int) -> c_int { + ok_or_errno(ax_posix_api::sys_dup(old_fd)) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(not(feature = "fd"))] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dup(_old_fd: c_int) -> c_int { + fail(LinuxError::ENOSYS) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fd")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dup2(old_fd: c_int, new_fd: c_int) -> c_int { + ok_or_errno(ax_posix_api::sys_dup2(old_fd, new_fd)) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(not(feature = "fd"))] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dup2(_old_fd: c_int, _new_fd: c_int) -> c_int { + fail(LinuxError::ENOSYS) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dup3(old_fd: c_int, new_fd: c_int, flags: c_int) -> c_int { + if old_fd == new_fd { + return fail(LinuxError::EINVAL); + } + let new_fd = unsafe { dup2(old_fd, new_fd) }; + if new_fd >= 0 && flags & libc::O_CLOEXEC != 0 { + let _ = unsafe { fcntl(new_fd, libc::F_SETFD, libc::FD_CLOEXEC as usize) }; + } + new_fd +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fd")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fcntl(fd: c_int, cmd: c_int, arg: usize) -> c_int { + ok_or_errno(ax_posix_api::sys_fcntl(fd, cmd, arg)) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(not(feature = "fd"))] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fcntl(_fd: c_int, _cmd: c_int, _arg: usize) -> c_int { + fail(LinuxError::ENOSYS) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fd")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn poll(fds: *mut libc::pollfd, nfds: libc::nfds_t, timeout: c_int) -> c_int { + ok_or_errno(ax_posix_api::sys_poll(fds.cast(), nfds as _, timeout)) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(not(feature = "fd"))] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn poll( + _fds: *mut libc::pollfd, + _nfds: libc::nfds_t, + _timeout: c_int, +) -> c_int { + fail(LinuxError::ENOSYS) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn isatty(_fd: c_int) -> c_int { + fail(LinuxError::ENOTTY) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn open(path: *const c_char, flags: c_int, mode: ModeT) -> c_int { + let path_string = ax_posix_api::utils::char_ptr_to_str(path) + .ok() + .map(ToString::to_string); + let fd = ok_or_errno(ax_posix_api::sys_open(path, flags, mode)); + if fd >= 0 + && let Some(path) = path_string + { + track_fd_path(fd, path, flags & libc::O_DIRECTORY != 0); + } + fd +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn open64(path: *const c_char, flags: c_int, mode: ModeT) -> c_int { + unsafe { open(path, flags, mode) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lseek(fd: c_int, offset: OffT, whence: c_int) -> OffT { + ok_or_errno_isize(ax_posix_api::sys_lseek(fd, offset, whence) as isize) as OffT +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lseek64(fd: c_int, offset: OffT, whence: c_int) -> OffT { + unsafe { lseek(fd, offset, whence) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stat(path: *const c_char, buf: *mut libc::stat) -> c_int { + let mut ax_stat = AxStat::default(); + let ret = unsafe { ax_posix_api::sys_stat(path, &mut ax_stat) }; + stat_ret_to_libc(ret, ax_stat, buf) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stat64(path: *const c_char, buf: *mut libc::stat64) -> c_int { + unsafe { stat(path, buf.cast()) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fstat(fd: c_int, buf: *mut libc::stat) -> c_int { + let mut ax_stat = AxStat::default(); + let ret = unsafe { ax_posix_api::sys_fstat(fd, &mut ax_stat) }; + stat_ret_to_libc(ret, ax_stat, buf) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fstat64(fd: c_int, buf: *mut libc::stat64) -> c_int { + unsafe { fstat(fd, buf.cast()) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lstat(path: *const c_char, buf: *mut libc::stat) -> c_int { + let mut ax_stat = AxStat::default(); + let ret = unsafe { ax_posix_api::sys_lstat(path, &mut ax_stat) }; + stat_ret_to_libc(ret as c_int, ax_stat, buf) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lstat64(path: *const c_char, buf: *mut libc::stat64) -> c_int { + unsafe { lstat(path, buf.cast()) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getcwd(buf: *mut c_char, size: SizeT) -> *mut c_char { + ax_posix_api::sys_getcwd(buf, size) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rename(old: *const c_char, new: *const c_char) -> c_int { + ok_or_errno(ax_posix_api::sys_rename(old, new)) +} + +#[cfg(feature = "fs")] +fn path_from_c(path: *const c_char) -> Result { + Ok(ax_posix_api::utils::char_ptr_to_str(path)?.to_string()) +} + +#[cfg(feature = "fs")] +fn resolve_at_path(dirfd: c_int, path: *const c_char) -> Result { + let path = path_from_c(path)?; + if path.starts_with('/') || dirfd == libc::AT_FDCWD { + return Ok(path); + } + let paths = FD_PATHS.lock(); + let Some(parent) = paths.get(&dirfd) else { + return Err(LinuxError::EBADF); + }; + if !parent.is_dir { + return Err(LinuxError::ENOTDIR); + } + Ok(join_paths(&parent.path, &path)) +} + +#[cfg(feature = "fs")] +fn join_paths(base: &str, child: &str) -> String { + let base = base.trim_end_matches('/'); + if base.is_empty() { + alloc::format!("/{child}") + } else { + alloc::format!("{base}/{child}") + } +} + +#[cfg(feature = "fs")] +fn track_fd_path(fd: c_int, path: String, is_dir: bool) { + FD_PATHS.lock().insert(fd, FdPath { path, is_dir }); +} + +#[cfg(feature = "fs")] +fn c_string_from_string(mut value: String) -> Vec { + value.retain(|ch| ch != '\0'); + let mut bytes = value.into_bytes(); + bytes.push(0); + bytes.into_iter().map(|byte| byte as c_char).collect() +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn unlink(path: *const c_char) -> c_int { + let path = match path_from_c(path) { + Ok(path) => path, + Err(err) => return fail(err), + }; + match ax_api::fs::ax_remove_file(&path) { + Ok(()) => 0, + Err(err) if LinuxError::from(err) == LinuxError::EISDIR => ok_or_errno( + ax_api::fs::ax_remove_dir(&path) + .map_or_else(|err| -(LinuxError::from(err) as i32), |()| 0), + ), + Err(err) => fail(LinuxError::from(err)), + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn unlinkat(dirfd: c_int, path: *const c_char, flags: c_int) -> c_int { + let path = match resolve_at_path(dirfd, path) { + Ok(path) => path, + Err(err) => return fail(err), + }; + if flags & libc::AT_REMOVEDIR != 0 { + match ax_api::fs::ax_remove_dir(&path) { + Ok(()) => 0, + Err(err) => fail(LinuxError::from(err)), + } + } else { + match ax_api::fs::ax_remove_file(&path) { + Ok(()) => 0, + Err(err) => fail(LinuxError::from(err)), + } + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openat( + dirfd: c_int, + path: *const c_char, + flags: c_int, + mode: ModeT, +) -> c_int { + let path = match resolve_at_path(dirfd, path) { + Ok(path) => path, + Err(err) => return fail(err), + }; + let path = c_string_from_string(path); + unsafe { open(path.as_ptr(), flags, mode) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn openat64( + dirfd: c_int, + path: *const c_char, + flags: c_int, + mode: ModeT, +) -> c_int { + unsafe { openat(dirfd, path, flags, mode) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkdir(path: *const c_char, _mode: ModeT) -> c_int { + let path = match path_from_c(path) { + Ok(path) => path, + Err(err) => return fail(err), + }; + match ax_api::fs::ax_create_dir(&path) { + Ok(()) => 0, + Err(err) => fail(LinuxError::from(err)), + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mkdirat(dirfd: c_int, path: *const c_char, mode: ModeT) -> c_int { + let path = match resolve_at_path(dirfd, path) { + Ok(path) => path, + Err(err) => return fail(err), + }; + let path = c_string_from_string(path); + unsafe { mkdir(path.as_ptr(), mode) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn rmdir(path: *const c_char) -> c_int { + let path = match path_from_c(path) { + Ok(path) => path, + Err(err) => return fail(err), + }; + match ax_api::fs::ax_remove_dir(&path) { + Ok(()) => 0, + Err(err) => fail(LinuxError::from(err)), + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn chdir(path: *const c_char) -> c_int { + let path = match path_from_c(path) { + Ok(path) => path, + Err(err) => return fail(err), + }; + match ax_api::fs::ax_set_current_dir(&path) { + Ok(()) => 0, + Err(err) => fail(LinuxError::from(err)), + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getdents64(fd: c_int, buf: *mut c_void, len: SizeT) -> SSizeT { + ok_or_errno_isize(unsafe { ax_posix_api::sys_getdents64(fd, buf.cast(), len) } as isize) + as SSizeT +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn opendir(path: *const c_char) -> *mut libc::DIR { + let fd = unsafe { + open( + path, + libc::O_RDONLY | libc::O_DIRECTORY | libc::O_CLOEXEC, + 0, + ) + }; + if fd < 0 { + return ptr::null_mut(); + } + unsafe { fdopendir(fd) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fdopendir(fd: c_int) -> *mut libc::DIR { + let entries = match read_dir_entries(fd) { + Ok(entries) => entries, + Err(err) => { + set_errno(err as i32); + FD_PATHS.lock().remove(&fd); + return ptr::null_mut(); + } + }; + let stream = DirStream { + fd, + entries, + next: 0, + current: [0; LIBC_DIRENT64_SIZE], + }; + alloc::boxed::Box::into_raw(alloc::boxed::Box::new(stream)).cast::() +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn readdir(dirp: *mut libc::DIR) -> *mut libc::dirent { + unsafe { readdir64(dirp).cast::() } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn readdir64(dirp: *mut libc::DIR) -> *mut libc::dirent64 { + if dirp.is_null() { + set_errno(LinuxError::EINVAL as i32); + return ptr::null_mut(); + } + let stream = unsafe { &mut *dirp.cast::() }; + while let Some(entry) = stream.entries.get(stream.next) { + stream.next += 1; + if write_libc_dirent(&mut stream.current, entry, stream.next as i64) { + return stream.current.as_mut_ptr().cast::(); + } + } + ptr::null_mut() +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn dirfd(dirp: *mut libc::DIR) -> c_int { + if dirp.is_null() { + return fail(LinuxError::EINVAL); + } + unsafe { (*dirp.cast::()).fd } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn closedir(dirp: *mut libc::DIR) -> c_int { + if dirp.is_null() { + return fail(LinuxError::EINVAL); + } + let stream = unsafe { alloc::boxed::Box::from_raw(dirp.cast::()) }; + FD_PATHS.lock().remove(&stream.fd); + unsafe { close(stream.fd) } +} + +#[cfg(feature = "fs")] +fn read_dir_entries(fd: c_int) -> Result, LinuxError> { + let mut entries = Vec::new(); + let mut buf = [0_u8; 4096]; + loop { + let ret = unsafe { ax_posix_api::sys_getdents64(fd, buf.as_mut_ptr(), buf.len()) }; + if ret < 0 { + return Err(LinuxError::try_from((-ret) as i32).unwrap_or(LinuxError::EIO)); + } + if ret == 0 { + return Ok(entries); + } + let mut offset = 0; + let used = ret as usize; + while offset + LINUX_DIRENT64_NAME_OFFSET <= used { + let reclen = u16::from_ne_bytes([buf[offset + 16], buf[offset + 17]]) as usize; + if reclen == 0 || offset + reclen > used { + return Err(LinuxError::EIO); + } + let d_type = buf[offset + 18]; + let name_start = offset + LINUX_DIRENT64_NAME_OFFSET; + let name_end = buf[name_start..offset + reclen] + .iter() + .position(|&byte| byte == 0) + .map(|pos| name_start + pos) + .unwrap_or(offset + reclen); + let name = &buf[name_start..name_end]; + if name != b"." && name != b".." { + entries.push(DirEntryBuf { + name: name.to_vec(), + d_type, + }); + } + offset += reclen; + } + } +} + +#[cfg(feature = "fs")] +fn write_libc_dirent(buf: &mut [u8; LIBC_DIRENT64_SIZE], entry: &DirEntryBuf, off: i64) -> bool { + let name_len = entry + .name + .len() + .min(LIBC_DIRENT64_SIZE - LIBC_DIRENT64_NAME_OFFSET - 1); + let reclen = LIBC_DIRENT64_NAME_OFFSET + name_len + 1; + buf.fill(0); + unsafe { + let ptr = buf.as_mut_ptr(); + ptr.cast::().write_unaligned(1); + ptr.add(8).cast::().write_unaligned(off); + ptr.add(16).cast::().write_unaligned(reclen as u16); + ptr.add(18).write(entry.d_type); + ptr.add(LIBC_DIRENT64_NAME_OFFSET) + .copy_from_nonoverlapping(entry.name.as_ptr(), name_len); + } + true +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn chmod(_path: *const c_char, _mode: ModeT) -> c_int { + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fchmod(_fd: c_int, _mode: ModeT) -> c_int { + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ftruncate(_fd: c_int, _length: OffT) -> c_int { + fail(LinuxError::ENOSYS) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn ftruncate64(fd: c_int, length: OffT) -> c_int { + unsafe { ftruncate(fd, length) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fsync(_fd: c_int) -> c_int { + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn fdatasync(_fd: c_int) -> c_int { + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn futimens(_fd: c_int, _times: *const libc::timespec) -> c_int { + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn readlink( + _path: *const c_char, + _buf: *mut c_char, + _bufsiz: SizeT, +) -> SSizeT { + fail(LinuxError::EINVAL) as SSizeT +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn symlink(_target: *const c_char, _linkpath: *const c_char) -> c_int { + fail(LinuxError::ENOSYS) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn link(_oldpath: *const c_char, _newpath: *const c_char) -> c_int { + fail(LinuxError::ENOSYS) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[cfg(feature = "fs")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn linkat( + _olddirfd: c_int, + _oldpath: *const c_char, + _newdirfd: c_int, + _newpath: *const c_char, + _flags: c_int, +) -> c_int { + fail(LinuxError::ENOSYS) +} + +#[cfg(not(feature = "fs"))] +mod fs_stubs { + use super::*; + + type ModeT = libc::mode_t; + + macro_rules! fs_stub { + ($(fn $name:ident($($arg:ident: $ty:ty),*) -> $ret:ty => $body:block)*) => { + $( + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name($($arg: $ty),*) -> $ret { + let _ = ($($arg,)*); + $body + } + )* + }; + } + + fs_stub! { + fn open(path: *const c_char, flags: c_int, mode: ModeT) -> c_int => { fail(LinuxError::ENOSYS) } + fn open64(path: *const c_char, flags: c_int, mode: ModeT) -> c_int => { fail(LinuxError::ENOSYS) } + fn lseek(fd: c_int, offset: OffT, whence: c_int) -> OffT => { fail(LinuxError::ENOSYS) as OffT } + fn lseek64(fd: c_int, offset: OffT, whence: c_int) -> OffT => { fail(LinuxError::ENOSYS) as OffT } + fn stat(path: *const c_char, buf: *mut libc::stat) -> c_int => { fail(LinuxError::ENOSYS) } + fn stat64(path: *const c_char, buf: *mut libc::stat64) -> c_int => { fail(LinuxError::ENOSYS) } + fn fstat(fd: c_int, buf: *mut libc::stat) -> c_int => { fail(LinuxError::ENOSYS) } + fn fstat64(fd: c_int, buf: *mut libc::stat64) -> c_int => { fail(LinuxError::ENOSYS) } + fn lstat(path: *const c_char, buf: *mut libc::stat) -> c_int => { fail(LinuxError::ENOSYS) } + fn lstat64(path: *const c_char, buf: *mut libc::stat64) -> c_int => { fail(LinuxError::ENOSYS) } + fn rename(old: *const c_char, new: *const c_char) -> c_int => { fail(LinuxError::ENOSYS) } + fn unlink(path: *const c_char) -> c_int => { fail(LinuxError::ENOSYS) } + fn unlinkat(dirfd: c_int, path: *const c_char, flags: c_int) -> c_int => { fail(LinuxError::ENOSYS) } + fn openat(dirfd: c_int, path: *const c_char, flags: c_int, mode: ModeT) -> c_int => { fail(LinuxError::ENOSYS) } + fn openat64(dirfd: c_int, path: *const c_char, flags: c_int, mode: ModeT) -> c_int => { fail(LinuxError::ENOSYS) } + fn mkdir(path: *const c_char, mode: ModeT) -> c_int => { fail(LinuxError::ENOSYS) } + fn mkdirat(dirfd: c_int, path: *const c_char, mode: ModeT) -> c_int => { fail(LinuxError::ENOSYS) } + fn rmdir(path: *const c_char) -> c_int => { fail(LinuxError::ENOSYS) } + fn chdir(path: *const c_char) -> c_int => { fail(LinuxError::ENOSYS) } + fn getdents64(fd: c_int, buf: *mut c_void, len: SizeT) -> SSizeT => { fail(LinuxError::ENOSYS) as SSizeT } + fn fdopendir(fd: c_int) -> *mut libc::DIR => { + set_errno(LinuxError::ENOSYS as i32); + ptr::null_mut() + } + fn readdir(dirp: *mut libc::DIR) -> *mut libc::dirent => { + set_errno(LinuxError::ENOSYS as i32); + ptr::null_mut() + } + fn readdir64(dirp: *mut libc::DIR) -> *mut libc::dirent64 => { + set_errno(LinuxError::ENOSYS as i32); + ptr::null_mut() + } + fn dirfd(dirp: *mut libc::DIR) -> c_int => { fail(LinuxError::ENOSYS) } + fn closedir(dirp: *mut libc::DIR) -> c_int => { fail(LinuxError::ENOSYS) } + fn chmod(path: *const c_char, mode: ModeT) -> c_int => { fail(LinuxError::ENOSYS) } + fn fchmod(fd: c_int, mode: ModeT) -> c_int => { fail(LinuxError::ENOSYS) } + fn ftruncate(fd: c_int, length: OffT) -> c_int => { fail(LinuxError::ENOSYS) } + fn ftruncate64(fd: c_int, length: OffT) -> c_int => { fail(LinuxError::ENOSYS) } + fn fsync(fd: c_int) -> c_int => { fail(LinuxError::ENOSYS) } + fn fdatasync(fd: c_int) -> c_int => { fail(LinuxError::ENOSYS) } + fn futimens(fd: c_int, times: *const libc::timespec) -> c_int => { fail(LinuxError::ENOSYS) } + fn readlink(path: *const c_char, buf: *mut c_char, bufsiz: SizeT) -> SSizeT => { fail(LinuxError::ENOSYS) as SSizeT } + fn symlink(target: *const c_char, linkpath: *const c_char) -> c_int => { fail(LinuxError::ENOSYS) } + fn link(oldpath: *const c_char, newpath: *const c_char) -> c_int => { fail(LinuxError::ENOSYS) } + fn linkat(olddirfd: c_int, oldpath: *const c_char, newdirfd: c_int, newpath: *const c_char, flags: c_int) -> c_int => { fail(LinuxError::ENOSYS) } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn getcwd(_buf: *mut c_char, _size: SizeT) -> *mut c_char { + set_errno(LinuxError::ENOSYS as i32); + ptr::null_mut() + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn opendir(_path: *const c_char) -> *mut libc::DIR { + set_errno(LinuxError::ENOSYS as i32); + ptr::null_mut() + } +} + +#[cfg(not(feature = "fs"))] +pub use fs_stubs::*; + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn clock_gettime(clk: libc::clockid_t, ts: *mut libc::timespec) -> c_int { + ok_or_errno(unsafe { ax_posix_api::sys_clock_gettime(clk, ts.cast()) }) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn nanosleep(req: *const libc::timespec, rem: *mut libc::timespec) -> c_int { + ok_or_errno(unsafe { ax_posix_api::sys_nanosleep(req.cast(), rem.cast()) }) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn clock_nanosleep( + _clk: libc::clockid_t, + flags: c_int, + req: *const libc::timespec, + rem: *mut libc::timespec, +) -> c_int { + if flags != 0 { + return LinuxError::ENOSYS as c_int; + } + if unsafe { nanosleep(req, rem) } == 0 { + 0 + } else { + unsafe { errno } + } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sysconf(name: c_int) -> c_long { + ax_posix_api::sys_sysconf(name) +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getrandom(buf: *mut c_void, buflen: SizeT, _flags: c_uint) -> SSizeT { + if buf.is_null() && buflen > 0 { + return fail(LinuxError::EFAULT) as SSizeT; + } + fill_random(unsafe { core::slice::from_raw_parts_mut(buf.cast::(), buflen) }); + buflen as SSizeT +} + +fn fill_random(buf: &mut [u8]) { + static STATE: AtomicU64 = AtomicU64::new(0x9e37_79b9_7f4a_7c15); + let mut seed = STATE + .fetch_add(0xa076_1d64_78bd_642f, Ordering::Relaxed) + .wrapping_add(ax_hal::time::monotonic_time_nanos()); + for byte in buf { + seed ^= seed << 7; + seed ^= seed >> 9; + seed = seed.wrapping_mul(0xa24b_aed4_963e_e407); + *byte = seed as u8; + } + STATE.store(seed, Ordering::Relaxed); +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn getauxval(_ty: libc::c_ulong) -> libc::c_ulong { + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn signal(_sig: c_int, handler: libc::sighandler_t) -> libc::sighandler_t { + handler +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigaction( + _signum: c_int, + _act: *const libc::sigaction, + oldact: *mut libc::sigaction, +) -> c_int { + if !oldact.is_null() { + unsafe { ptr::write_bytes(oldact, 0, 1) }; + } + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn sigaltstack( + _ss: *const libc::stack_t, + old_ss: *mut libc::stack_t, +) -> c_int { + if !old_ss.is_null() { + unsafe { + old_ss.write(libc::stack_t { + ss_sp: ptr::null_mut(), + ss_flags: libc::SS_DISABLE, + ss_size: 0, + }); + } + } + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mmap( + addr: *mut c_void, + len: SizeT, + _prot: c_int, + flags: c_int, + _fd: c_int, + _offset: OffT, +) -> *mut c_void { + if len == 0 { + set_errno(LinuxError::EINVAL as i32); + return libc::MAP_FAILED; + } + + if flags & libc::MAP_FIXED != 0 && !addr.is_null() { + return addr; + } + + let ptr = unsafe { malloc(len) }; + if ptr.is_null() { + return libc::MAP_FAILED; + } + MMAP_ALLOCS.lock().insert(ptr as usize, len); + ptr +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mmap64( + addr: *mut c_void, + len: SizeT, + prot: c_int, + flags: c_int, + fd: c_int, + offset: libc::off64_t, +) -> *mut c_void { + unsafe { mmap(addr, len, prot, flags, fd, offset as OffT) } +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn munmap(addr: *mut c_void, _len: SizeT) -> c_int { + if addr.is_null() { + return fail(LinuxError::EINVAL); + } + if MMAP_ALLOCS.lock().remove(&(addr as usize)).is_some() { + unsafe { free(addr) }; + } + 0 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn mprotect(_addr: *mut c_void, _len: SizeT, _prot: c_int) -> c_int { + 0 +} + +#[repr(C)] +pub struct UnwindContext { + _private: [u8; 0], +} + +type UnwindTraceFn = extern "C" fn(*mut UnwindContext, *mut c_void) -> c_int; + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _Unwind_GetIP(_ctx: *mut UnwindContext) -> *const u8 { + ptr::null() +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _Unwind_Backtrace( + _trace: UnwindTraceFn, + _trace_argument: *mut c_void, +) -> c_int { + 5 +} + +/// # Safety +/// +/// Callers must uphold the Linux/musl ABI contract for this libc symbol. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn syscall( + num: c_long, + a0: c_long, + a1: c_long, + a2: c_long, + a3: c_long, + a4: c_long, + a5: c_long, +) -> c_long { + let _ = (a4, a5); + match num { + libc::SYS_futex => { + (unsafe { + futex_syscall( + a0 as *mut u32, + a1 as c_int, + a2 as u32, + a3 as *const libc::timespec, + ) + }) as c_long + } + libc::SYS_getrandom => { + (unsafe { getrandom(a0 as *mut c_void, a1 as usize, a2 as c_uint) }) as c_long + } + libc::SYS_gettid => unsafe { gettid() as c_long }, + #[cfg(feature = "fs")] + libc::SYS_getdents64 => { + (unsafe { getdents64(a0 as c_int, a1 as *mut c_void, a2 as usize) }) as c_long + } + _ => { + set_errno(LinuxError::ENOSYS as i32); + -1 + } + } +} + +unsafe fn futex_syscall( + addr: *mut u32, + op: c_int, + expected: u32, + timeout: *const libc::timespec, +) -> c_int { + if addr.is_null() { + return fail(LinuxError::EFAULT); + } + match op & FUTEX_CMD_MASK { + FUTEX_WAIT | FUTEX_WAIT_BITSET => unsafe { futex_wait(addr, expected, timeout) }, + FUTEX_WAKE | FUTEX_WAKE_BITSET => futex_wake(addr, expected), + _ => fail(LinuxError::ENOSYS), + } +} + +unsafe fn futex_wait(addr: *mut u32, expected: u32, timeout: *const libc::timespec) -> c_int { + #[cfg(not(feature = "multitask"))] + { + let _ = (addr, expected, timeout); + fail(LinuxError::ENOSYS) + } + #[cfg(feature = "multitask")] + { + if unsafe { addr.read_volatile() } != expected { + return fail(LinuxError::EAGAIN); + } + + let key = addr as usize; + let wq = { + let mut map = FUTEX_QUEUES.lock(); + map.entry(key) + .or_insert_with(|| Arc::new(ax_api::task::AxWaitQueueHandle::new())) + .clone() + }; + let timed_out = ax_api::task::ax_wait_queue_wait(&wq, unsafe { futex_timeout(timeout) }); + if timed_out { + fail(LinuxError::ETIMEDOUT) + } else { + 0 + } + } +} + +fn futex_wake(addr: *mut u32, count: u32) -> c_int { + #[cfg(not(feature = "multitask"))] + { + let _ = (addr, count); + fail(LinuxError::ENOSYS) + } + #[cfg(feature = "multitask")] + { + let Some(wq) = FUTEX_QUEUES.lock().get(&(addr as usize)).cloned() else { + return 0; + }; + let count = if count == FUTEX_BITSET_MATCH_ANY { + u32::MAX + } else { + count + }; + ax_api::task::ax_wait_queue_wake(&wq, count); + count.min(i32::MAX as u32) as c_int + } +} + +#[cfg(feature = "multitask")] +unsafe fn futex_timeout(timeout: *const libc::timespec) -> Option { + if timeout.is_null() { + return None; + } + let ts = unsafe { *timeout }; + if ts.tv_sec < 0 || ts.tv_nsec < 0 { + return Some(Duration::ZERO); + } + let deadline = Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32); + let now = ax_hal::time::monotonic_time(); + Some(deadline.saturating_sub(now)) +} + +#[cfg(feature = "multitask")] +static FUTEX_QUEUES: LazyLock>>> = + LazyLock::new(|| Mutex::new(BTreeMap::new())); +static MMAP_ALLOCS: LazyLock>> = + LazyLock::new(|| Mutex::new(BTreeMap::new())); +#[cfg(feature = "fs")] +static FD_PATHS: LazyLock>> = + LazyLock::new(|| Mutex::new(BTreeMap::new())); + +#[cfg(feature = "multitask")] +mod pthread { + use super::*; + + type PthreadTlsValues = [*mut c_void; MAX_PTHREAD_KEYS]; + type PthreadTlsMap = BTreeMap>; + type CxaThreadDtorMap = BTreeMap>; + + static KEY_SLOTS: LazyLock>>> = + LazyLock::new(|| Mutex::new(Vec::new())); + static TLS_VALUES: LazyLock> = + LazyLock::new(|| Mutex::new(BTreeMap::new())); + static CXA_THREAD_DTORS: LazyLock> = + LazyLock::new(|| Mutex::new(BTreeMap::new())); + static NEXT_COND_ID: AtomicUsize = AtomicUsize::new(1); + static CONDVARS: LazyLock>>> = + LazyLock::new(|| Mutex::new(BTreeMap::new())); + + struct TlsKey { + destructor: Option, + } + + #[derive(Clone, Copy)] + struct ForceSendSync(T); + + unsafe impl Send for ForceSendSync {} + unsafe impl Sync for ForceSendSync {} + + struct PthreadStart { + start: extern "C" fn(*mut c_void) -> *mut c_void, + arg: *mut c_void, + } + + extern "C" fn pthread_start_trampoline(arg: *mut c_void) -> *mut c_void { + let start = unsafe { Box::from_raw(arg.cast::()) }; + let ret = (start.start)(start.arg); + super::run_cxa_thread_dtors(); + ret + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_self() -> libc::pthread_t { + ax_posix_api::sys_pthread_self() as libc::pthread_t + } + + fn current_task_key() -> u64 { + ax_api::task::ax_current_task_id() + } + + pub(super) fn push_cxa_thread_dtor(record: CxaThreadDtor) { + CXA_THREAD_DTORS + .lock() + .entry(current_task_key()) + .or_default() + .push(record); + } + + pub(super) fn run_cxa_thread_dtors() { + let task = current_task_key(); + let mut records = CXA_THREAD_DTORS.lock().remove(&task).unwrap_or_default(); + while let Some(record) = records.pop() { + unsafe { (record.dtor)(record.arg) }; + } + run_pthread_key_dtors(task); + TLS_VALUES.lock().remove(&task); + } + + fn run_pthread_key_dtors(task: u64) { + for _ in 0..PTHREAD_DESTRUCTOR_ITERATIONS { + let mut records = Vec::new(); + { + let keys = KEY_SLOTS.lock(); + let mut tls_values = TLS_VALUES.lock(); + let Some(values) = tls_values.get_mut(&task) else { + break; + }; + for (key, slot) in keys.iter().enumerate() { + let Some(destructor) = slot.as_ref().and_then(|slot| slot.destructor) else { + continue; + }; + let value = values.0[key]; + if !value.is_null() { + values.0[key] = ptr::null_mut(); + records.push((destructor, value)); + } + } + } + + if records.is_empty() { + break; + } + + for (destructor, value) in records { + unsafe { destructor(value) }; + } + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_create( + res: *mut libc::pthread_t, + attr: *const libc::pthread_attr_t, + start: extern "C" fn(*mut c_void) -> *mut c_void, + arg: *mut c_void, + ) -> c_int { + let start = Box::into_raw(Box::new(PthreadStart { start, arg })); + let ret = unsafe { + ax_posix_api::sys_pthread_create( + res.cast(), + attr.cast(), + pthread_start_trampoline, + start.cast(), + ) + }; + if ret != 0 { + unsafe { drop(Box::from_raw(start)) }; + } + ret + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_exit(retval: *mut c_void) -> ! { + super::run_cxa_thread_dtors(); + ax_posix_api::sys_pthread_exit(retval) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_join( + thread: libc::pthread_t, + retval: *mut *mut c_void, + ) -> c_int { + unsafe { ax_posix_api::sys_pthread_join(thread as _, retval) } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_detach(_thread: libc::pthread_t) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_getattr_np( + _thread: libc::pthread_t, + _attr: *mut libc::pthread_attr_t, + ) -> c_int { + LinuxError::ENOSYS as c_int + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_init(attr: *mut libc::pthread_attr_t) -> c_int { + if attr.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { + ptr::write_bytes(attr.cast::(), 0, PTHREAD_ATTR_SIZE); + write_attr_stack_size(attr, DEFAULT_STACK_SIZE); + } + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_destroy(_attr: *mut libc::pthread_attr_t) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_getstack( + _attr: *const libc::pthread_attr_t, + stack_addr: *mut *mut c_void, + stack_size: *mut SizeT, + ) -> c_int { + if stack_addr.is_null() || stack_size.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { + stack_addr.write(ptr::null_mut()); + stack_size.write(0); + } + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_getguardsize( + _attr: *const libc::pthread_attr_t, + guard_size: *mut SizeT, + ) -> c_int { + if guard_size.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { + guard_size.write(0); + } + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_setstacksize( + attr: *mut libc::pthread_attr_t, + stack_size: SizeT, + ) -> c_int { + if attr.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { write_attr_stack_size(attr, stack_size) }; + 0 + } + + unsafe fn write_attr_stack_size(attr: *mut libc::pthread_attr_t, stack_size: SizeT) { + unsafe { + let bytes = attr.cast::().add(PTHREAD_ATTR_STACK_SIZE_OFFSET); + ptr::write_unaligned(bytes.cast::(), stack_size); + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutex_init( + mutex: *mut libc::pthread_mutex_t, + attr: *const libc::pthread_mutexattr_t, + ) -> c_int { + ax_posix_api::sys_pthread_mutex_init(mutex.cast(), attr.cast()) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutex_lock(mutex: *mut libc::pthread_mutex_t) -> c_int { + ax_posix_api::sys_pthread_mutex_lock(mutex.cast()) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutex_trylock(mutex: *mut libc::pthread_mutex_t) -> c_int { + ax_posix_api::sys_pthread_mutex_trylock(mutex.cast()) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutex_unlock(mutex: *mut libc::pthread_mutex_t) -> c_int { + ax_posix_api::sys_pthread_mutex_unlock(mutex.cast()) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutex_destroy(mutex: *mut libc::pthread_mutex_t) -> c_int { + ax_posix_api::sys_pthread_mutex_destroy(mutex.cast()) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutexattr_init(attr: *mut libc::pthread_mutexattr_t) -> c_int { + if attr.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { ptr::write_bytes(attr.cast::(), 0, size_of::()) }; + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutexattr_settype( + _attr: *mut libc::pthread_mutexattr_t, + _ty: c_int, + ) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutexattr_destroy( + _attr: *mut libc::pthread_mutexattr_t, + ) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_init( + cond: *mut libc::pthread_cond_t, + _attr: *const libc::pthread_condattr_t, + ) -> c_int { + if cond.is_null() { + return LinuxError::EFAULT as c_int; + } + let id = NEXT_COND_ID.fetch_add(1, Ordering::Relaxed).max(1); + unsafe { + ptr::write_bytes(cond.cast::(), 0, PTHREAD_COND_SIZE); + ptr::write_unaligned(cond.cast::(), id); + } + CONDVARS + .lock() + .insert(id, Arc::new(ax_api::task::AxWaitQueueHandle::new())); + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_signal(cond: *mut libc::pthread_cond_t) -> c_int { + if let Some(wq) = unsafe { cond_wait_queue(cond) } { + ax_api::task::ax_wait_queue_wake(&wq, 1); + } + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_broadcast(cond: *mut libc::pthread_cond_t) -> c_int { + if let Some(wq) = unsafe { cond_wait_queue(cond) } { + ax_api::task::ax_wait_queue_wake(&wq, u32::MAX); + } + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_wait( + cond: *mut libc::pthread_cond_t, + mutex: *mut libc::pthread_mutex_t, + ) -> c_int { + unsafe { pthread_cond_wait_inner(cond, mutex, None) } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_timedwait( + cond: *mut libc::pthread_cond_t, + mutex: *mut libc::pthread_mutex_t, + abstime: *const libc::timespec, + ) -> c_int { + unsafe { pthread_cond_wait_inner(cond, mutex, super::futex_timeout(abstime)) } + } + + unsafe fn pthread_cond_wait_inner( + cond: *mut libc::pthread_cond_t, + mutex: *mut libc::pthread_mutex_t, + timeout: Option, + ) -> c_int { + let Some(wq) = (unsafe { cond_wait_queue(cond) }) else { + return LinuxError::EINVAL as c_int; + }; + let unlock_ret = unsafe { pthread_mutex_unlock(mutex) }; + if unlock_ret != 0 { + return unlock_ret; + } + let timed_out = ax_api::task::ax_wait_queue_wait(&wq, timeout); + let lock_ret = unsafe { pthread_mutex_lock(mutex) }; + if timed_out { + LinuxError::ETIMEDOUT as c_int + } else { + lock_ret + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_destroy(cond: *mut libc::pthread_cond_t) -> c_int { + if cond.is_null() { + return LinuxError::EFAULT as c_int; + } + let id = unsafe { ptr::read_unaligned(cond.cast::()) }; + CONDVARS.lock().remove(&id); + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_condattr_init(attr: *mut libc::pthread_condattr_t) -> c_int { + if attr.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { ptr::write_bytes(attr.cast::(), 0, size_of::()) }; + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_condattr_setclock( + _attr: *mut libc::pthread_condattr_t, + _clock: libc::clockid_t, + ) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_condattr_destroy( + _attr: *mut libc::pthread_condattr_t, + ) -> c_int { + 0 + } + + unsafe fn cond_wait_queue( + cond: *mut libc::pthread_cond_t, + ) -> Option> { + if cond.is_null() { + return None; + } + let mut id = unsafe { ptr::read_unaligned(cond.cast::()) }; + if id == 0 { + let ret = unsafe { pthread_cond_init(cond, ptr::null()) }; + if ret != 0 { + return None; + } + id = unsafe { ptr::read_unaligned(cond.cast::()) }; + } + CONDVARS.lock().get(&id).cloned() + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_key_create( + key: *mut libc::pthread_key_t, + destructor: Option, + ) -> c_int { + if key.is_null() { + return LinuxError::EFAULT as c_int; + } + let mut keys = KEY_SLOTS.lock(); + let index = if let Some((index, slot)) = + keys.iter_mut().enumerate().find(|(_, slot)| slot.is_none()) + { + *slot = Some(TlsKey { destructor }); + index + } else { + if keys.len() >= MAX_PTHREAD_KEYS { + return LinuxError::EAGAIN as c_int; + } + keys.push(Some(TlsKey { destructor })); + keys.len() - 1 + }; + unsafe { key.write(index as libc::pthread_key_t) }; + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_key_delete(key: libc::pthread_key_t) -> c_int { + let mut keys = KEY_SLOTS.lock(); + let Some(slot) = keys.get_mut(key as usize) else { + return LinuxError::EINVAL as c_int; + }; + *slot = None; + for values in TLS_VALUES.lock().values_mut() { + values.0[key as usize] = ptr::null_mut(); + } + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_getspecific(key: libc::pthread_key_t) -> *mut c_void { + if key as usize >= MAX_PTHREAD_KEYS { + return ptr::null_mut(); + } + TLS_VALUES + .lock() + .get(¤t_task_key()) + .map(|values| values.0[key as usize]) + .unwrap_or(ptr::null_mut()) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_setspecific( + key: libc::pthread_key_t, + value: *const c_void, + ) -> c_int { + let keys = KEY_SLOTS.lock(); + if keys + .get(key as usize) + .and_then(|slot| slot.as_ref()) + .is_none() + { + return LinuxError::EINVAL as c_int; + } + let mut tls_values = TLS_VALUES.lock(); + tls_values + .entry(current_task_key()) + .or_insert_with(|| ForceSendSync([ptr::null_mut(); MAX_PTHREAD_KEYS])) + .0[key as usize] = value.cast_mut(); + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_setname_np( + _thread: libc::pthread_t, + _name: *const c_char, + ) -> c_int { + 0 + } +} + +#[cfg(feature = "multitask")] +pub use pthread::*; + +#[cfg(not(feature = "multitask"))] +mod pthread_stubs { + use super::*; + + macro_rules! pthread_errno_stub { + ($(fn $name:ident($($arg:ident: $ty:ty),*) -> c_int;)*) => { + $( + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name($($arg: $ty),*) -> c_int { + let _ = ($($arg,)*); + LinuxError::ENOSYS as c_int + } + )* + }; + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[cfg(target_env = "musl")] + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_self() -> libc::pthread_t { + ptr::dangling_mut::().cast() + } + + /// # Safety + /// + /// Callers must uphold the Linux/glibc ABI contract for this libc symbol. + #[cfg(not(target_env = "musl"))] + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_self() -> libc::pthread_t { + 1 + } + + pthread_errno_stub! { + fn pthread_create( + res: *mut libc::pthread_t, + attr: *const libc::pthread_attr_t, + start: extern "C" fn(*mut c_void) -> *mut c_void, + arg: *mut c_void + ) -> c_int; + fn pthread_join(thread: libc::pthread_t, retval: *mut *mut c_void) -> c_int; + fn pthread_getattr_np(thread: libc::pthread_t, attr: *mut libc::pthread_attr_t) -> c_int; + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_exit(_retval: *mut c_void) -> ! { + unsafe { exit(0) } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_detach(_thread: libc::pthread_t) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_init(attr: *mut libc::pthread_attr_t) -> c_int { + if attr.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { ptr::write_bytes(attr.cast::(), 0, size_of::()) }; + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_destroy(_attr: *mut libc::pthread_attr_t) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_getstack( + _attr: *const libc::pthread_attr_t, + stack_addr: *mut *mut c_void, + stack_size: *mut SizeT, + ) -> c_int { + if stack_addr.is_null() || stack_size.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { + stack_addr.write(ptr::null_mut()); + stack_size.write(0); + } + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_getguardsize( + _attr: *const libc::pthread_attr_t, + guard_size: *mut SizeT, + ) -> c_int { + if guard_size.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { guard_size.write(0) }; + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_attr_setstacksize( + _attr: *mut libc::pthread_attr_t, + _stack_size: SizeT, + ) -> c_int { + 0 + } + + pthread_errno_stub! { + fn pthread_mutex_init( + mutex: *mut libc::pthread_mutex_t, + attr: *const libc::pthread_mutexattr_t + ) -> c_int; + fn pthread_mutex_lock(mutex: *mut libc::pthread_mutex_t) -> c_int; + fn pthread_mutex_trylock(mutex: *mut libc::pthread_mutex_t) -> c_int; + fn pthread_mutex_unlock(mutex: *mut libc::pthread_mutex_t) -> c_int; + fn pthread_cond_wait( + cond: *mut libc::pthread_cond_t, + mutex: *mut libc::pthread_mutex_t + ) -> c_int; + fn pthread_cond_timedwait( + cond: *mut libc::pthread_cond_t, + mutex: *mut libc::pthread_mutex_t, + abstime: *const libc::timespec + ) -> c_int; + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutex_destroy(_mutex: *mut libc::pthread_mutex_t) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutexattr_init(attr: *mut libc::pthread_mutexattr_t) -> c_int { + if attr.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { ptr::write_bytes(attr.cast::(), 0, size_of::()) }; + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutexattr_settype( + _attr: *mut libc::pthread_mutexattr_t, + _ty: c_int, + ) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_mutexattr_destroy( + _attr: *mut libc::pthread_mutexattr_t, + ) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_init( + cond: *mut libc::pthread_cond_t, + _attr: *const libc::pthread_condattr_t, + ) -> c_int { + if cond.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { ptr::write_bytes(cond.cast::(), 0, size_of::()) }; + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_signal(_cond: *mut libc::pthread_cond_t) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_broadcast(_cond: *mut libc::pthread_cond_t) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_cond_destroy(_cond: *mut libc::pthread_cond_t) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_condattr_init(attr: *mut libc::pthread_condattr_t) -> c_int { + if attr.is_null() { + return LinuxError::EFAULT as c_int; + } + unsafe { ptr::write_bytes(attr.cast::(), 0, size_of::()) }; + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_condattr_setclock( + _attr: *mut libc::pthread_condattr_t, + _clock: libc::clockid_t, + ) -> c_int { + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_condattr_destroy( + _attr: *mut libc::pthread_condattr_t, + ) -> c_int { + 0 + } + + pthread_errno_stub! { + fn pthread_key_create( + key: *mut libc::pthread_key_t, + destructor: Option + ) -> c_int; + fn pthread_key_delete(key: libc::pthread_key_t) -> c_int; + fn pthread_setspecific(key: libc::pthread_key_t, value: *const c_void) -> c_int; + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_getspecific(_key: libc::pthread_key_t) -> *mut c_void { + ptr::null_mut() + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn pthread_setname_np( + _thread: libc::pthread_t, + _name: *const c_char, + ) -> c_int { + 0 + } +} + +#[cfg(not(feature = "multitask"))] +pub use pthread_stubs::*; + +#[cfg(feature = "net")] +mod net { + use ax_posix_api::ctypes as ax_ctypes; + + use super::*; + + const LINUX_SOCKET_FLAG_MASK: c_int = libc::SOCK_NONBLOCK | libc::SOCK_CLOEXEC; + + fn linux_domain_to_ax(domain: c_int) -> Result { + match domain { + libc::AF_UNSPEC => Ok(ax_ctypes::AF_UNSPEC as c_int), + libc::AF_INET => Ok(ax_ctypes::AF_INET as c_int), + libc::AF_INET6 => Ok(ax_ctypes::AF_INET6 as c_int), + _ => Err(LinuxError::EAFNOSUPPORT), + } + } + + fn linux_socktype_to_ax(ty: c_int) -> Result { + match ty & !LINUX_SOCKET_FLAG_MASK { + libc::SOCK_STREAM => Ok(ax_ctypes::SOCK_STREAM as c_int), + libc::SOCK_DGRAM => Ok(ax_ctypes::SOCK_DGRAM as c_int), + _ => Err(LinuxError::EINVAL), + } + } + + fn apply_socket_flags(fd: c_int, flags: c_int) -> c_int { + if flags & libc::SOCK_NONBLOCK != 0 { + let ret = ax_posix_api::sys_fcntl( + fd, + ax_ctypes::F_SETFL as c_int, + ax_ctypes::O_NONBLOCK as usize, + ); + if ret < 0 { + let _ = ax_posix_api::sys_close(fd); + return ok_or_errno(ret); + } + } + fd + } + + unsafe fn linux_sockaddr_to_ax( + addr: *const libc::sockaddr, + len: libc::socklen_t, + ) -> Result<(ax_ctypes::sockaddr, ax_ctypes::socklen_t), LinuxError> { + if addr.is_null() { + return Err(LinuxError::EFAULT); + } + if (len as usize) < size_of::() { + return Err(LinuxError::EINVAL); + } + + match unsafe { (*addr).sa_family as c_int } { + libc::AF_INET => { + if (len as usize) < size_of::() { + return Err(LinuxError::EINVAL); + } + let src = unsafe { *(addr.cast::()) }; + let ax_addr = ax_ctypes::sockaddr_in { + sin_family: ax_ctypes::AF_INET as ax_ctypes::sa_family_t, + sin_port: src.sin_port, + sin_addr: ax_ctypes::in_addr { + s_addr: src.sin_addr.s_addr, + }, + sin_zero: [0; 8], + }; + Ok(( + unsafe { *(&ax_addr as *const _ as *const ax_ctypes::sockaddr) }, + size_of::() as ax_ctypes::socklen_t, + )) + } + _ => Err(LinuxError::EAFNOSUPPORT), + } + } + + unsafe fn write_linux_sockaddr( + src: &ax_ctypes::sockaddr, + addr: *mut libc::sockaddr, + addrlen: *mut libc::socklen_t, + ) -> Result<(), LinuxError> { + if addr.is_null() || addrlen.is_null() { + return Err(LinuxError::EFAULT); + } + if unsafe { *addrlen as usize } < size_of::() { + return Err(LinuxError::EINVAL); + } + + let src = unsafe { *(src as *const _ as *const ax_ctypes::sockaddr_in) }; + if src.sin_family != ax_ctypes::AF_INET as ax_ctypes::sa_family_t { + return Err(LinuxError::EAFNOSUPPORT); + } + + let dst = libc::sockaddr_in { + sin_family: libc::AF_INET as libc::sa_family_t, + sin_port: src.sin_port, + sin_addr: libc::in_addr { + s_addr: src.sin_addr.s_addr, + }, + sin_zero: [0; 8], + }; + unsafe { + ptr::copy_nonoverlapping( + &dst as *const _ as *const u8, + addr.cast::(), + size_of::(), + ); + *addrlen = size_of::() as libc::socklen_t; + } + Ok(()) + } + + fn linux_sockopt_to_ax(level: c_int, optname: c_int) -> Result<(c_int, c_int), LinuxError> { + if level == libc::SOL_SOCKET { + let optname: c_int = match optname { + libc::SO_REUSEADDR => ax_ctypes::SO_REUSEADDR as c_int, + libc::SO_KEEPALIVE => ax_ctypes::SO_KEEPALIVE as c_int, + libc::SO_BROADCAST => ax_ctypes::SO_BROADCAST as c_int, + libc::SO_LINGER => ax_ctypes::SO_LINGER as c_int, + libc::SO_SNDBUF => ax_ctypes::SO_SNDBUF as c_int, + libc::SO_RCVBUF => ax_ctypes::SO_RCVBUF as c_int, + libc::SO_SNDTIMEO => ax_ctypes::SO_SNDTIMEO as c_int, + libc::SO_RCVTIMEO => ax_ctypes::SO_RCVTIMEO as c_int, + libc::SO_ERROR => ax_ctypes::SO_ERROR as c_int, + _ => return Err(LinuxError::ENOPROTOOPT), + }; + Ok((ax_ctypes::SOL_SOCKET as c_int, optname)) + } else { + Ok((level, optname)) + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn socket(domain: c_int, ty: c_int, protocol: c_int) -> c_int { + let domain = match linux_domain_to_ax(domain) { + Ok(domain) => domain, + Err(err) => return fail(err), + }; + let socktype = match linux_socktype_to_ax(ty) { + Ok(socktype) => socktype, + Err(err) => return fail(err), + }; + let fd = ok_or_errno(ax_posix_api::sys_socket(domain, socktype, protocol)); + if fd < 0 { + fd + } else { + apply_socket_flags(fd, ty & LINUX_SOCKET_FLAG_MASK) + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn bind( + fd: c_int, + addr: *const libc::sockaddr, + len: libc::socklen_t, + ) -> c_int { + let (addr, len) = match unsafe { linux_sockaddr_to_ax(addr, len) } { + Ok(addr) => addr, + Err(err) => return fail(err), + }; + ok_or_errno(ax_posix_api::sys_bind(fd, &addr, len)) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn connect( + fd: c_int, + addr: *const libc::sockaddr, + len: libc::socklen_t, + ) -> c_int { + let (addr, len) = match unsafe { linux_sockaddr_to_ax(addr, len) } { + Ok(addr) => addr, + Err(err) => return fail(err), + }; + ok_or_errno(ax_posix_api::sys_connect(fd, &addr, len)) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn listen(fd: c_int, backlog: c_int) -> c_int { + ok_or_errno(ax_posix_api::sys_listen(fd, backlog)) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn accept( + fd: c_int, + addr: *mut libc::sockaddr, + len: *mut libc::socklen_t, + ) -> c_int { + let mut ax_addr = ax_ctypes::sockaddr::default(); + let mut ax_len = size_of::() as ax_ctypes::socklen_t; + let new_fd = + ok_or_errno(unsafe { ax_posix_api::sys_accept(fd, &mut ax_addr, &mut ax_len) }); + if new_fd >= 0 + && !addr.is_null() + && let Err(err) = unsafe { write_linux_sockaddr(&ax_addr, addr, len) } + { + let _ = ax_posix_api::sys_close(new_fd); + return fail(err); + } + new_fd + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn accept4( + fd: c_int, + addr: *mut libc::sockaddr, + len: *mut libc::socklen_t, + flags: c_int, + ) -> c_int { + if flags & !LINUX_SOCKET_FLAG_MASK != 0 { + return fail(LinuxError::EINVAL); + } + let new_fd = unsafe { accept(fd, addr, len) }; + if new_fd < 0 { + new_fd + } else { + apply_socket_flags(new_fd, flags) + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn send( + fd: c_int, + buf: *const c_void, + len: SizeT, + flags: c_int, + ) -> SSizeT { + ok_or_errno_isize(ax_posix_api::sys_send(fd, buf, len, flags) as isize) as SSizeT + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn sendto( + fd: c_int, + buf: *const c_void, + len: SizeT, + flags: c_int, + addr: *const libc::sockaddr, + addrlen: libc::socklen_t, + ) -> SSizeT { + if addr.is_null() && addrlen == 0 { + return unsafe { send(fd, buf, len, flags) }; + } + let (addr, addrlen) = match unsafe { linux_sockaddr_to_ax(addr, addrlen) } { + Ok(addr) => addr, + Err(err) => return fail(err) as SSizeT, + }; + ok_or_errno_isize(ax_posix_api::sys_sendto(fd, buf, len, flags, &addr, addrlen) as isize) + as SSizeT + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn recv(fd: c_int, buf: *mut c_void, len: SizeT, flags: c_int) -> SSizeT { + ok_or_errno_isize(ax_posix_api::sys_recv(fd, buf, len, flags) as isize) as SSizeT + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn recvfrom( + fd: c_int, + buf: *mut c_void, + len: SizeT, + flags: c_int, + addr: *mut libc::sockaddr, + addrlen: *mut libc::socklen_t, + ) -> SSizeT { + if addr.is_null() { + return unsafe { recv(fd, buf, len, flags) }; + } + let mut ax_addr = ax_ctypes::sockaddr::default(); + let mut ax_len = size_of::() as ax_ctypes::socklen_t; + let ret = ok_or_errno_isize(unsafe { + ax_posix_api::sys_recvfrom(fd, buf, len, flags, &mut ax_addr, &mut ax_len) + } as isize) as SSizeT; + if ret >= 0 + && let Err(err) = unsafe { write_linux_sockaddr(&ax_addr, addr, addrlen) } + { + return fail(err) as SSizeT; + } + ret + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn shutdown(fd: c_int, how: c_int) -> c_int { + ok_or_errno(ax_posix_api::sys_shutdown(fd, how)) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn getsockname( + fd: c_int, + addr: *mut libc::sockaddr, + len: *mut libc::socklen_t, + ) -> c_int { + let mut ax_addr = ax_ctypes::sockaddr::default(); + let mut ax_len = size_of::() as ax_ctypes::socklen_t; + let ret = + ok_or_errno(unsafe { ax_posix_api::sys_getsockname(fd, &mut ax_addr, &mut ax_len) }); + if ret < 0 { + return ret; + } + match unsafe { write_linux_sockaddr(&ax_addr, addr, len) } { + Ok(()) => 0, + Err(err) => fail(err), + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn getpeername( + fd: c_int, + addr: *mut libc::sockaddr, + len: *mut libc::socklen_t, + ) -> c_int { + let mut ax_addr = ax_ctypes::sockaddr::default(); + let mut ax_len = size_of::() as ax_ctypes::socklen_t; + let ret = + ok_or_errno(unsafe { ax_posix_api::sys_getpeername(fd, &mut ax_addr, &mut ax_len) }); + if ret < 0 { + return ret; + } + match unsafe { write_linux_sockaddr(&ax_addr, addr, len) } { + Ok(()) => 0, + Err(err) => fail(err), + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn setsockopt( + fd: c_int, + level: c_int, + optname: c_int, + optval: *const c_void, + optlen: libc::socklen_t, + ) -> c_int { + let (level, optname) = match linux_sockopt_to_ax(level, optname) { + Ok(opt) => opt, + Err(err) => return fail(err), + }; + ok_or_errno(unsafe { ax_posix_api::sys_setsockopt(fd, level, optname, optval, optlen) }) + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn getsockopt( + _fd: c_int, + level: c_int, + optname: c_int, + optval: *mut c_void, + optlen: *mut libc::socklen_t, + ) -> c_int { + if optval.is_null() || optlen.is_null() { + return fail(LinuxError::EFAULT); + } + if level == libc::SOL_SOCKET { + match optname { + libc::SO_ERROR => { + let value: c_int = 0; + unsafe { write_sockopt(optval, optlen, &value) } + } + libc::SO_RCVTIMEO | libc::SO_SNDTIMEO => { + let value = libc::timeval { + tv_sec: 0, + tv_usec: 0, + }; + unsafe { write_sockopt(optval, optlen, &value) } + } + libc::SO_LINGER => { + let value = libc::linger { + l_onoff: 0, + l_linger: 0, + }; + unsafe { write_sockopt(optval, optlen, &value) } + } + libc::SO_REUSEADDR | libc::SO_KEEPALIVE => { + let value: c_int = 0; + unsafe { write_sockopt(optval, optlen, &value) } + } + libc::SO_SNDBUF | libc::SO_RCVBUF => { + let value: c_int = 64 * 1024; + unsafe { write_sockopt(optval, optlen, &value) } + } + _ => fail(LinuxError::ENOPROTOOPT), + } + } else if level == libc::IPPROTO_TCP && optname == libc::TCP_NODELAY { + let value: c_int = 0; + unsafe { write_sockopt(optval, optlen, &value) } + } else { + fail(LinuxError::ENOPROTOOPT) + } + } + + unsafe fn write_sockopt( + optval: *mut c_void, + optlen: *mut libc::socklen_t, + value: &T, + ) -> c_int { + let len = size_of::(); + if unsafe { *optlen as usize } < len { + return fail(LinuxError::EINVAL); + } + unsafe { + ptr::copy_nonoverlapping(value as *const T as *const u8, optval.cast::(), len); + *optlen = len as libc::socklen_t; + } + 0 + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn ioctl(fd: c_int, cmd: c_int, argp: *mut c_void) -> c_int { + if cmd as libc::c_ulong == libc::FIONBIO as libc::c_ulong { + if argp.is_null() { + return fail(LinuxError::EFAULT); + } + let nonblocking = unsafe { *(argp as *const c_int) } != 0; + let flags = if nonblocking { + libc::O_NONBLOCK as usize + } else { + 0 + }; + unsafe { fcntl(fd, libc::F_SETFL, flags) } + } else { + fail(LinuxError::EINVAL) + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn getaddrinfo( + node: *const c_char, + service: *const c_char, + hints: *const libc::addrinfo, + res: *mut *mut libc::addrinfo, + ) -> c_int { + let ret = unsafe { ax_posix_api::sys_getaddrinfo(node, service, hints.cast(), res.cast()) }; + if ret < 0 { + libc::EAI_FAIL + } else if ret == 0 { + libc::EAI_NONAME + } else { + 0 + } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn freeaddrinfo(res: *mut libc::addrinfo) { + unsafe { ax_posix_api::sys_freeaddrinfo(res.cast()) }; + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn gai_strerror(_errcode: c_int) -> *const c_char { + c"getaddrinfo error".as_ptr() + } +} + +#[cfg(feature = "net")] +pub use net::*; + +#[cfg(not(feature = "net"))] +mod net_stubs { + use super::*; + + macro_rules! net_stub { + ($(fn $name:ident($($arg:ident: $ty:ty),*) -> $ret:ty => $body:block)*) => { + $( + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn $name($($arg: $ty),*) -> $ret { + let _ = ($($arg,)*); + $body + } + )* + }; + } + + fn fail_eai_system() -> c_int { + set_errno(LinuxError::ENOSYS as i32); + libc::EAI_SYSTEM + } + + net_stub! { + fn socket(domain: c_int, ty: c_int, protocol: c_int) -> c_int => { fail(LinuxError::ENOSYS) } + fn bind(fd: c_int, addr: *const libc::sockaddr, len: libc::socklen_t) -> c_int => { fail(LinuxError::ENOSYS) } + fn connect(fd: c_int, addr: *const libc::sockaddr, len: libc::socklen_t) -> c_int => { fail(LinuxError::ENOSYS) } + fn listen(fd: c_int, backlog: c_int) -> c_int => { fail(LinuxError::ENOSYS) } + fn accept(fd: c_int, addr: *mut libc::sockaddr, len: *mut libc::socklen_t) -> c_int => { fail(LinuxError::ENOSYS) } + fn accept4(fd: c_int, addr: *mut libc::sockaddr, len: *mut libc::socklen_t, flags: c_int) -> c_int => { fail(LinuxError::ENOSYS) } + fn send(fd: c_int, buf: *const c_void, len: SizeT, flags: c_int) -> SSizeT => { fail(LinuxError::ENOSYS) as SSizeT } + fn sendto(fd: c_int, buf: *const c_void, len: SizeT, flags: c_int, addr: *const libc::sockaddr, addrlen: libc::socklen_t) -> SSizeT => { fail(LinuxError::ENOSYS) as SSizeT } + fn recv(fd: c_int, buf: *mut c_void, len: SizeT, flags: c_int) -> SSizeT => { fail(LinuxError::ENOSYS) as SSizeT } + fn recvfrom(fd: c_int, buf: *mut c_void, len: SizeT, flags: c_int, addr: *mut libc::sockaddr, addrlen: *mut libc::socklen_t) -> SSizeT => { fail(LinuxError::ENOSYS) as SSizeT } + fn shutdown(fd: c_int, how: c_int) -> c_int => { fail(LinuxError::ENOSYS) } + fn getsockname(fd: c_int, addr: *mut libc::sockaddr, len: *mut libc::socklen_t) -> c_int => { fail(LinuxError::ENOSYS) } + fn getpeername(fd: c_int, addr: *mut libc::sockaddr, len: *mut libc::socklen_t) -> c_int => { fail(LinuxError::ENOSYS) } + fn setsockopt(fd: c_int, level: c_int, optname: c_int, optval: *const c_void, optlen: libc::socklen_t) -> c_int => { fail(LinuxError::ENOSYS) } + fn getsockopt(fd: c_int, level: c_int, optname: c_int, optval: *mut c_void, optlen: *mut libc::socklen_t) -> c_int => { fail(LinuxError::ENOSYS) } + fn ioctl(fd: c_int, cmd: c_int, argp: *mut c_void) -> c_int => { fail(LinuxError::ENOSYS) } + fn getaddrinfo(node: *const c_char, service: *const c_char, hints: *const libc::addrinfo, res: *mut *mut libc::addrinfo) -> c_int => { fail_eai_system() } + } + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn freeaddrinfo(_res: *mut libc::addrinfo) {} + + /// # Safety + /// + /// Callers must uphold the Linux/musl ABI contract for this libc symbol. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn gai_strerror(_errcode: c_int) -> *const c_char { + c"getaddrinfo error".as_ptr() + } +} + +#[cfg(not(feature = "net"))] +pub use net_stubs::*; + +#[cfg_attr(not(doc), ax_runtime::ax_app_entry)] +fn axstd_std_check_entry() { + unsafe extern "C" { + safe fn main(argc: c_int, argv: *const *const c_char) -> c_int; + } + let argv: [*const c_char; 1] = [ptr::null()]; + FD_LAYER_READY.store(true, Ordering::Release); + main(0, argv.as_ptr()); +} diff --git a/os/axvisor/Cargo.toml b/os/axvisor/Cargo.toml index 54f6f17384..475f7940fb 100644 --- a/os/axvisor/Cargo.toml +++ b/os/axvisor/Cargo.toml @@ -54,7 +54,7 @@ serial = [] [dependencies] spin = { workspace = true } -[target.'cfg(not(any(windows, unix)))'.dependencies] +[target.'cfg(any(not(any(windows, unix)), target_env = "musl"))'.dependencies] ax-kspin = { workspace = true } ax-lazyinit = { workspace = true } log = "0.4" @@ -75,7 +75,7 @@ ax-memory-addr = { workspace = true } ax-driver.workspace = true # xtask dependencies (only used on host platforms) -[target.'cfg(any(windows, unix))'.dependencies] +[target.'cfg(any(windows, all(unix, not(target_env = "musl"))))'.dependencies] anyhow.workspace = true axbuild = { workspace = true } clap = { version = "4.6", features = ["derive"] } @@ -88,3 +88,6 @@ proc-macro2 = "1.0" quote = "1.0" syn = "2.0" toml = "0.9" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/os/axvisor/src/main.rs b/os/axvisor/src/main.rs index a19be015e7..1465b72817 100644 --- a/os/axvisor/src/main.rs +++ b/os/axvisor/src/main.rs @@ -21,9 +21,9 @@ //! The implementation is intentionally small so that the boot order is visible //! from a single file. -#![no_std] -#![no_main] -#![cfg(target_os = "none")] +#![cfg(any(target_os = "none", arceos_std))] +#![cfg_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] #[macro_use] extern crate log; @@ -31,6 +31,9 @@ extern crate log; #[macro_use] extern crate alloc; +#[cfg(arceos_std)] +use ax_std as _; +#[cfg(target_os = "none")] extern crate ax_std as std; mod config; @@ -84,7 +87,7 @@ fn print_logo() { /// 2. Check and enable hardware virtualization on every CPU. /// 3. Build and start configured guest VMs. /// 4. Enter the management shell after the default guests have exited. -#[unsafe(no_mangle)] +#[cfg_attr(target_os = "none", unsafe(no_mangle))] fn main() { print_logo(); diff --git a/os/axvisor/src/shell/command/base.rs b/os/axvisor/src/shell/command/base.rs index 0da4facb81..938df2ff30 100644 --- a/os/axvisor/src/shell/command/base.rs +++ b/os/axvisor/src/shell/command/base.rs @@ -17,6 +17,8 @@ use std::collections::BTreeMap; use std::fs::{self, File, FileType}; #[cfg(feature = "fs")] use std::io::{self, Read, Write}; +#[cfg(feature = "fs")] +use std::os::unix::fs::{FileTypeExt, PermissionsExt}; use std::println; use std::string::{String, ToString}; @@ -80,11 +82,12 @@ fn do_ls(cmd: &ParsedCommand) { let mut entries = fs::read_dir(name)? .filter_map(|e| e.ok()) .map(|e| e.file_name()) - .filter(|name| show_all || !name.starts_with('.')) + .filter(|name| show_all || !name.to_string_lossy().starts_with('.')) .collect::>(); entries.sort(); for entry in entries { + let entry = entry.to_string_lossy(); let path = format!("{name}/{entry}"); if let Err(e) = show_entry_info(&path, &entry, show_long) { print_err!("ls", path, e); @@ -231,7 +234,7 @@ fn do_rm(cmd: &ParsedCommand) { } else if rm_dir { fs::remove_dir(path) } else { - Err(io::Error::Unsupported) + Err(io::Error::from(io::ErrorKind::Unsupported)) } } else { fs::remove_file(path) @@ -256,8 +259,9 @@ fn remove_dir_recursive(path: &str, _force: bool) -> io::Result<()> { // Remove all child items for entry_result in entries { let entry = entry_result?; - let entry_path = format!("{}/{}", path, entry.file_name()); - let metadata = entry.file_type(); + let entry_name = entry.file_name(); + let entry_path = format!("{}/{}", path, entry_name.to_string_lossy()); + let metadata = entry.file_type()?; if metadata.is_dir() { // Recursively delete subdirectory @@ -295,7 +299,7 @@ fn do_pwd(cmd: &ParsedCommand) { let _logical = cmd.flags.contains("logical"); match std::env::current_dir() { - Ok(pwd) => println!("{}", pwd), + Ok(pwd) => println!("{}", pwd.display()), Err(e) => { print_err!("pwd", e); } @@ -516,7 +520,7 @@ fn do_cp(cmd: &ParsedCommand) { if recursive { copy_dir_recursive(source, dest) } else { - Err(io::Error::Unsupported) + Err(io::Error::from(io::ErrorKind::Unsupported)) } } else { copy_file(source, dest) @@ -556,10 +560,11 @@ fn copy_dir_recursive(src: &str, dst: &str) -> io::Result<()> { for entry_result in entries { let entry = entry_result?; let file_name = entry.file_name(); + let file_name = file_name.to_string_lossy(); let src_path = format!("{src}/{file_name}"); let dst_path = format!("{dst}/{file_name}"); - let metadata = entry.file_type(); + let metadata = entry.file_type()?; if metadata.is_dir() { copy_dir_recursive(&src_path, &dst_path)?; } else { diff --git a/os/axvisor/src/shell/command/mod.rs b/os/axvisor/src/shell/command/mod.rs index 2f8236d927..729718a92e 100644 --- a/os/axvisor/src/shell/command/mod.rs +++ b/os/axvisor/src/shell/command/mod.rs @@ -479,7 +479,7 @@ pub fn prompt_string() -> String { #[cfg(feature = "fs")] { match std::env::current_dir() { - Ok(dir) => format!("axvisor:{dir}$ "), + Ok(dir) => format!("axvisor:{}$ ", dir.display()), Err(_) => "axvisor:$ ".to_string(), } } diff --git a/platforms/axplat-dyn/Cargo.toml b/platforms/axplat-dyn/Cargo.toml index 92f0a0d01f..b896572c0c 100644 --- a/platforms/axplat-dyn/Cargo.toml +++ b/platforms/axplat-dyn/Cargo.toml @@ -41,3 +41,6 @@ platform = "dyn" arch = "aarch64" crate = "axplat_dyn" dynamic = true + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/platforms/axplat-dyn/src/lib.rs b/platforms/axplat-dyn/src/lib.rs index cabac9f71a..fc43dee9af 100644 --- a/platforms/axplat-dyn/src/lib.rs +++ b/platforms/axplat-dyn/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] -#![cfg(not(any(windows, unix)))] +#![cfg(any(not(any(windows, unix)), arceos_std))] extern crate alloc; extern crate ax_driver as _; diff --git a/platforms/somehal/Cargo.toml b/platforms/somehal/Cargo.toml index d6eef69e12..39473eeb09 100644 --- a/platforms/somehal/Cargo.toml +++ b/platforms/somehal/Cargo.toml @@ -44,3 +44,6 @@ sbi-rt = { workspace = true, features = ["legacy"] } [target.'cfg(target_arch = "x86_64")'.dependencies] x2apic = "0.5" x86 = "0.52" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(arceos_std)'] } diff --git a/platforms/somehal/src/lib.rs b/platforms/somehal/src/lib.rs index bec4b660b3..4c62a96efc 100644 --- a/platforms/somehal/src/lib.rs +++ b/platforms/somehal/src/lib.rs @@ -2,7 +2,7 @@ #![no_main] #![allow(unused_features)] #![feature(used_with_arg)] -#![cfg(not(any(windows, unix)))] +#![cfg(any(not(any(windows, unix)), arceos_std))] #[macro_use] extern crate alloc; diff --git a/scripts/axbuild/src/arceos/build.rs b/scripts/axbuild/src/arceos/build.rs index 4e89652fb9..157f5756c6 100644 --- a/scripts/axbuild/src/arceos/build.rs +++ b/scripts/axbuild/src/arceos/build.rs @@ -123,6 +123,24 @@ pub(crate) fn load_cargo_config(request: &ResolvedBuildRequest) -> anyhow::Resul ) } +pub(crate) fn load_bare_cargo_config(request: &ResolvedBuildRequest) -> anyhow::Result { + let metadata = + build::cached_workspace_metadata().context("failed to load workspace metadata")?; + let makefile_features = build::makefile_features_from_env(); + let build_info = load_build_info_with_makefile_features_and_metadata( + request, + &makefile_features, + Some(metadata), + )?; + + build_info.into_bare_cargo_config_with_metadata( + &request.package, + &request.target, + request.plat_dyn, + metadata, + ) +} + pub(crate) fn default_build_info_path(package: &str, target: &str) -> anyhow::Result { Ok(build::default_build_info_path_in_workspace( &crate::context::workspace_root_path()?, @@ -243,7 +261,7 @@ mod tests { fn preparing_non_dynamic_aarch64_without_custom_platform_fails() { let metadata = repo_metadata(); let result = ArceosBuildInfo::default_for_target("aarch64-unknown-none-softfloat") - .into_prepared_base_cargo_config_with_metadata( + .into_bare_cargo_config_with_metadata( "ax-helloworld", "aarch64-unknown-none-softfloat", Some(false), @@ -259,7 +277,7 @@ mod tests { } #[test] - fn max_cpu_num_adds_axfeat_smp_feature() { + fn max_cpu_num_adds_smp_feature_for_std_build() { let metadata = repo_metadata(); let mut build_info = ArceosBuildInfo { features: vec!["ax-feat/net".to_string()], @@ -274,7 +292,7 @@ mod tests { &metadata, ); - assert!(build_info.features.contains(&"ax-feat/smp".to_string())); + assert!(build_info.features.contains(&"ax-std/smp".to_string())); } #[test] @@ -380,7 +398,7 @@ BACKTRACE = "y" ) .unwrap(); let request = request( - "ax-backtrace", + "arceos-backtrace", "aarch64-unknown-none-softfloat", None, path.clone(), @@ -404,7 +422,7 @@ BACKTRACE = "y" assert!( cargo .target - .ends_with("scripts/targets/pie/aarch64-unknown-none-softfloat.json") + .ends_with("scripts/targets/std/pie/aarch64-unknown-linux-musl.json") ); assert!(!cargo.env.contains_key("AX_CONFIG_PATH")); } @@ -474,13 +492,12 @@ AX_IP = "10.0.2.15" assert!( !cargo .features - .iter() - .any(|feature| feature.starts_with("ax-hal/riscv64-")) + .contains(&"ax-std/riscv64-qemu-virt".to_string()) ); assert!( cargo .target - .ends_with("scripts/targets/pie/riscv64gc-unknown-none-elf.json") + .ends_with("scripts/targets/std/pie/riscv64gc-unknown-linux-musl.json") ); } @@ -497,7 +514,7 @@ AX_IP = "10.0.2.15" } #[test] - fn apply_makefile_features_uses_axfeat_prefix_for_axfeat_packages() { + fn apply_makefile_features_uses_ax_std_prefix_for_unified_std_build() { let metadata = repo_metadata(); let mut build_info = ArceosBuildInfo { features: Vec::new(), @@ -511,8 +528,56 @@ AX_IP = "10.0.2.15" &metadata, ); - assert!(build_info.features.contains(&"ax-feat/lockdep".to_string())); - assert!(!build_info.features.contains(&"ax-std/lockdep".to_string())); + assert!(build_info.features.contains(&"lockdep".to_string())); + assert!(!build_info.features.contains(&"ax-feat/lockdep".to_string())); + } + + #[test] + fn prepared_cargo_config_uses_unified_std_target() { + let metadata = repo_metadata(); + let cargo = ArceosBuildInfo { + features: vec!["lockdep".to_string()], + ..ArceosBuildInfo::default_for_target("aarch64-unknown-none-softfloat") + } + .into_prepared_base_cargo_config_with_metadata( + "ax-helloworld", + "aarch64-unknown-none-softfloat", + None, + &metadata, + ) + .unwrap(); + + assert!( + cargo + .target + .ends_with("scripts/targets/std/pie/aarch64-unknown-linux-musl.json") + ); + assert!(cargo.features.contains(&"ax-std/lockdep".to_string())); + } + + #[test] + fn bare_cargo_config_remains_available_for_c_builds() { + let metadata = repo_metadata(); + let cargo = ArceosBuildInfo::default_for_target("x86_64-unknown-none") + .into_bare_cargo_config_with_metadata( + "ax-helloworld", + "x86_64-unknown-none", + Some(false), + &metadata, + ) + .unwrap(); + + assert!( + cargo + .target + .ends_with("scripts/targets/no-pie/x86_64-unknown-none.json") + ); + assert!( + cargo + .args + .windows(2) + .any(|pair| pair == ["-Z", "build-std=core,alloc"]) + ); } #[test] diff --git a/scripts/axbuild/src/arceos/cbuild.rs b/scripts/axbuild/src/arceos/cbuild.rs index 25aefe3337..152d264f47 100644 --- a/scripts/axbuild/src/arceos/cbuild.rs +++ b/scripts/axbuild/src/arceos/cbuild.rs @@ -37,7 +37,7 @@ pub(crate) fn build_c_app( request: &ResolvedBuildRequest, input: &ArceosCBuildInput, ) -> anyhow::Result { - let mut cargo = build::load_cargo_config(request)?; + let mut cargo = build::load_bare_cargo_config(request)?; cargo.package = AX_LIBC_PACKAGE.to_string(); cargo.target = request.target.clone(); cargo.to_bin = false; diff --git a/scripts/axbuild/src/axvisor/build.rs b/scripts/axbuild/src/axvisor/build.rs index 75c43a74c5..4cc45fb5e6 100644 --- a/scripts/axbuild/src/axvisor/build.rs +++ b/scripts/axbuild/src/axvisor/build.rs @@ -78,6 +78,7 @@ pub(crate) fn load_board_file(path: &Path) -> anyhow::Result { path.display() ) })?; + crate::build::reject_removed_std_field(path, &content)?; toml::from_str(&content).map_err(|e| { anyhow!( "failed to parse Axvisor board config {}: {e}", @@ -455,6 +456,7 @@ pub(crate) fn load_target_from_build_config(path: &Path) -> anyhow::Result(&content) { return Ok(Some(board_file.target)); @@ -526,6 +528,7 @@ fn load_build_config(request: &ResolvedAxvisorRequest) -> anyhow::Result(&content) { let mut loaded = board_config.into_loaded(); @@ -734,7 +737,7 @@ plat_dyn = true assert!( cargo .target - .ends_with("scripts/targets/pie/aarch64-unknown-none-softfloat.json") + .ends_with("scripts/targets/std/pie/aarch64-unknown-linux-musl.json") ); assert_eq!( cargo.env.get("AX_ARCH").map(String::as_str), @@ -784,6 +787,30 @@ vm_configs = [] ); } + #[test] + fn load_target_from_build_config_rejects_removed_std_field() { + let root = tempdir().unwrap(); + let path = root.path().join("qemu-aarch64.toml"); + fs::write( + &path, + r#" +std = true +env = {} +features = [] +log = "Info" +target = "aarch64-unknown-none-softfloat" +"#, + ) + .unwrap(); + + let err = load_target_from_build_config(&path).unwrap_err(); + + assert!( + err.to_string().contains("uses removed `std` field"), + "{err:#}" + ); + } + #[test] fn load_cargo_config_uses_board_defaults_when_default_file_is_missing() { let root = tempdir().unwrap(); @@ -1050,7 +1077,7 @@ plat_dyn = false assert!( cargo .target - .ends_with("scripts/targets/no-pie/loongarch64-unknown-none-softfloat.json") + .ends_with("scripts/targets/std/loongarch64-unknown-linux-musl.json") ); } } diff --git a/scripts/axbuild/src/backtrace.rs b/scripts/axbuild/src/backtrace.rs index 4f5cf83c82..070f977fae 100644 --- a/scripts/axbuild/src/backtrace.rs +++ b/scripts/axbuild/src/backtrace.rs @@ -62,8 +62,8 @@ pub fn execute(command: Command) -> anyhow::Result<()> { const HOST_SYMBOLIZE_HEADER: &str = "=== host backtrace symbolize ==="; -/// Resolved ELF path for an ArceOS Rust test package built via the workspace `target/` dir. -pub(crate) fn arceos_rust_elf_path( +/// Resolved ELF path for an ArceOS std test package built via the workspace `target/` dir. +pub(crate) fn arceos_std_elf_path( workspace_root: &Path, target: &str, package: &str, @@ -1197,8 +1197,8 @@ BACKTRACE_END } #[test] - fn arceos_rust_elf_path_uses_release_profile() { - let path = arceos_rust_elf_path( + fn arceos_std_elf_path_uses_release_profile() { + let path = arceos_std_elf_path( Path::new("/ws"), "x86_64-unknown-none", "arceos-test-suit", diff --git a/scripts/axbuild/src/build.rs b/scripts/axbuild/src/build.rs index c2b0299c68..d9819ec3c0 100644 --- a/scripts/axbuild/src/build.rs +++ b/scripts/axbuild/src/build.rs @@ -1,8 +1,11 @@ +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; use std::{ collections::HashMap, fs, path::{Path, PathBuf}, sync::OnceLock, + time::{SystemTime, UNIX_EPOCH}, }; use anyhow::{Context, anyhow, bail}; @@ -13,6 +16,7 @@ use ostool::build::config::Cargo; pub use ostool::build::config::LogLevel; use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use sha2::{Digest, Sha256}; use crate::context::{axbuild_tmp_dir, workspace_manifest_path, workspace_metadata_root_manifest}; @@ -57,12 +61,12 @@ pub(crate) fn build_info_enables_backtrace_path(path: &Path) -> bool { .is_some_and(|info| build_info_enables_backtrace(&info)) } -const LOONGARCH64_HERMIT_JSON: &str = - include_str!("../../../os/arceos/examples/std/loongarch64-unknown-hermit.json"); const TARGET_JSON_ROOT: &str = "scripts/targets"; const NO_PIE_TARGET_DIR: &str = "no-pie"; const PIE_TARGET_DIR: &str = "pie"; pub(crate) const ARCEOS_LINKER_SCRIPT: &str = "linker.x"; +const STD_TARGET_DIR: &str = "std"; +const AXSTD_STD_PACKAGE: &str = "ax-std"; #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum AxFeaturePrefixFamily { @@ -96,9 +100,6 @@ pub struct BuildInfo { /// Whether to use the dynamic platform linker flow when supported. #[serde(default, skip_serializing_if = "is_false")] pub plat_dyn: bool, - /// Build this package as an ArceOS std/Hermit application. - #[serde(default, rename = "std", skip_serializing_if = "is_false")] - pub std_build: bool, } impl BuildInfo { @@ -192,28 +193,58 @@ impl BuildInfo { plat_dyn_override: Option, metadata: &Metadata, ) -> anyhow::Result { - if self.std_build { - self.validated_max_cpu_num()?; - let plat_dyn = self.effective_plat_dyn(target, plat_dyn_override); - if plat_dyn && !has_std_platform_selection_feature(&self.features) { - self.features.push("plat-dyn".to_string()); - } - self.prepare_non_dynamic_platform_for(package, target, plat_dyn, metadata)?; - self.resolve_std_features(); - let std_target = std_build_target_for(target)?; - let mut cargo = self.into_base_cargo_config_with_log( - package.to_string(), - std_target.target, - std_target.cargo_args, - ); - cargo.env.extend(std_target.env); - prepare_std_build_env(&mut cargo.env, target, plat_dyn, metadata)?; - pass_std_build_nested_features(&mut cargo.env, &mut cargo.features); - cargo.extra_config = Some(std_cargo_config_path(plat_dyn)?.display().to_string()); - cargo.to_bin = plat_dyn && target.starts_with("x86_64-"); - return Ok(cargo); - } + self.validated_max_cpu_num()?; + let plat_dyn = self.effective_plat_dyn(target, plat_dyn_override); + self.resolve_std_features_with_metadata(package, target, plat_dyn, metadata); + let axconfig_overrides = self.axconfig_overrides.clone(); + let std_target = std_build_target_for(target, plat_dyn)?; + let fake_lib_dir = std_fake_lib_dir(&std_target.target_name)?; + let wrapper = std_linker_wrapper_path(&std_target.target_name, &fake_lib_dir, plat_dyn)?; + let mut cargo = self.into_base_cargo_config_with_log( + package.to_string(), + std_target.target.clone(), + std_target.cargo_args, + ); + cargo.env.extend(std_target.env); + prepare_std_build_env_for_package( + &mut cargo.env, + package, + target, + plat_dyn, + &cargo.features, + metadata, + &axconfig_overrides, + )?; + let app_features = package_feature_names(package, metadata)?; + let axstd_features = package_feature_names(AXSTD_STD_PACKAGE, metadata)?; + inject_arceos_feature_for_std_build(&mut cargo.features, &app_features); + pass_std_build_nested_features( + &mut cargo.env, + &mut cargo.features, + &app_features, + &axstd_features, + ); + cargo.pre_build_cmds.push( + std_fake_lib_prebuild_script_path(&std_target.target_name, &fake_lib_dir, &cargo.env)? + .display() + .to_string(), + ); + cargo.extra_config = Some( + std_cargo_config_path(&std_target.target_name, &wrapper, plat_dyn)? + .display() + .to_string(), + ); + cargo.to_bin = default_to_bin_for_target_config(target, plat_dyn); + Ok(cargo) + } + pub(crate) fn into_bare_cargo_config_with_metadata( + mut self, + package: &str, + target: &str, + plat_dyn_override: Option, + metadata: &Metadata, + ) -> anyhow::Result { let plat_dyn = self.effective_plat_dyn(target, plat_dyn_override); self.validated_max_cpu_num()?; self.prepare_non_dynamic_platform_for(package, target, plat_dyn, metadata)?; @@ -248,6 +279,37 @@ impl BuildInfo { self.features.dedup(); } + fn resolve_std_features_with_metadata( + &mut self, + package: &str, + target: &str, + plat_dyn: bool, + metadata: &Metadata, + ) { + self.features + .extend(std_package_metadata_features(package, metadata)); + self.resolve_std_features(); + + if self.max_cpu_num.is_some_and(|max_cpu_num| max_cpu_num > 1) { + self.features.push("smp".to_string()); + } + if plat_dyn { + self.features.push("smp".to_string()); + self.features.push("plat-dyn".to_string()); + self.features.push("ax-driver/plat-dyn".to_string()); + } else if !has_myplat_feature(&self.features) + && !has_defplat_feature(&self.features) + && !has_ax_hal_platform_feature(&self.features, Some(metadata)) + { + self.features.push( + default_ax_hal_platform_feature(target, Some(metadata)) + .unwrap_or_else(|_| "ax-hal/defplat".to_string()), + ); + } + + self.resolve_std_features(); + } + pub(crate) fn prepare_non_dynamic_platform_for( &mut self, package: &str, @@ -469,7 +531,6 @@ impl Default for BuildInfo { max_cpu_num: None, axconfig_overrides: Vec::new(), plat_dyn: false, - std_build: false, } } } @@ -501,43 +562,79 @@ pub(crate) fn cargo_target_json_path(target: &str, plat_dyn: bool) -> anyhow::Re } struct StdBuildTarget { + target_name: String, target: String, cargo_args: Vec, env: HashMap, } -fn std_build_target_for(target: &str) -> anyhow::Result { - if target.starts_with("x86_64-") { - Ok(StdBuildTarget { - target: "x86_64-unknown-hermit".to_string(), - cargo_args: Vec::new(), - env: HashMap::new(), - }) +fn std_build_target_for(target: &str, plat_dyn: bool) -> anyhow::Result { + let (target_name, tool_prefix) = if target.starts_with("x86_64-") { + ("x86_64-unknown-linux-musl", "x86_64-linux-musl") } else if target.starts_with("aarch64-") { - Ok(StdBuildTarget { - target: "aarch64-unknown-hermit".to_string(), - cargo_args: Vec::new(), - env: HashMap::new(), - }) + ("aarch64-unknown-linux-musl", "aarch64-linux-musl") } else if target.starts_with("riscv64") { - Ok(StdBuildTarget { - target: "riscv64gc-unknown-hermit".to_string(), - cargo_args: Vec::new(), - env: HashMap::new(), - }) + ("riscv64gc-unknown-linux-musl", "riscv64-linux-musl") } else if target.starts_with("loongarch64-") { - let path = std_loongarch64_target_json_path()?; - Ok(StdBuildTarget { - target: path.display().to_string(), - cargo_args: vec!["-Z".to_string(), "json-target-spec".to_string()], - env: [( - "CARGO_UNSTABLE_JSON_TARGET_SPEC".to_string(), - "true".to_string(), - )] - .into(), - }) + ("loongarch64-unknown-linux-musl", "loongarch64-linux-musl") } else { - bail!("unsupported ArceOS std target triple `{target}`") + bail!("unsupported ArceOS std target triple `{target}`"); + }; + + let mut env = HashMap::new(); + env.insert( + "CARGO_UNSTABLE_JSON_TARGET_SPEC".to_string(), + "true".to_string(), + ); + env.extend(std_c_toolchain_env(target_name, tool_prefix)); + + Ok(StdBuildTarget { + target_name: target_name.to_string(), + target: std_target_json_path(target_name, plat_dyn) + .display() + .to_string(), + cargo_args: vec!["-Z".to_string(), "json-target-spec".to_string()], + env, + }) +} + +fn std_c_toolchain_env(target_name: &str, tool_prefix: &str) -> HashMap { + let mut env = HashMap::new(); + let target_env = target_name.replace('-', "_"); + let cc = format!("{tool_prefix}-cc"); + let ar = format!("{tool_prefix}-ar"); + env.insert(format!("CC_{target_env}"), cc.clone()); + env.insert(format!("AR_{target_env}"), ar); + + if let Some(sysroot) = musl_toolchain_sysroot(&cc) { + env.insert( + format!("BINDGEN_EXTRA_CLANG_ARGS_{target_env}"), + format!("--target={tool_prefix} --sysroot={sysroot}"), + ); + } + + env +} + +fn musl_toolchain_sysroot(cc: &str) -> Option { + let output = std::process::Command::new(cc) + .arg("-print-sysroot") + .output() + .ok()?; + if !output.status.success() { + return None; + } + let sysroot = String::from_utf8(output.stdout).ok()?; + let sysroot = sysroot.trim(); + (!sysroot.is_empty()).then(|| sysroot.to_string()) +} + +fn std_target_json_path(target: &str, plat_dyn: bool) -> PathBuf { + let path = Path::new(TARGET_JSON_ROOT).join(STD_TARGET_DIR); + if plat_dyn { + path.join(PIE_TARGET_DIR).join(format!("{target}.json")) + } else { + path.join(format!("{target}.json")) } } @@ -547,14 +644,34 @@ pub(crate) fn prepare_std_build_env( plat_dyn: bool, metadata: &Metadata, ) -> anyhow::Result<()> { + prepare_std_build_env_for_package( + envs, + AXSTD_STD_PACKAGE, + target, + plat_dyn, + &[], + metadata, + &[], + ) +} + +fn prepare_std_build_env_for_package( + envs: &mut HashMap, + package: &str, + target: &str, + plat_dyn: bool, + features: &[String], + metadata: &Metadata, + axconfig_overrides: &[String], +) -> anyhow::Result<()> { + envs.insert("AX_TARGET".to_string(), target.to_string()); + if plat_dyn { return Ok(()); } - let arch = target_arch_name(target)?; - let platform_package = require_default_platform_package(metadata, arch)?; - let platform_config = resolve_platform_config_by_package(&platform_package, metadata)?; - let out_config = generated_axconfig_path("arceos-rust", target)?; + let platform_config = resolve_platform_config(package, target, features, metadata)?; + let out_config = generated_axconfig_path(package, target)?; generate_axconfig( &crate::context::workspace_root_path()?, target, @@ -567,66 +684,86 @@ pub(crate) fn prepare_std_build_env( .with_context(|| format!("invalid SMP value `{smp}`")) }) .transpose()?, - &[], + axconfig_overrides, )?; envs.insert( - "ARCEOS_RUST_CONFIG".to_string(), + "AX_CONFIG_PATH".to_string(), out_config.display().to_string(), ); + envs.insert("AX_PLATFORM".to_string(), platform_config.name); Ok(()) } -fn pass_std_build_nested_features(envs: &mut HashMap, features: &mut Vec) { - let mut nested = Vec::new(); - features.retain(|feature| { - if feature.starts_with("ax-hal/") || feature.starts_with("ax-driver/") { - nested.push(feature.clone()); - false +fn pass_std_build_nested_features( + _envs: &mut HashMap, + features: &mut Vec, + app_features: &[String], + axstd_features: &[String], +) { + let mut cargo_features = Vec::new(); + + for feature in features.drain(..) { + let feature = normalize_std_feature(&feature); + if matches!(feature.as_str(), "ax-std" | "ax-feat") { + continue; + } + if is_log_level_feature(&feature) { + continue; + } + if is_axstd_std_check_feature(&feature) { + if std_feature_stays_on_app(&feature, app_features) { + cargo_features.push(feature.clone()); + } + let axstd_feature = axstd_feature_name(&feature); + if axstd_feature_is_available(axstd_feature, axstd_features) { + cargo_features.push(format!("ax-std/{axstd_feature}")); + } else if feature.contains('/') { + cargo_features.push(feature); + } } else { - true + cargo_features.push(feature); } - }); - if nested.is_empty() { - return; } - envs.insert("ARCEOS_RUST_FEATURES".to_string(), nested.join(",")); + + cargo_features.sort(); + cargo_features.dedup(); + + *features = cargo_features; } -fn has_std_platform_selection_feature(features: &[String]) -> bool { - features.iter().any(|feature| { - let normalized = normalize_legacy_feature_alias(feature); - matches!( - normalized.as_str(), - "plat-dyn" - | "defplat" - | "myplat" - | "ax-std/plat-dyn" - | "ax-std/defplat" - | "ax-std/myplat" - | "ax-feat/plat-dyn" - | "ax-feat/defplat" - | "ax-feat/myplat" - | "arceos-rust/plat-dyn" - | "arceos-rust/defplat" - | "arceos-rust/myplat" - ) || normalized.starts_with("ax-hal/") - }) +fn inject_arceos_feature_for_std_build(features: &mut Vec, app_features: &[String]) { + if app_features.iter().any(|feature| feature == "arceos") + && !features.iter().any(|feature| feature == "arceos") + { + features.push("arceos".to_string()); + } } -fn std_cargo_config_path(plat_dyn: bool) -> anyhow::Result { - let file_name = if plat_dyn { - "config-dyn.toml" - } else { - "config-static.toml" - }; - let path = std_build_dir()?.join(file_name); - let link_args = if plat_dyn { - r#" "-C", "link-arg=-pie", - "-C", "link-arg=-Tlinker.x","# - } else { - r#" "-C", "link-arg=-no-pie", - "-C", "link-arg=-Tlinker.x","# - }; +fn axstd_feature_name(feature: &str) -> &str { + feature + .strip_prefix("ax-hal/") + .or_else(|| feature.strip_prefix("ax-driver/")) + .unwrap_or(feature) +} + +fn package_feature_names(package: &str, metadata: &Metadata) -> anyhow::Result> { + Ok(workspace_package(metadata, package)? + .features + .keys() + .cloned() + .collect()) +} + +fn axstd_feature_is_available(feature: &str, axstd_features: &[String]) -> bool { + axstd_features + .iter() + .any(|axstd_feature| axstd_feature == feature) +} + +fn std_cargo_config_path(target: &str, linker: &Path, plat_dyn: bool) -> anyhow::Result { + let mode = std_link_mode_suffix(plat_dyn); + let path = std_build_dir()?.join(format!("config-{target}-{mode}.toml")); + let rustflags = std_rustflags_toml(target, plat_dyn); write_if_changed( &path, &format!( @@ -638,22 +775,333 @@ build-std-features = [] lto = false panic = "abort" -[target.'cfg(target_os = "hermit")'] +[target.{target}] +linker = "{}" rustflags = [ -{link_args} +{} ] -"# +"#, + toml_escape_string(&linker.display().to_string()), + rustflags, ), )?; Ok(path) } -fn std_loongarch64_target_json_path() -> anyhow::Result { - let path = std_build_dir()?.join("loongarch64-unknown-hermit.json"); - write_if_changed(&path, LOONGARCH64_HERMIT_JSON)?; +fn std_link_mode_suffix(plat_dyn: bool) -> &'static str { + if plat_dyn { "dynamic" } else { "static" } +} + +fn std_rustflags_toml(target: &str, plat_dyn: bool) -> String { + let flags = [ + r#" "--cfg", "arceos_std","#, + r#" "--check-cfg=cfg(arceos_std)","#, + ]; + let _ = (target, plat_dyn); + flags.join("\n") +} + +fn std_fake_lib_dir(target: &str) -> anyhow::Result { + let dir = axbuild_tmp_dir(&crate::context::workspace_root_path()?) + .join("std-libs") + .join(target) + .join("release"); + fs::create_dir_all(&dir) + .with_context(|| format!("failed to create std fake lib dir {}", dir.display()))?; + Ok(dir) +} + +fn std_linker_wrapper_path( + target: &str, + fake_lib_dir: &Path, + plat_dyn: bool, +) -> anyhow::Result { + let mode = std_link_mode_suffix(plat_dyn); + let path = std_build_dir()?.join(format!("linker-{target}-{mode}.sh")); + write_if_changed( + &path, + &std_linker_wrapper_script(target, fake_lib_dir, plat_dyn)?, + )?; + set_executable(&path)?; + Ok(path) +} + +fn std_fake_lib_prebuild_script_path( + target_name: &str, + fake_lib_dir: &Path, + envs: &HashMap, +) -> anyhow::Result { + let contents = std_fake_lib_prebuild_script(target_name, fake_lib_dir, envs); + let hash = short_content_hash(&contents); + let path = std_build_dir()? + .join("prebuild") + .join(format!("prebuild-{target_name}-{hash}.sh")); + write_if_changed(&path, &contents)?; + set_executable(&path)?; Ok(path) } +fn std_fake_lib_prebuild_script( + target_name: &str, + fake_lib_dir: &Path, + envs: &HashMap, +) -> String { + let mut env_exports = Vec::new(); + let mut sorted_envs: Vec<_> = envs.iter().collect(); + sorted_envs.sort_by_key(|(key, _)| *key); + for (key, value) in sorted_envs { + if key + .chars() + .all(|ch| ch == '_' || ch.is_ascii_uppercase() || ch.is_ascii_digit()) + { + env_exports.push(format!("export {key}={}", shell_single_quote(value))); + } + } + + format!( + r#"#!/usr/bin/env bash +set -euo pipefail + +{} + +target_name={} +fake_dir={} + +archive_tool() {{ + if command -v rust-ar >/dev/null 2>&1; then + command -v rust-ar + return + fi + + local sysroot_llvm_ar + sysroot_llvm_ar="$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin/llvm-ar" + if [[ -x "$sysroot_llvm_ar" ]]; then + printf '%s\n' "$sysroot_llvm_ar" + return + fi + + if command -v llvm-ar >/dev/null 2>&1; then + command -v llvm-ar + return + fi + + if command -v ar >/dev/null 2>&1; then + command -v ar + return + fi + + echo "failed to find archive tool; tried rust-ar, rust toolchain llvm-ar, llvm-ar, ar" >&2 + exit 127 +}} + +create_empty_archive() {{ + local archive="$1" + rm -f "$archive" + "$(archive_tool)" crs "$archive" +}} + +mkdir -p "$fake_dir" +create_empty_archive "$fake_dir/libc.a" +create_empty_archive "$fake_dir/libunwind.a" +"#, + env_exports.join("\n"), + shell_single_quote(target_name), + shell_single_quote(&fake_lib_dir.display().to_string()), + ) +} + +fn std_linker_wrapper_script( + target: &str, + fake_lib_dir: &Path, + plat_dyn: bool, +) -> anyhow::Result { + let machine = lld_machine_for_std_target(target)?; + let entry_symbol = if plat_dyn { "_head" } else { "_start" }; + let link_mode_args = if plat_dyn { + r#"("-pie")"# + } else { + r#"("-static" "-no-pie")"# + }; + let dynamic_platform = if plat_dyn { "1" } else { "0" }; + Ok(format!( + r#"#!/usr/bin/env bash +set -euo pipefail + +fake_dir={} +target_name={} +lld_args=("-m" "{}") +link_search_dirs=() +entry_symbol={} +link_mode_args={} +dynamic_platform={} + +add_link_search_dir() {{ + local dir="$1" + [[ -n "$dir" ]] && link_search_dirs+=("$dir") +}} + +find_linker_script() {{ + local dir + for dir in "${{link_search_dirs[@]}}"; do + if [[ -f "$dir/linker.x" ]]; then + printf '%s\n' "$dir/linker.x" + return + fi + done +}} + +add_arg() {{ + local arg="$1" + if [[ "${{expect_link_search_dir:-0}}" == "1" ]]; then + add_link_search_dir "$arg" + lld_args+=("$arg") + expect_link_search_dir=0 + return + fi + + if [[ "${{skip_next_lld_driver_arg:-0}}" == "1" ]]; then + skip_next_lld_driver_arg=0 + return + fi + + case "$arg" in + */rcrt1.o|rcrt1.o|*/crt1.o|crt1.o|*/Scrt1.o|Scrt1.o|*/crti.o|crti.o|*/crtn.o|crtn.o|*/crtbegin*.o|crtbegin*.o|*/crtend*.o|crtend*.o) + return + ;; + -L) + lld_args+=("$arg") + expect_link_search_dir=1 + return + ;; + -L*) + add_link_search_dir "${{arg#-L}}" + lld_args+=("$arg") + return + ;; + -flavor) + skip_next_lld_driver_arg=1 + return + ;; + -flavor=*|-T*) + return + ;; + -lc) + lld_args+=("$fake_dir/libc.a") + return + ;; + -lgcc_s|-lgcc) + return + ;; + -lunwind) + if [[ -f "$fake_dir/libunwind.a" ]]; then + lld_args+=("$fake_dir/libunwind.a") + fi + return + ;; + -static-pie) + if [[ "$dynamic_platform" == "1" ]]; then + lld_args+=("-pie") + else + lld_args+=("-static" "-no-pie") + fi + return + ;; + -static) + if [[ "$dynamic_platform" == "0" ]]; then + lld_args+=("-static") + fi + return + ;; + -pie) + if [[ "$dynamic_platform" == "1" ]]; then + lld_args+=("-pie") + fi + return + ;; + -no-pie) + if [[ "$dynamic_platform" == "0" ]]; then + lld_args+=("-no-pie") + fi + return + ;; + -nostartfiles|-nodefaultlibs|-nostdlib|-m*) + return + ;; + --eh-frame-hdr|-z|relro|now|noexecstack) + return + ;; + -Wl,*) + local IFS=, + local parts=(${{arg#-Wl,}}) + local part + for part in "${{parts[@]}}"; do + [[ -n "$part" ]] && add_arg "$part" + done + return + ;; + esac + lld_args+=("$arg") +}} + +for arg in "$@"; do + add_arg "$arg" +done + +linker_script="$(find_linker_script)" +if [[ -z "$linker_script" ]]; then + echo "failed to find linker.x in current linker search dirs for $target_name" >&2 + exit 1 +fi + +lld_args+=("-L$fake_dir" "${{link_mode_args[@]}}" "--gc-sections" "-znorelro" "-znostart-stop-gc" "-T$linker_script" "-u" "$entry_symbol") +exec rust-lld -flavor gnu "${{lld_args[@]}}" +"#, + shell_single_quote(&fake_lib_dir.display().to_string()), + shell_single_quote(target), + machine, + shell_single_quote(entry_symbol), + link_mode_args, + dynamic_platform, + )) +} + +fn lld_machine_for_std_target(target: &str) -> anyhow::Result<&'static str> { + if target.starts_with("x86_64-") { + Ok("elf_x86_64") + } else if target.starts_with("aarch64-") { + Ok("aarch64elf") + } else if target.starts_with("riscv64") { + Ok("elf64lriscv") + } else if target.starts_with("loongarch64-") { + Ok("elf64loongarch") + } else { + bail!("unsupported ArceOS std linker target `{target}`") + } +} + +fn toml_escape_string(value: &str) -> String { + value.replace('\\', "\\\\").replace('"', "\\\"") +} + +fn shell_single_quote(value: &str) -> String { + format!("'{}'", value.replace('\'', "'\\''")) +} + +fn short_content_hash(contents: &str) -> String { + let digest = Sha256::digest(contents.as_bytes()); + format!("{digest:x}").chars().take(16).collect() +} + +fn set_executable(path: &Path) -> anyhow::Result<()> { + #[cfg(unix)] + { + let mut permissions = fs::metadata(path)?.permissions(); + permissions.set_mode(0o755); + fs::set_permissions(path, permissions)?; + } + Ok(()) +} + fn std_build_dir() -> anyhow::Result { let dir = axbuild_tmp_dir(&crate::context::workspace_root_path()?).join("std"); fs::create_dir_all(&dir) @@ -669,7 +1117,32 @@ fn write_if_changed(path: &Path, contents: &str) -> anyhow::Result<()> { fs::create_dir_all(parent) .with_context(|| format!("failed to create parent dir {}", parent.display()))?; } - fs::write(path, contents).with_context(|| format!("failed to write {}", path.display())) + let tmp_path = temporary_sibling_path(path); + fs::write(&tmp_path, contents) + .with_context(|| format!("failed to write {}", tmp_path.display()))?; + if let Err(err) = fs::rename(&tmp_path, path) { + let _ = fs::remove_file(path); + fs::rename(&tmp_path, path).with_context(|| { + format!( + "failed to replace {} with {} after initial rename error: {err}", + path.display(), + tmp_path.display() + ) + })?; + } + Ok(()) +} + +fn temporary_sibling_path(path: &Path) -> PathBuf { + let file_name = path + .file_name() + .map(|name| name.to_string_lossy()) + .unwrap_or_else(|| "axbuild".into()); + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|duration| duration.as_nanos()) + .unwrap_or_default(); + path.with_file_name(format!(".{file_name}.{}.{}.tmp", std::process::id(), nanos)) } pub(crate) fn ensure_build_info(path: &Path, default: impl FnOnce() -> T) -> anyhow::Result<()> @@ -699,7 +1172,9 @@ pub(crate) fn load_build_info(path: &Path) -> anyhow::Result where T: DeserializeOwned, { - toml::from_str::(&std::fs::read_to_string(path)?) + let contents = std::fs::read_to_string(path)?; + reject_removed_std_field(path, &contents)?; + toml::from_str::(&contents) .with_context(|| format!("failed to parse build info {}", path.display())) } @@ -724,6 +1199,20 @@ fn build_info_declares_plat_dyn(content: &str) -> bool { .is_some_and(|table| table.contains_key("plat_dyn") || table.contains_key("plat-dyn")) } +pub(crate) fn reject_removed_std_field(path: &Path, contents: &str) -> anyhow::Result<()> { + if let Ok(table) = toml::from_str::(contents) + && table.contains_key("std") + { + bail!( + "build config {} uses removed `std` field; std-aware Rust builds are now the default, \ + remove `std = ...`", + path.display() + ); + } + + Ok(()) +} + fn is_false(value: &bool) -> bool { !*value } @@ -772,14 +1261,79 @@ fn normalize_std_feature(feature: &str) -> String { "ax-std" | "ax-feat" => normalized, feature if feature.starts_with("ax-std/") || feature.starts_with("ax-feat/") => feature .split_once('/') - .map(|(_, feature)| format!("arceos-rust/{feature}")) + .map(|(_, feature)| feature.to_string()) .unwrap_or_else(|| normalized.clone()), feature if feature.starts_with("ax-hal/") || feature.starts_with("ax-driver/") => { normalized } - feature if feature.starts_with("arceos-rust/") => normalized, - feature => format!("arceos-rust/{feature}"), + feature => feature.to_string(), + } +} + +fn is_axstd_std_check_feature(feature: &str) -> bool { + matches!(feature, "ax-std" | "ax-feat") + || feature.starts_with("ax-hal/") + || feature.starts_with("ax-driver/") + || is_known_axstd_feature(feature) +} + +fn std_feature_stays_on_app(feature: &str, app_features: &[String]) -> bool { + if feature == "arceos" { + return true; } + !is_axstd_std_check_feature(feature) + || app_features + .iter() + .any(|app_feature| app_feature == feature) +} + +fn is_known_axstd_feature(feature: &str) -> bool { + matches!( + feature, + "smp" + | "fp-simd" + | "uspace" + | "hv" + | "irq" + | "ipi" + | "myplat" + | "defplat" + | "plat-dyn" + | "alloc" + | "paging" + | "dma" + | "tls" + | "multitask" + | "lockdep" + | "task-ext" + | "sched-fifo" + | "sched-rr" + | "sched-cfs" + | "stack-guard-page" + | "fs" + | "ext4fs" + | "fatfs" + | "net" + | "dns" + | "display" + | "input" + | "rtc" + | "backtrace" + | "dwarf" + | "std-compat" + ) +} + +fn is_log_level_feature(feature: &str) -> bool { + matches!( + feature, + "log-level-off" + | "log-level-error" + | "log-level-warn" + | "log-level-info" + | "log-level-debug" + | "log-level-trace" + ) } pub(crate) fn parse_makefile_features(input: &str) -> Vec { @@ -802,71 +1356,36 @@ pub(crate) fn makefile_features_from_env() -> Vec { pub(crate) fn apply_makefile_features( build_info: &mut BuildInfo, - package: &str, + _package: &str, makefile_features: &[String], ) { if makefile_features.is_empty() { return; } - let prefix_family = workspace_metadata() - .and_then(|metadata| detect_ax_feature_prefix_family(package, &metadata)) - .map_err(|err| err.context("failed to load workspace metadata")); - apply_makefile_features_with_prefix_family( - build_info, - package, - makefile_features, - prefix_family, - ); + apply_std_makefile_features(build_info, makefile_features); } pub(crate) fn apply_makefile_features_with_metadata( build_info: &mut BuildInfo, - package: &str, + _package: &str, makefile_features: &[String], - metadata: &Metadata, + _metadata: &Metadata, ) { - apply_makefile_features_with_prefix_family( - build_info, - package, - makefile_features, - detect_ax_feature_prefix_family(package, metadata), - ); + apply_std_makefile_features(build_info, makefile_features); } +#[cfg(test)] fn apply_makefile_features_with_prefix_family( build_info: &mut BuildInfo, - package: &str, + _package: &str, makefile_features: &[String], - prefix_family: anyhow::Result, + _prefix_family: anyhow::Result, ) { if makefile_features.is_empty() { return; } - if build_info.std_build { - apply_std_makefile_features(build_info, makefile_features); - return; - } - - let prefix_family = build_info.resolve_ax_feature_prefix_family(package, prefix_family); - - for feature in makefile_features { - let normalized = normalize_legacy_feature_alias(feature); - let mapped = - if normalized.contains('/') || matches!(normalized.as_str(), "ax-std" | "ax-feat") { - normalized - } else { - format!("{}{}", prefix_family.prefix(), normalized) - }; - - if !build_info - .features - .iter() - .any(|existing| existing == &mapped) - { - build_info.features.push(mapped); - } - } + apply_std_makefile_features(build_info, makefile_features); } fn apply_std_makefile_features(build_info: &mut BuildInfo, makefile_features: &[String]) { @@ -1079,6 +1598,13 @@ struct AxplatMetadata { dynamic: bool, } +#[derive(Debug, Clone, Default, Deserialize)] +#[serde(default)] +#[serde(rename_all = "kebab-case")] +struct AxstdMetadata { + features: Vec, +} + #[derive(Debug, Clone)] struct PlatformPackage { package: String, @@ -1094,6 +1620,21 @@ fn platform_metadata(package: &Package) -> Option { .and_then(|metadata| serde_json::from_value(metadata).ok()) } +fn axstd_metadata(package: &Package) -> Option { + package + .metadata + .get("axstd") + .cloned() + .and_then(|metadata| serde_json::from_value(metadata).ok()) +} + +fn std_package_metadata_features(package: &str, metadata: &Metadata) -> Vec { + metadata_package(metadata, package) + .and_then(axstd_metadata) + .map(|metadata| metadata.features) + .unwrap_or_default() +} + fn platform_packages(metadata: &Metadata) -> Vec { metadata .packages @@ -1561,23 +2102,36 @@ mod tests { "dns".to_string(), ]; - pass_std_build_nested_features(&mut envs, &mut features); + pass_std_build_nested_features( + &mut envs, + &mut features, + &["dns".to_string()], + &[ + "dns".to_string(), + "loongarch64-qemu-virt".to_string(), + "plat-dyn".to_string(), + "virtio-blk".to_string(), + "virtio-net".to_string(), + ], + ); - assert_eq!(features, vec!["dns".to_string()]); assert_eq!( - envs.get("ARCEOS_RUST_FEATURES"), - Some( - &"ax-hal/loongarch64-qemu-virt,ax-driver/plat-dyn,ax-driver/virtio-blk,ax-driver/\ - virtio-net" - .to_string() - ) + features, + vec![ + "ax-std/dns".to_string(), + "ax-std/loongarch64-qemu-virt".to_string(), + "ax-std/plat-dyn".to_string(), + "ax-std/virtio-blk".to_string(), + "ax-std/virtio-net".to_string(), + "dns".to_string(), + ] ); + assert!(envs.is_empty()); } #[test] fn std_build_runtime_features_are_passed_through_after_normalization() { let mut info = BuildInfo { - std_build: true, features: vec![ "ax-hal/loongarch64-qemu-virt".to_string(), "dns".to_string(), @@ -1587,12 +2141,226 @@ mod tests { info.resolve_std_features(); let mut envs = HashMap::new(); - pass_std_build_nested_features(&mut envs, &mut info.features); + pass_std_build_nested_features( + &mut envs, + &mut info.features, + &["dns".to_string()], + &["dns".to_string(), "loongarch64-qemu-virt".to_string()], + ); + + assert_eq!( + info.features, + vec![ + "ax-std/dns".to_string(), + "ax-std/loongarch64-qemu-virt".to_string(), + "dns".to_string() + ] + ); + assert!(envs.is_empty()); + } + + #[test] + fn std_build_cargo_config_builds_fake_lib_before_app() { + let metadata = repo_metadata(); + let cargo = BuildInfo { + features: vec![ + "ax-std".to_string(), + "ax-hal/x86-pc".to_string(), + "fs".to_string(), + "dns".to_string(), + ], + ..BuildInfo::default() + } + .into_prepared_base_cargo_config_with_metadata( + "arceos-std-helloworld", + "x86_64-unknown-none", + None, + &metadata, + ) + .unwrap(); + + assert!( + cargo + .target + .ends_with("scripts/targets/std/x86_64-unknown-linux-musl.json") + ); + assert!( + cargo + .args + .windows(2) + .any(|pair| pair == ["-Z", "json-target-spec"]) + ); + assert_eq!( + cargo.features, + vec![ + "arceos".to_string(), + "ax-std/dns".to_string(), + "ax-std/fs".to_string(), + "ax-std/x86-pc".to_string(), + ] + ); + assert!(!cargo.to_bin); + assert_eq!( + cargo.env.get("CARGO_UNSTABLE_JSON_TARGET_SPEC"), + Some(&"true".to_string()) + ); + assert!(!cargo.env.contains_key("AXSTD_STD_DEFAULT_FEATURES")); + assert_eq!( + cargo.env.get("AX_TARGET"), + Some(&"x86_64-unknown-none".to_string()) + ); + assert!( + cargo + .extra_config + .as_ref() + .is_some_and(|path| path.ends_with("config-x86_64-unknown-linux-musl-static.toml")) + ); + assert_eq!(cargo.pre_build_cmds.len(), 1); + let prebuild = fs::read_to_string(&cargo.pre_build_cmds[0]).unwrap(); + assert!(prebuild.contains("target_name='x86_64-unknown-linux-musl'")); + assert!(!prebuild.contains("cargo}\" build -p ax-std")); + assert!(!prebuild.contains("libax_std.a")); + assert!(prebuild.contains("libc.a")); + assert!(prebuild.contains("archive_tool()")); + assert!(prebuild.contains("$(rustc --print sysroot)")); + assert!(prebuild.contains("create_empty_archive \"$fake_dir/libc.a\"")); + assert!(prebuild.contains("create_empty_archive \"$fake_dir/libunwind.a\"")); + } + + #[test] + fn load_build_info_rejects_removed_std_field() { + let dir = tempdir().unwrap(); + let path = dir.path().join("build.toml"); + fs::write( + &path, + r#" +std = true +features = [] +log = "Info" + +[env] +AX_IP = "10.0.2.15" +"#, + ) + .unwrap(); + + let err = load_build_info::(&path).unwrap_err(); + + assert!( + err.to_string().contains("uses removed `std` field"), + "{err:#}" + ); + } + + #[test] + fn std_build_uses_package_axstd_metadata_for_ax_std_features() { + let workspace = temp_workspace("std-app", "").unwrap(); + let app_manifest = workspace.join("app/Cargo.toml"); + fs::write( + &app_manifest, + "[package]\nname = \"std-app\"\nversion = \"0.1.0\"\nedition = \ + \"2024\"\n\n[package.metadata.axstd]\nfeatures = [\"multitask\", \"net\", \ + \"log-level-debug\"]\n", + ) + .unwrap(); + + let metadata = metadata_for_manifest(&workspace.join("Cargo.toml")); + let mut info = BuildInfo { + features: vec!["dns".to_string()], + ..BuildInfo::default() + }; + + info.resolve_std_features_with_metadata("std-app", "x86_64-unknown-none", false, &metadata); + let mut envs = HashMap::new(); + pass_std_build_nested_features( + &mut envs, + &mut info.features, + &[], + &[ + "dns".to_string(), + "multitask".to_string(), + "net".to_string(), + "x86-pc".to_string(), + ], + ); - assert_eq!(info.features, vec!["arceos-rust/dns".to_string()]); assert_eq!( - envs.get("ARCEOS_RUST_FEATURES"), - Some(&"ax-hal/loongarch64-qemu-virt".to_string()) + info.features, + vec![ + "ax-std/dns".to_string(), + "ax-std/multitask".to_string(), + "ax-std/net".to_string(), + "ax-std/x86-pc".to_string(), + ] + ); + assert!(envs.is_empty()); + } + + #[test] + fn std_build_auto_enables_app_arceos_feature_when_declared() { + let metadata = repo_metadata(); + let cargo = BuildInfo { + features: Vec::new(), + ..BuildInfo::default() + } + .into_prepared_base_cargo_config_with_metadata( + "arceos-std-helloworld", + "x86_64-unknown-none", + None, + &metadata, + ) + .unwrap(); + + assert!(cargo.features.contains(&"arceos".to_string())); + } + + #[test] + fn std_build_does_not_inject_arceos_feature_when_app_lacks_it() { + let mut features = vec!["dns".to_string()]; + + inject_arceos_feature_for_std_build(&mut features, &["dns".to_string()]); + + assert_eq!(features, vec!["dns".to_string()]); + } + + #[test] + fn std_build_plat_dyn_uses_dynamic_platform_features_without_static_hal_platform() { + let metadata = repo_metadata(); + let cargo = BuildInfo { + plat_dyn: true, + features: vec![ + "ax-std".to_string(), + "ax-driver/virtio-net".to_string(), + "net".to_string(), + ], + ..BuildInfo::default() + } + .into_prepared_base_cargo_config_with_metadata( + "test-arceos-std-httpclient", + "aarch64-unknown-none-softfloat", + None, + &metadata, + ) + .unwrap(); + + assert!( + cargo + .target + .ends_with("scripts/targets/std/pie/aarch64-unknown-linux-musl.json") + ); + assert!(cargo.features.contains(&"ax-std/plat-dyn".to_string())); + assert!(cargo.features.contains(&"ax-std/smp".to_string())); + assert!(cargo.features.contains(&"ax-std/virtio-net".to_string())); + assert!(cargo.features.contains(&"ax-std/net".to_string())); + assert!(cargo.to_bin); + assert_eq!( + cargo.env.get("AX_TARGET"), + Some(&"aarch64-unknown-none-softfloat".to_string()) + ); + assert!( + !cargo + .features + .contains(&"ax-std/aarch64-qemu-virt".to_string()) ); } @@ -1600,67 +2368,421 @@ mod tests { fn std_build_aarch64_defaults_to_dynamic_platform() { let metadata = repo_metadata(); let cargo = BuildInfo { - std_build: true, ..BuildInfo::default_for_target("aarch64-unknown-none-softfloat") } .into_prepared_base_cargo_config_with_metadata( - "test-arceos-std-app", + "test-arceos-std-helloworld", "aarch64-unknown-none-softfloat", None, &metadata, ) .unwrap(); - assert!(cargo.features.contains(&"arceos-rust/plat-dyn".to_string())); - assert!(!cargo.env.contains_key("ARCEOS_RUST_FEATURES")); - assert!(!cargo.env.contains_key("ARCEOS_RUST_CONFIG")); + assert!( + cargo + .target + .ends_with("scripts/targets/std/pie/aarch64-unknown-linux-musl.json") + ); + assert!(!cargo.env.contains_key("AX_CONFIG_PATH")); + assert!(cargo.features.contains(&"ax-std/plat-dyn".to_string())); + assert!(cargo.features.contains(&"ax-std/smp".to_string())); + assert!( + !cargo + .features + .contains(&"ax-std/aarch64-qemu-virt".to_string()) + ); let config = std::fs::read_to_string(cargo.extra_config.unwrap()).unwrap(); - assert!(config.contains("link-arg=-pie")); - assert!(config.contains("link-arg=-Tlinker.x")); - assert!(!config.contains("link-arg=-Taxplat.x")); + assert!(config.contains("arceos_std")); + assert!(!config.contains("relocation-model")); + assert!(!config.contains("code-model")); + } + + #[test] + fn std_build_target_maps_arceos_targets_to_linux_musl_by_link_mode() { + let cases = [ + ( + "x86_64-unknown-none", + false, + "scripts/targets/std/x86_64-unknown-linux-musl.json", + ), + ( + "aarch64-unknown-none-softfloat", + false, + "scripts/targets/std/aarch64-unknown-linux-musl.json", + ), + ( + "aarch64-unknown-none-softfloat", + true, + "scripts/targets/std/pie/aarch64-unknown-linux-musl.json", + ), + ( + "riscv64gc-unknown-none-elf", + false, + "scripts/targets/std/riscv64gc-unknown-linux-musl.json", + ), + ( + "riscv64gc-unknown-none-elf", + true, + "scripts/targets/std/pie/riscv64gc-unknown-linux-musl.json", + ), + ( + "loongarch64-unknown-none-softfloat", + false, + "scripts/targets/std/loongarch64-unknown-linux-musl.json", + ), + ]; + + for (bare_target, plat_dyn, expected_path) in cases { + let mapped = std_build_target_for(bare_target, plat_dyn).unwrap(); + assert!(mapped.target.ends_with(expected_path)); + assert!( + mapped + .cargo_args + .windows(2) + .any(|pair| pair == ["-Z", "json-target-spec"]) + ); + assert_eq!( + mapped.env.get("CARGO_UNSTABLE_JSON_TARGET_SPEC"), + Some(&"true".to_string()) + ); + } + + let riscv = std_build_target_for("riscv64gc-unknown-none-elf", true).unwrap(); + assert_eq!( + riscv.env.get("CC_riscv64gc_unknown_linux_musl"), + Some(&"riscv64-linux-musl-cc".to_string()) + ); + assert_eq!( + riscv.env.get("AR_riscv64gc_unknown_linux_musl"), + Some(&"riscv64-linux-musl-ar".to_string()) + ); + if let Some(bindgen_args) = riscv + .env + .get("BINDGEN_EXTRA_CLANG_ARGS_riscv64gc_unknown_linux_musl") + { + assert!(bindgen_args.contains("--target=riscv64-linux-musl")); + assert!(bindgen_args.contains("--sysroot=")); + } + } + + #[test] + fn std_c_toolchain_env_does_not_require_installed_cross_compiler() { + let env = std_c_toolchain_env("riscv64gc-unknown-linux-musl", "definitely-missing-musl"); + + assert_eq!( + env.get("CC_riscv64gc_unknown_linux_musl"), + Some(&"definitely-missing-musl-cc".to_string()) + ); + assert_eq!( + env.get("AR_riscv64gc_unknown_linux_musl"), + Some(&"definitely-missing-musl-ar".to_string()) + ); + assert!(!env.contains_key("BINDGEN_EXTRA_CLANG_ARGS_riscv64gc_unknown_linux_musl")); + } + + #[test] + fn std_target_specs_keep_none_kernel_fields_with_minimal_std_identity() { + for (bare_target, std_target, plat_dyn) in [ + ("x86_64-unknown-none", "x86_64-unknown-linux-musl", false), + ( + "aarch64-unknown-none-softfloat", + "aarch64-unknown-linux-musl", + false, + ), + ( + "aarch64-unknown-none-softfloat", + "aarch64-unknown-linux-musl", + true, + ), + ( + "riscv64gc-unknown-none-elf", + "riscv64gc-unknown-linux-musl", + false, + ), + ( + "riscv64gc-unknown-none-elf", + "riscv64gc-unknown-linux-musl", + true, + ), + ( + "loongarch64-unknown-none-softfloat", + "loongarch64-unknown-linux-musl", + false, + ), + ] { + let workspace = crate::context::workspace_root_path().unwrap(); + let base_path = workspace.join(cargo_target_json_path(bare_target, plat_dyn).unwrap()); + let std_path = workspace.join(std_target_json_path(std_target, plat_dyn)); + assert!( + std_path.exists(), + "missing std target spec {}", + std_path.display() + ); + + let base_spec: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&base_path).unwrap()).unwrap(); + let mut std_spec: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&std_path).unwrap()).unwrap(); + + assert_eq!(std_spec["os"], "linux"); + assert_eq!(std_spec["env"], "musl"); + assert_eq!(std_spec["target-family"], serde_json::json!(["unix"])); + assert_eq!(std_spec["has-thread-local"], true); + let expected_tls_model = if std_target.starts_with("riscv64") { + "local-exec" + } else { + "initial-exec" + }; + assert_eq!(std_spec["tls-model"], expected_tls_model); + assert_eq!(std_spec["metadata"]["std"], true); + assert!( + std_spec + .pointer("/metadata/description") + .and_then(|value| value.as_str()) + .is_some_and(|description| description.contains("musl identity")) + ); + assert_eq!(std_spec["llvm-target"], base_spec["llvm-target"]); + + std_spec.as_object_mut().unwrap().remove("os"); + std_spec.as_object_mut().unwrap().remove("env"); + std_spec.as_object_mut().unwrap().remove("target-family"); + std_spec.as_object_mut().unwrap().remove("has-thread-local"); + std_spec.as_object_mut().unwrap().remove("tls-model"); + std_spec["metadata"]["description"] = base_spec["metadata"]["description"].clone(); + std_spec["metadata"]["std"] = base_spec["metadata"]["std"].clone(); + + assert_eq!( + std_spec, base_spec, + "std target {std_target} must keep all kernel fields from {bare_target}" + ); + } + + let loongarch = serde_json::from_str::( + &fs::read_to_string(crate::context::workspace_root_path().unwrap().join( + std_target_json_path("loongarch64-unknown-linux-musl", false), + )) + .unwrap(), + ) + .unwrap(); + assert_eq!(loongarch["llvm-abiname"], "lp64s"); + assert_eq!(loongarch["features"], "-f,-d"); + } + + #[test] + fn std_target_specs_do_not_import_linux_userspace_link_fields() { + for (target, plat_dyn) in [ + ("x86_64-unknown-linux-musl", false), + ("aarch64-unknown-linux-musl", false), + ("aarch64-unknown-linux-musl", true), + ("riscv64gc-unknown-linux-musl", false), + ("riscv64gc-unknown-linux-musl", true), + ("loongarch64-unknown-linux-musl", false), + ] { + let path = crate::context::workspace_root_path() + .unwrap() + .join(std_target_json_path(target, plat_dyn)); + assert!(path.exists(), "missing std target spec {}", path.display()); + + let spec: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap(); + assert!(spec.get("dynamic-linking").is_none()); + assert!(spec.get("has-rpath").is_none()); + assert!(spec.get("pre-link-objects-fallback").is_none()); + assert!(spec.get("post-link-objects-fallback").is_none()); + assert!(spec.get("crt-static-default").is_none()); + assert!(spec.get("crt-static-respected").is_none()); + assert!(spec.get("supported-split-debuginfo").is_none()); + assert!(spec.get("supports-xray").is_none()); + } + } + + #[test] + fn std_cargo_config_uses_linux_musl_wrapper_and_arceos_std_cfg() { + let fake_dir = std_fake_lib_dir("x86_64-unknown-linux-musl").unwrap(); + let wrapper = + std_linker_wrapper_path("x86_64-unknown-linux-musl", &fake_dir, false).unwrap(); + let config = std_cargo_config_path("x86_64-unknown-linux-musl", &wrapper, false).unwrap(); + let config = fs::read_to_string(config).unwrap(); + + assert!(config.contains("build-std = [\"std\", \"panic_abort\"]")); + assert!(config.contains("build-std-features = []")); + assert!(config.contains("[target.x86_64-unknown-linux-musl]")); + assert!(config.contains("arceos_std")); + assert!(config.contains("--check-cfg=cfg(arceos_std)")); + assert!(config.contains(&wrapper.display().to_string())); + assert!(!config.contains("relocation-model")); + assert!(!config.contains("code-model")); + assert!(!config.contains("target_os = \"hermit\"")); + } + + #[test] + fn std_cargo_config_leaves_kernel_codegen_to_target_spec() { + let fake_dir = std_fake_lib_dir("loongarch64-unknown-linux-musl").unwrap(); + let wrapper = + std_linker_wrapper_path("loongarch64-unknown-linux-musl", &fake_dir, false).unwrap(); + let config = + std_cargo_config_path("loongarch64-unknown-linux-musl", &wrapper, false).unwrap(); + let config = fs::read_to_string(config).unwrap(); + + assert!(config.contains("arceos_std")); + assert!(!config.contains("relocation-model")); + assert!(!config.contains("code-model")); + } + + #[test] + fn std_cargo_config_uses_static_link_mode_without_codegen_override() { + let fake_dir = std_fake_lib_dir("aarch64-unknown-linux-musl").unwrap(); + let wrapper = + std_linker_wrapper_path("aarch64-unknown-linux-musl", &fake_dir, false).unwrap(); + let app_config = + std_cargo_config_path("aarch64-unknown-linux-musl", &wrapper, false).unwrap(); + + let config = fs::read_to_string(app_config).unwrap(); + assert!(config.contains("arceos_std")); + assert!(!config.contains("relocation-model")); + assert!(!config.contains("code-model")); + } + + #[test] + fn std_cargo_config_uses_dynamic_link_mode_without_codegen_override() { + let fake_dir = std_fake_lib_dir("aarch64-unknown-linux-musl").unwrap(); + let wrapper = + std_linker_wrapper_path("aarch64-unknown-linux-musl", &fake_dir, true).unwrap(); + let app_config = + std_cargo_config_path("aarch64-unknown-linux-musl", &wrapper, true).unwrap(); + + let config = fs::read_to_string(app_config).unwrap(); + assert!(config.contains("arceos_std")); + assert!(!config.contains("relocation-model")); + assert!(!config.contains("code-model")); + } + + #[test] + fn std_cargo_config_and_wrapper_paths_are_partitioned_by_link_mode() { + let fake_dir = std_fake_lib_dir("aarch64-unknown-linux-musl").unwrap(); + let static_wrapper = + std_linker_wrapper_path("aarch64-unknown-linux-musl", &fake_dir, false).unwrap(); + let dynamic_wrapper = + std_linker_wrapper_path("aarch64-unknown-linux-musl", &fake_dir, true).unwrap(); + let static_config = + std_cargo_config_path("aarch64-unknown-linux-musl", &static_wrapper, false).unwrap(); + let dynamic_config = + std_cargo_config_path("aarch64-unknown-linux-musl", &dynamic_wrapper, true).unwrap(); + + assert_ne!(static_wrapper, dynamic_wrapper); + assert_ne!(static_config, dynamic_config); + assert!( + static_wrapper + .display() + .to_string() + .ends_with("linker-aarch64-unknown-linux-musl-static.sh") + ); + assert!( + dynamic_wrapper + .display() + .to_string() + .ends_with("linker-aarch64-unknown-linux-musl-dynamic.sh") + ); + assert!( + static_config + .display() + .to_string() + .ends_with("config-aarch64-unknown-linux-musl-static.toml") + ); + assert!( + dynamic_config + .display() + .to_string() + .ends_with("config-aarch64-unknown-linux-musl-dynamic.toml") + ); + } + + #[test] + fn std_linker_wrapper_filters_crt_and_replaces_fixed_libs() { + let fake_dir = std_fake_lib_dir("x86_64-unknown-linux-musl").unwrap(); + let wrapper = + std_linker_wrapper_path("x86_64-unknown-linux-musl", &fake_dir, false).unwrap(); + let wrapper = fs::read_to_string(wrapper).unwrap(); + + assert!(wrapper.contains("rust-lld")); + assert!(wrapper.contains("link_search_dirs=()")); + assert!(wrapper.contains("add_link_search_dir")); + assert!(wrapper.contains("find_linker_script")); + assert!(wrapper.contains("failed to find linker.x in current linker search dirs")); + assert!(wrapper.contains("entry_symbol='_start'")); + assert!(wrapper.contains("link_mode_args=(\"-static\" \"-no-pie\")")); + assert!(wrapper.contains("dynamic_platform=0")); + assert!(wrapper.contains("crtbegin")); + assert!(wrapper.contains("static-pie")); + assert!(wrapper.contains("-flavor")); + assert!(wrapper.contains("-T*")); + assert!(wrapper.contains("--eh-frame-hdr")); + assert!(wrapper.contains("relro")); + assert!(wrapper.contains("noexecstack")); + assert!(wrapper.contains("-znorelro")); + assert!(wrapper.contains("libc.a")); + assert!(wrapper.contains("libunwind.a")); + assert!(wrapper.contains("-lgcc_s|-lgcc")); + assert!(!wrapper.contains("--whole-archive")); + assert!(wrapper.contains("-u")); + assert!(wrapper.contains("_start")); + } + + #[test] + fn std_linker_wrapper_uses_explicit_dynamic_platform_mode() { + let fake_dir = std_fake_lib_dir("aarch64-unknown-linux-musl").unwrap(); + let wrapper = + std_linker_wrapper_path("aarch64-unknown-linux-musl", &fake_dir, true).unwrap(); + let wrapper = fs::read_to_string(wrapper).unwrap(); + + assert!(wrapper.contains("find_linker_script")); + assert!(!wrapper.contains("latest_build_output_script axplat.x")); + assert!(wrapper.contains("entry_symbol='_head'")); + assert!(wrapper.contains("link_mode_args=(\"-pie\")")); + assert!(wrapper.contains("dynamic_platform=1")); } #[test] fn std_build_dynamic_x86_64_prepares_binary_artifact() { let metadata = repo_metadata(); let cargo = BuildInfo { - std_build: true, ..BuildInfo::default_for_target("x86_64-unknown-none") } .into_prepared_base_cargo_config_with_metadata( - "test-arceos-std-app", + "test-arceos-std-helloworld", "x86_64-unknown-none", None, &metadata, ) .unwrap(); - assert_eq!(cargo.target, "x86_64-unknown-hermit"); + assert!( + cargo + .target + .ends_with("scripts/targets/std/pie/x86_64-unknown-linux-musl.json") + ); assert!(cargo.to_bin); - assert!(cargo.features.contains(&"arceos-rust/plat-dyn".to_string())); - assert!(!cargo.env.contains_key("ARCEOS_RUST_FEATURES")); + assert!(cargo.features.contains(&"ax-std/plat-dyn".to_string())); + assert!(cargo.features.contains(&"ax-std/smp".to_string())); + assert_eq!( + cargo.env.get("AX_TARGET"), + Some(&"x86_64-unknown-none".to_string()) + ); } #[test] fn std_build_plat_dyn_stays_on_arceos_rust_dependency() { let mut info = BuildInfo { - std_build: true, features: vec!["ax-feat/plat-dyn".to_string(), "alloc".to_string()], ..BuildInfo::default() }; info.resolve_std_features(); let mut envs = HashMap::new(); - pass_std_build_nested_features(&mut envs, &mut info.features); + pass_std_build_nested_features(&mut envs, &mut info.features, &[], &["alloc".to_string()]); - assert_eq!( - info.features, - vec![ - "arceos-rust/alloc".to_string(), - "arceos-rust/plat-dyn".to_string() - ] - ); - assert!(!envs.contains_key("ARCEOS_RUST_FEATURES")); + assert_eq!(info.features, vec!["ax-std/alloc".to_string()]); + assert!(envs.is_empty()); } #[test] @@ -1820,30 +2942,37 @@ mod tests { } #[test] - fn std_build_maps_arceos_features_to_arceos_rust_dependency() { + fn std_build_maps_arceos_features_to_ax_std_dependency() { let mut info = BuildInfo { - std_build: true, features: vec![ "ax-std".to_string(), "lockdep".to_string(), - "axstd/smp".to_string(), + "ax-std/smp".to_string(), ], ..BuildInfo::default() }; info.resolve_std_features(); + let mut envs = HashMap::new(); + pass_std_build_nested_features( + &mut envs, + &mut info.features, + &[], + &["lockdep".to_string(), "smp".to_string()], + ); - assert!(info.features.contains(&"ax-std".to_string())); - assert!(info.features.contains(&"arceos-rust/lockdep".to_string())); - assert!(info.features.contains(&"arceos-rust/smp".to_string())); - assert!(!info.features.contains(&"ax-std/lockdep".to_string())); + assert_eq!( + info.features, + vec!["ax-std/lockdep".to_string(), "ax-std/smp".to_string()] + ); + assert!(envs.is_empty()); + assert!(!envs.values().any(|value| value.contains("arceos"))); assert!(!info.features.contains(&"lockdep".to_string())); } #[test] - fn makefile_features_use_arceos_rust_prefix_for_std_build() { + fn makefile_features_use_ax_std_dependency_for_std_build() { let mut info = BuildInfo { - std_build: true, features: Vec::new(), ..BuildInfo::default() }; @@ -1855,7 +2984,17 @@ mod tests { Err(anyhow::anyhow!("std test packages do not depend on ax-std")), ); - assert_eq!(info.features, vec!["arceos-rust/lockdep".to_string()]); + info.resolve_std_features(); + let mut envs = HashMap::new(); + pass_std_build_nested_features( + &mut envs, + &mut info.features, + &[], + &["lockdep".to_string()], + ); + + assert_eq!(info.features, vec!["ax-std/lockdep".to_string()]); + assert!(envs.is_empty()); } #[test] diff --git a/scripts/axbuild/src/clippy.rs b/scripts/axbuild/src/clippy.rs index cb05c8def3..1af4e16961 100644 --- a/scripts/axbuild/src/clippy.rs +++ b/scripts/axbuild/src/clippy.rs @@ -15,8 +15,10 @@ use crate::support::process::run_cargo_status_with_env; const DEFAULT_FEATURE: &str = "default"; const AX_CONFIG_PATH_ENV: &str = "AX_CONFIG_PATH"; const AXCONFIG_FILE: &str = "axconfig.toml"; -const ARCEOS_RUST_PACKAGE: &str = "arceos-rust"; -const ARCEOS_RUST_CLIPPY_TARGET: &str = "x86_64-unknown-none"; +const AXSTD_STD_PACKAGE: &str = "ax-std"; +const AXSTD_STD_DEFAULT_FEATURE: &str = "default"; +const AXSTD_STD_CLIPPY_FEATURES: &str = "x86-pc,fs,multitask,irq,net"; +const AXSTD_STD_CLIPPY_TARGET: &str = "x86_64-unknown-none"; const DOCS_RS_METADATA: &str = "docs.rs"; const DOCS_METADATA: &str = "docs"; const RS_METADATA: &str = "rs"; @@ -387,6 +389,18 @@ impl ClippyCheck { if matches!(self.deps_mode, ClippyDepsMode::NoDeps) { args.insert(1, "--no-deps".into()); } + if self.package == AXSTD_STD_PACKAGE + && matches!(&self.kind, ClippyCheckKind::Feature(feature) if feature == AXSTD_STD_DEFAULT_FEATURE) + { + args = vec![ + "clippy".into(), + "-p".into(), + self.package.clone(), + "--no-default-features".into(), + "--features".into(), + AXSTD_STD_CLIPPY_FEATURES.into(), + ]; + } if let Some(target) = &self.target { args.extend(["--target".into(), target.clone()]); } @@ -476,20 +490,16 @@ fn skip_unsupported_packages(packages: Vec) -> Vec