diff --git a/Cargo.lock b/Cargo.lock index 9b901252d6..da50ee546c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,35 +248,11 @@ dependencies = [ "num-traits", ] -[[package]] -name = "arceos-rust" -version = "0.1.2" -dependencies = [ - "walkdir", -] - -[[package]] -name = "arceos-rust-interface" -version = "1.1.0" -dependencies = [ - "ax-api", - "ax-driver", - "ax-errno 0.6.0", - "ax-feat", - "ax-hal", - "ax-io", - "ax-kspin", - "ax-posix-api", - "ax-runtime", - "log", - "rand 0.9.4", -] - [[package]] name = "arceos-std-arce_agent" version = "0.2.0" dependencies = [ - "arceos-rust", + "ax-std", "env_logger", "log", "minreq", @@ -288,42 +264,42 @@ dependencies = [ name = "arceos-std-helloworld" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "arceos-std-httpclient" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "arceos-std-httpserver" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "arceos-std-io_test" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "arceos-std-thread_test" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "arceos-std-tokio_test" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", "tokio", ] @@ -1333,12 +1309,18 @@ dependencies = [ name = "ax-std" version = "0.5.16" dependencies = [ + "ax-alloc", "ax-api", + "ax-driver", "ax-errno 0.6.0", "ax-feat", + "ax-hal", "ax-io", "ax-kspin", "ax-lazyinit", + "ax-posix-api", + "ax-runtime", + "libc", "lock_api", "spin 0.12.0", ] @@ -7889,6 +7871,7 @@ dependencies = [ "ax-driver", "ax-feat", "ax-hal", + "ax-std", "axbuild", "axplat-dyn", "clap", @@ -8169,7 +8152,7 @@ dependencies = [ name = "test-arceos-std-arce_agent" version = "0.2.0" dependencies = [ - "arceos-rust", + "ax-std", "env_logger", "log", "minreq", @@ -8181,42 +8164,42 @@ dependencies = [ name = "test-arceos-std-helloworld" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "test-arceos-std-httpclient" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "test-arceos-std-httpserver" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "test-arceos-std-io_test" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "test-arceos-std-thread_test" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", ] [[package]] name = "test-arceos-std-tokio_test" version = "0.1.0" dependencies = [ - "arceos-rust", + "ax-std", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index cfcd8d2802..30f142aa98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,6 @@ members = [ "os/arceos/tools/deptool", "os/arceos/tools/raspi4/chainloader", "os/arceos/ulib/*", - "os/arceos/ulib/arceos-rust/lib", "os/axvisor", "scripts/axbuild", "test-suit/arceos/rust", @@ -133,7 +132,6 @@ members = [ aarch64_sysreg = { version = "0.3.8", path = "virtualization/aarch64_sysreg" } arm-scmi-rs = { version = "0.1.3", path = "drivers/firmware/arm-scmi-rs" } arceos-test-suit = { version = "0.3.1", path = "test-suit/arceos/rust" } -arceos-rust = { version = "0.1.2", path = "os/arceos/ulib/arceos-rust", default-features = false } arm_vcpu = { version = "0.5.10", path = "virtualization/arm_vcpu" } arm_vgic = { version = "0.4.11", path = "virtualization/arm_vgic" } ax-alloc = { version = "0.8.0", path = "os/arceos/modules/axalloc", default-features = false } @@ -177,6 +175,7 @@ ax-lockdep = { version = "0.1.2", path = "components/lockdep" } ax-kspin = { version = "0.3.10", path = "components/kspin" } ax-lazyinit = { version = "0.4.8", path = "components/ax-lazyinit" } ax-libc = { version = "0.5.16", path = "os/arceos/ulib/axlibc" } +libc = { version = "0.2", default-features = false } ax-linked-list-r4l = { version = "0.5.5", path = "components/linked_list_r4l" } ax-log = { version = "0.5.13", path = "os/arceos/modules/axlog" } ax-memory-addr = { version = "0.6.9", path = "memory/memory_addr" } 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/src/x86_64/asm.rs b/components/axcpu/src/x86_64/asm.rs index 92be733393..053b961d44 100644 --- a/components/axcpu/src/x86_64/asm.rs +++ b/components/axcpu/src/x86_64/asm.rs @@ -9,22 +9,12 @@ use x86_64::instructions::interrupts; /// Allows the current CPU to respond to interrupts. #[inline] pub fn enable_irqs() { - #[cfg(not(target_os = "none"))] - { - warn!("enable_irqs: not implemented"); - } - #[cfg(target_os = "none")] interrupts::enable() } /// Makes the current CPU to ignore interrupts. #[inline] pub fn disable_irqs() { - #[cfg(not(target_os = "none"))] - { - warn!("disable_irqs: not implemented"); - } - #[cfg(target_os = "none")] interrupts::disable() } @@ -39,11 +29,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") { - unsafe { asm!("hlt") } - } else { - core::hint::spin_loop() - } + unsafe { asm!("hlt") } } /// Halt the current CPU. 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/axpoll/Cargo.toml b/components/axpoll/Cargo.toml index 0506dcc555..d9c73f7d39 100644 --- a/components/axpoll/Cargo.toml +++ b/components/axpoll/Cargo.toml @@ -25,6 +25,7 @@ spin = { version = "0.12", default-features = false, features = [ ] } [dev-dependencies] +ax-kspin = { workspace = true, features = ["host-test"] } tokio = { version = "1.37", features = [ "rt-multi-thread", "macros", diff --git a/components/kernel_guard/Cargo.toml b/components/kernel_guard/Cargo.toml index 2cf444537d..c7c9be737c 100644 --- a/components/kernel_guard/Cargo.toml +++ b/components/kernel_guard/Cargo.toml @@ -11,6 +11,7 @@ license = "Apache-2.0" [features] preempt = [] +host-test = [] default = [] [dependencies] diff --git a/components/kernel_guard/src/arch/mod.rs b/components/kernel_guard/src/arch/mod.rs index 5a320b2ed3..3d6737aaa8 100644 --- a/components/kernel_guard/src/arch/mod.rs +++ b/components/kernel_guard/src/arch/mod.rs @@ -1,7 +1,8 @@ -#![cfg_attr(not(target_os = "none"), allow(dead_code, unused_imports))] - cfg_if::cfg_if! { - if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + if #[cfg(feature = "host-test")] { + mod noop; + pub use self::noop::*; + } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { mod x86; pub use self::x86::*; } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { diff --git a/components/kernel_guard/src/arch/noop.rs b/components/kernel_guard/src/arch/noop.rs new file mode 100644 index 0000000000..3645346a88 --- /dev/null +++ b/components/kernel_guard/src/arch/noop.rs @@ -0,0 +1,9 @@ +#[inline] +pub fn local_irq_save_and_disable() -> usize { + 0 +} + +#[inline] +pub fn local_irq_restore(flags: usize) { + let _ = flags; +} diff --git a/components/kernel_guard/src/lib.rs b/components/kernel_guard/src/lib.rs index c8b19c615e..f472907aee 100644 --- a/components/kernel_guard/src/lib.rs +++ b/components/kernel_guard/src/lib.rs @@ -24,6 +24,8 @@ //! need to implement the [`KernelGuardIf`] trait in other crates. Otherwise //! the preemption enable/disable operations will be no-ops. This feature is //! disabled by default. +//! - `host-test`: Avoid privileged IRQ instructions for host unit tests. This +//! feature is disabled by default. //! //! # Examples //! @@ -86,35 +88,19 @@ pub trait BaseGuard { /// A no-op guard that does nothing around the critical section. 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))] { - /// A guard that disables/enables local IRQs around the critical section. - pub struct IrqSave(usize); - - /// A guard that disables/enables kernel preemption around the critical - /// section. - pub struct NoPreempt; - - /// A guard that disables/enables both kernel preemption and local IRQs - /// around the critical section. - /// - /// When entering the critical section, it disables kernel preemption - /// first, followed by local IRQs. When leaving the critical section, it - /// re-enables local IRQs first, followed by kernel preemption. - pub struct NoPreemptIrqSave(usize); - } else { - /// Alias of [`NoOp`]. - pub type IrqSave = NoOp; - - /// Alias of [`NoOp`]. - pub type NoPreempt = NoOp; - - /// Alias of [`NoOp`]. - pub type NoPreemptIrqSave = NoOp; - } -} +/// A guard that disables/enables local IRQs around the critical section. +pub struct IrqSave(usize); + +/// A guard that disables/enables kernel preemption around the critical section. +pub struct NoPreempt; + +/// A guard that disables/enables both kernel preemption and local IRQs around +/// the critical section. +/// +/// When entering the critical section, it disables kernel preemption first, +/// followed by local IRQs. When leaving the critical section, it re-enables +/// local IRQs first, followed by kernel preemption. +pub struct NoPreemptIrqSave(usize); impl BaseGuard for NoOp { type State = (); @@ -139,7 +125,6 @@ impl Drop for NoOp { fn drop(&mut self) {} } -#[cfg(any(target_os = "none", doc))] mod imp { use super::*; diff --git a/components/kspin/Cargo.toml b/components/kspin/Cargo.toml index 523a364464..73e65bebae 100644 --- a/components/kspin/Cargo.toml +++ b/components/kspin/Cargo.toml @@ -12,6 +12,7 @@ license = "Apache-2.0" [features] # To use in the multi-core environment smp = [] +host-test = ["ax-kernel-guard/host-test"] lockdep = ["dep:ax-lockdep"] # Expose raw spin locks implementing `lock_api::RawMutex`. lock_api = ["dep:lock_api"] diff --git a/components/percpu/percpu/Cargo.toml b/components/percpu/percpu/Cargo.toml index 9740737faf..715a7dcd8d 100644 --- a/components/percpu/percpu/Cargo.toml +++ b/components/percpu/percpu/Cargo.toml @@ -12,6 +12,9 @@ license = "Apache-2.0" [features] default = [] +# Enable Linux user-space fallback used by ax-percpu's own host tests. +host-test = ["dep:spin"] + # For single CPU use, just make the per-CPU data a global variable. sp-naive = ["ax-percpu-macros/sp-naive"] @@ -34,8 +37,7 @@ custom-base = ["ax-percpu-macros/custom-base"] cfg-if = "1.0" ax-kernel-guard = { workspace = true, optional = true } ax-percpu-macros = { workspace = true } +spin = { workspace = true, optional = true } + [target.'cfg(target_arch = "x86_64")'.dependencies] x86 = "0.52" - -[target.'cfg(not(target_os = "none"))'.dependencies] -spin = { workspace = true } diff --git a/components/percpu/percpu/README.md b/components/percpu/percpu/README.md index 835af13b92..f5ef56be3b 100644 --- a/components/percpu/percpu/README.md +++ b/components/percpu/percpu/README.md @@ -43,8 +43,8 @@ cargo fmt --all # Run clippy cargo clippy --all-targets --all-features -# Run tests -cargo test --all-features +# Run Linux host tests +cargo test --features host-test,non-zero-vma # Build documentation cargo doc --no-deps diff --git a/components/percpu/percpu/README_CN.md b/components/percpu/percpu/README_CN.md index 64571a8149..22651eed78 100644 --- a/components/percpu/percpu/README_CN.md +++ b/components/percpu/percpu/README_CN.md @@ -43,8 +43,8 @@ cargo fmt --all # 运行 clippy cargo clippy --all-targets --all-features -# 运行测试 -cargo test --all-features +# 运行 Linux host 测试 +cargo test --features host-test,non-zero-vma # 生成文档 cargo doc --no-deps diff --git a/components/percpu/percpu/build.rs b/components/percpu/percpu/build.rs index 00c601cc0f..eba2f1740a 100644 --- a/components/percpu/percpu/build.rs +++ b/components/percpu/percpu/build.rs @@ -1,7 +1,7 @@ use std::path::Path; fn main() { - if cfg!(target_os = "linux") && cfg!(not(feature = "sp-naive")) { + if cfg!(feature = "host-test") && cfg!(not(feature = "sp-naive")) { let ld = if cfg!(feature = "custom-base") { "test_percpu_custom.x" } else { diff --git a/components/percpu/percpu/src/custom/mod.rs b/components/percpu/percpu/src/custom/mod.rs index 4229c4dd5a..03279e6e5e 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(feature = "host-test")] { _linux::init(4) } - #[cfg(not(target_os = "linux"))] + #[cfg(not(feature = "host-test"))] { 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(feature = "host-test")] mod _linux { use std::sync::Mutex; diff --git a/components/percpu/percpu/src/custom/tp.rs b/components/percpu/percpu/src/custom/tp.rs index d8dc985e95..846a6fc1b9 100644 --- a/components/percpu/percpu/src/custom/tp.rs +++ b/components/percpu/percpu/src/custom/tp.rs @@ -6,18 +6,14 @@ pub fn read_percpu_reg() -> usize { unsafe { cfg_if::cfg_if! { if #[cfg(target_arch = "x86_64")] { - #[cfg(target_os = "linux")] + #[cfg(feature = "host-test")] { tp = GS; } - #[cfg(target_os = "none")] + #[cfg(not(feature = "host-test"))] { tp = x86::msr::rdmsr(x86::msr::IA32_GS_BASE) as usize } - #[cfg(all(not(target_os = "linux"), not(target_os = "none")))] - { - unimplemented!() - } } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { core::arch::asm!("mv {}, gp", out(reg) tp) } else if #[cfg(all(target_arch = "aarch64", not(feature = "arm-el2")))] { @@ -45,18 +41,14 @@ pub unsafe fn write_percpu_reg(tp: usize) { unsafe { cfg_if::cfg_if! { if #[cfg(target_arch = "x86_64")] { - #[cfg(target_os = "linux")] + #[cfg(feature = "host-test")] { GS = tp; } - #[cfg(target_os = "none")] + #[cfg(not(feature = "host-test"))] { x86::msr::wrmsr(x86::msr::IA32_GS_BASE, tp as u64); } - #[cfg(all(not(target_os = "linux"), not(target_os = "none")))] - { - unimplemented!() - } SELF_PTR.write_current_raw(tp); } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { core::arch::asm!("mv gp, {}", in(reg) tp) @@ -81,12 +73,12 @@ use crate as ax_percpu; #[ax_percpu_macros::def_percpu] static SELF_PTR: usize = 0; -#[cfg(target_os = "linux")] +#[cfg(feature = "host-test")] static mut GS: usize = 0; /// . /// # Safety -#[cfg(target_os = "linux")] +#[cfg(feature = "host-test")] #[unsafe(no_mangle)] pub unsafe extern "C" fn __get_gs() -> usize { unsafe { GS } diff --git a/components/percpu/percpu/src/imp.rs b/components/percpu/percpu/src/imp.rs index aca6343b2c..647c8831cc 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(feature = "host-test")] static PERCPU_AREA_BASE: spin::once::Once = spin::once::Once::new(); unsafe extern "C" { @@ -44,10 +44,10 @@ 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")] { - let base = _percpu_start as *const () as usize; - } else { + if #[cfg(feature = "host-test")] { let base = *PERCPU_AREA_BASE.get().unwrap(); + } else { + let base = _percpu_start as *const () as usize; } } base + cpu_id * align_up_64(percpu_area_size()) @@ -85,7 +85,7 @@ pub fn init() -> usize { ) } - #[cfg(target_os = "linux")] + #[cfg(feature = "host-test")] { // 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(not(feature = "host-test"))] assert!(secondary_base + size <= _percpu_end as *const () as usize); // copy per-cpu data of the primary CPU to other CPUs. unsafe { @@ -116,12 +116,10 @@ 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!(feature = "host-test") { SELF_PTR.read_current_raw() - } else if cfg!(target_os = "none") { - x86::msr::rdmsr(x86::msr::IA32_GS_BASE) as usize } else { - unimplemented!() + x86::msr::rdmsr(x86::msr::IA32_GS_BASE) as usize }; } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { core::arch::asm!("mv {}, gp", out(reg) tp) @@ -164,7 +162,7 @@ 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!(feature = "host-test") { const ARCH_SET_GS: u32 = 0x1001; const SYS_ARCH_PRCTL: u32 = 158; core::arch::asm!( @@ -173,10 +171,8 @@ pub unsafe fn write_percpu_reg(tp: usize) { in("edi") ARCH_SET_GS, in("rsi") tp, ); - } else if cfg!(target_os = "none") { - x86::msr::wrmsr(x86::msr::IA32_GS_BASE, tp as u64); } else { - unimplemented!() + x86::msr::wrmsr(x86::msr::IA32_GS_BASE, tp as u64); } SELF_PTR.write_current_raw(tp); } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { diff --git a/components/percpu/percpu/src/lib.rs b/components/percpu/percpu/src/lib.rs index cf4129e6d6..a432d65850 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(not(feature = "host-test"), no_std)] #![cfg_attr(docsrs, feature(doc_cfg))] #![doc = include_str!("../README.md")] diff --git a/components/percpu/percpu/tests/test_percpu.rs b/components/percpu/percpu/tests/test_percpu.rs index c34f39e970..6d500d05bc 100644 --- a/components/percpu/percpu/tests/test_percpu.rs +++ b/components/percpu/percpu/tests/test_percpu.rs @@ -1,4 +1,4 @@ -#![cfg(not(target_os = "macos"))] +#![cfg(all(target_os = "linux", feature = "host-test", feature = "non-zero-vma"))] use ax_percpu::*; @@ -30,7 +30,6 @@ struct Struct { #[def_percpu] static STRUCT: Struct = Struct { foo: 0, bar: 0 }; -#[cfg(all(target_os = "linux", feature = "non-zero-vma"))] #[test] fn test_percpu() { println!("feature = \"sp-naive\": {}", cfg!(feature = "sp-naive")); @@ -106,11 +105,7 @@ fn test_percpu() { test_remote_access(); } -#[cfg(all( - target_os = "linux", - not(feature = "sp-naive"), - feature = "non-zero-vma" -))] +#[cfg(not(feature = "sp-naive"))] fn test_remote_access() { // test remote write unsafe { diff --git a/components/scope-local/Cargo.toml b/components/scope-local/Cargo.toml index 6739e75665..6aff28125d 100644 --- a/components/scope-local/Cargo.toml +++ b/components/scope-local/Cargo.toml @@ -14,4 +14,4 @@ spin = { version = "0.12", default-features = false, features = ["lazylock"] } [dev-dependencies] ctor = "0.6" -ax-percpu = { workspace = true, features = ["non-zero-vma"] } +ax-percpu = { workspace = true, features = ["host-test", "non-zero-vma"] } 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/build.rs b/components/someboot/build.rs index f50fcce366..5aedfee5f6 100644 --- a/components/someboot/build.rs +++ b/components/someboot/build.rs @@ -7,12 +7,6 @@ fn main() { println!("cargo::rustc-check-cfg=cfg(uspace)"); println!("cargo::rustc-check-cfg=cfg(hv)"); - let target = std::env::var("TARGET").unwrap(); - - if target.contains("windows") || target.contains("linux") || target.contains("darwin") { - return; - } - let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); println!("cargo:rustc-link-search={}", out_dir.display()); diff --git a/components/someboot/src/arch/aarch64/link.ld b/components/someboot/src/arch/aarch64/link.ld index b1c476f19d..4b61dc66cb 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,11 @@ SECTIONS _etdata = .; } :text :tls + . = ALIGN(8); + _edata = .; + __kernel_load_end = .; + __bss_start = .; + .tbss : { _stbss = .; *(.tbss .tbss.*) @@ -82,7 +94,6 @@ SECTIONS } :text :tls . = ALIGN(8); - __bss_start = .; .sbss : { *(.dynsbss) *(.sbss) *(.scommon) } :text @@ -136,6 +147,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/entry.rs b/components/someboot/src/arch/loongarch64/entry.rs index 53c6f740b7..026719ebb0 100644 --- a/components/someboot/src/arch/loongarch64/entry.rs +++ b/components/someboot/src/arch/loongarch64/entry.rs @@ -42,8 +42,13 @@ pub unsafe extern "C" fn kernel_entry( " la.pcrel $t0, __bss_start # clear .bss + la.pcrel $t1, __bss_stop +1: + bgeu $t0, $t1, 2f st.d $zero, $t0, 0 - la.pcrel $t1, __bss_stop - {LONGSIZE} + addi.d $t0, $t0, {LONGSIZE} + b 1b +2: ", " diff --git a/components/someboot/src/arch/loongarch64/link.ld b/components/someboot/src/arch/loongarch64/link.ld index 131f8b1008..21bf1e72b2 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__ */ } @@ -84,15 +85,16 @@ SECTIONS *(.sdata) . = ALIGN(PAGE_SIZE); } - _edata = .; - __kernel_load_end = .; - __bss_start = .; - .tdata : { _stdata = .; *(.tdata .tdata.*) _etdata = .; - } + } :text :tls + + . = ALIGN(8); + _edata = .; + __kernel_load_end = .; + __bss_start = .; .tbss : { _stbss = .; @@ -100,8 +102,9 @@ SECTIONS *(.init.bss) *(.tcommon) _etbss = .; - } + } :text :tls + . = ALIGN(8); .sbss : { *(.dynsbss) *(.sbss) *(.scommon) } @@ -158,4 +161,3 @@ SECTIONS *(.eh_frame) } } - diff --git a/components/someboot/src/consts.rs b/components/someboot/src/consts.rs index 00ccbad178..ad8d80f6ee 100644 --- a/components/someboot/src/consts.rs +++ b/components/someboot/src/consts.rs @@ -1,4 +1,3 @@ -#[cfg(target_os = "none")] 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..cb27a48e80 100644 --- a/components/someboot/src/lib.rs +++ b/components/someboot/src/lib.rs @@ -1,6 +1,5 @@ #![no_std] #![no_main] -#![cfg(not(any(windows, unix)))] #![cfg_attr(target_arch = "x86_64", feature(abi_x86_interrupt))] #[allow(unused_imports)] diff --git a/components/starry-process/Cargo.toml b/components/starry-process/Cargo.toml index ccb2387e16..1941107069 100644 --- a/components/starry-process/Cargo.toml +++ b/components/starry-process/Cargo.toml @@ -15,4 +15,4 @@ weak-map = "0.1" [dev-dependencies] ctor = "0.4.1" -ax-kspin = { workspace = true, features = ["smp"] } +ax-kspin = { workspace = true, features = ["host-test", "smp"] } diff --git a/components/starry-signal/Cargo.toml b/components/starry-signal/Cargo.toml index a1c7cd9826..9579697d1d 100644 --- a/components/starry-signal/Cargo.toml +++ b/components/starry-signal/Cargo.toml @@ -25,4 +25,5 @@ starry-vm = { workspace = true } strum = { version = "0.27", default-features = false, features = ["derive"] } [dev-dependencies] +ax-kspin = { workspace = true, features = ["host-test"] } extern-trait = "0.4" diff --git a/components/starry-signal/src/api/thread.rs b/components/starry-signal/src/api/thread.rs index e6a349ae9d..f483184f07 100644 --- a/components/starry-signal/src/api/thread.rs +++ b/components/starry-signal/src/api/thread.rs @@ -67,11 +67,19 @@ pub struct ThreadSignalManager { impl ThreadSignalManager { pub fn new(tid: u32, proc: Arc) -> Arc { + Self::new_with_blocked(tid, proc, SignalSet::default()) + } + + pub fn new_with_blocked( + tid: u32, + proc: Arc, + blocked: SignalSet, + ) -> Arc { let this = Arc::new(Self { proc: proc.clone(), pending: SpinNoIrq::new(PendingSignals::default()), - blocked: SpinNoIrq::new(SignalSet::default()), + blocked: SpinNoIrq::new(blocked), stack: SpinNoIrq::new(SignalStack::default()), stack_active_depth: SpinNoIrq::new(0), 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/build.rs b/drivers/ax-driver/build.rs index 7dd7f5cb87..72c5c9924b 100644 --- a/drivers/ax-driver/build.rs +++ b/drivers/ax-driver/build.rs @@ -39,7 +39,6 @@ fn main() { let has_plat_static = has_feature("plat-static"); 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 target_has_cvsd = matches!(target_arch.as_str(), "riscv32" | "riscv64"); if has_plat_dyn { @@ -50,10 +49,10 @@ 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 && 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 && 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..f59fb130c2 100644 --- a/drivers/ax-driver/src/net/binding.rs +++ b/drivers/ax-driver/src/net/binding.rs @@ -47,17 +47,7 @@ pub fn pci_legacy_irq(endpoint: &EndpointRc) -> Option { endpoint.interrupt_pin(), endpoint.interrupt_line(), || { - #[cfg(all( - plat_dyn, - target_os = "none", - any( - feature = "intel-net", - feature = "ixgbe", - feature = "realtek-rtl8125", - feature = "virtio-net", - feature = "xhci-pci", - ) - ))] + #[cfg(pci_dyn_acpi_intx_route)] { let interrupt_pin = endpoint.interrupt_pin(); if interrupt_pin != 0 { @@ -74,17 +64,7 @@ pub fn pci_legacy_irq(endpoint: &EndpointRc) -> Option { None }, || { - #[cfg(all( - plat_dyn, - target_os = "none", - any( - feature = "intel-net", - feature = "ixgbe", - feature = "realtek-rtl8125", - feature = "virtio-net", - feature = "xhci-pci", - ) - ))] + #[cfg(pci_dyn_intx_route)] { let interrupt_pin = endpoint.interrupt_pin(); if interrupt_pin != 0 { diff --git a/drivers/ax-driver/src/usb/mod.rs b/drivers/ax-driver/src/usb/mod.rs index 801c57cd6a..e9ae08db27 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,11 +203,11 @@ 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 { - #[cfg(plat_dyn)] + #[cfg(pci_dyn_intx_route)] if let Some(irq) = crate::pci::fdt_irq_for_endpoint(endpoint.address(), endpoint.interrupt_pin())? { 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..4470e593c8 100644 --- a/os/StarryOS/kernel/Cargo.toml +++ b/os/StarryOS/kernel/Cargo.toml @@ -19,14 +19,12 @@ 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"] +std-compat = [] k230-kpu = ["ax-feat/xuantie-c9xx", "dep:axklib", "dep:k230-kpu"] plat-dyn = [ + "dep:axplat-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 +66,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 +75,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 } diff --git a/os/StarryOS/kernel/src/entry.rs b/os/StarryOS/kernel/src/entry.rs index ea53fe4027..bdb1a76a3f 100644 --- a/os/StarryOS/kernel/src/entry.rs +++ b/os/StarryOS/kernel/src/entry.rs @@ -85,7 +85,7 @@ pub fn init(args: &[String], envs: &[String]) { .expect("Failed to add stdio"); } - let thr = Thread::new(pid, proc, None); + let thr = Thread::new(pid, proc, None, starry_signal::SignalSet::default()); *task.task_ext_mut() = Some(AxTaskExt::from_impl(thr)); let task = spawn_task(task); diff --git a/os/StarryOS/kernel/src/kmod/kprint.rs b/os/StarryOS/kernel/src/kmod/kprint.rs index 96bad85d73..8b76f2ee05 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(target_env = "musl"))] #[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/dev/card1.rs b/os/StarryOS/kernel/src/pseudofs/dev/card1.rs index a32facb7c0..9456eb040a 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/card1.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/card1.rs @@ -10,15 +10,12 @@ use core::{ use ax_driver::rknpu::{self, RknpuAction, RknpuMemCreate, RknpuMemMap, RknpuMemSync, RknpuSubmit}; use ax_errno::{AxError, AxResult}; use ax_memory_addr::PhysAddrRange; -use ax_runtime::hal::mem::virt_to_phys; +use ax_runtime::hal::{cpu::asm::user_copy, mem::virt_to_phys}; use axfs_ng_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; use axpoll::{IoEvents, Pollable}; use linux_raw_sys::general::O_CLOEXEC; -use super::{ - rknpu_card::{RknpuCmd, copy_from_user, copy_to_user}, - rknpu_drm::DrmVersion, -}; +use super::rknpu_drm::DrmVersion; use crate::{ file::FileLike, pseudofs::{ @@ -38,9 +35,6 @@ const DRM1_DESC: &CStr = c"RKNPU driver"; /// Device ID for /dev/dri/card1 pub const CARD1_SYSTEM_DEVICE_ID: DeviceId = DeviceId::new(0xe2, 1); -/// Device ID for /dev/rknpu (pick an unused major/minor) -pub const RKNPU_DEVICE_ID: DeviceId = DeviceId::new(251, 0); - /// Page shift constant (4KB pages) const PAGE_SHIFT: u32 = 12; /// Maximum ioctl command number @@ -67,6 +61,44 @@ pub struct DrmUnique { pub unique: *mut c_char, } +/// RKNPU command types +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RknpuCmd { + /// Action command + Action = 0x00, + /// Submit command + Submit = 0x01, + /// Memory create command + MemCreate = 0x02, + /// Memory map command + MemMap = 0x03, + /// Memory destroy command + MemDestroy = 0x04, + /// Memory sync command + MemSync = 0x05, +} + +impl TryFrom for RknpuCmd { + type Error = (); + + /// Tries to convert a u32 value to an RknpuCmd + fn try_from(nr: u32) -> Result { + match nr { + 0x00 | 0x40 => Ok(RknpuCmd::Action), + 0x01 | 0x41 => Ok(RknpuCmd::Submit), + 0x02 | 0x42 => Ok(RknpuCmd::MemCreate), + 0x03 | 0x43 => Ok(RknpuCmd::MemMap), + 0x04 | 0x44 => Ok(RknpuCmd::MemDestroy), + 0x05 | 0x45 => Ok(RknpuCmd::MemSync), + _ => { + warn!("Unknown ioctl nr: {nr:#x}",); + Err(()) + } + } + } +} + /// Represents an RKNPU user action with flags and value #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -253,6 +285,28 @@ fn map_rknpu_err(err: rknpu::Error) -> VfsError { } } +/// Copies data from user space to kernel space +pub fn copy_from_user(dst: *mut u8, src: *const u8, size: usize) -> Result<(), VfsError> { + let ret = unsafe { user_copy(dst, src, size) }; + + if ret != 0 { + warn!("[rknpu]: copy_from_user failed, ret={}", ret); + return Err(VfsError::InvalidData); + } + Ok(()) +} + +/// Copies data from kernel space to user space +pub fn copy_to_user(dst: *mut u8, src: *const u8, size: usize) -> Result<(), VfsError> { + let ret = unsafe { user_copy(dst, src, size) }; + + if ret != 0 { + warn!("[rknpu]: copy_to_user failed, ret={}", ret); + return Err(VfsError::InvalidData); + } + Ok(()) +} + /// Handles RKNPU action ioctl commands pub fn rknpu_driver_ioctl(op: RknpuCmd, arg: usize) -> VfsResult { info!("rknpu_driver_ioctl: op = {:?}", op); @@ -381,50 +435,6 @@ pub fn rknpu_driver_ioctl(op: RknpuCmd, arg: usize) -> VfsResult { Ok(0) } -/// Handles RKNPU submit ioctl command -pub fn rknpu_submit_ioctl(arg: usize) -> VfsResult { - let mut submit_args = RknpuSubmit::default(); - - copy_from_user( - &mut submit_args as *mut _ as *mut u8, - arg as *const u8, - mem::size_of::(), - )?; - - if let Err(e) = rknpu::submit(&mut submit_args).map_err(map_rknpu_err) { - warn!("rknpu submit ioctl failed: {:?}", e); - } - - copy_to_user( - arg as *mut u8, - &submit_args as *const _ as *const u8, - mem::size_of::(), - )?; - Ok(0) -} - -/// Handles RKNPU memory create ioctl command -pub fn rknpu_mem_create_ioctl(arg: usize) -> VfsResult { - let mut mem_create_args = RknpuMemCreate::default(); - - copy_from_user( - &mut mem_create_args as *mut _ as *mut u8, - arg as *const u8, - mem::size_of::(), - )?; - - if let Err(e) = rknpu::mem_create(&mut mem_create_args).map_err(map_rknpu_err) { - warn!("rknpu mem_create ioctl failed: {:?}", e); - } - - copy_to_user( - arg as *mut u8, - &mem_create_args as *const _ as *const u8, - mem::size_of::(), - )?; - Ok(0) -} - /// DRM_IOCTL_GEM_FLINK ioctl argument type #[repr(C)] #[derive(Debug, Clone, Copy, Default)] @@ -618,19 +628,3 @@ pub fn drm_get_unique(data: &mut [u8]) -> VfsResult<()> { Ok(()) } - -/// DRM_SET_UNIQUE ioctl handler (stub implementation) -/// -/// This function handles DRM_IOCTL_SET_UNIQUE requests. For this -/// implementation, we return success but don't actually set the unique -/// identifier, as this is typically not used/needed in embedded systems. -pub fn drm_set_unique(data: &mut [u8]) -> VfsResult<()> { - let unique_data = unsafe { &*(data.as_ptr() as *const DrmUnique) }; - info!("drm_set_unique called: {:?}", unique_data); - - // For this implementation, we just log the attempt and return success - // In a real implementation, this would validate and store the unique ID - warn!("[drm_set_unique] Setting unique identifier is not supported in this implementation"); - - Ok(()) -} diff --git a/os/StarryOS/kernel/src/pseudofs/dev/dma_heap.rs b/os/StarryOS/kernel/src/pseudofs/dev/dma_heap.rs index 1576b68d07..8465f7ffbb 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/dma_heap.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/dma_heap.rs @@ -41,24 +41,15 @@ impl DeviceOps for DmaHeapSystem { fn ioctl(&self, cmd: u32, arg: usize) -> VfsResult { warn!("dma_heap: ioctl called cmd={:#x}, arg={:#x}", cmd, arg); - // Handle common DMA heap ioctls - match cmd { - // For now, we just return success for all ioctls and zero the first u32 - // if arg is a user pointer, similar to rknpu implementation - _ => { - // Best-effort: if arg is a user pointer, zero the first u32 there so - // user-space doesn't read uninitialized memory - if arg != 0 { - // write a safe default (0) to the user pointer - // Use vm_write to safely write across the VM boundary. - if let Err(e) = (arg as *mut u32).vm_write(0u32) { - warn!("dma_heap: ioctl vm_write failed: {:?}", e); - return Err(VfsError::InvalidInput); - } - } - Ok(0) - } + // For now, return success for all ioctls and zero the first u32 if arg + // is a user pointer, similar to the rknpu implementation. + if arg != 0 + && let Err(e) = (arg as *mut u32).vm_write(0u32) + { + warn!("dma_heap: ioctl vm_write failed: {:?}", e); + return Err(VfsError::InvalidInput); } + Ok(0) } fn as_any(&self) -> &dyn Any { diff --git a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs index c85371647e..ade86a203f 100644 --- a/os/StarryOS/kernel/src/pseudofs/dev/mod.rs +++ b/os/StarryOS/kernel/src/pseudofs/dev/mod.rs @@ -1,9 +1,9 @@ //! Special devices mod card0; -#[cfg(all(feature = "rknpu", not(any(windows, unix))))] +#[cfg(feature = "rknpu")] mod card1; -#[cfg(all(feature = "rknpu", not(any(windows, unix))))] +#[cfg(feature = "rknpu")] mod dma_heap; mod drm; #[cfg(feature = "input")] @@ -22,9 +22,7 @@ pub use r#loop::LoopDevice; pub mod ion; #[cfg(feature = "memtrack")] mod memtrack; -#[cfg(all(feature = "rknpu", not(any(windows, unix))))] -mod rknpu_card; -#[cfg(all(feature = "rknpu", not(any(windows, unix))))] +#[cfg(feature = "rknpu")] mod rknpu_drm; mod rtc; #[cfg(all(feature = "sg2002", not(feature = "plat-dyn")))] @@ -367,7 +365,7 @@ fn builder(fs: Arc) -> DirMaker { ), ); - #[cfg(all(feature = "rknpu", not(any(windows, unix))))] + #[cfg(feature = "rknpu")] { // DMA heap devices (rknpu only) let mut dma_heap_dir = DirMapping::new(); diff --git a/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs b/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs deleted file mode 100644 index c0f5295590..0000000000 --- a/os/StarryOS/kernel/src/pseudofs/dev/rknpu_card.rs +++ /dev/null @@ -1,312 +0,0 @@ -use core::{ - any::Any, - convert::TryFrom, - ffi::{c_char, c_ulong}, -}; - -use ax_runtime::hal::cpu::asm::user_copy; -use axfs_ng_vfs::{DeviceId, NodeFlags, VfsError, VfsResult}; - -use super::rknpu_drm::DrmVersion; -use crate::pseudofs::{ - DeviceOps, - dev::rknpu_drm::{io_size, ioctl_nr, is_driver_ioctl}, -}; - -/// Driver name for DRM device -const DRM0_NAME: &str = "rockchip"; -/// Driver date for DRM device -const DRM0_DATE: &str = "20140818"; -/// Driver description for DRM device -const DRM0_DESC: &str = "RockChip Soc DRM"; - -/// Device ID for /dev/rknpu (pick an unused major/minor) -pub const RKNPU_DEVICE_ID: DeviceId = DeviceId::new(251, 0); - -/// Device ID for /dev/dri/card0 -pub const CARD0_SYSTEM_DEVICE_ID: DeviceId = DeviceId::new(0xe2, 0); - -/// DRM card0 device implementation -pub struct Card0; - -impl Card0 { - /// Creates a new /dev/dri/card0 device. - pub fn new() -> Card0 { - Self - } -} - -impl Default for Card0 { - fn default() -> Self { - Self::new() - } -} - -impl DeviceOps for Card0 { - /// Reads data from the device (not supported for card0) - fn read_at(&self, _buf: &mut [u8], _offset: u64) -> VfsResult { - trace!("card0: read_at called"); - // card0 devices are not meant to be read directly - Err(VfsError::InvalidInput) - } - - /// Writes data to the device (not supported for card0) - fn write_at(&self, _buf: &[u8], _offset: u64) -> VfsResult { - trace!("card0: write_at called"); - // card0 devices are not meant to be written directly - Err(VfsError::InvalidInput) - } - - /// Handles ioctl commands for the device - fn ioctl(&self, cmd: u32, arg: usize) -> VfsResult { - if arg == 0 { - warn!("[rknpu]: ioctl received null arg pointer"); - return Err(VfsError::InvalidData); - } - let nr = ioctl_nr(cmd); - info!("card0: cmd {cmd:#x}, nr {nr:#x}, arg {arg:#x}"); - - let is_driver_ioctl = is_driver_ioctl(ioctl_nr(cmd)); - info!("card0: is_driver_ioctl = {}", is_driver_ioctl); - - let mut stack_data = [0u8; 128]; - - let in_size = io_size(cmd) as usize; - let out_size = in_size; - - copy_from_user(stack_data.as_mut_ptr(), arg as _, in_size)?; - - if is_driver_ioctl { - panic!("card0: driver ioctls are not supported"); - } else { - assert!(nr <= 0xcf, "card0: unsupported ioctl nr {nr}"); - - match nr { - 0 => { - info!("drm get version"); - drm_version(&mut stack_data)?; - } - _ => { - panic!("card0: unsupported ioctl nr {nr}"); - } - } - } - - copy_to_user(arg as _, stack_data.as_mut_ptr(), out_size)?; - - Ok(0) - } - - /// Returns a reference to the object as Any for dynamic type checking - fn as_any(&self) -> &dyn Any { - self - } - - /// Returns the node flags for the device - fn flags(&self) -> NodeFlags { - NodeFlags::NON_CACHEABLE - } -} - -/// Rust implementation of Linux kernel's drm_copy_field function -/// -/// This function safely copies a string value to user space buffer, -/// similar to the Linux kernel implementation with proper error handling. -unsafe fn drm_copy_field( - buf: *mut u8, - buf_len: &mut c_ulong, - value: *const u8, -) -> Result<(), VfsError> { - // Handle NULL value case - same as kernel's WARN_ONCE check - if value.is_null() { - warn!("[drm_copy_field] BUG: the value to copy was not set!"); - *buf_len = 0; - return Ok(()); - } - - // Calculate actual string length using C string semantics - let mut len = 0; - unsafe { - let mut ptr = value; - while *ptr != 0 { - len += 1; - ptr = ptr.add(1); - } - } - - // Get the original buffer size - let original_buf_len = *buf_len; - - // Update user's buffer length with actual string length (same as kernel) - *buf_len = len; - - // Don't overflow user buffer - limit copy to available space - let copy_len = if len > original_buf_len { - original_buf_len as usize - } else { - len as usize - }; - - // Finally, try filling in the userbuf (same logic as kernel) - if copy_len > 0 && !buf.is_null() { - copy_to_user(buf as _, value, copy_len)?; - } - - Ok(()) -} - -/// Sets the DRM version information for the device -fn drm_version(data: &mut [u8]) -> VfsResult<()> { - let data = unsafe { &mut *(data.as_mut_ptr() as *mut DrmVersion) }; - info!("drm_version called: {:?}", data); - - // Set version information - data.version_major = 3; - data.version_minor = 0; - data.version_patchlevel = 0; - - // Use drm_copy_field to handle string copying properly - unsafe { - // Copy driver name - let ret = drm_copy_field(data.name as *mut u8, &mut data.name_len, DRM0_NAME.as_ptr()); - if let Err(e) = ret { - warn!("[drm_version] Failed to copy driver name: {:?}", e); - return Err(VfsError::InvalidData); - } - - // Copy driver date - let ret = drm_copy_field(data.date as *mut u8, &mut data.date_len, DRM0_DATE.as_ptr()); - if let Err(e) = ret { - warn!("[drm_version] Failed to copy driver date: {:?}", e); - return Err(VfsError::InvalidData); - } - - // Copy driver description - let ret = drm_copy_field( - data.desc.cast(), - &mut data.desc_len, - DRM0_DESC.as_ptr().cast(), - ); - if let Err(e) = ret { - warn!("[drm_version] Failed to copy driver description: {:?}", e); - return Err(VfsError::InvalidData); - } - } - - info!( - "[drm_version] Set driver info: name_len={}, date_len={}, desc_len={}", - data.name_len, data.date_len, data.desc_len - ); - - Ok(()) -} - -/// Copies data from user space to kernel space -pub fn copy_from_user(dst: *mut u8, src: *const u8, size: usize) -> Result<(), VfsError> { - let ret = unsafe { user_copy(dst, src, size) }; - - if ret != 0 { - warn!("[rknpu]: copy_from_user failed, ret={}", ret); - return Err(VfsError::InvalidData); - } - Ok(()) -} - -/// Copies data from kernel space to user space -pub fn copy_to_user(dst: *mut u8, src: *const u8, size: usize) -> Result<(), VfsError> { - let ret = unsafe { user_copy(dst, src, size) }; - - if ret != 0 { - warn!("[rknpu]: copy_to_user failed, ret={}", ret); - return Err(VfsError::InvalidData); - } - Ok(()) -} - -/// RKNPU command types -#[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum RknpuCmd { - /// Action command - Action = 0x00, - /// Submit command - Submit = 0x01, - /// Memory create command - MemCreate = 0x02, - /// Memory map command - MemMap = 0x03, - /// Memory destroy command - MemDestroy = 0x04, - /// Memory sync command - MemSync = 0x05, -} - -impl TryFrom for RknpuCmd { - type Error = (); - - /// Tries to convert a u32 value to an RknpuCmd - fn try_from(nr: u32) -> Result { - match nr { - 0x00 | 0x40 => Ok(RknpuCmd::Action), - 0x01 | 0x41 => Ok(RknpuCmd::Submit), - 0x02 | 0x42 => Ok(RknpuCmd::MemCreate), - 0x03 | 0x43 => Ok(RknpuCmd::MemMap), - 0x04 | 0x44 => Ok(RknpuCmd::MemDestroy), - 0x05 | 0x45 => Ok(RknpuCmd::MemSync), - _ => { - warn!("Unknown ioctl nr: {nr:#x}",); - Err(()) - } - } - } -} - -/// DRM_IOCTL_GET_UNIQUE ioctl argument type. -/// -/// This structure is used with DRM_IOCTL_GET_UNIQUE to retrieve the unique -/// identifier for a DRM device, typically the bus ID. This corresponds to the -/// Linux kernel's struct drm_unique. -/// -/// \sa drmGetBusid() and drmSetBusid() in libdrm. -#[repr(C)] -#[derive(Debug, Clone, Copy, Default)] -pub struct DrmUnique { - /// Length of unique string identifier - pub unique_len: c_ulong, - /// Pointer to user-space buffer holding unique name for driver - /// instantiation - pub unique: *mut c_char, -} - -impl DrmUnique { - /// Creates a new DrmUnique with default values - pub const fn new() -> Self { - Self { - unique_len: 0, - unique: core::ptr::null_mut(), - } - } - - /// Creates a new DrmUnique with specified buffer and length - pub fn with_buffer(buffer: *mut c_char, len: c_ulong) -> Self { - Self { - unique_len: len, - unique: buffer, - } - } - - /// Returns true if the unique pointer is null - pub fn is_null(&self) -> bool { - self.unique.is_null() - } - - /// Returns the length of the unique identifier - pub fn len(&self) -> c_ulong { - self.unique_len - } - - /// Sets the length of the unique identifier - pub fn set_len(&mut self, len: c_ulong) { - self.unique_len = len; - } -} 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/kernel/src/syscall/task/clone.rs b/os/StarryOS/kernel/src/syscall/task/clone.rs index 9411ed25a1..0998f578d8 100644 --- a/os/StarryOS/kernel/src/syscall/task/clone.rs +++ b/os/StarryOS/kernel/src/syscall/task/clone.rs @@ -200,7 +200,8 @@ impl CloneArgs { }; let curr = current(); - let old_proc_data = &curr.as_thread().proc_data; + let curr_thread = curr.as_thread(); + let old_proc_data = &curr_thread.proc_data; let mut new_task = new_user_task(&curr.name(), new_uctx, set_child_tid); @@ -333,9 +334,14 @@ impl CloneArgs { new_proc_data.proc.add_thread(tid); - let parent_cred = Some(curr.as_thread().cred()); - let thr = Thread::new(tid, new_proc_data.clone(), parent_cred); - if curr.as_thread().no_new_privs() { + let parent_cred = Some(curr_thread.cred()); + let thr = Thread::new( + tid, + new_proc_data.clone(), + parent_cred, + curr_thread.signal.blocked(), + ); + if curr_thread.no_new_privs() { thr.set_no_new_privs(); } if flags.contains(CloneFlags::CHILD_CLEARTID) { diff --git a/os/StarryOS/kernel/src/task/mod.rs b/os/StarryOS/kernel/src/task/mod.rs index a8d833b59b..560a99e773 100644 --- a/os/StarryOS/kernel/src/task/mod.rs +++ b/os/StarryOS/kernel/src/task/mod.rs @@ -27,7 +27,7 @@ use scope_local::{ActiveScope, Scope}; use spin::RwLock; use starry_process::Process; use starry_signal::{ - SignalInfo, Signo, + SignalInfo, SignalSet, Signo, api::{ProcessSignalManager, SignalActions, ThreadSignalManager}, }; @@ -201,11 +201,20 @@ impl Thread { /// /// If `parent_cred` is `Some`, the thread inherits the parent's credentials; /// otherwise it starts with root credentials (used for the init process). - pub fn new(tid: u32, proc_data: Arc, parent_cred: Option>) -> Box { + pub fn new( + tid: u32, + proc_data: Arc, + parent_cred: Option>, + signal_mask: SignalSet, + ) -> Box { let cred = parent_cred.unwrap_or_else(|| Arc::new(Cred::root())); Box::new(Thread { tid: AtomicU32::new(tid), - signal: ThreadSignalManager::new(tid, proc_data.signal.clone()), + signal: ThreadSignalManager::new_with_blocked( + tid, + proc_data.signal.clone(), + signal_mask, + ), proc_data, clear_child_tid: AtomicUsize::new(0), robust_list_head: AtomicUsize::new(0), diff --git a/os/StarryOS/starryos/Cargo.toml b/os/StarryOS/starryos/Cargo.toml index a003d1812c..89334f5eac 100644 --- a/os/StarryOS/starryos/Cargo.toml +++ b/os/StarryOS/starryos/Cargo.toml @@ -45,6 +45,7 @@ k230 = [ plat-dyn = ["dep:axplat-dyn", "starry-kernel/plat-dyn"] rknpu = ["ax-driver/rknpu", "starry-kernel/rknpu"] +std-compat = ["starry-kernel/std-compat"] vf2 = ["ax-hal/riscv64-visionfive2"] [[bin]] @@ -57,12 +58,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"] } diff --git a/os/StarryOS/starryos/src/main.rs b/os/StarryOS/starryos/src/main.rs index 14a4a4723c..d80b98b10f 100644 --- a/os/StarryOS/starryos/src/main.rs +++ b/os/StarryOS/starryos/src/main.rs @@ -1,14 +1,16 @@ -#![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}; +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_api/src/imp/task.rs b/os/arceos/api/arceos_api/src/imp/task.rs index 97009b7f19..e92bdc6aea 100644 --- a/os/arceos/api/arceos_api/src/imp/task.rs +++ b/os/arceos/api/arceos_api/src/imp/task.rs @@ -139,7 +139,9 @@ cfg_task! { wq.0.notify_all(true); } else { for _ in 0..count { - wq.0.notify_one(true); + if !wq.0.notify_one(true) { + break; + } } } } diff --git a/os/arceos/api/arceos_posix_api/Cargo.toml b/os/arceos/api/arceos_posix_api/Cargo.toml index 96d37af06a..638906146d 100644 --- a/os/arceos/api/arceos_posix_api/Cargo.toml +++ b/os/arceos/api/arceos_posix_api/Cargo.toml @@ -18,8 +18,6 @@ license.workspace = true [features] default = [] -use-hermit-types = [] - smp = ["ax-feat/smp"] irq = ["ax-feat/irq"] alloc = ["dep:ax-alloc", "ax-feat/alloc"] 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/hermit_abi/errno.rs b/os/arceos/api/arceos_posix_api/src/hermit_abi/errno.rs deleted file mode 100644 index 585e765739..0000000000 --- a/os/arceos/api/arceos_posix_api/src/hermit_abi/errno.rs +++ /dev/null @@ -1,532 +0,0 @@ -/// Operation not permitted -pub const EPERM: i32 = 1; - -/// No such file or directory -pub const ENOENT: i32 = 2; - -/// No such process -pub const ESRCH: i32 = 3; - -/// Interrupted system call -pub const EINTR: i32 = 4; - -/// I/O error -pub const EIO: i32 = 5; - -/// No such device or address -pub const ENXIO: i32 = 6; - -/// Argument list too long -pub const E2BIG: i32 = 7; - -/// Exec format error -pub const ENOEXEC: i32 = 8; - -/// Bad file number -pub const EBADF: i32 = 9; - -/// No child processes -pub const ECHILD: i32 = 10; - -/// Try again -pub const EAGAIN: i32 = 11; - -/// Out of memory -pub const ENOMEM: i32 = 12; - -/// Permission denied -pub const EACCES: i32 = 13; - -/// Bad address -pub const EFAULT: i32 = 14; - -/// Block device required -pub const ENOTBLK: i32 = 15; - -/// Device or resource busy -pub const EBUSY: i32 = 16; - -/// File exists -pub const EEXIST: i32 = 17; - -/// Cross-device link -pub const EXDEV: i32 = 18; - -/// No such device -pub const ENODEV: i32 = 19; - -/// Not a directory -pub const ENOTDIR: i32 = 20; - -/// Is a directory -pub const EISDIR: i32 = 21; - -/// Invalid argument -pub const EINVAL: i32 = 22; - -/// File table overflow -pub const ENFILE: i32 = 23; - -/// Too many open files -pub const EMFILE: i32 = 24; - -/// Not a typewriter -pub const ENOTTY: i32 = 25; - -/// Text file busy -pub const ETXTBSY: i32 = 26; - -/// File too large -pub const EFBIG: i32 = 27; - -/// No space left on device -pub const ENOSPC: i32 = 28; - -/// Illegal seek -pub const ESPIPE: i32 = 29; - -/// Read-only file system -pub const EROFS: i32 = 30; - -/// Too many links -pub const EMLINK: i32 = 31; - -/// Broken pipe -pub const EPIPE: i32 = 32; - -/// Math argument out of domain of func -pub const EDOM: i32 = 33; - -/// Math result not representable -pub const ERANGE: i32 = 34; - -/// Resource deadlock would occur -pub const EDEADLK: i32 = 35; - -/// File name too long -pub const ENAMETOOLONG: i32 = 36; - -/// No record locks available -pub const ENOLCK: i32 = 37; - -/// Function not implemented -pub const ENOSYS: i32 = 38; - -/// Directory not empty -pub const ENOTEMPTY: i32 = 39; - -/// Too many symbolic links encountered -pub const ELOOP: i32 = 40; - -/// Operation would block -pub const EWOULDBLOCK: i32 = EAGAIN; - -/// No message of desired type -pub const ENOMSG: i32 = 42; - -/// Identifier removed -pub const EIDRM: i32 = 43; - -/// Channel number out of range -pub const ECHRNG: i32 = 44; - -/// Level 2 not synchronized -pub const EL2NSYNC: i32 = 45; - -/// Level 3 halted -pub const EL3HLT: i32 = 46; - -/// Level 3 reset -pub const EL3RST: i32 = 47; - -/// Link number out of range -pub const ELNRNG: i32 = 48; - -/// Protocol driver not attached -pub const EUNATCH: i32 = 49; - -/// No CSI structure available -pub const ENOCSI: i32 = 50; - -/// Level 2 halted -pub const EL2HLT: i32 = 51; - -/// Invalid exchange -pub const EBADE: i32 = 52; - -/// Invalid request descriptor -pub const EBADR: i32 = 53; - -/// Exchange full -pub const EXFULL: i32 = 54; - -/// No anode -pub const ENOANO: i32 = 55; - -/// Invalid request code -pub const EBADRQC: i32 = 56; - -/// Invalid slot -pub const EBADSLT: i32 = 57; - -pub const EDEADLOCK: i32 = EDEADLK; - -/// Bad font file format -pub const EBFONT: i32 = 59; - -/// Device not a stream -pub const ENOSTR: i32 = 60; - -/// No data available -pub const ENODATA: i32 = 61; - -/// Timer expired -pub const ETIME: i32 = 62; - -/// Out of streams resources -pub const ENOSR: i32 = 63; - -/// Machine is not on the network -pub const ENONET: i32 = 64; - -/// Package not installed -pub const ENOPKG: i32 = 65; - -/// Object is remote -pub const EREMOTE: i32 = 66; - -/// Link has been severed -pub const ENOLINK: i32 = 67; - -/// Advertise error -pub const EADV: i32 = 68; - -/// Srmount error -pub const ESRMNT: i32 = 69; - -/// Communication error on send -pub const ECOMM: i32 = 70; - -/// Protocol error -pub const EPROTO: i32 = 71; - -/// Multihop attempted -pub const EMULTIHOP: i32 = 72; - -/// RFS specific error -pub const EDOTDOT: i32 = 73; - -/// Not a data message -pub const EBADMSG: i32 = 74; - -/// Value too large for defined data type -pub const EOVERFLOW: i32 = 75; - -/// Name not unique on network -pub const ENOTUNIQ: i32 = 76; - -/// File descriptor in bad state -pub const EBADFD: i32 = 77; - -/// Remote address changed -pub const EREMCHG: i32 = 78; - -/// Can not access a needed shared library -pub const ELIBACC: i32 = 79; - -/// Accessing a corrupted shared library -pub const ELIBBAD: i32 = 80; - -/// .lib section in a.out corrupted -pub const ELIBSCN: i32 = 81; - -/// Attempting to link in too many shared libraries -pub const ELIBMAX: i32 = 82; - -/// Cannot exec a shared library directly -pub const ELIBEXEC: i32 = 83; - -/// Illegal byte sequence -pub const EILSEQ: i32 = 84; - -/// Interrupted system call should be restarted -pub const ERESTART: i32 = 85; - -/// Streams pipe error -pub const ESTRPIPE: i32 = 86; - -/// Too many users -pub const EUSERS: i32 = 87; - -/// Socket operation on non-socket -pub const ENOTSOCK: i32 = 88; - -/// Destination address required -pub const EDESTADDRREQ: i32 = 89; - -/// Message too long -pub const EMSGSIZE: i32 = 90; - -/// Protocol wrong type for socket -pub const EPROTOTYPE: i32 = 91; - -/// Protocol not available -pub const ENOPROTOOPT: i32 = 92; - -/// Protocol not supported -pub const EPROTONOSUPPORT: i32 = 93; - -/// Socket type not supported -pub const ESOCKTNOSUPPORT: i32 = 94; - -/// Operation not supported on transport endpoint -pub const EOPNOTSUPP: i32 = 95; - -/// Protocol family not supported -pub const EPFNOSUPPORT: i32 = 96; - -/// Address family not supported by protocol -pub const EAFNOSUPPORT: i32 = 97; - -/// Address already in use -pub const EADDRINUSE: i32 = 98; - -/// Cannot assign requested address -pub const EADDRNOTAVAIL: i32 = 99; - -/// Network is down -pub const ENETDOWN: i32 = 100; - -/// Network is unreachable -pub const ENETUNREACH: i32 = 101; - -/// Network dropped connection because of reset -pub const ENETRESET: i32 = 102; - -/// Software caused connection abort -pub const ECONNABORTED: i32 = 103; - -/// Connection reset by peer -pub const ECONNRESET: i32 = 104; - -/// No buffer space available -pub const ENOBUFS: i32 = 105; - -/// Transport endpoint is already connected -pub const EISCONN: i32 = 106; - -/// Transport endpoint is not connected -pub const ENOTCONN: i32 = 107; - -/// Cannot send after transport endpoint shutdown -pub const ESHUTDOWN: i32 = 108; - -/// Too many references: cannot splice -pub const ETOOMANYREFS: i32 = 109; - -/// Connection timed out -pub const ETIMEDOUT: i32 = 110; - -/// Connection refused -pub const ECONNREFUSED: i32 = 111; - -/// Host is down -pub const EHOSTDOWN: i32 = 112; - -/// No route to host -pub const EHOSTUNREACH: i32 = 113; - -/// Operation already in progress -pub const EALREADY: i32 = 114; - -/// Operation now in progress -pub const EINPROGRESS: i32 = 115; - -/// Stale file handle -pub const ESTALE: i32 = 116; - -/// Structure needs cleaning -pub const EUCLEAN: i32 = 117; - -/// Not a XENIX named type file -pub const ENOTNAM: i32 = 118; - -/// No XENIX semaphores available -pub const ENAVAIL: i32 = 119; - -/// Is a named type file -pub const EISNAM: i32 = 120; - -/// Remote I/O error -pub const EREMOTEIO: i32 = 121; - -/// Quota exceeded -pub const EDQUOT: i32 = 122; - -/// No medium found -pub const ENOMEDIUM: i32 = 123; - -/// Wrong medium type -pub const EMEDIUMTYPE: i32 = 124; - -/// Operation Canceled -pub const ECANCELED: i32 = 125; - -/// Required key not available -pub const ENOKEY: i32 = 126; - -/// Key has expired -pub const EKEYEXPIRED: i32 = 127; - -/// Key has been revoked -pub const EKEYREVOKED: i32 = 128; - -/// Key was rejected by service -pub const EKEYREJECTED: i32 = 129; - -/// Robust mutexes: Owner died -pub const EOWNERDEAD: i32 = 130; - -/// Robust mutexes: State not recoverable -pub const ENOTRECOVERABLE: i32 = 131; - -/// Robust mutexes: Operation not possible due to RF-kill -pub const ERFKILL: i32 = 132; - -/// Robust mutexes: Memory page has hardware error -pub const EHWPOISON: i32 = 133; - -/// Converts an error number to a corresponding error string -pub fn error_string(errno: i32) -> &'static str { - match errno { - 0 => "Operation successful", - EPERM => "Operation not permitted", - ENOENT => "No such file or directory", - ESRCH => "No such process", - EINTR => "Interrupted system call", - EIO => "I/O error", - ENXIO => "No such device or address", - E2BIG => "Argument list too long", - ENOEXEC => "Exec format error", - EBADF => "Bad file number", - ECHILD => "No child processes", - EAGAIN => "Try again", - ENOMEM => "Out of memory", - EACCES => "Permission denied", - EFAULT => "Bad address", - ENOTBLK => "Block device required", - EBUSY => "Device or resource busy", - EEXIST => "File exists", - EXDEV => "Cross-device link", - ENODEV => "No such device", - ENOTDIR => "Not a directory", - EISDIR => "Is a directory", - EINVAL => "Invalid argument", - ENFILE => "File table overflow", - EMFILE => "Too many open files", - ENOTTY => "Not a typewriter", - ETXTBSY => "Text file busy", - EFBIG => "File too large", - ENOSPC => "No space left on device", - ESPIPE => "Illegal seek", - EROFS => "Read-only file system", - EMLINK => "Too many links", - EPIPE => "Broken pipe", - EDOM => "Math argument out of domain of func", - ERANGE => "Math result not representable", - EDEADLK => "Resource deadlock would occur", - ENAMETOOLONG => "File name too long", - ENOLCK => "No record locks available", - ENOSYS => "Function not implemented", - ENOTEMPTY => "Directory not empty", - ELOOP => "Too many symbolic links encountered", - ENOMSG => "No message of desired type", - EIDRM => "Identifier removed", - ECHRNG => "Channel number out of range", - EL2NSYNC => "Level 2 not synchronized", - EL3HLT => "Level 3 halted", - EL3RST => "Level 3 reset", - ELNRNG => "Link number out of range", - EUNATCH => "Protocol driver not attached", - ENOCSI => "No CSI structure available", - EL2HLT => "Level 2 halted", - EBADE => "Invalid exchange", - EBADR => "Invalid request descriptor", - EXFULL => "Exchange full", - ENOANO => "No anode", - EBADRQC => "Invalid request code", - EBADSLT => "Invalid slot", - EBFONT => "Bad font file format", - ENOSTR => "Device not a stream", - ENODATA => "No data available", - ETIME => "Timer expired", - ENOSR => "Out of streams resources", - ENONET => "Machine is not on the network", - ENOPKG => "Package not installed", - EREMOTE => "Object is remote", - ENOLINK => "Link has been severed", - EADV => "Advertise error", - ESRMNT => "Srmount error", - ECOMM => "Communication error on send", - EPROTO => "Protocol error", - EMULTIHOP => "Multihop attempted", - EDOTDOT => "RFS specific error", - EBADMSG => "Not a data message", - EOVERFLOW => "Value too large for defined data type", - ENOTUNIQ => "Name not unique on network", - EBADFD => "File descriptor in bad state", - EREMCHG => "Remote address changed", - ELIBACC => "Can not access a needed shared library", - ELIBBAD => "Accessing a corrupted shared library", - ELIBSCN => "Lib section in a.out corrupted", - ELIBMAX => "Attempting to link in too many shared libraries", - ELIBEXEC => "Cannot exec a shared library directly", - EILSEQ => "Illegal byte sequence", - ERESTART => "Interrupted system call should be restarted", - ESTRPIPE => "Streams pipe error", - EUSERS => "Too many users", - ENOTSOCK => "Socket operation on non-socket", - EDESTADDRREQ => "Destination address required", - EMSGSIZE => "Message too long", - EPROTOTYPE => "Protocol wrong type for socket", - ENOPROTOOPT => "Protocol not available", - EPROTONOSUPPORT => "Protocol not supported", - ESOCKTNOSUPPORT => "Socket type not supported", - EOPNOTSUPP => "Operation not supported on transport endpoint", - EPFNOSUPPORT => "Protocol family not supported", - EAFNOSUPPORT => "Address family not supported by protocol", - EADDRINUSE => "Address already in use", - EADDRNOTAVAIL => "Cannot assign requested address", - ENETDOWN => "Network is down", - ENETUNREACH => "Network is unreachable", - ENETRESET => "Network dropped connection because of reset", - ECONNABORTED => "Software caused connection abort", - ECONNRESET => "Connection reset by peer", - ENOBUFS => "No buffer space available", - EISCONN => "Transport endpoint is already connected", - ENOTCONN => "Transport endpoint is not connected", - ESHUTDOWN => "Cannot send after transport endpoint shutdown", - ETOOMANYREFS => "Too many references: cannot splice", - ETIMEDOUT => "Connection timed out", - ECONNREFUSED => "Connection refused", - EHOSTDOWN => "Host is down", - EHOSTUNREACH => "No route to host", - EALREADY => "Operation already in progress", - EINPROGRESS => "Operation now in progress", - ESTALE => "Stale file handle", - EUCLEAN => "Structure needs cleaning", - EDQUOT => "Quota exceeded", - ENOMEDIUM => "No medium found", - EMEDIUMTYPE => "Wrong medium type", - ECANCELED => "Operation Canceled", - ENOKEY => "Required key not available", - EKEYEXPIRED => "Key has expired", - EKEYREVOKED => "Key has been revoked", - EKEYREJECTED => "Key was rejected by service", - EOWNERDEAD => "Robust mutexes: Owner died", - ENOTRECOVERABLE => "Robust mutexes: State not recoverable", - ERFKILL => "Robust mutexes: Operation not possible due to RF-kill", - EHWPOISON => "Robust mutexes: Memory page has hardware error", - _ => "Unknown error", - } -} diff --git a/os/arceos/api/arceos_posix_api/src/hermit_abi/hermit_types.rs b/os/arceos/api/arceos_posix_api/src/hermit_abi/hermit_types.rs deleted file mode 100644 index a9529d8bfd..0000000000 --- a/os/arceos/api/arceos_posix_api/src/hermit_abi/hermit_types.rs +++ /dev/null @@ -1,396 +0,0 @@ -use core::ffi::c_char; -pub use core::ffi::{c_int, c_short, c_void}; - -use crate::ctypes_gen; - -pub type ssize_t = isize; -pub type size_t = usize; - -pub use crate::ctypes_gen::{mode_t, off_t}; - -/// A thread handle type -pub type Tid = u32; - -/// Maximum number of priorities -pub const NO_PRIORITIES: usize = 31; - -/// Priority of a thread -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] -pub struct Priority(u8); - -impl Priority { - pub const fn into(self) -> u8 { - self.0 - } - - pub const fn from(x: u8) -> Self { - Priority(x) - } -} - -pub const HIGH_PRIO: Priority = Priority::from(3); -pub const NORMAL_PRIO: Priority = Priority::from(2); -pub const LOW_PRIO: Priority = Priority::from(1); - -pub const FUTEX_RELATIVE_TIMEOUT: u32 = 1; -pub const CLOCK_REALTIME: clockid_t = 1; -pub const CLOCK_MONOTONIC: clockid_t = 4; -pub const STDIN_FILENO: c_int = 0; -pub const STDOUT_FILENO: c_int = 1; -pub const STDERR_FILENO: c_int = 2; -pub const O_RDONLY: u32 = 0o0; -pub const O_WRONLY: u32 = 0o1; -pub const O_RDWR: u32 = 0o2; -pub const O_CREAT: u32 = 0o100; -pub const O_EXCL: u32 = 0o200; -pub const O_TRUNC: u32 = 0o1000; -pub const O_APPEND: u32 = 0o2000; -pub const O_NONBLOCK: u32 = 0o4000; -pub const O_DIRECTORY: u32 = 0o200000; -pub const O_EXEC: u32 = crate::ctypes_gen::O_EXEC; -pub const F_DUPFD: u32 = 0; -pub const F_GETFD: u32 = 1; -pub const F_SETFD: u32 = 2; -pub const F_GETFL: u32 = 3; -pub const F_SETFL: u32 = 4; -pub const FD_CLOEXEC: i32 = 1; - -pub const F_DUPFD_CLOEXEC: u32 = 1030; - -/// returns true if file descriptor `fd` is a tty -pub fn isatty(_fd: c_int) -> bool { - false -} - -/// `timespec` is used by `clock_gettime` to retrieve the -/// current time -#[derive(Default, Copy, Clone, Debug)] -#[repr(C)] -pub struct timespec { - /// seconds - pub tv_sec: time_t, - /// nanoseconds - pub tv_nsec: i32, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct timeval { - pub tv_sec: time_t, - pub tv_usec: suseconds_t, -} - -/// The largest number `rand` will return -pub const RAND_MAX: i32 = 2_147_483_647; - -// POSIX abi use this -pub const MAXADDRS: u32 = 48; - -pub const AF_UNSPEC: u32 = 0; -/// Socket address family: IPv4 -pub const AF_INET: u32 = 3; -/// Socket address family: IPv6 -pub const AF_INET6: u32 = 1; -/// Socket address family: VSOCK protocol for hypervisor-guest communication -pub const AF_VSOCK: u32 = 2; -pub const IPPROTO_IP: u32 = 0; -pub const IPPROTO_IPV6: u32 = 41; -pub const IPPROTO_UDP: u32 = 17; -pub const IPPROTO_TCP: u32 = 6; -pub const IPV6_ADD_MEMBERSHIP: i32 = 12; -pub const IPV6_DROP_MEMBERSHIP: i32 = 13; -pub const IPV6_MULTICAST_LOOP: i32 = 19; -pub const IPV6_V6ONLY: i32 = 27; -pub const IP_TOS: i32 = 1; -pub const IP_TTL: i32 = 2; -pub const IP_MULTICAST_TTL: i32 = 5; -pub const IP_MULTICAST_LOOP: i32 = 7; -pub const IP_ADD_MEMBERSHIP: i32 = 3; -pub const IP_DROP_MEMBERSHIP: i32 = 4; -pub const SHUT_RD: i32 = 0; -pub const SHUT_WR: i32 = 1; -pub const SHUT_RDWR: i32 = 2; -/// Socket supports datagrams (connectionless, unreliable messages of a fixed maximum length) -pub const SOCK_DGRAM: u32 = 2; -/// Socket provides sequenced, reliable, two-way, connection-based byte streams. -pub const SOCK_STREAM: u32 = 1; -/// Set the O_NONBLOCK file status flag on the open socket -pub const SOCK_NONBLOCK: u32 = 0o4000; -/// Set the close-on-exec flag on the new socket -pub const SOCK_CLOEXEC: u32 = 0o40000; -pub const SOL_SOCKET: i32 = 4095; -pub const SO_REUSEADDR: i32 = 0x0004; -pub const SO_KEEPALIVE: i32 = 0x0008; -pub const SO_BROADCAST: i32 = 0x0020; -pub const SO_LINGER: i32 = 0x0080; -pub const SO_SNDBUF: i32 = 0x1001; -pub const SO_RCVBUF: i32 = 0x1002; -pub const SO_SNDTIMEO: i32 = 0x1005; -pub const SO_RCVTIMEO: i32 = 0x1006; -pub const SO_ERROR: i32 = 0x1007; -pub const TCP_NODELAY: i32 = 1; -pub const MSG_PEEK: i32 = 1; -pub const FIONBIO: i32 = 0x8008667eu32 as i32; -pub const EAI_AGAIN: i32 = 2; -pub const EAI_BADFLAGS: i32 = 3; -pub const EAI_FAIL: i32 = 4; -pub const EAI_FAMILY: i32 = 5; -pub const EAI_MEMORY: i32 = 6; -pub const EAI_NODATA: i32 = 7; -pub const EAI_NONAME: i32 = 8; -pub const EAI_SERVICE: i32 = 9; -pub const EAI_SOCKTYPE: i32 = 10; -pub const EAI_SYSTEM: i32 = 11; -pub const EAI_OVERFLOW: i32 = 14; -pub const POLLIN: i16 = 0x1; -pub const POLLPRI: i16 = 0x2; -pub const POLLOUT: i16 = 0x4; -pub const POLLERR: i16 = 0x8; -pub const POLLHUP: i16 = 0x10; -pub const POLLNVAL: i16 = 0x20; -pub const POLLRDNORM: i16 = 0x040; -pub const POLLRDBAND: i16 = 0x080; -pub const POLLWRNORM: i16 = 0x0100; -pub const POLLWRBAND: i16 = 0x0200; -pub const POLLRDHUP: i16 = 0x2000; -pub const EFD_SEMAPHORE: i16 = 0o1; -pub const EFD_NONBLOCK: i16 = 0o4000; -pub const EFD_CLOEXEC: i16 = 0o40000; -pub const IOV_MAX: usize = 1024; -/// VMADDR_CID_ANY means that any address is possible for binding -pub const VMADDR_CID_ANY: u32 = u32::MAX; -pub const VMADDR_CID_HYPERVISOR: u32 = 0; -pub const VMADDR_CID_LOCAL: u32 = 1; -pub const VMADDR_CID_HOST: u32 = 2; -pub type sa_family_t = u8; -pub type socklen_t = u32; -pub type in_addr_t = u32; -pub type in_port_t = u16; -pub type time_t = i64; -pub type useconds_t = u32; -pub type suseconds_t = i32; -pub type nfds_t = usize; -pub type sem_t = *const c_void; -pub type pid_t = i32; -pub type clockid_t = u32; - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default)] -pub struct in_addr { - pub s_addr: in_addr_t, -} - -#[repr(C, align(4))] -#[derive(Debug, Copy, Clone, Default)] -pub struct in6_addr { - pub s6_addr: [u8; 16], -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default)] -pub struct sockaddr { - pub sa_len: u8, - pub sa_family: sa_family_t, - pub sa_data: [c_char; 14], -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default)] -pub struct sockaddr_vm { - pub svm_len: u8, - pub svm_family: sa_family_t, - pub svm_reserved1: u16, - pub svm_port: u32, - pub svm_cid: u32, - pub svm_zero: [u8; 4], -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default)] -pub struct sockaddr_in { - pub sin_len: u8, - pub sin_family: sa_family_t, - pub sin_port: in_port_t, - pub sin_addr: in_addr, - pub sin_zero: [c_char; 8], -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default)] -pub struct sockaddr_in6 { - pub sin6_len: u8, - pub sin6_family: sa_family_t, - pub sin6_port: in_port_t, - pub sin6_flowinfo: u32, - pub sin6_addr: in6_addr, - pub sin6_scope_id: u32, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct addrinfo { - pub ai_flags: i32, - pub ai_family: i32, - pub ai_socktype: i32, - pub ai_protocol: i32, - pub ai_addrlen: socklen_t, - pub ai_canonname: *mut c_char, - pub ai_addr: *mut sockaddr, - pub ai_next: *mut addrinfo, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct sockaddr_storage { - pub s2_len: u8, - pub ss_family: sa_family_t, - __ss_pad1: [u8; 6], - __ss_align: i64, - __ss_pad2: [u8; 112], -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default)] -pub struct ip_mreq { - pub imr_multiaddr: in_addr, - pub imr_interface: in_addr, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default)] -pub struct ipv6_mreq { - pub ipv6mr_multiaddr: in6_addr, - pub ipv6mr_interface: u32, -} - -// fix compile error, hermit does not use them -#[repr(C)] -#[derive(Copy, Clone)] -pub struct aibuf { - pub ai: addrinfo, - pub sa: aibuf_sa, - pub lock: [c_int; 1usize], - pub slot: c_short, - pub ref_: c_short, -} - -#[repr(C)] -#[derive(Copy, Clone)] -pub union aibuf_sa { - pub sin: sockaddr_in, - pub sin6: sockaddr_in6, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default)] -pub struct linger { - pub l_onoff: i32, - pub l_linger: i32, -} - -#[repr(C)] -#[derive(Debug, Copy, Clone, Default)] -pub struct pollfd { - /// file descriptor - pub fd: i32, - /// events to look for - pub events: i16, - /// events returned - pub revents: i16, -} - -#[repr(C)] -#[derive(Debug, Default, Copy, Clone)] -pub struct stat { - pub st_dev: u64, - pub st_ino: u64, - pub st_nlink: u64, - /// access permissions - pub st_mode: u32, - /// user id - pub st_uid: u32, - /// group id - pub st_gid: u32, - /// device id - pub st_rdev: u64, - /// size in bytes - pub st_size: i64, - /// block size - pub st_blksize: i64, - /// size in blocks - pub st_blocks: i64, - /// time of last access - pub st_atim: timespec, - /// time of last modification - pub st_mtim: timespec, - /// time of last status change - pub st_ctim: timespec, -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -pub struct dirent64 { - /// 64-bit inode number - pub d_ino: u64, - /// 64-bit offset to next structure - pub d_off: i64, - /// Size of this dirent - pub d_reclen: u16, - /// File type - pub d_type: u8, - /// Filename (null-terminated) - pub d_name: [c_char; 256], -} - -#[repr(C)] -#[derive(Debug, Clone, Copy)] -/// Describes a region of memory, beginning at `iov_base` address and with the size of `iov_len` bytes. -pub struct iovec { - /// Starting address - pub iov_base: *mut c_void, - /// Size of the memory pointed to by iov_base. - pub iov_len: usize, -} - -pub const DT_UNKNOWN: u8 = 0; -pub const DT_FIFO: u8 = 1; -pub const DT_CHR: u8 = 2; -pub const DT_DIR: u8 = 4; -pub const DT_BLK: u8 = 6; -pub const DT_REG: u8 = 8; -pub const DT_LNK: u8 = 10; -pub const DT_SOCK: u8 = 12; -pub const DT_WHT: u8 = 14; - -pub const S_IFIFO: u32 = 0o1_0000; -pub const S_IFCHR: u32 = 0o2_0000; -pub const S_IFBLK: u32 = 0o6_0000; -pub const S_IFDIR: u32 = 0o4_0000; -pub const S_IFREG: u32 = 0o10_0000; -pub const S_IFLNK: u32 = 0o12_0000; -pub const S_IFSOCK: u32 = 0o14_0000; -pub const S_IFMT: u32 = 0o17_0000; - -/// Pages may not be accessed. -pub const PROT_NONE: u32 = 0; -/// Indicates that the memory region should be readable. -pub const PROT_READ: u32 = 1 << 0; -/// Indicates that the memory region should be writable. -pub const PROT_WRITE: u32 = 1 << 1; -/// Indicates that the memory region should be executable. -pub const PROT_EXEC: u32 = 1 << 2; - -/// The file offset is set to offset bytes. -pub const SEEK_SET: i32 = 0; -/// The file offset is set to its current location plus offset bytes. -pub const SEEK_CUR: i32 = 1; -/// The file offset is set to the size of the file plus offset bytes. -pub const SEEK_END: i32 = 2; - -/// imported ctypes -/// sysconf -pub use ctypes_gen::{ - _SC_AVPHYS_PAGES, _SC_NPROCESSORS_ONLN, _SC_OPEN_MAX, _SC_PAGE_SIZE, _SC_PHYS_PAGES, -}; -/// rlimit -pub use ctypes_gen::{RLIMIT_DATA, RLIMIT_NOFILE, RLIMIT_STACK, rlimit}; diff --git a/os/arceos/api/arceos_posix_api/src/hermit_abi/mod.rs b/os/arceos/api/arceos_posix_api/src/hermit_abi/mod.rs deleted file mode 100644 index 985ab67cf6..0000000000 --- a/os/arceos/api/arceos_posix_api/src/hermit_abi/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#![allow(nonstandard_style, dead_code, missing_docs)] - -pub mod errno; -pub mod hermit_types; diff --git a/os/arceos/api/arceos_posix_api/src/imp/fs.rs b/os/arceos/api/arceos_posix_api/src/imp/fs.rs index e761a17b82..593354c89b 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/fs.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/fs.rs @@ -1,7 +1,8 @@ use alloc::sync::Arc; -use core::ffi::{c_char, c_int}; -#[cfg(not(feature = "use-hermit-types"))] -use core::mem::size_of; +use core::{ + ffi::{c_char, c_int}, + mem::size_of, +}; use ax_errno::{LinuxError, LinuxResult}; use ax_fs::fops::OpenOptions; @@ -19,11 +20,6 @@ pub struct Directory { inner: Mutex, } -// ============================================================================ -// Linux-style getdents64 implementation (for normal Linux targets) -// ============================================================================ - -#[cfg(not(feature = "use-hermit-types"))] #[repr(C, packed)] struct LinuxDirent64Head { d_ino: u64, @@ -32,13 +28,11 @@ struct LinuxDirent64Head { d_type: u8, } -#[cfg(not(feature = "use-hermit-types"))] struct DirBuffer<'a> { buf: &'a mut [u8], offset: usize, } -#[cfg(not(feature = "use-hermit-types"))] impl<'a> DirBuffer<'a> { fn new(buf: &'a mut [u8]) -> Self { Self { buf, offset: 0 } @@ -82,83 +76,6 @@ impl<'a> DirBuffer<'a> { } } -// ============================================================================ -// Hermit-style getdents64 implementation (for hermit targets) -// ============================================================================ - -#[cfg(feature = "use-hermit-types")] -use core::mem; - -#[cfg(feature = "use-hermit-types")] -struct HermitDirBuffer<'a> { - buf: &'a mut [u8], - offset: usize, -} - -#[cfg(feature = "use-hermit-types")] -impl<'a> HermitDirBuffer<'a> { - fn new(buf: &'a mut [u8]) -> Self { - Self { buf, offset: 0 } - } - - fn used_len(&self) -> usize { - self.offset - } - - fn remaining_space(&self) -> usize { - self.buf.len().saturating_sub(self.offset) - } - - fn write_entry(&mut self, d_ino: u64, d_type: u8, name: &[u8]) -> bool { - // Hermit dirent64 structure layout: - // offset 0: d_ino (u64, 8 bytes) - // offset 8: d_off (i64, 8 bytes) - // offset 16: d_reclen (u16, 2 bytes) - // offset 18: d_type (u8, 1 byte) - // offset 19: d_name (variable-length null-terminated c_char array) - const NAME_OFFSET: usize = 19; - - let name_len = name.len().min(255); - // Total size: fixed header (19 bytes) + name + null terminator - let dirent_len = NAME_OFFSET + name_len + 1; - // Align to dirent64 struct alignment (8 bytes for u64) - let reclen = dirent_len.next_multiple_of(mem::align_of::()); - - if self.remaining_space() < reclen { - return false; - } - - unsafe { - let entry_ptr = self.buf.as_mut_ptr().add(self.offset); - - // Write fixed fields - let d_ino_ptr = entry_ptr.cast::(); - d_ino_ptr.write_unaligned(d_ino); - - let d_off_ptr = entry_ptr.add(8).cast::(); - d_off_ptr.write_unaligned(0); // d_off is not meaningful in Hermit - - let d_reclen_ptr = entry_ptr.add(16).cast::(); - d_reclen_ptr.write_unaligned(reclen as u16); - - let d_type_ptr = entry_ptr.add(18); - d_type_ptr.write(d_type); - - // Write d_name (starting at offset 19) - let name_ptr = entry_ptr.add(NAME_OFFSET); - name_ptr.copy_from_nonoverlapping(name.as_ptr(), name_len); - name_ptr.add(name_len).write(0); // null terminator - } - - self.offset += reclen; - true - } -} - -// ============================================================================ -// Common file type conversion -// ============================================================================ - fn file_type_to_d_type(ty: ax_fs::fops::FileType) -> u8 { match ty { ax_fs::fops::FileType::Dir => 4, // DT_DIR @@ -338,15 +255,10 @@ pub fn sys_open(filename: *const c_char, flags: c_int, mode: ctypes::mode_t) -> }) } -// ============================================================================ -// Linux-style sys_getdents64 (standard Linux targets) -// ============================================================================ - /// Read directory entries from `fd` into Linux-style linux_dirent64 buffer. /// /// Reference: Starry OS implementation /// Return number of bytes written on success. -#[cfg(not(feature = "use-hermit-types"))] pub unsafe fn sys_getdents64(fd: c_int, buf: *mut u8, len: usize) -> ctypes::ssize_t { debug!("sys_getdents64 (Linux) <= {fd} {:#x} {len}", buf as usize); syscall_body!(sys_getdents64, { @@ -381,60 +293,6 @@ pub unsafe fn sys_getdents64(fd: c_int, buf: *mut u8, len: usize) -> ctypes::ssi }) } -// ============================================================================ -// Hermit-style sys_getdents64 (Hermit/BSD-like targets) -// ============================================================================ - -/// Read directory entries from `fd` into Hermit-style dirent64 buffer. -/// -/// Reference: Hermit OS official implementation -/// Parameters: -/// - `fd`: File Descriptor of the directory in question. -/// - `buf`: Memory for the kernel to store the filled `Dirent64` objects including -/// the c-strings with the filenames. -/// - `len`: Size of the memory region described by `buf` in bytes. -/// -/// Return: -/// The number of bytes read into `buf` on success. Zero indicates that no more -/// entries remain and the directory's read position needs to be reset using `sys_lseek`. -/// Negative numbers encode errors. -#[cfg(feature = "use-hermit-types")] -pub unsafe fn sys_getdents64(fd: c_int, buf: *mut u8, len: usize) -> ctypes::ssize_t { - debug!("sys_getdents64 (Hermit) <= {fd} {:#x} {len}", buf as usize); - syscall_body!(sys_getdents64, { - // Hermit ABI: null buffer or zero-sized buffer are invalid - if buf.is_null() || len == 0 { - return Err(LinuxError::EINVAL); - } - - // Hermit returns EINVAL for invalid directory objects - let dir = Directory::from_fd(fd).map_err(|_| LinuxError::EINVAL)?; - let mut dir = dir.inner.lock(); - - let out = unsafe { core::slice::from_raw_parts_mut(buf, len) }; - let mut dir_buf = HermitDirBuffer::new(out); - - let mut entries: [ax_fs::fops::DirEntry; 16] = - core::array::from_fn(|_| ax_fs::fops::DirEntry::default()); - loop { - let nr = dir.read_dir(&mut entries)?; - if nr == 0 { - break; - } - - for entry in entries.iter().take(nr) { - let d_type = file_type_to_d_type(entry.entry_type()); - // Hermit style: only d_ino and d_type, d_off is not meaningful - if !dir_buf.write_entry(1, d_type, entry.name_as_bytes()) { - return Ok(dir_buf.used_len() as ctypes::ssize_t); - } - } - } - - Ok(dir_buf.used_len() as ctypes::ssize_t) - }) -} - /// Set the position of the file indicated by `fd`. /// /// Return its position after seek. diff --git a/os/arceos/api/arceos_posix_api/src/imp/io_mpx/poll.rs b/os/arceos/api/arceos_posix_api/src/imp/io_mpx/poll.rs index 6b95d6a496..f639abee95 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/io_mpx/poll.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/io_mpx/poll.rs @@ -5,24 +5,9 @@ use ax_hal::time::wall_time; use crate::{ctypes, imp::fd_ops::get_file_like}; -#[cfg(feature = "use-hermit-types")] -const POLLIN_EVENT: i16 = ctypes::POLLIN; -#[cfg(not(feature = "use-hermit-types"))] const POLLIN_EVENT: i16 = ctypes::POLLIN as i16; - -#[cfg(feature = "use-hermit-types")] -const POLLOUT_EVENT: i16 = ctypes::POLLOUT; -#[cfg(not(feature = "use-hermit-types"))] const POLLOUT_EVENT: i16 = ctypes::POLLOUT as i16; - -#[cfg(feature = "use-hermit-types")] -const POLLERR_EVENT: i16 = ctypes::POLLERR; -#[cfg(not(feature = "use-hermit-types"))] const POLLERR_EVENT: i16 = ctypes::POLLERR as i16; - -#[cfg(feature = "use-hermit-types")] -const POLLNVAL_EVENT: i16 = ctypes::POLLNVAL; -#[cfg(not(feature = "use-hermit-types"))] const POLLNVAL_EVENT: i16 = ctypes::POLLNVAL as i16; /// Poll file descriptors for I/O readiness (POSIX `poll` semantics). diff --git a/os/arceos/api/arceos_posix_api/src/imp/net.rs b/os/arceos/api/arceos_posix_api/src/imp/net.rs index b9c7a16775..cdb8276e65 100644 --- a/os/arceos/api/arceos_posix_api/src/imp/net.rs +++ b/os/arceos/api/arceos_posix_api/src/imp/net.rs @@ -619,7 +619,7 @@ pub unsafe fn sys_setsockopt( } // Accept and silently ignore timeouts — ArceOS's smoltcp // stack does not track per-socket read/write timeouts, but - // hermit std calls setsockopt for these during + // std calls setsockopt for these during // set_read_timeout / set_write_timeout. ctypes::SO_RCVTIMEO | ctypes::SO_SNDTIMEO => { debug!( 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..7a617a6fb3 100644 --- a/os/arceos/api/arceos_posix_api/src/lib.rs +++ b/os/arceos/api/arceos_posix_api/src/lib.rs @@ -31,14 +31,6 @@ pub mod ctypes_gen { include!(concat!(env!("OUT_DIR"), "/ctypes_gen.rs")); } -#[cfg(not(feature = "use-hermit-types"))] -pub use self::ctypes_gen as ctypes; - -#[cfg(feature = "use-hermit-types")] -mod hermit_abi; - -#[cfg(feature = "use-hermit-types")] -pub use hermit_abi::hermit_types as ctypes; #[cfg(feature = "fd")] pub use imp::fd_ops::{sys_close, sys_dup, sys_dup2, sys_fcntl}; #[cfg(feature = "fs")] @@ -61,7 +53,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}; @@ -72,3 +65,5 @@ pub use imp::{ task::{sys_exit, sys_getpid, sys_sched_yield}, time::{sys_clock_gettime, sys_nanosleep}, }; + +pub use self::ctypes_gen as ctypes; diff --git a/os/arceos/doc/std_support_readme.md b/os/arceos/doc/std_support_readme.md index c3468cd4e7..3c194e5c1b 100644 --- a/os/arceos/doc/std_support_readme.md +++ b/os/arceos/doc/std_support_readme.md @@ -1,130 +1,97 @@ # 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, +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"` +- 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/.cargo/config.toml b/os/arceos/examples/std/.cargo/config.toml deleted file mode 100644 index 845da276dc..0000000000 --- a/os/arceos/examples/std/.cargo/config.toml +++ /dev/null @@ -1,117 +0,0 @@ -# Template Cargo runner config for ArceOS std examples. -# -# Usage: -# 1) Copy this file to: /.cargo/config.toml. -# 2) Modify qemu arguments according to features enabled. -# 3) If feature `fs` is enabled, put disk.img in that example root. -# -# Notes for qemu arguments: -# - `make A=... ARCH=... run` default is `BUS=pci` for all 4 arches: -# x86_64 / riscv64 / aarch64 / loongarch64 -# so virtio devices use "-pci" suffix by default. -# - If BUS=mmio, replace: -# virtio-net-pci -> virtio-net-device -# virtio-blk-pci -> virtio-blk-device -# - For server examples, we use `hostfwd` to forward ports. -# - Graphics mode: -# -device virtio-gpu- -vga none -serial mon:stdio -# - Input devices: -# -device virtio-mouse-pci -device virtio-keyboard-pci -# - Vsock: -# -device vhost-vsock-pci,id=virtiosocket0,guest-cid=103 -# - Netdev backends: -# user: -netdev user,id=net0[,hostfwd=...] -# tap: -netdev tap,id=net0,script=scripts/net/qemu-ifup.sh,downscript=no,vhost=,vhostforce= -# bridge: -netdev bridge,id=net0,br=virbr0 -# - Packet dump: -# -object filter-dump,id=dump0,netdev=net0,file=netdump.pcap -# - VFIO passthrough: -# --device vfio-pci,host= -# - QEMU internal logging: -# -D qemu.log -d in_asm,int,mmu,pcall,cpu_reset,guest_errors -# - Deterministic icount mode: -# -icount shift=1 -# - Acceleration: -# Linux: -cpu host -accel kvm -# macOS: -cpu host -accel hvf - -[target.riscv64gc-unknown-hermit] -rustflags = [ - "-C", "link-arg=-no-pie", - "-C", "link-arg=-Tlinker.x" -] -runner = [ - "qemu-system-riscv64", - "-m", "128M", # RAM size - "-smp", "1", # Number of CPU cores - "-machine", "virt", - "-nographic", - "-device", "virtio-net-pci,netdev=net0", # Network support (PCI) - # "-netdev", "user,id=net0", # Use user network - "-netdev", "user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555", # Use user network, forwarding ports for server examples - "-device", "virtio-blk-pci,drive=disk0", # Disk support (PCI) - "-drive", "id=disk0,if=none,format=raw,file=../disk.img", # Use `disk.img` as disk image - "-kernel", -] - -[target.x86_64-unknown-hermit] -rustflags = [ - "-C", "link-arg=-no-pie", - "-C", "link-arg=-Tlinker.x" -] -runner = [ - "qemu-system-x86_64", - "-m", "128M", # RAM size - "-smp", "1", # Number of CPU cores - "-machine", "q35", - "-nographic", - "-device", "virtio-net-pci,netdev=net0", # Network support (PCI) - # "-netdev", "user,id=net0", # Use user network - "-netdev", "user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555", # Use user network, forwarding ports for server examples - "-device", "virtio-blk-pci,drive=disk0", # Disk support (PCI) - "-drive", "id=disk0,if=none,format=raw,file=../disk.img", # Use `disk.img` as disk image - "-kernel", -] - -[target.aarch64-unknown-hermit] -rustflags = [ - "-C", "link-arg=-no-pie", - "-C", "link-arg=-Tlinker.x" -] -runner = [ - "qemu-system-aarch64", - "-m", "128M", # RAM size - "-smp", "1", # Number of CPU cores - "-machine", "virt", - "-cpu", "cortex-a72", - "-nographic", - "-device", "virtio-net-pci,netdev=net0", # Network support (PCI) - # "-netdev", "user,id=net0", # Use user network - "-netdev", "user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555", # Use user network, forwarding ports for server examples - "-device", "virtio-blk-pci,drive=disk0", # Disk support (MMIO) - "-drive", "id=disk0,if=none,format=raw,file=../disk.img", # Use `disk.img` as disk image - "-kernel", -] - -[target.loongarch64-unknown-hermit] -rustflags = [ - "-C", "link-arg=-no-pie", - "-C", "link-arg=-Tlinker.x" -] -runner = [ - "qemu-system-loongarch64", - "-m", "128M", # RAM size - "-smp", "1", # Number of CPU cores - "-machine", "virt", - "-nographic", - "-device", "virtio-net-pci,netdev=net0", # Network support (PCI) - # "-netdev", "user,id=net0", # Use user network - "-netdev", "user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555", # Use user network, forwarding ports for server examples - "-device", "virtio-blk-pci,drive=disk0", # Disk support (PCI) - "-drive", "id=disk0,if=none,format=raw,file=../disk.img", # Use `disk.img` as disk image - "-kernel", -] - -[unstable] -build-std = ["std", "panic_abort"] -build-std-features = [] 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/lineedit.rs b/os/arceos/examples/std/arce_agent/src/lineedit.rs index a46d26ee81..79b230959d 100644 --- a/os/arceos/examples/std/arce_agent/src/lineedit.rs +++ b/os/arceos/examples/std/arce_agent/src/lineedit.rs @@ -8,7 +8,7 @@ // - Insert in the middle of the line with redraw // - Command history (Up / Down arrows) // -// No external crate dependencies — works on ArceOS Hermit target. +// No external crate dependencies; works on the ArceOS std target. use std::io::{self, Read, Write}; 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/loongarch64-unknown-hermit.json b/os/arceos/examples/std/loongarch64-unknown-hermit.json deleted file mode 100644 index 5407a8a81c..0000000000 --- a/os/arceos/examples/std/loongarch64-unknown-hermit.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "llvm-target": "loongarch64-unknown-none", - "os": "hermit", - "arch": "loongarch64", - "abi": "softfloat", - "data-layout": "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128", - "linker-flavor": "gnu-lld", - "linker": "rust-lld", - "features": "-f,-d", - "llvm-abiname": "lp64s", - "has-thread-local": true, - "tls-model": "initial-exec", - "code-model": "medium", - "relocation-model": "static", - "panic-strategy": "abort", - "max-atomic-width": 64, - "target-pointer-width": 64, - "metadata": { - "description": "LoongArch64 Hermit (fused from loong none + x86 hermit delta)", - "host_tools": false, - "std": true, - "tier": 3 - } -} 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/Cargo.toml b/os/arceos/modules/axalloc/Cargo.toml index 0dce42d4cd..800e5e5bbb 100644 --- a/os/arceos/modules/axalloc/Cargo.toml +++ b/os/arceos/modules/axalloc/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true [features] default = [] +global-allocator = [] tlsf = [] buddy-slab = ["dep:buddy-slab-allocator", "dep:ax-percpu", "dep:ax-plat"] tracking = ["dep:ax-percpu", "dep:axbacktrace"] diff --git a/os/arceos/modules/axalloc/src/buddy_slab.rs b/os/arceos/modules/axalloc/src/buddy_slab.rs index e1f8d5392b..bed7c248f5 100644 --- a/os/arceos/modules/axalloc/src/buddy_slab.rs +++ b/os/arceos/modules/axalloc/src/buddy_slab.rs @@ -16,7 +16,10 @@ use buddy_slab_allocator::{ use super::{AllocResult, AllocatorOps, UsageKind, Usages}; /// The global allocator instance for buddy-slab mode. -#[cfg_attr(all(target_os = "none", not(test)), global_allocator)] +#[cfg_attr( + all(any(target_os = "none", feature = "global-allocator"), not(test)), + global_allocator +)] static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new(); /// The default byte allocator for buddy-slab mode. diff --git a/os/arceos/modules/axalloc/src/stub_impl.rs b/os/arceos/modules/axalloc/src/stub_impl.rs index 53c7b6d8e2..958e015668 100644 --- a/os/arceos/modules/axalloc/src/stub_impl.rs +++ b/os/arceos/modules/axalloc/src/stub_impl.rs @@ -10,7 +10,10 @@ use ax_kspin::SpinNoIrq; use super::{AllocResult, AllocatorOps, UsageKind, Usages}; /// The global allocator instance (stub). -#[cfg_attr(all(target_os = "none", not(test)), global_allocator)] +#[cfg_attr( + all(any(target_os = "none", feature = "global-allocator"), not(test)), + global_allocator +)] static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new(); /// Placeholder byte allocator type when no backend is enabled. diff --git a/os/arceos/modules/axalloc/src/tlsf_impl.rs b/os/arceos/modules/axalloc/src/tlsf_impl.rs index 7cbf68f925..2ebfd83fb4 100644 --- a/os/arceos/modules/axalloc/src/tlsf_impl.rs +++ b/os/arceos/modules/axalloc/src/tlsf_impl.rs @@ -11,7 +11,10 @@ use rlsf::Tlsf; use super::{AllocResult, AllocatorOps, UsageKind, Usages}; /// The global allocator instance for TLSF mode. -#[cfg_attr(all(target_os = "none", not(test)), global_allocator)] +#[cfg_attr( + all(any(target_os = "none", feature = "global-allocator"), not(test)), + global_allocator +)] static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new(); const PAGE_SIZE: usize = 0x1000; diff --git a/os/arceos/modules/axhal/Cargo.toml b/os/arceos/modules/axhal/Cargo.toml index 0c98cba5c1..d01a4aed4e 100644 --- a/os/arceos/modules/axhal/Cargo.toml +++ b/os/arceos/modules/axhal/Cargo.toml @@ -63,6 +63,7 @@ hv = [ axvisor-linker = [] # Custom or default platforms +host-test = [] myplat = [] plat-dyn = ["axplat-dyn"] x86-pc = ["dep:ax-plat-x86-pc"] @@ -92,6 +93,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 +101,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 } diff --git a/os/arceos/modules/axhal/build.rs b/os/arceos/modules/axhal/build.rs index a6dde5ccfb..51304518cd 100644 --- a/os/arceos/modules/axhal/build.rs +++ b/os/arceos/modules/axhal/build.rs @@ -54,9 +54,9 @@ const DEFAULT_PLATFORMS: &[(&str, &str)] = &[ fn main() { println!("cargo:rustc-check-cfg=cfg(plat_dyn)"); - 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"); + println!("cargo:rerun-if-env-changed={}", feature_env("host-test")); println!("cargo:rerun-if-env-changed={}", feature_env("myplat")); println!("cargo:rerun-if-env-changed={}", feature_env("defplat")); for platform in PLATFORM_FEATURES { @@ -67,9 +67,8 @@ 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 selected_platform = check_platform_features(&arch); + gen_selected_platform(&arch, selected_platform).unwrap(); let config = load_linker_config().unwrap(); @@ -85,17 +84,13 @@ fn main() { } } -fn check_platform_features(arch: &str, target_os: &str) -> Option<&'static PlatformFeature> { +fn check_platform_features(arch: &str) -> Option<&'static PlatformFeature> { let has_myplat = feature_enabled("myplat"); let enabled_platforms = PLATFORM_FEATURES .iter() .filter(|platform| feature_enabled(platform.feature)) .collect::>(); - if has_myplat || !enabled_platforms.is_empty() { - println!("cargo:rustc-cfg=ax_hal_any_platform_feature"); - } - if has_myplat && !enabled_platforms.is_empty() { panic!("ax-hal/myplat must not be combined with a built-in ax-hal platform feature"); } @@ -121,16 +116,14 @@ fn check_platform_features(arch: &str, target_os: &str) -> Option<&'static Platf } } - if target_os == "none" { - for platform in &enabled_platforms { - if let Some(target_arch) = platform.target_arch - && arch != target_arch - { - panic!( - "ax-hal/{} requires target_arch = \"{}\"", - platform.feature, target_arch - ); - } + for platform in &enabled_platforms { + if let Some(target_arch) = platform.target_arch + && arch != target_arch + { + panic!( + "ax-hal/{} requires target_arch = \"{}\"", + platform.feature, target_arch + ); } } @@ -141,21 +134,17 @@ fn check_platform_features(arch: &str, target_os: &str) -> Option<&'static Platf }) } -fn gen_selected_platform( - arch: &str, - target_os: &str, - platform: Option<&PlatformFeature>, -) -> Result<()> { +fn gen_selected_platform(arch: &str, 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) + 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 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..52936c0a08 100644 --- a/os/arceos/modules/axhal/src/platform.rs +++ b/os/arceos/modules/axhal/src/platform.rs @@ -1,25 +1,7 @@ -#[cfg(all(not(test), not(feature = "myplat")))] +#[cfg(all(not(test), not(feature = "host-test"), not(feature = "myplat")))] include!(concat!(env!("OUT_DIR"), "/selected_platform.rs")); -#[cfg(all( - target_os = "none", - not(test), - not(feature = "myplat"), - not(feature = "defplat"), - not(ax_hal_any_platform_feature) -))] -compile_error!("select an ax-hal platform feature or enable ax-hal/myplat"); - -#[cfg(test)] -#[path = "dummy.rs"] -mod dummy; - -#[cfg(all( - not(test), - not(target_os = "none"), - not(feature = "defplat"), - not(ax_hal_any_platform_feature) -))] +#[cfg(any(test, feature = "host-test"))] #[path = "dummy.rs"] mod dummy; diff --git a/os/arceos/modules/axnet-ng/Cargo.toml b/os/arceos/modules/axnet-ng/Cargo.toml index 2c242e9b41..010186713e 100644 --- a/os/arceos/modules/axnet-ng/Cargo.toml +++ b/os/arceos/modules/axnet-ng/Cargo.toml @@ -54,3 +54,7 @@ features = [ # "reassembly-buffer-size-65536", "reassembly-buffer-count-32", # "assembler-max-segment-count-32", ] + +[dev-dependencies] +ax-hal = { workspace = true, features = ["host-test"] } +ax-kspin = { workspace = true, features = ["host-test"] } diff --git a/os/arceos/modules/axruntime/Cargo.toml b/os/arceos/modules/axruntime/Cargo.toml index 62f5211481..b713c97ad1 100644 --- a/os/arceos/modules/axruntime/Cargo.toml +++ b/os/arceos/modules/axruntime/Cargo.toml @@ -12,6 +12,7 @@ default = [] dma = ["paging"] ext-ld = [] +std-compat = [] ipi = ["dep:ax-ipi"] irq = [ "ax-hal/irq", diff --git a/os/arceos/modules/axruntime/src/block/mod.rs b/os/arceos/modules/axruntime/src/block/mod.rs index 5bb5f943e0..9a80deac2c 100644 --- a/os/arceos/modules/axruntime/src/block/mod.rs +++ b/os/arceos/modules/axruntime/src/block/mod.rs @@ -1,36 +1,19 @@ -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(feature = "fs-ng")] use alloc::vec::Vec; #[cfg(all( feature = "irq", - any( - all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") - ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) - ) + any(all(feature = "fs", not(feature = "fs-ng")), feature = "fs-ng") ))] use core::ptr::NonNull; -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(feature = "fs-ng")] mod root; -#[cfg(any( - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")), - test -))] +#[cfg(any(feature = "fs-ng", test))] pub(crate) mod volume; #[cfg(all( feature = "irq", - any( - all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") - ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) - ) + any(all(feature = "fs", not(feature = "fs-ng")), feature = "fs-ng") ))] struct BlockIrqState { handler: ax_driver::block::BlockIrqHandler, @@ -39,14 +22,7 @@ struct BlockIrqState { #[cfg(all( feature = "irq", - any( - all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") - ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) - ) + any(all(feature = "fs", not(feature = "fs-ng")), feature = "fs-ng") ))] pub(crate) struct BlockIrqRegistration { _state: alloc::boxed::Box, @@ -54,27 +30,13 @@ pub(crate) struct BlockIrqRegistration { #[cfg(all( not(feature = "irq"), - any( - all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") - ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) - ) + any(all(feature = "fs", not(feature = "fs-ng")), feature = "fs-ng") ))] pub(crate) type BlockIrqRegistration = (); #[cfg(all( feature = "irq", - any( - all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") - ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) - ) + any(all(feature = "fs", not(feature = "fs-ng")), feature = "fs-ng") ))] unsafe fn handle_block_irq( _ctx: axklib::irq::IrqContext, @@ -87,14 +49,7 @@ unsafe fn handle_block_irq( #[cfg(all( feature = "irq", - any( - all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") - ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) - ) + any(all(feature = "fs", not(feature = "fs-ng")), feature = "fs-ng") ))] impl Drop for BlockIrqState { fn drop(&mut self) { @@ -106,13 +61,13 @@ impl Drop for BlockIrqState { } } -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(feature = "fs-ng")] struct FsNgBlockDevice { _irq: Option, block: ax_driver::block::Block, } -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(feature = "fs-ng")] impl FsNgBlockDevice { fn new(mut block: ax_driver::block::Block) -> Self { let irq = register_irq_handler(&mut block); @@ -120,7 +75,7 @@ impl FsNgBlockDevice { } } -#[cfg(all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")))] +#[cfg(feature = "fs-ng")] impl ax_fs_ng::FsBlockDevice for FsNgBlockDevice { fn name(&self) -> &str { self.block.name() @@ -147,14 +102,7 @@ impl ax_fs_ng::FsBlockDevice for FsNgBlockDevice { } } -#[cfg(any( - all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") - ), - all(feature = "fs-ng", any(not(feature = "plat-dyn"), target_os = "none")) -))] +#[cfg(any(all(feature = "fs", not(feature = "fs-ng")), feature = "fs-ng"))] pub(crate) fn register_irq_handler( block: &mut ax_driver::block::Block, ) -> Option { @@ -190,11 +138,7 @@ 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")] init_fs_ng_from_blocks(take_block_devices(), bootargs); - - #[cfg(not(target_os = "none"))] - let _ = bootargs; } #[cfg(all(feature = "fs-ng", not(feature = "plat-dyn")))] @@ -202,12 +146,12 @@ 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(feature = "fs-ng")] 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(feature = "fs-ng")] 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..9f9ded95df 100644 --- a/os/arceos/modules/axruntime/src/devices.rs +++ b/os/arceos/modules/axruntime/src/devices.rs @@ -11,23 +11,17 @@ 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")] - { - if !rdrive::is_initialized() { - return alloc::vec::Vec::new(); - } - let devices = ax_driver::block::take_block_devices(); - devices - .into_iter() - .map(|dev| { - alloc::boxed::Box::new(FsBlockDevice::new(dev)) - as alloc::boxed::Box - }) - .collect() + if !rdrive::is_initialized() { + return alloc::vec::Vec::new(); } - - #[cfg(not(target_os = "none"))] - alloc::vec::Vec::new() + let devices = ax_driver::block::take_block_devices(); + devices + .into_iter() + .map(|dev| { + alloc::boxed::Box::new(FsBlockDevice::new(dev)) + as alloc::boxed::Box + }) + .collect() } #[cfg(all(feature = "fs", not(feature = "fs-ng"), not(feature = "plat-dyn")))] @@ -45,25 +39,19 @@ 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")] - { - if !rdrive::is_initialized() { - ax_display::init_display(core::iter::empty::()); - return; - } - let devices = ax_driver::display::take_display_devices() - .unwrap_or_else(|err| panic!("failed to open display devices: {err:?}")) - .into_iter() - .map(|dev| { - let display = ax_display::rdif::RdifDisplayDevice::new(dev) - .unwrap_or_else(|err| panic!("failed to adapt display device: {err:?}")); - ax_display::ErasedDisplayDevice::new(display) - }); - ax_display::init_display(devices); + if !rdrive::is_initialized() { + ax_display::init_display(core::iter::empty::()); + return; } - - #[cfg(not(target_os = "none"))] - ax_display::init_display(core::iter::empty::()); + let devices = ax_driver::display::take_display_devices() + .unwrap_or_else(|err| panic!("failed to open display devices: {err:?}")) + .into_iter() + .map(|dev| { + let display = ax_display::rdif::RdifDisplayDevice::new(dev) + .unwrap_or_else(|err| panic!("failed to adapt display device: {err:?}")); + ax_display::ErasedDisplayDevice::new(display) + }); + ax_display::init_display(devices); } #[cfg(all(feature = "display", not(feature = "plat-dyn")))] @@ -81,21 +69,15 @@ pub(crate) fn init_static_display() { #[cfg(all(feature = "input", feature = "plat-dyn"))] pub(crate) fn init_dyn_input() { - #[cfg(target_os = "none")] - { - if !rdrive::is_initialized() { - ax_input::init_input(core::iter::empty::()); - return; - } - let devices = ax_driver::input::take_input_devices() - .unwrap_or_else(|err| panic!("failed to open input devices: {err:?}")) - .into_iter() - .map(|dev| ax_input::ErasedInputDevice::new(ax_input::rdif::RdifInputDevice::new(dev))); - ax_input::init_input(devices); + if !rdrive::is_initialized() { + ax_input::init_input(core::iter::empty::()); + return; } - - #[cfg(not(target_os = "none"))] - ax_input::init_input(core::iter::empty::()); + let devices = ax_driver::input::take_input_devices() + .unwrap_or_else(|err| panic!("failed to open input devices: {err:?}")) + .into_iter() + .map(|dev| ax_input::ErasedInputDevice::new(ax_input::rdif::RdifInputDevice::new(dev))); + ax_input::init_input(devices); } #[cfg(all(feature = "input", not(feature = "plat-dyn")))] @@ -155,19 +137,13 @@ 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")] - { - if !rdrive::is_initialized() { - ax_net_ng::init_vsock(alloc::vec::Vec::new()); - return; - } - let devices = ax_driver::vsock::take_vsock_devices() - .unwrap_or_else(|err| panic!("failed to open vsock devices: {err:?}")); - ax_net_ng::init_vsock(devices); + if !rdrive::is_initialized() { + ax_net_ng::init_vsock(alloc::vec::Vec::new()); + return; } - - #[cfg(not(target_os = "none"))] - ax_net_ng::init_vsock(alloc::vec::Vec::new()); + let devices = ax_driver::vsock::take_vsock_devices() + .unwrap_or_else(|err| panic!("failed to open vsock devices: {err:?}")); + ax_net_ng::init_vsock(devices); } #[cfg(all(feature = "vsock", not(feature = "plat-dyn")))] @@ -179,67 +155,46 @@ 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")] - { - if !rdrive::is_initialized() { - return alloc::vec::Vec::new(); - } - let mut devices = alloc::vec::Vec::new(); - for dev in rdrive::get_list::() { - let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) - .unwrap_or_else(|err| panic!("failed to open net device: {err:?}")); - let driver = ax_net::RdNetDriver::new(name, net, irq_num) - .unwrap_or_else(|err| panic!("failed to adapt net device: {err:?}")); - devices.push( - alloc::boxed::Box::new(driver) as alloc::boxed::Box - ); - } + if !rdrive::is_initialized() { + return alloc::vec::Vec::new(); + } + let mut devices = alloc::vec::Vec::new(); + for dev in rdrive::get_list::() { + let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) + .unwrap_or_else(|err| panic!("failed to open net device: {err:?}")); + let driver = ax_net::RdNetDriver::new(name, net, irq_num) + .unwrap_or_else(|err| panic!("failed to adapt net device: {err:?}")); devices + .push(alloc::boxed::Box::new(driver) as alloc::boxed::Box); } - - #[cfg(not(target_os = "none"))] - alloc::vec::Vec::new() + devices } #[cfg(all(feature = "net-ng", feature = "plat-dyn"))] fn take_dyn_net_ng_drivers() -> alloc::vec::Vec> { - #[cfg(target_os = "none")] - { - if !rdrive::is_initialized() { - return alloc::vec::Vec::new(); - } - let mut devices = alloc::vec::Vec::new(); - for dev in rdrive::get_list::() { - let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) - .unwrap_or_else(|err| panic!("failed to open net device: {err:?}")); - let driver = ax_net_ng::RdNetDriver::new(name, net, irq_num) - .unwrap_or_else(|err| panic!("failed to adapt net device: {err:?}")); - devices - .push(alloc::boxed::Box::new(driver) - as alloc::boxed::Box); - } - devices + if !rdrive::is_initialized() { + return alloc::vec::Vec::new(); } - - #[cfg(not(target_os = "none"))] - alloc::vec::Vec::new() + let mut devices = alloc::vec::Vec::new(); + for dev in rdrive::get_list::() { + let (net, name, irq_num) = ax_driver::net::take_rd_net_device(dev) + .unwrap_or_else(|err| panic!("failed to open net device: {err:?}")); + let driver = ax_net_ng::RdNetDriver::new(name, net, irq_num) + .unwrap_or_else(|err| panic!("failed to adapt net device: {err:?}")); + devices.push( + alloc::boxed::Box::new(driver) as alloc::boxed::Box + ); + } + devices } -#[cfg(all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") -))] +#[cfg(all(feature = "fs", not(feature = "fs-ng")))] struct FsBlockDevice { _irq: Option, block: ax_driver::block::Block, } -#[cfg(all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") -))] +#[cfg(all(feature = "fs", not(feature = "fs-ng")))] impl FsBlockDevice { fn new(mut block: ax_driver::block::Block) -> Self { let irq = crate::block::register_irq_handler(&mut block); @@ -247,11 +202,7 @@ impl FsBlockDevice { } } -#[cfg(all( - feature = "fs", - not(feature = "fs-ng"), - any(not(feature = "plat-dyn"), target_os = "none") -))] +#[cfg(all(feature = "fs", not(feature = "fs-ng")))] impl ax_fs::FsBlockDevice for FsBlockDevice { fn name(&self) -> &str { self.block.name() diff --git a/os/arceos/modules/axruntime/src/lib.rs b/os/arceos/modules/axruntime/src/lib.rs index 2f318d72a9..0fc3a1bf99 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(feature = "std-compat"), not(test)))] mod lang_items; #[cfg(feature = "smp")] diff --git a/os/arceos/modules/axsync/src/mutex.rs b/os/arceos/modules/axsync/src/mutex.rs index 2afdc50892..93435cc4ce 100644 --- a/os/arceos/modules/axsync/src/mutex.rs +++ b/os/arceos/modules/axsync/src/mutex.rs @@ -7,9 +7,9 @@ use ax_task::{WaitQueue, current, might_sleep}; /// A [`lock_api::RawMutex`] implementation. /// /// When the mutex is locked, the current task will block and be put into the -/// wait queue. When the mutex is unlocked, ownership is handed off to at most -/// one task waiting on the queue; if no tasks are waiting, the mutex simply -/// becomes unlocked. +/// wait queue. When the mutex is unlocked, ownership is released before waking +/// at most one waiting task; the woken task then acquires the mutex with the +/// normal compare-exchange path. pub struct RawMutex { wq: WaitQueue, owner_id: AtomicU64, @@ -129,16 +129,8 @@ unsafe impl lock_api::RawMutex for RawMutex { ); #[cfg(feature = "lockdep")] crate::lockdep::release(self); - // Wake one waiting thread. The callback receives the waiter's ID. - // When the wait queue is empty, notify_one_with calls the callback - // with id=0, which clears owner_id via the swap below. This is - // what makes is_locked_inner() return false — the unlock handoff - // DEPENDS on notify_one_with always invoking the callback, even - // for empty queues. If that contract changes, add an explicit - // owner_id.store(0, Ordering::Release) fallback here. - self.wq.notify_one_with(true, |id: u64| { - self.owner_id.swap(id, Ordering::Release); - }); + self.owner_id.store(0, Ordering::Release); + self.wq.notify_one(true); } #[inline(always)] @@ -174,7 +166,10 @@ impl RawMutex { owner_id, current_id, "Thread({current_id}) tried to acquire mutex it already owns.", ); - // Wait until someone hands off lock to me or lock is released + // Wait until the lock is released. The woken waiter + // competes through the normal CAS path, avoiding a state + // where the owner id names a task that has not returned a + // guard yet. self.wq .wait_until(|| self.is_owner(current_id) || !self.is_locked_inner()); // This check is necessary: some newcomers may race with a wakened one. diff --git a/os/arceos/modules/axtask/Cargo.toml b/os/arceos/modules/axtask/Cargo.toml index 5a743d5804..b0e563abe8 100644 --- a/os/arceos/modules/axtask/Cargo.toml +++ b/os/arceos/modules/axtask/Cargo.toml @@ -51,7 +51,8 @@ sched-cfs = ["multitask", "preempt"] sched-fifo = ["multitask"] sched-rr = ["multitask", "preempt"] -test = ["ax-percpu?/sp-naive"] +host-test = ["ax-kspin?/host-test", "ax-percpu?/sp-naive"] +test = ["host-test"] [dependencies] ax-alloc = {workspace = true, optional = true} diff --git a/os/arceos/modules/axtask/src/api.rs b/os/arceos/modules/axtask/src/api.rs index 0dcf290ce9..daace71739 100644 --- a/os/arceos/modules/axtask/src/api.rs +++ b/os/arceos/modules/axtask/src/api.rs @@ -255,11 +255,6 @@ pub fn set_current_affinity(cpumask: AxCpuMask) -> bool { // Migrate the current task to the correct CPU using the migration task. current_run_queue::().migrate_current(migration_task); - - assert!( - cpumask.get(ax_hal::percpu::this_cpu_id()), - "Migration failed" - ); } true } diff --git a/os/arceos/modules/axtask/src/lib.rs b/os/arceos/modules/axtask/src/lib.rs index 3b37d91dc1..f629e89daf 100644 --- a/os/arceos/modules/axtask/src/lib.rs +++ b/os/arceos/modules/axtask/src/lib.rs @@ -20,6 +20,7 @@ //! the `multitask` and `preempt` features if it is enabled. //! - `sched-cfs`: Use the [Completely Fair Scheduler][3]. It also enables the //! the `multitask` and `preempt` features if it is enabled. +//! - `host-test`: Use host-safe fallbacks for unit tests. //! //! [1]: ax_sched::FifoScheduler //! [2]: ax_sched::RRScheduler diff --git a/os/arceos/modules/axtask/src/run_queue.rs b/os/arceos/modules/axtask/src/run_queue.rs index 0d31ff3af4..3c0247f5a9 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(not(feature = "host-test"))] 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(feature = "host-test")] 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(feature = "irq", not(feature = "host-test")))] assert!( !ax_hal::asm::irqs_enabled(), "IRQs must be disabled during scheduling" @@ -791,11 +791,14 @@ fn gc_entry() { #[cfg(feature = "smp")] pub(crate) fn migrate_entry(migrated_task: AxTaskRef) { let rq = select_run_queue::(&migrated_task); - migrated_task.set_cpu_id(rq.inner.cpu_id as _); + let cpu_id = rq.inner.cpu_id; + migrated_task.set_cpu_id(cpu_id as _); rq.inner .scheduler .lock() - .put_prev_task(migrated_task, false) + .put_prev_task(migrated_task, false); + #[cfg(all(feature = "smp", feature = "ipi"))] + kick_remote_cpu(cpu_id); } /// Clear the `on_cpu` field of previous task running on this CPU. 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..3e1d913eda 100644 --- a/os/arceos/ulib/axstd/Cargo.toml +++ b/os/arceos/ulib/axstd/Cargo.toml @@ -14,11 +14,18 @@ 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"] + +# libc compatibility surface used when ax-std is linked as fake libc for Rust std. +std-compat = ["ax-alloc/global-allocator", "ax-runtime/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 +37,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 +65,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 +112,16 @@ 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 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..ee9aabc468 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(feature = "std-compat")] +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..3f9f444096 --- /dev/null +++ b/os/arceos/ulib/axstd/src/os/libc_compat.rs @@ -0,0 +1,3249 @@ +#[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_until( + &wq, + || unsafe { addr.read_volatile() } != expected, + 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..4ab27e4cb1 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"] } diff --git a/os/axvisor/src/main.rs b/os/axvisor/src/main.rs index a19be015e7..d4a862642e 100644 --- a/os/axvisor/src/main.rs +++ b/os/axvisor/src/main.rs @@ -21,9 +21,8 @@ //! 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_attr(target_os = "none", no_std)] +#![cfg_attr(target_os = "none", no_main)] #[macro_use] extern crate log; @@ -31,6 +30,8 @@ extern crate log; #[macro_use] extern crate alloc; +use ax_std as _; +#[cfg(target_os = "none")] extern crate ax_std as std; mod config; @@ -84,7 +85,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/ax-plat-x86-qemu-q35/src/lib.rs b/platforms/ax-plat-x86-qemu-q35/src/lib.rs index 556f7f5bba..8b775da0be 100644 --- a/platforms/ax-plat-x86-qemu-q35/src/lib.rs +++ b/platforms/ax-plat-x86-qemu-q35/src/lib.rs @@ -13,7 +13,7 @@ // limitations under the License. #![no_std] -#![cfg(all(target_arch = "x86_64", target_os = "none"))] +#![cfg(target_arch = "x86_64")] #![allow(missing_abi)] #![feature(used_with_arg)] diff --git a/platforms/axplat-dyn/src/lib.rs b/platforms/axplat-dyn/src/lib.rs index cabac9f71a..f82916bfc9 100644 --- a/platforms/axplat-dyn/src/lib.rs +++ b/platforms/axplat-dyn/src/lib.rs @@ -1,5 +1,4 @@ #![no_std] -#![cfg(not(any(windows, unix)))] extern crate alloc; extern crate ax_driver as _; diff --git a/platforms/somehal/src/lib.rs b/platforms/somehal/src/lib.rs index bec4b660b3..50d6c4d70c 100644 --- a/platforms/somehal/src/lib.rs +++ b/platforms/somehal/src/lib.rs @@ -2,7 +2,6 @@ #![no_main] #![allow(unused_features)] #![feature(used_with_arg)] -#![cfg(not(any(windows, unix)))] #[macro_use] extern crate alloc; diff --git a/scripts/axbuild/src/arceos/build.rs b/scripts/axbuild/src/arceos/build.rs index 4e89652fb9..8a95ff98b0 100644 --- a/scripts/axbuild/src/arceos/build.rs +++ b/scripts/axbuild/src/arceos/build.rs @@ -1,10 +1,14 @@ -use std::{fs, path::PathBuf}; +use std::{ + fs, + path::{Path, PathBuf}, +}; -use anyhow::Context; +use anyhow::{Context, bail}; use cargo_metadata::Metadata; use log::warn; use ostool::build::config::Cargo; pub use ostool::build::config::LogLevel; +use serde::{Deserialize, Serialize}; use crate::{ build::{self, BuildInfo}, @@ -13,6 +17,30 @@ use crate::{ pub type ArceosBuildInfo = BuildInfo; +#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] +#[serde(default)] +pub(crate) struct ArceosBuildConfig { + #[serde(flatten, default)] + pub(crate) build_info: ArceosBuildInfo, + #[serde(rename = "app-c", skip_serializing_if = "Option::is_none")] + pub(crate) app_c: Option, +} + +impl ArceosBuildConfig { + fn default_for_target(target: &str) -> Self { + Self { + build_info: ArceosBuildInfo::default_for_target(target), + app_c: None, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum ArceosBuildMode { + RustStd, + AppC { app_dir: PathBuf, app_name: String }, +} + pub(crate) fn resolve_build_info_path( package: &str, target: &str, @@ -48,72 +76,96 @@ fn load_build_info_with_makefile_features( ) } +#[cfg(test)] fn load_build_info_with_makefile_features_and_metadata( request: &ResolvedBuildRequest, makefile_features: &[String], metadata: Option<&Metadata>, ) -> anyhow::Result { + Ok( + load_build_config_with_makefile_features_and_metadata( + request, + makefile_features, + metadata, + )? + .build_info, + ) +} + +fn load_build_config_with_makefile_features_and_metadata( + request: &ResolvedBuildRequest, + makefile_features: &[String], + metadata: Option<&Metadata>, +) -> anyhow::Result { build::ensure_build_info(&request.build_info_path, || { - ArceosBuildInfo::default_for_target(&request.target) + ArceosBuildConfig::default_for_target(&request.target) })?; let content = fs::read_to_string(&request.build_info_path)?; - let mut build_info: ArceosBuildInfo = toml::from_str(&content).with_context(|| { + build::reject_removed_std_field(&request.build_info_path, &content)?; + let mut config: ArceosBuildConfig = toml::from_str(&content).with_context(|| { format!( "failed to parse build info {}", request.build_info_path.display() ) })?; build::apply_target_defaults_if_plat_dyn_unspecified( - &mut build_info, + &mut config.build_info, &request.target, &content, ); - if build_info.normalize_legacy_feature_aliases() { + if config.build_info.normalize_legacy_feature_aliases() { warn!( "normalizing legacy feature aliases in build config {}", request.build_info_path.display() ); - fs::write( - &request.build_info_path, - toml::to_string_pretty(&build_info)?, - ) - .with_context(|| { - format!( - "failed to rewrite normalized build info {}", - request.build_info_path.display() - ) - })?; + fs::write(&request.build_info_path, toml::to_string_pretty(&config)?).with_context( + || { + format!( + "failed to rewrite normalized build info {}", + request.build_info_path.display() + ) + }, + )?; } match metadata { Some(metadata) => build::apply_makefile_features_with_metadata( - &mut build_info, + &mut config.build_info, &request.package, makefile_features, metadata, ), - None => { - build::apply_makefile_features(&mut build_info, &request.package, makefile_features) - } + None => build::apply_makefile_features( + &mut config.build_info, + &request.package, + makefile_features, + ), } if let Some(smp) = request.smp { - build_info.max_cpu_num = Some(smp); + config.build_info.max_cpu_num = Some(smp); } - Ok(build_info) + Ok(config) } pub(crate) fn load_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( + let config = load_build_config_with_makefile_features_and_metadata( request, &makefile_features, Some(metadata), )?; + if config.app_c.is_some() { + bail!( + "ArceOS build config {} uses `app-c`; use the C app build path", + request.build_info_path.display() + ); + } + let build_info = config.build_info; build_info.into_prepared_base_cargo_config_with_metadata( &request.package, @@ -123,6 +175,126 @@ pub(crate) fn load_cargo_config(request: &ResolvedBuildRequest) -> anyhow::Resul ) } +pub(crate) fn load_c_app_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 mut build_info = load_build_config_with_makefile_features_and_metadata( + request, + &makefile_features, + Some(metadata), + )? + .build_info; + let plat_dyn = build_info.effective_plat_dyn(&request.target, request.plat_dyn); + + build_info.validated_max_cpu_num()?; + build_info.prepare_non_dynamic_platform_for( + &request.package, + &request.target, + plat_dyn, + metadata, + )?; + build_info.resolve_features_with_metadata( + &request.package, + &request.target, + plat_dyn, + metadata, + ); + let rustflags = build::toolchain_rustflags(&build_info.env); + let args = ArceosBuildInfo::build_cargo_args(&request.target, &rustflags); + + build_info.prepare_log_env(); + build_info.prepare_max_cpu_num_env()?; + + Ok(build_info.into_base_cargo_config_with_to_bin( + request.package.clone(), + request.target.clone(), + args, + false, + )) +} + +pub(crate) fn load_arceos_build_config(path: &Path) -> anyhow::Result { + let content = fs::read_to_string(path) + .with_context(|| format!("failed to read ArceOS build config {}", path.display()))?; + build::reject_removed_std_field(path, &content)?; + toml::from_str(&content) + .with_context(|| format!("failed to parse ArceOS build config {}", path.display())) +} + +pub(crate) fn load_arceos_build_mode(path: &Path) -> anyhow::Result { + let config = load_arceos_build_config(path)?; + match config.app_c { + Some(app_c) => resolve_app_c_mode(path, &app_c), + None => Ok(ArceosBuildMode::RustStd), + } +} + +pub(crate) fn resolve_app_c_mode( + config_path: &Path, + app_c: &Path, +) -> anyhow::Result { + let app_dir = resolve_app_c_dir(config_path, app_c)?; + let app_name = c_app_name(&app_dir) + .with_context(|| format!("failed to derive C app name from {}", app_dir.display()))?; + + Ok(ArceosBuildMode::AppC { app_dir, app_name }) +} + +pub(crate) fn resolve_app_c_dir(config_path: &Path, app_c: &Path) -> anyhow::Result { + let app_dir = if app_c.is_absolute() { + app_c.to_path_buf() + } else { + config_path + .parent() + .unwrap_or_else(|| Path::new(".")) + .join(app_c) + }; + + if !app_dir.is_dir() { + bail!( + "app-c source directory {} configured by {} does not exist or is not a directory", + app_dir.display(), + config_path.display() + ); + } + if !dir_has_direct_c_source(&app_dir)? { + bail!( + "app-c source directory {} configured by {} must contain at least one direct .c file", + app_dir.display(), + config_path.display() + ); + } + + app_dir.canonicalize().with_context(|| { + format!( + "failed to resolve app-c source directory {}", + app_dir.display() + ) + }) +} + +fn dir_has_direct_c_source(dir: &Path) -> anyhow::Result { + Ok(fs::read_dir(dir) + .with_context(|| format!("failed to read app-c source directory {}", dir.display()))? + .collect::, _>>()? + .into_iter() + .any(|entry| entry.path().extension().is_some_and(|ext| ext == "c"))) +} + +fn c_app_name(app_dir: &Path) -> Option { + let name_dir = if app_dir.file_name().and_then(|name| name.to_str()) == Some("c") { + app_dir.parent().unwrap_or(app_dir) + } else { + app_dir + }; + + name_dir + .file_name() + .and_then(|name| name.to_str()) + .map(ToOwned::to_owned) +} + 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()?, @@ -196,14 +368,7 @@ mod tests { assert!(!build_info.features.contains(&"ax-hal/plat-dyn".to_string())); assert!(!build_info.features.contains(&"ax-std/defplat".to_string())); - let args = ArceosBuildInfo::build_cargo_args( - "scripts/targets/pie/aarch64-unknown-none-softfloat.json", - &[], - ); - assert!( - args.windows(2) - .any(|pair| pair == ["-Z", "json-target-spec"]) - ); + let args = ArceosBuildInfo::build_cargo_args("aarch64-unknown-none-softfloat", &[]); assert!( args.windows(2) .any(|pair| pair == ["-Z", "build-std=core,alloc"]) @@ -224,14 +389,7 @@ mod tests { ); assert!(!build_info.features.contains(&"ax-std/plat-dyn".to_string())); - let args = ArceosBuildInfo::build_cargo_args( - "scripts/targets/no-pie/aarch64-unknown-none-softfloat.json", - &[], - ); - assert!( - args.windows(2) - .any(|pair| pair == ["-Z", "json-target-spec"]) - ); + let args = ArceosBuildInfo::build_cargo_args("aarch64-unknown-none-softfloat", &[]); assert!( args.windows(2) .any(|pair| pair == ["-Z", "build-std=core,alloc"]) @@ -240,15 +398,15 @@ mod tests { } #[test] - fn preparing_non_dynamic_aarch64_without_custom_platform_fails() { + fn preparing_c_app_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( - "ax-helloworld", - "aarch64-unknown-none-softfloat", - Some(false), - &metadata, - ); + let mut build_info = ArceosBuildInfo::default_for_target("aarch64-unknown-none-softfloat"); + let result = build_info.prepare_non_dynamic_platform_for( + "ax-helloworld", + "aarch64-unknown-none-softfloat", + false, + &metadata, + ); assert!( result @@ -259,7 +417,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 +432,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] @@ -333,6 +491,86 @@ mod tests { ); } + #[test] + fn build_config_without_app_c_uses_std_rust_mode() { + let root = tempdir().unwrap(); + let path = root.path().join("build-x86_64-unknown-none.toml"); + fs::write( + &path, + "features = [\"ax-std\"]\nlog = \"Warn\"\n\n[env]\nAX_IP = \"10.0.2.15\"\n", + ) + .unwrap(); + + let mode = load_arceos_build_mode(&path).unwrap(); + + assert_eq!(mode, ArceosBuildMode::RustStd); + } + + #[test] + fn app_c_build_config_resolves_source_dir_relative_to_config() { + let root = tempdir().unwrap(); + let case_dir = root.path().join("case"); + let source_dir = case_dir.join("c"); + fs::create_dir_all(&source_dir).unwrap(); + fs::write(source_dir.join("main.c"), "int main(void) { return 0; }\n").unwrap(); + let path = case_dir.join("build-x86_64-unknown-none.toml"); + fs::write( + &path, + "app-c = \"c\"\nfeatures = []\nlog = \"Warn\"\n\n[env]\nAX_IP = \"10.0.2.15\"\n", + ) + .unwrap(); + + let mode = load_arceos_build_mode(&path).unwrap(); + + assert_eq!( + mode, + ArceosBuildMode::AppC { + app_dir: source_dir.canonicalize().unwrap(), + app_name: "case".to_string() + } + ); + } + + #[test] + fn app_c_build_config_rejects_missing_source_dir() { + let root = tempdir().unwrap(); + let path = root.path().join("build-x86_64-unknown-none.toml"); + fs::write( + &path, + "app-c = \"missing\"\nfeatures = []\nlog = \"Warn\"\n\n[env]\nAX_IP = \"10.0.2.15\"\n", + ) + .unwrap(); + + let err = load_arceos_build_mode(&path).unwrap_err(); + + assert!( + err.to_string().contains("app-c source directory"), + "{err:#}" + ); + } + + #[test] + fn app_c_build_config_rejects_source_dir_without_c_files() { + let root = tempdir().unwrap(); + let source_dir = root.path().join("c"); + fs::create_dir_all(&source_dir).unwrap(); + fs::write(source_dir.join("main.rs"), "fn main() {}\n").unwrap(); + let path = root.path().join("build-x86_64-unknown-none.toml"); + fs::write( + &path, + "app-c = \"c\"\nfeatures = []\nlog = \"Warn\"\n\n[env]\nAX_IP = \"10.0.2.15\"\n", + ) + .unwrap(); + + let err = load_arceos_build_mode(&path).unwrap_err(); + + assert!( + err.to_string() + .contains("must contain at least one direct .c file"), + "{err:#}" + ); + } + #[test] fn load_build_info_normalizes_legacy_feature_aliases() { let root = tempdir().unwrap(); @@ -380,7 +618,7 @@ BACKTRACE = "y" ) .unwrap(); let request = request( - "ax-backtrace", + "arceos-test-suit", "aarch64-unknown-none-softfloat", None, path.clone(), @@ -404,7 +642,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 +712,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 +734,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 +748,58 @@ 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 c_app_cargo_config_uses_builtin_bare_target_without_json_spec() { + let root = tempdir().unwrap(); + let build_config = root.path().join("build-x86_64-unknown-none.toml"); + let build_info = ArceosBuildInfo { + features: vec!["ax-std".to_string()], + ..ArceosBuildInfo::default_for_target("x86_64-unknown-none") + }; + fs::write(&build_config, toml::to_string_pretty(&build_info).unwrap()).unwrap(); + let request = request( + "ax-helloworld", + "x86_64-unknown-none", + Some(false), + build_config, + ); + let cargo = load_c_app_cargo_config(&request).unwrap(); + + assert_eq!(cargo.target, "x86_64-unknown-none"); + assert!(!cargo.env.contains_key("CARGO_UNSTABLE_JSON_TARGET_SPEC")); + assert!( + cargo + .args + .windows(2) + .any(|pair| pair == ["-Z", "build-std=core,alloc"]) + ); } #[test] @@ -558,7 +845,7 @@ AX_IP = "10.0.2.15" assert!( cargo .target - .ends_with("scripts/targets/pie/x86_64-unknown-none.json") + .ends_with("scripts/targets/std/pie/x86_64-unknown-linux-musl.json") ); assert!(cargo.features.contains(&"ax-std/plat-dyn".to_string())); assert!(!cargo.features.contains(&"ax-hal/x86-pc".to_string())); diff --git a/scripts/axbuild/src/arceos/cbuild.rs b/scripts/axbuild/src/arceos/cbuild.rs index 25aefe3337..31e2b1189e 100644 --- a/scripts/axbuild/src/arceos/cbuild.rs +++ b/scripts/axbuild/src/arceos/cbuild.rs @@ -18,6 +18,12 @@ const AX_LIBC_PACKAGE: &str = "ax-libc"; const PIC_RUSTFLAG: &str = "-Crelocation-model=pic"; const C_DEFINE_FEATURE_PREFIX: &str = "c-define:"; +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct ArceosCArtifactPaths { + pub(crate) target_dir: PathBuf, + pub(crate) out_dir: PathBuf, +} + #[derive(Debug, Clone)] pub(crate) struct ArceosCBuildInput { pub(crate) app_dir: PathBuf, @@ -32,12 +38,31 @@ pub(crate) struct ArceosCBuildOutput { pub(crate) elf_path: PathBuf, } +pub(crate) fn default_c_app_artifact_paths( + workspace_root: &Path, + app_name: &str, +) -> ArceosCArtifactPaths { + let target_dir = crate::context::axbuild_tmp_dir(workspace_root) + .join("arceos-c") + .join("cargo"); + let out_dir = crate::context::axbuild_tmp_dir(workspace_root) + .join("arceos-c") + .join("apps") + .join(sanitize_name(app_name)) + .join("out"); + + ArceosCArtifactPaths { + target_dir, + out_dir, + } +} + pub(crate) fn build_c_app( workspace_root: &Path, request: &ResolvedBuildRequest, input: &ArceosCBuildInput, ) -> anyhow::Result { - let mut cargo = build::load_cargo_config(request)?; + let mut cargo = build::load_c_app_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/arceos/mod.rs b/scripts/axbuild/src/arceos/mod.rs index ed7355e4d4..afd6bd5c21 100644 --- a/scripts/axbuild/src/arceos/mod.rs +++ b/scripts/axbuild/src/arceos/mod.rs @@ -6,6 +6,7 @@ use std::{ use anyhow::Context; use clap::{Args, Subcommand}; +use log::warn; use ostool::{build::config::Cargo, run::qemu::QemuConfig}; use crate::context::{AppContext, BuildCliArgs, ResolvedBuildRequest, SnapshotPersistence}; @@ -266,8 +267,16 @@ impl ArceOS { } async fn run_qemu_request(&mut self, request: ResolvedBuildRequest) -> anyhow::Result<()> { - let cargo = build::load_cargo_config(&request)?; - self.run_qemu_request_with_cargo(request, cargo).await + match build::load_arceos_build_mode(&request.build_info_path)? { + build::ArceosBuildMode::RustStd => { + let cargo = build::load_cargo_config(&request)?; + self.run_qemu_request_with_cargo(request, cargo).await + } + build::ArceosBuildMode::AppC { app_dir, app_name } => { + self.run_c_app_qemu_request(request, app_dir, app_name) + .await + } + } } async fn run_qemu_request_with_cargo( @@ -285,16 +294,120 @@ impl ArceOS { async fn run_build_request(&mut self, request: ResolvedBuildRequest) -> anyhow::Result<()> { self.app.set_debug_mode(request.debug)?; - let cargo = build::load_cargo_config(&request)?; - self.app.build(cargo, request.build_info_path).await + match build::load_arceos_build_mode(&request.build_info_path)? { + build::ArceosBuildMode::RustStd => { + let cargo = build::load_cargo_config(&request)?; + self.app.build(cargo, request.build_info_path).await + } + build::ArceosBuildMode::AppC { app_dir, app_name } => { + let request = c_app_internal_request(&request); + let output = self.build_c_app_request(&request, app_dir, app_name)?; + println!("Built ArceOS C app ELF: {}", output.elf_path.display()); + Ok(()) + } + } } async fn run_uboot_request(&mut self, request: ResolvedBuildRequest) -> anyhow::Result<()> { self.app.set_debug_mode(request.debug)?; - let cargo = build::load_cargo_config(&request)?; - let uboot = self.load_uboot_config(&request, &cargo).await?; - self.app.uboot(cargo, request.build_info_path, uboot).await + match build::load_arceos_build_mode(&request.build_info_path)? { + build::ArceosBuildMode::RustStd => { + let cargo = build::load_cargo_config(&request)?; + let uboot = self.load_uboot_config(&request, &cargo).await?; + self.app.uboot(cargo, request.build_info_path, uboot).await + } + build::ArceosBuildMode::AppC { app_dir, app_name } => { + self.run_c_app_uboot_request(request, app_dir, app_name) + .await + } + } + } + + fn build_c_app_request( + &mut self, + request: &ResolvedBuildRequest, + app_dir: PathBuf, + app_name: String, + ) -> anyhow::Result { + let workspace_root = self.app.workspace_root(); + let config = build::load_arceos_build_config(&request.build_info_path)?; + let paths = cbuild::default_c_app_artifact_paths(workspace_root, &app_name); + let input = cbuild::ArceosCBuildInput { + app_dir, + app_name, + target_dir: paths.target_dir, + out_dir: paths.out_dir, + features: config.build_info.features, + }; + + cbuild::build_c_app(workspace_root, request, &input) } + + async fn run_c_app_qemu_request( + &mut self, + request: ResolvedBuildRequest, + app_dir: PathBuf, + app_name: String, + ) -> anyhow::Result<()> { + self.app.set_debug_mode(request.debug)?; + let request = c_app_internal_request(&request); + let cargo = build::load_c_app_cargo_config(&request)?; + let mut qemu = self + .load_qemu_config(&request, &cargo) + .await? + .with_context(|| { + format!( + "ArceOS C app config {} requires an explicit qemu config", + request.build_info_path.display() + ) + })?; + let output = self.build_c_app_request(&request, app_dir, app_name)?; + crate::test::qemu::apply_dynamic_x86_64_qemu_boot(&mut qemu, &cargo); + ensure_qemu_runtime_assets(self.app.workspace_root(), &qemu)?; + self.app + .prepare_elf_artifact(output.elf_path, qemu.to_bin) + .await?; + self.app.run_prepared_qemu(qemu, None).await + } + + async fn run_c_app_uboot_request( + &mut self, + request: ResolvedBuildRequest, + app_dir: PathBuf, + app_name: String, + ) -> anyhow::Result<()> { + self.app.set_debug_mode(request.debug)?; + let request = c_app_internal_request(&request); + let cargo = build::load_c_app_cargo_config(&request)?; + let uboot = self + .load_uboot_config(&request, &cargo) + .await? + .with_context(|| { + format!( + "ArceOS C app config {} requires an explicit uboot config", + request.build_info_path.display() + ) + })?; + let output = self.build_c_app_request(&request, app_dir, app_name)?; + self.app.prepare_elf_artifact(output.elf_path, true).await?; + self.app.run_prepared_uboot(uboot).await + } +} + +fn warn_if_c_app_package_override(request: &ResolvedBuildRequest) { + if request.package != "ax-libc" { + warn!( + "ArceOS C app build ignores --package {}; using ax-libc internally", + request.package + ); + } +} + +fn c_app_internal_request(request: &ResolvedBuildRequest) -> ResolvedBuildRequest { + warn_if_c_app_package_override(request); + let mut request = request.clone(); + request.package = "ax-libc".to_string(); + request } #[cfg(test)] diff --git a/scripts/axbuild/src/arceos/test.rs b/scripts/axbuild/src/arceos/test.rs index c1fa2346d6..24273e2bbf 100644 --- a/scripts/axbuild/src/arceos/test.rs +++ b/scripts/axbuild/src/arceos/test.rs @@ -9,7 +9,6 @@ use anyhow::{Context, bail}; use clap::{Args, Subcommand}; use ostool::{build::config::Cargo, run::qemu::QemuConfig}; use regex::Regex; -use serde::Deserialize; use super::{ArceOS, build, cbuild, ensure_qemu_runtime_assets}; use crate::{ @@ -205,7 +204,6 @@ impl qemu_test::BuildConfigRef for PreparedArceosRustQemuCase { struct CTestDef { name: String, build_group: String, - case_dir: PathBuf, build_config_path: PathBuf, qemu_config_path: PathBuf, } @@ -220,12 +218,6 @@ impl qemu_test::BuildConfigRef for CTestDef { } } -#[derive(Debug, Clone, Deserialize)] -struct CTestBuildConfig { - #[serde(flatten)] - build: build::ArceosBuildInfo, -} - #[derive(Debug, Clone, PartialEq, Eq)] struct CTestArtifactPaths { target_dir: PathBuf, @@ -843,7 +835,7 @@ async fn run_rust_qemu_case( ); } - let elf = crate::backtrace::arceos_rust_elf_path(&workspace, target, package, debug); + let elf = crate::backtrace::std_test_elf_path(&workspace, target, package, debug); let stream_session = if auto_symbolize { crate::backtrace::BacktraceSymbolizeSession::try_new(&elf, case_name) } else { @@ -1279,7 +1271,6 @@ fn load_arceos_c_test_suit_qemu_case( Ok(CTestDef { name: feature.to_string(), build_group: ARCEOS_C_TEST_BUILD_GROUP.to_string(), - case_dir: root.to_path_buf(), build_config_path: arceos_c_test_suit_build_config_path(root, target)?, qemu_config_path: arceos_c_test_suit_qemu_config_path(root, arch)?, }) @@ -1301,9 +1292,17 @@ fn arceos_c_test_suit_qemu_config_path(root: &Path, arch: &str) -> anyhow::Resul bail!("ArceOS C test suite must provide {}", path.display()) } -fn load_c_test_build_config(path: &Path) -> anyhow::Result { - toml::from_str(&fs::read_to_string(path)?) - .with_context(|| format!("failed to parse C build config {}", path.display())) +fn load_c_test_build_config(path: &Path) -> anyhow::Result { + let config = build::load_arceos_build_config(path) + .with_context(|| format!("failed to parse C build config {}", path.display()))?; + if config.app_c.is_none() { + bail!( + "ArceOS C qemu test build config {} must set `app-c = \"c\"` or another C source \ + directory", + path.display() + ); + } + Ok(config) } fn load_c_test_qemu_config(path: &Path) -> anyhow::Result { @@ -1311,28 +1310,6 @@ fn load_c_test_qemu_config(path: &Path) -> anyhow::Result { .with_context(|| format!("failed to parse C qemu config {}", path.display())) } -fn resolve_c_test_source_dir(case_dir: &Path) -> anyhow::Result { - let source_dir = case_dir.join("c"); - if source_dir.is_dir() && dir_has_c_source(&source_dir)? { - return source_dir - .canonicalize() - .with_context(|| format!("failed to resolve C source dir {}", source_dir.display())); - } - - bail!( - "ArceOS C qemu test case {} must contain a c/ source asset directory with .c files", - case_dir.display() - ) -} - -fn dir_has_c_source(dir: &Path) -> anyhow::Result { - Ok(fs::read_dir(dir) - .with_context(|| format!("failed to read {}", dir.display()))? - .collect::, _>>()? - .into_iter() - .any(|entry| entry.path().extension().is_some_and(|ext| ext == "c"))) -} - fn c_test_artifact_index(test: &CTestDef) -> usize { let stem = test .qemu_config_path @@ -1417,6 +1394,14 @@ async fn build_and_run_c_test( let workspace_root = arceos.app.workspace_root().to_path_buf(); let build_config = load_c_test_build_config(&test.build_config_path)?; let qemu_config = load_c_test_qemu_config(&test.qemu_config_path)?; + let mode = build::load_arceos_build_mode(&test.build_config_path)?; + let build::ArceosBuildMode::AppC { app_dir, app_name } = mode else { + bail!( + "ArceOS C qemu test build config {} must set `app-c = \"c\"` or another C source \ + directory", + test.build_config_path.display() + ); + }; let artifacts = c_test_artifact_paths( &workspace_root, &test.build_group, @@ -1438,14 +1423,15 @@ async fn build_and_run_c_test( None, SnapshotPersistence::Discard, )?; - let cargo = build::load_cargo_config(&request)?; + let cargo = build::load_c_app_cargo_config(&request)?; let input = c_test_build_input( - &test.case_dir, + app_dir, + app_name, artifacts.target_dir, artifacts.out_dir, &test.name, - build_config.build.features.clone(), - )?; + build_config.build_info.features.clone(), + ); let output = cbuild::build_c_app(&workspace_root, &request, &input)?; let mut qemu = qemu_config; qemu_test::apply_dynamic_x86_64_qemu_boot(&mut qemu, &cargo); @@ -1462,21 +1448,21 @@ async fn build_and_run_c_test( } fn c_test_build_input( - case_dir: &Path, + app_dir: PathBuf, + app_name: String, target_dir: PathBuf, out_dir: PathBuf, feature: &str, mut features: Vec, -) -> anyhow::Result { - let source_dir = resolve_c_test_source_dir(case_dir)?; +) -> cbuild::ArceosCBuildInput { features.push(format!("c-define:{}", c_test_feature_define(feature))); - Ok(cbuild::ArceosCBuildInput { - app_dir: source_dir, - app_name: ARCEOS_C_TEST_BUILD_GROUP.to_string(), + cbuild::ArceosCBuildInput { + app_dir, + app_name, target_dir, out_dir, features, - }) + } } fn c_test_feature_define(feature: &str) -> String { @@ -1758,23 +1744,18 @@ mod tests { #[test] fn arceos_c_build_input_adds_selected_feature_define() { let dir = tempdir().unwrap(); - std::fs::create_dir_all(dir.path().join("c")).unwrap(); - std::fs::write( - dir.path().join("c/main.c"), - "int main(void) { return 0; }\n", - ) - .unwrap(); + let app_dir = dir.path().join("c"); let input = c_test_build_input( - dir.path(), + app_dir.clone(), + ARCEOS_C_TEST_BUILD_GROUP.to_string(), PathBuf::from("/tmp/target"), PathBuf::from("/tmp/out"), "pthread-basic", vec!["alloc".to_string()], - ) - .unwrap(); + ); assert_eq!(input.app_name, ARCEOS_C_TEST_BUILD_GROUP); - assert!(input.app_dir.ends_with("c")); + assert_eq!(input.app_dir, app_dir); assert!( input .features @@ -1806,7 +1787,6 @@ mod tests { assert_eq!(case.name, "mem"); assert_eq!(case.build_group, ARCEOS_C_TEST_BUILD_GROUP); - assert_eq!(case.case_dir, root); assert!( case.build_config_path .ends_with("build-x86_64-unknown-none.toml") @@ -1820,14 +1800,26 @@ mod tests { let path = dir.path().join("build-x86_64-unknown-none.toml"); std::fs::write( &path, - "features = [\"alloc\", \"paging\"]\nlog = \"Trace\"\nmax_cpu_num = 4\n\n[env]\n", + "app-c = \"c\"\nfeatures = [\"alloc\", \"paging\"]\nlog = \"Trace\"\nmax_cpu_num = \ + 4\n\n[env]\n", ) .unwrap(); let config = load_c_test_build_config(&path).unwrap(); - assert_eq!(config.build.features, vec!["alloc", "paging"]); - assert_eq!(config.build.log, build::LogLevel::Trace); - assert_eq!(config.build.max_cpu_num, Some(4)); + assert_eq!(config.app_c, Some(PathBuf::from("c"))); + assert_eq!(config.build_info.features, vec!["alloc", "paging"]); + assert_eq!(config.build_info.log, build::LogLevel::Trace); + assert_eq!(config.build_info.max_cpu_num, Some(4)); + } + + #[test] + fn load_c_test_build_config_rejects_missing_app_c() { + let dir = tempdir().unwrap(); + let path = dir.path().join("build-x86_64-unknown-none.toml"); + std::fs::write(&path, "features = [\"alloc\"]\nlog = \"Info\"\n\n[env]\n").unwrap(); + + let err = load_c_test_build_config(&path).unwrap_err(); + assert!(err.to_string().contains("must set `app-c = \"c\"`")); } #[test] diff --git a/scripts/axbuild/src/axvisor/build.rs b/scripts/axbuild/src/axvisor/build.rs index 75c43a74c5..e342a3119b 100644 --- a/scripts/axbuild/src/axvisor/build.rs +++ b/scripts/axbuild/src/axvisor/build.rs @@ -78,6 +78,8 @@ pub(crate) fn load_board_file(path: &Path) -> anyhow::Result { path.display() ) })?; + crate::build::reject_removed_std_field(path, &content)?; + crate::build::reject_arceos_app_c_field(path, &content)?; toml::from_str(&content).map_err(|e| { anyhow!( "failed to parse Axvisor board config {}: {e}", @@ -455,6 +457,8 @@ pub(crate) fn load_target_from_build_config(path: &Path) -> anyhow::Result(&content) { return Ok(Some(board_file.target)); @@ -526,6 +530,8 @@ fn load_build_config(request: &ResolvedAxvisorRequest) -> anyhow::Result(&content) { let mut loaded = board_config.into_loaded(); @@ -734,7 +740,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 +790,54 @@ 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_target_from_build_config_rejects_arceos_app_c_field() { + let root = tempdir().unwrap(); + let path = root.path().join("qemu-aarch64.toml"); + fs::write( + &path, + r#" +app-c = "c" +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 ArceOS-only `app-c` field"), + "{err:#}" + ); + } + #[test] fn load_cargo_config_uses_board_defaults_when_default_file_is_missing() { let root = tempdir().unwrap(); @@ -1050,7 +1104,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..c8d31465b6 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 std_test_elf_path( workspace_root: &Path, target: &str, package: &str, @@ -72,11 +72,25 @@ pub(crate) fn arceos_rust_elf_path( let profile = if debug { "debug" } else { "release" }; workspace_root .join("target") - .join(target) + .join(std_test_target_dir(target)) .join(profile) .join(package) } +fn std_test_target_dir(target: &str) -> &str { + if target.starts_with("x86_64-") { + "x86_64-unknown-linux-musl" + } else if target.starts_with("aarch64-") { + "aarch64-unknown-linux-musl" + } else if target.starts_with("riscv64") { + "riscv64gc-unknown-linux-musl" + } else if target.starts_with("loongarch64-") { + "loongarch64-unknown-linux-musl" + } else { + target + } +} + fn case_name_kind_hint(case_name: &str) -> Option<&'static str> { const KINDS: &[&str] = &["raw", "panic", "trap"]; for segment in case_name.split(['/', '-']) { @@ -1197,8 +1211,22 @@ BACKTRACE_END } #[test] - fn arceos_rust_elf_path_uses_release_profile() { - let path = arceos_rust_elf_path( + fn std_test_elf_path_uses_release_profile() { + let path = std_test_elf_path( + Path::new("/ws"), + "x86_64-unknown-none", + "arceos-test-suit", + false, + ); + assert_eq!( + path, + PathBuf::from("/ws/target/x86_64-unknown-linux-musl/release/arceos-test-suit") + ); + } + + #[test] + fn std_test_elf_path_maps_arceos_none_target_to_std_target_dir() { + let path = std_test_elf_path( Path::new("/ws"), "x86_64-unknown-none", "arceos-test-suit", @@ -1206,7 +1234,7 @@ BACKTRACE_END ); assert_eq!( path, - PathBuf::from("/ws/target/x86_64-unknown-none/release/arceos-test-suit") + PathBuf::from("/ws/target/x86_64-unknown-linux-musl/release/arceos-test-suit") ); } diff --git a/scripts/axbuild/src/build.rs b/scripts/axbuild/src/build.rs index c2b0299c68..9fae377fd7 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}; @@ -25,7 +29,7 @@ fn env_truthy(env: &HashMap, key: &str) -> bool { }) } -fn toolchain_rustflags(env: &HashMap) -> Vec { +pub(crate) fn toolchain_rustflags(env: &HashMap) -> Vec { let mut flags = Vec::new(); let dwarf = env_truthy(env, "DWARF"); let backtrace = env_truthy(env, "BACKTRACE") || dwarf; @@ -57,12 +61,11 @@ 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 +99,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 { @@ -149,7 +149,7 @@ impl BuildInfo { ) } - fn into_base_cargo_config_with_to_bin( + pub(crate) fn into_base_cargo_config_with_to_bin( self, package: String, target: String, @@ -192,50 +192,50 @@ 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); - } - - 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)?; - self.resolve_features_with_metadata(package, target, plat_dyn, metadata); - let cargo_target = cargo_target_json_path(target, plat_dyn)?; - let cargo_target = cargo_target.display().to_string(); - let rustflags = toolchain_rustflags(&self.env); - let args = Self::build_cargo_args(&cargo_target, &rustflags); - self.env.insert( - "CARGO_UNSTABLE_JSON_TARGET_SPEC".to_string(), - "true".to_string(), - ); - - self.prepare_log_env(); - self.prepare_max_cpu_num_env() - .expect("max_cpu_num validation should run before cargo config generation"); - Ok(self.into_base_cargo_config_with_to_bin( + 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(), - cargo_target, - args, - default_to_bin_for_target_config(target, plat_dyn), - )) + 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(), + ); + let rustflags = toolchain_rustflags(&cargo.env); + cargo.extra_config = Some( + std_cargo_config_path(&std_target.target_name, &wrapper, plat_dyn, &rustflags)? + .display() + .to_string(), + ); + cargo.to_bin = default_to_bin_for_target_config(target, plat_dyn); + Ok(cargo) } fn resolve_std_features(&mut self) { @@ -248,6 +248,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, @@ -425,18 +456,9 @@ impl BuildInfo { } pub(crate) fn build_cargo_args(target: &str, extra_rustflags: &[String]) -> Vec { - let mut args = vec![ - "-Z".to_string(), - "json-target-spec".to_string(), - "-Z".to_string(), - "build-std=core,alloc".to_string(), - ]; + let mut args = vec!["-Z".to_string(), "build-std=core,alloc".to_string()]; if !extra_rustflags.is_empty() { - // Cargo resolves `target..rustflags` for a JSON target spec by the - // spec file *stem* (e.g. `x86_64-unknown-none`), not the path passed to - // `--target`. Using the full path as the key makes cargo silently drop the - // entry, so flags like `-Cforce-frame-pointers=yes` never reach rustc. let target_key = Path::new(target) .file_stem() .and_then(|stem| stem.to_str()) @@ -469,75 +491,123 @@ impl Default for BuildInfo { max_cpu_num: None, axconfig_overrides: Vec::new(), plat_dyn: false, - std_build: false, - } - } -} - -pub(crate) fn cargo_target_json_path(target: &str, plat_dyn: bool) -> anyhow::Result { - let target_file = match target { - "aarch64-unknown-none-softfloat" - | "riscv64gc-unknown-none-elf" - | "x86_64-unknown-none" - | "loongarch64-unknown-none-softfloat" => format!("{target}.json"), - _ => bail!("unsupported target triple `{target}`"), - }; - - if plat_dyn { - if !matches!( - target, - "aarch64-unknown-none-softfloat" | "riscv64gc-unknown-none-elf" | "x86_64-unknown-none" - ) { - bail!("unsupported PIE target `{target}`"); } - Ok(Path::new(TARGET_JSON_ROOT) - .join(PIE_TARGET_DIR) - .join(target_file)) - } else { - Ok(Path::new(TARGET_JSON_ROOT) - .join(NO_PIE_TARGET_DIR) - .join(target_file)) } } 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}`"); + }; + + 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"); + let c_flags = std_c_target_flags(target_name).join(" "); + env.insert(format!("CC_{target_env}"), cc.clone()); + env.insert(format!("AR_{target_env}"), ar); + if !c_flags.is_empty() { + env.insert(format!("CFLAGS_{target_env}"), c_flags.clone()); + env.insert(format!("CXXFLAGS_{target_env}"), c_flags.clone()); + } + + if let Some(sysroot) = musl_toolchain_sysroot(&cc) { + let mut bindgen_args = vec![ + format!("--target={tool_prefix}"), + format!("--sysroot={sysroot}"), + ]; + bindgen_args.extend( + std_c_target_flags(target_name) + .into_iter() + .map(str::to_string), + ); + env.insert( + format!("BINDGEN_EXTRA_CLANG_ARGS_{target_env}"), + bindgen_args.join(" "), + ); + } + + env +} + +fn std_c_target_flags(target_name: &str) -> Vec<&'static str> { + if target_name.starts_with("x86_64-") { + vec![ + "-mno-mmx", + "-mno-sse", + "-mno-sse2", + "-mno-sse3", + "-mno-ssse3", + "-mno-sse4.1", + "-mno-sse4.2", + "-mno-avx", + "-mno-avx2", + "-msoft-float", + ] + } else if target_name.starts_with("aarch64-") { + vec!["-mgeneral-regs-only"] + } else if target_name.starts_with("riscv64") { + vec!["-march=rv64gc", "-mabi=lp64d", "-mcmodel=medany"] + } else if target_name.starts_with("loongarch64-") { + vec!["-mabi=lp64s", "-msoft-float"] + } else { + Vec::new() + } +} + +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 { - bail!("unsupported ArceOS std target triple `{target}`") + path.join(format!("{target}.json")) } } @@ -547,14 +617,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 +657,95 @@ 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(",")); + + if axstd_feature_is_available("std-compat", axstd_features) { + cargo_features.push("ax-std/std-compat".to_string()); + } + + 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, + extra_rustflags: &[String], +) -> 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(extra_rustflags); write_if_changed( &path, &format!( @@ -638,22 +757,346 @@ 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(extra_rustflags: &[String]) -> String { + extra_rustflags + .iter() + .map(|flag| format!(" {},", toml::Value::String(flag.clone()))) + .collect::>() + .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 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=() +archive_args=() +dynamic_platform={} + +add_link_search_dir() {{ + local dir="$1" + [[ -n "$dir" ]] && link_search_dirs+=("$dir") +}} + +append_lld_arg() {{ + local arg="$1" + case "$arg" in + *.a|*.rlib) + archive_args+=("$arg") + ;; + *) + flush_archive_group + lld_args+=("$arg") + ;; + esac +}} + +flush_archive_group() {{ + if (( ${{#archive_args[@]}} == 0 )); then + return + fi + lld_args+=("--start-group" "${{archive_args[@]}}" "--end-group") + archive_args=() +}} + +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" + append_lld_arg "$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) + append_lld_arg "$arg" + expect_link_search_dir=1 + return + ;; + -L*) + add_link_search_dir "${{arg#-L}}" + append_lld_arg "$arg" + return + ;; + -flavor) + skip_next_lld_driver_arg=1 + return + ;; + -flavor=*|-T*) + return + ;; + -lc) + append_lld_arg "$fake_dir/libc.a" + return + ;; + -lgcc_s|-lgcc) + return + ;; + -lunwind) + if [[ -f "$fake_dir/libunwind.a" ]]; then + append_lld_arg "$fake_dir/libunwind.a" + fi + return + ;; + -static-pie) + if [[ "$dynamic_platform" == "1" ]]; then + append_lld_arg "-pie" + else + append_lld_arg "-static" + append_lld_arg "-no-pie" + fi + return + ;; + -static) + if [[ "$dynamic_platform" == "0" ]]; then + append_lld_arg "-static" + fi + return + ;; + -pie) + if [[ "$dynamic_platform" == "1" ]]; then + append_lld_arg "-pie" + fi + return + ;; + -no-pie) + if [[ "$dynamic_platform" == "0" ]]; then + append_lld_arg "-no-pie" + fi + return + ;; + -nostartfiles|-nodefaultlibs|-nostdlib|-m*) + return + ;; + --eh-frame-hdr|-z|relro|norelro|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 + append_lld_arg "$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 + +flush_archive_group +lld_args+=("-L$fake_dir" "-T$linker_script") +exec rust-lld -flavor gnu "${{lld_args[@]}}" +"#, + shell_single_quote(&fake_lib_dir.display().to_string()), + shell_single_quote(target), + machine, + 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 +1112,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 +1167,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 +1194,34 @@ 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(()) +} + +pub(crate) fn reject_arceos_app_c_field(path: &Path, contents: &str) -> anyhow::Result<()> { + if let Ok(table) = toml::from_str::(contents) + && table.contains_key("app-c") + { + bail!( + "build config {} uses ArceOS-only `app-c` field; remove it or use an ArceOS build \ + command", + path.display() + ); + } + + Ok(()) +} + fn is_false(value: &bool) -> bool { !*value } @@ -772,14 +1270,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 +1365,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 +1607,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 +1629,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 @@ -1472,6 +2022,15 @@ mod tests { workspace_metadata().unwrap() } + fn gnu_lld_pre_link_args(spec: &serde_json::Value) -> Vec<&str> { + spec["pre-link-args"]["gnu-lld"] + .as_array() + .unwrap() + .iter() + .map(|arg| arg.as_str().unwrap()) + .collect() + } + fn temp_workspace( package_name: &str, dependency_block: &str, @@ -1561,23 +2120,38 @@ 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(), + "std-compat".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/std-compat".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,116 +2161,789 @@ 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(), + "std-compat".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/loongarch64-qemu-virt".to_string(), + "ax-std/std-compat".to_string(), + "dns".to_string() + ] ); + assert!(envs.is_empty()); } #[test] - fn std_build_aarch64_defaults_to_dynamic_platform() { + fn std_build_cargo_config_builds_fake_lib_before_app() { let metadata = repo_metadata(); let cargo = BuildInfo { - std_build: true, - ..BuildInfo::default_for_target("aarch64-unknown-none-softfloat") + 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( - "test-arceos-std-app", - "aarch64-unknown-none-softfloat", + "arceos-std-helloworld", + "x86_64-unknown-none", 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")); - 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!( + 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/std-compat".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 std_build_dynamic_x86_64_prepares_binary_artifact() { + 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(), + "std-compat".to_string(), + "x86-pc".to_string(), + ], + ); + + assert_eq!( + info.features, + vec![ + "ax-std/dns".to_string(), + "ax-std/multitask".to_string(), + "ax-std/net".to_string(), + "ax-std/std-compat".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 { - std_build: true, - ..BuildInfo::default_for_target("x86_64-unknown-none") + features: Vec::new(), + ..BuildInfo::default() } .into_prepared_base_cargo_config_with_metadata( - "test-arceos-std-app", + "arceos-std-helloworld", "x86_64-unknown-none", None, &metadata, ) .unwrap(); - assert_eq!(cargo.target, "x86_64-unknown-hermit"); - 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(&"arceos".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()], + 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(); - info.resolve_std_features(); - let mut envs = HashMap::new(); - pass_std_build_nested_features(&mut envs, &mut info.features); + 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/std-compat".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()) + ); + } + #[test] + fn std_build_aarch64_defaults_to_dynamic_platform() { + let metadata = repo_metadata(); + let cargo = BuildInfo { + ..BuildInfo::default_for_target("aarch64-unknown-none-softfloat") + } + .into_prepared_base_cargo_config_with_metadata( + "test-arceos-std-helloworld", + "aarch64-unknown-none-softfloat", + None, + &metadata, + ) + .unwrap(); + + 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/std-compat".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("--cfg")); + assert!(!config.contains("--check-cfg")); + assert!(!config.contains("relocation-model")); + assert!(!config.contains("code-model")); + } + + #[test] + fn std_build_config_preserves_backtrace_rustflags_from_env() { + let metadata = repo_metadata(); + let mut info = BuildInfo::default_for_target("x86_64-unknown-none"); + info.env.insert("DWARF".to_string(), "y".to_string()); + + let cargo = info + .into_prepared_base_cargo_config_with_metadata( + "test-arceos-std-helloworld", + "x86_64-unknown-none", + None, + &metadata, + ) + .unwrap(); + + let config = std::fs::read_to_string(cargo.extra_config.unwrap()).unwrap(); + assert!(config.contains(r#""-Cdebuginfo=2""#)); + assert!(config.contains(r#""-Cstrip=none""#)); + assert!(config.contains(r#""-Cforce-frame-pointers=yes""#)); + } + + #[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!( - info.features, - vec![ - "arceos-rust/alloc".to_string(), - "arceos-rust/plat-dyn".to_string() - ] + 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=")); + assert!(bindgen_args.contains("-march=rv64gc")); + assert!(bindgen_args.contains("-mabi=lp64d")); + } + } + + #[test] + fn scripts_targets_keep_only_std_specs() { + let workspace = crate::context::workspace_root_path().unwrap(); + + for removed_dir in ["pie", "no-pie"] { + assert!(!workspace.join("scripts/targets").join(removed_dir).exists()); + } + } + + #[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_eq!( + env.get("CFLAGS_riscv64gc_unknown_linux_musl"), + Some(&"-march=rv64gc -mabi=lp64d -mcmodel=medany".to_string()) + ); + assert_eq!( + env.get("CXXFLAGS_riscv64gc_unknown_linux_musl"), + Some(&"-march=rv64gc -mabi=lp64d -mcmodel=medany".to_string()) + ); + assert!(!env.contains_key("BINDGEN_EXTRA_CLANG_ARGS_riscv64gc_unknown_linux_musl")); + } + + #[test] + fn std_c_toolchain_env_exports_loongarch_softfloat_abi_flags() { + let env = std_c_toolchain_env("loongarch64-unknown-linux-musl", "loongarch64-linux-musl"); + + assert_eq!( + env.get("CFLAGS_loongarch64_unknown_linux_musl"), + Some(&"-mabi=lp64s -msoft-float".to_string()) + ); + assert_eq!( + env.get("CXXFLAGS_loongarch64_unknown_linux_musl"), + Some(&"-mabi=lp64s -msoft-float".to_string()) ); - assert!(!envs.contains_key("ARCEOS_RUST_FEATURES")); + if let Some(bindgen_args) = + env.get("BINDGEN_EXTRA_CLANG_ARGS_loongarch64_unknown_linux_musl") + { + assert!(bindgen_args.contains("--target=loongarch64-linux-musl")); + assert!(bindgen_args.contains("-mabi=lp64s")); + assert!(bindgen_args.contains("-msoft-float")); + } + } + + #[test] + fn std_target_specs_keep_kernel_fields_with_std_identity() { + for (std_target, plat_dyn, llvm_target, arch, pointer_width) in [ + ( + "x86_64-unknown-linux-musl", + false, + "x86_64-unknown-none-elf", + "x86_64", + 64, + ), + ( + "x86_64-unknown-linux-musl", + true, + "x86_64-unknown-none-elf", + "x86_64", + 64, + ), + ( + "aarch64-unknown-linux-musl", + false, + "aarch64-unknown-none", + "aarch64", + 64, + ), + ( + "aarch64-unknown-linux-musl", + true, + "aarch64-unknown-none", + "aarch64", + 64, + ), + ( + "riscv64gc-unknown-linux-musl", + false, + "riscv64", + "riscv64", + 64, + ), + ( + "riscv64gc-unknown-linux-musl", + true, + "riscv64", + "riscv64", + 64, + ), + ( + "loongarch64-unknown-linux-musl", + false, + "loongarch64-unknown-none", + "loongarch64", + 64, + ), + ] { + let workspace = crate::context::workspace_root_path().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 std_spec: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&std_path).unwrap()).unwrap(); + + assert_eq!(std_spec["arch"], arch); + assert_eq!(std_spec["llvm-target"], llvm_target); + assert_eq!(std_spec["target-pointer-width"], pointer_width); + 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["eh-frame-header"], false); + assert_eq!(std_spec["relro-level"], "off"); + assert_eq!(std_spec["linker"], "rust-lld"); + assert_eq!(std_spec["linker-flavor"], "gnu-lld"); + assert_eq!(std_spec["panic-strategy"], "abort"); + } + + 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), + ("x86_64-unknown-linux-musl", true), + ("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 cargo_target_json_path_maps_no_pie_targets() { + fn std_target_specs_embed_final_link_policy() { let cases = [ - "aarch64-unknown-none-softfloat", - "riscv64gc-unknown-none-elf", - "x86_64-unknown-none", - "loongarch64-unknown-none-softfloat", + ("x86_64-unknown-linux-musl", false, "_start", "-no-pie"), + ("x86_64-unknown-linux-musl", true, "_head", "-pie"), + ("aarch64-unknown-linux-musl", false, "_start", "-no-pie"), + ("aarch64-unknown-linux-musl", true, "_head", "-pie"), + ("riscv64gc-unknown-linux-musl", false, "_start", "-no-pie"), + ("riscv64gc-unknown-linux-musl", true, "_head", "-pie"), + ("loongarch64-unknown-linux-musl", false, "_start", "-no-pie"), ]; - for target in cases { - let path = cargo_target_json_path(target, false).unwrap(); - assert!(path.ends_with(format!("scripts/targets/no-pie/{target}.json"))); + for (target, plat_dyn, entry, mode_arg) in cases { + let path = crate::context::workspace_root_path() + .unwrap() + .join(std_target_json_path(target, plat_dyn)); + let spec: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&path).unwrap()).unwrap(); + let link_args = gnu_lld_pre_link_args(&spec); + + assert!(link_args.contains(&mode_arg)); + assert!(link_args.contains(&"--gc-sections")); + assert!(link_args.contains(&"-znorelro")); + assert!(link_args.contains(&"-znostart-stop-gc")); + assert!(link_args.contains(&"-Tlinker.x")); + assert!(link_args.contains(&"-u")); + assert!(link_args.contains(&entry)); + assert_eq!(spec["eh-frame-header"], false); + assert_eq!(spec["relro-level"], "off"); + + if plat_dyn { + assert!(!link_args.contains(&"-static")); + assert!(!link_args.contains(&"-no-pie")); + } else { + assert!(link_args.contains(&"-static")); + assert!(!link_args.contains(&"-pie")); + } } } #[test] - fn cargo_target_json_path_maps_aarch64_pie_target() { - let path = cargo_target_json_path("aarch64-unknown-none-softfloat", true).unwrap(); + fn std_cargo_config_uses_linux_musl_wrapper_without_custom_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("--cfg")); + assert!(!config.contains("--check-cfg")); + 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("--cfg")); + assert!(!config.contains("--check-cfg")); + 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("--cfg")); + assert!(!config.contains("--check-cfg")); + 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(); - assert!(path.ends_with("scripts/targets/pie/aarch64-unknown-none-softfloat.json")); + let config = fs::read_to_string(app_config).unwrap(); + assert!(!config.contains("--cfg")); + assert!(!config.contains("--check-cfg")); + assert!(!config.contains("relocation-model")); + assert!(!config.contains("code-model")); } #[test] - fn cargo_target_json_path_maps_riscv64_pie_target() { - let path = cargo_target_json_path("riscv64gc-unknown-none-elf", true).unwrap(); + 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") + ); + } - assert!(path.ends_with("scripts/targets/pie/riscv64gc-unknown-none-elf.json")); + #[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("archive_args=()")); + assert!(wrapper.contains("add_link_search_dir")); + assert!(wrapper.contains("append_lld_arg")); + assert!(wrapper.contains("flush_archive_group")); + assert!(wrapper.contains("--start-group")); + assert!(wrapper.contains("--end-group")); + assert!(wrapper.contains("find_linker_script")); + assert!(wrapper.contains("failed to find linker.x in current linker search dirs")); + assert!(!wrapper.contains("entry_symbol=")); + assert!(!wrapper.contains("link_mode_args=")); + 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("--gc-sections")); + assert!(!wrapper.contains("-znostart-stop-gc")); + 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 cargo_target_json_path_maps_x86_64_dyn_to_pie_target() { - let path = cargo_target_json_path("x86_64-unknown-none", true).unwrap(); + 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!(path.ends_with("scripts/targets/pie/x86_64-unknown-none.json")); + assert!(wrapper.contains("find_linker_script")); + assert!(!wrapper.contains("latest_build_output_script axplat.x")); + assert!(!wrapper.contains("entry_symbol=")); + assert!(!wrapper.contains("link_mode_args=")); + assert!(wrapper.contains("dynamic_platform=1")); + assert!(!wrapper.contains("_head")); + } + + #[test] + fn std_build_dynamic_x86_64_prepares_binary_artifact() { + let metadata = repo_metadata(); + let cargo = BuildInfo { + ..BuildInfo::default_for_target("x86_64-unknown-none") + } + .into_prepared_base_cargo_config_with_metadata( + "test-arceos-std-helloworld", + "x86_64-unknown-none", + None, + &metadata, + ) + .unwrap(); + + assert!( + cargo + .target + .ends_with("scripts/targets/std/pie/x86_64-unknown-linux-musl.json") + ); + assert!(cargo.to_bin); + 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 { + 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, &[], &["alloc".to_string()]); + + assert_eq!(info.features, vec!["ax-std/alloc".to_string()]); + assert!(envs.is_empty()); } #[test] @@ -1715,16 +2962,9 @@ mod tests { } #[test] - fn build_cargo_args_uses_json_target_spec_and_build_std() { - let args = BuildInfo::build_cargo_args( - "scripts/targets/no-pie/aarch64-unknown-none-softfloat.json", - &[], - ); + fn build_cargo_args_uses_builtin_target_and_build_std() { + let args = BuildInfo::build_cargo_args("aarch64-unknown-none-softfloat", &[]); - assert!( - args.windows(2) - .any(|pair| pair == ["-Z", "json-target-spec"]) - ); assert!( args.windows(2) .any(|pair| pair == ["-Z", "build-std=core,alloc"]) @@ -1737,12 +2977,10 @@ mod tests { #[test] fn build_cargo_args_uses_target_stem_as_rustflags_key() { let args = BuildInfo::build_cargo_args( - "scripts/targets/no-pie/aarch64-unknown-none-softfloat.json", + "aarch64-unknown-none-softfloat", &["-Cforce-frame-pointers=yes".to_string()], ); - // Cargo matches the JSON target by file stem, so the config key must be the - // stem (`aarch64-unknown-none-softfloat`) and not the full spec path. assert!(args.windows(2).any(|pair| { pair[0] == "--config" && pair[1].starts_with("target.aarch64-unknown-none-softfloat.rustflags=") @@ -1751,64 +2989,11 @@ mod tests { assert!( !args .iter() - .any(|arg| arg.contains("scripts/targets/no-pie")), - "config key must not use the spec path" + .any(|arg| arg.starts_with("target.") && arg.contains('/')), + "config key must not use a removed spec path" ); } - #[test] - fn target_specs_embed_only_final_linker_script() { - let specs = [ - include_str!("../../targets/no-pie/aarch64-unknown-none-softfloat.json"), - include_str!("../../targets/no-pie/loongarch64-unknown-none-softfloat.json"), - include_str!("../../targets/no-pie/riscv64gc-unknown-none-elf.json"), - include_str!("../../targets/no-pie/x86_64-unknown-none.json"), - include_str!("../../targets/pie/aarch64-unknown-none-softfloat.json"), - include_str!("../../targets/pie/riscv64gc-unknown-none-elf.json"), - include_str!("../../targets/pie/x86_64-unknown-none.json"), - ]; - - for spec in specs { - assert!(spec.contains("-Tlinker.x")); - assert!(!spec.contains("-Taxplat.x")); - assert!(!spec.contains("-Truntime.x")); - } - } - - #[test] - fn x86_64_no_pie_target_preserves_pic_codegen_without_pie_link() { - let spec: serde_json::Value = serde_json::from_str(include_str!( - "../../targets/no-pie/x86_64-unknown-none.json" - )) - .unwrap(); - - assert_eq!(spec["relocation-model"], "pic"); - assert_eq!(spec.get("position-independent-executables"), None); - assert_eq!(spec.get("static-position-independent-executables"), None); - - let link_args = spec["pre-link-args"]["gnu-lld"].as_array().unwrap(); - assert!(link_args.iter().any(|arg| arg == "-no-pie")); - assert!(!link_args.iter().any(|arg| arg == "-pie")); - } - - #[test] - fn x86_64_pie_target_uses_final_linker_script() { - let spec: serde_json::Value = - serde_json::from_str(include_str!("../../targets/pie/x86_64-unknown-none.json")) - .unwrap(); - - assert_eq!(spec["position-independent-executables"], true); - assert_eq!(spec["static-position-independent-executables"], true); - assert_eq!(spec["relocation-model"], "pic"); - - let link_args = spec["pre-link-args"]["gnu-lld"].as_array().unwrap(); - assert!(link_args.iter().any(|arg| arg == "-pie")); - assert!(link_args.iter().any(|arg| arg == "-znostart-stop-gc")); - assert!(link_args.iter().any(|arg| arg == "-Tlinker.x")); - assert!(!link_args.iter().any(|arg| arg == "-Taxplat.x")); - assert!(!link_args.iter().any(|arg| arg == "-no-pie")); - } - #[test] fn detects_axfeat_direct_dependency_via_metadata() { let workspace = temp_workspace("ax-feat-app", "ax-feat = \"0.1.0\"\n").unwrap(); @@ -1820,30 +3005,45 @@ 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(), + "std-compat".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(), + "ax-std/std-compat".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 +3055,23 @@ 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(), "std-compat".to_string()], + ); + + assert_eq!( + info.features, + vec![ + "ax-std/lockdep".to_string(), + "ax-std/std-compat".to_string() + ] + ); + assert!(envs.is_empty()); } #[test] diff --git a/scripts/axbuild/src/clippy.rs b/scripts/axbuild/src/clippy.rs index cb05c8def3..d1dc81009b 100644 --- a/scripts/axbuild/src/clippy.rs +++ b/scripts/axbuild/src/clippy.rs @@ -15,14 +15,17 @@ 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 = "std-compat,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"; const TARGETS_METADATA: &str = "targets"; const AXBUILD_METADATA: &str = "axbuild"; const CLIPPY_FEATURE_AXCONFIG_OVERRIDES_METADATA: &str = "clippy-feature-axconfig-overrides"; +const AX_HAL_PACKAGE: &str = "ax-hal"; const UNSUPPORTED_CLIPPY_PACKAGES: &[(&str, &str)] = &[ ( @@ -47,6 +50,15 @@ const CLIPPY_TARGET_ALIASES: &[(&str, &str)] = &[ ), ]; +const AX_HAL_PLATFORM_FEATURE_TARGET_ARCHES: &[(&str, &[&str])] = &[ + ("plat-dyn", &["aarch64", "loongarch64", "riscv64", "x86_64"]), + ("loongarch64-qemu-virt", &["loongarch64"]), + ("riscv64-sg2002", &["riscv64"]), + ("riscv64-visionfive2", &["riscv64"]), + ("x86-pc", &["x86_64"]), + ("x86-qemu-q35", &["x86_64"]), +]; + pub(crate) fn run_workspace_clippy_command(args: &crate::ClippyArgs) -> anyhow::Result<()> { validate_clippy_args(args)?; let started_at = Local::now(); @@ -387,6 +399,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()]); } @@ -453,6 +477,76 @@ fn normalize_clippy_target(target: &str) -> &str { .unwrap_or(target) } +fn clippy_target_arch(target: &str) -> Option<&'static str> { + if target.starts_with("aarch64-") { + Some("aarch64") + } else if target.starts_with("loongarch64-") { + Some("loongarch64") + } else if target.starts_with("riscv64") { + Some("riscv64") + } else if target.starts_with("x86_64-") { + Some("x86_64") + } else { + None + } +} + +fn ax_hal_platform_target_arches(feature: &str) -> Option<&'static [&'static str]> { + AX_HAL_PLATFORM_FEATURE_TARGET_ARCHES + .iter() + .find_map(|(platform_feature, target_arches)| { + (*platform_feature == feature).then_some(*target_arches) + }) +} + +fn ax_hal_feature_dependency(feature_dependency: &str) -> Option<&str> { + feature_dependency + .strip_prefix("ax-hal/") + .or_else(|| feature_dependency.strip_prefix("ax-hal?/")) +} + +fn ax_hal_platform_constraints<'a>( + package: &'a Package, + feature: &'a str, +) -> Vec<&'static [&'static str]> { + let mut constraints = Vec::new(); + if package.name == AX_HAL_PACKAGE + && let Some(target_arches) = ax_hal_platform_target_arches(feature) + { + constraints.push(target_arches); + } + + if let Some(feature_dependencies) = package.features.get(feature) { + constraints.extend( + feature_dependencies + .iter() + .filter_map(|feature_dependency| ax_hal_feature_dependency(feature_dependency)) + .filter_map(ax_hal_platform_target_arches), + ); + } + + constraints +} + +fn feature_supported_on_clippy_target( + package: &Package, + feature: &str, + target: Option<&str>, +) -> bool { + let constraints = ax_hal_platform_constraints(package, feature); + if constraints.is_empty() { + return true; + } + let Some(target) = target else { + return false; + }; + clippy_target_arch(target).is_some_and(|arch| { + constraints + .iter() + .all(|target_arches| target_arches.contains(&arch)) + }) +} + fn clippy_skip_reason(package: &Package) -> Option<&str> { UNSUPPORTED_CLIPPY_PACKAGES .iter() @@ -476,20 +570,16 @@ fn skip_unsupported_packages(packages: Vec) -> Vec