From cf0627c18db41c4139ecc4e6eab296b69f19d0ca Mon Sep 17 00:00:00 2001 From: lgw <15035660024@163.com> Date: Sun, 10 Sep 2023 21:02:31 +0800 Subject: [PATCH] add env&argv funcs --- Cargo.lock | 95 ++++++++++++- Cargo.toml | 1 + Makefile | 6 + api/arceos_posix_api/src/lib.rs | 3 + apps/c/envtest/expect_info.out | 22 +++ apps/c/envtest/features.txt | 2 + apps/c/envtest/main.c | 21 +++ apps/c/envtest/test_cmd | 2 + apps/c/memtest/expect_trace.out | 2 +- apps/c/memtest/memtest.c | 2 +- apps/c/redis/README.md | 15 +- apps/c/redis/redis.patch | 24 +--- crates/dtb/Cargo.toml | 14 ++ crates/dtb/src/lib.rs | 114 +++++++++++++++ modules/axhal/Cargo.toml | 2 + modules/axhal/src/lib.rs | 7 + .../src/platform/aarch64_qemu_virt/mod.rs | 3 + .../src/platform/riscv64_qemu_virt/mod.rs | 3 + modules/axhal/src/platform/x86_pc/mod.rs | 14 +- modules/axruntime/Cargo.toml | 4 +- modules/axruntime/src/env.rs | 81 +++++++++++ modules/axruntime/src/lib.rs | 64 ++++++++- scripts/make/qemu.mk | 3 +- scripts/test/app_test.sh | 1 + ulib/axlibc/c/env.c | 40 ------ ulib/axlibc/c/string.c | 14 -- ulib/axlibc/src/env.rs | 132 ++++++++++++++++++ ulib/axlibc/src/lib.rs | 12 +- ulib/axlibc/src/string.rs | 20 +++ 29 files changed, 631 insertions(+), 92 deletions(-) create mode 100644 apps/c/envtest/expect_info.out create mode 100644 apps/c/envtest/features.txt create mode 100644 apps/c/envtest/main.c create mode 100644 apps/c/envtest/test_cmd create mode 100644 crates/dtb/Cargo.toml create mode 100644 crates/dtb/src/lib.rs create mode 100644 modules/axruntime/src/env.rs delete mode 100644 ulib/axlibc/c/env.c create mode 100644 ulib/axlibc/src/env.rs create mode 100644 ulib/axlibc/src/string.rs diff --git a/Cargo.lock b/Cargo.lock index b305bb432..ca17afeef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -375,6 +375,7 @@ dependencies = [ "bitflags 2.4.0", "cfg-if", "crate_interface", + "dtb", "dw_apb_uart", "handler_table", "kernel_guard", @@ -457,7 +458,9 @@ dependencies = [ "axlog", "axnet", "axtask", + "cfg-if", "crate_interface", + "dtb", "kernel_guard", "lazy_init", "percpu", @@ -821,7 +824,7 @@ dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset", + "memoffset 0.9.0", "scopeguard", ] @@ -914,6 +917,14 @@ dependencies = [ "virtio-drivers", ] +[[package]] +name = "dtb" +version = "0.1.0" +dependencies = [ + "fdt-rs", + "lazy_init", +] + [[package]] name = "dw_apb_uart" version = "0.1.0" @@ -960,6 +971,12 @@ dependencies = [ "void", ] +[[package]] +name = "endian-type-rs" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6419a5c75e40011b9fe0174db3fe24006ab122fbe1b7e9cc5974b338a755c76" + [[package]] name = "equivalent" version = "1.0.1" @@ -987,6 +1004,12 @@ dependencies = [ "libc", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fatfs" version = "0.4.0" @@ -996,6 +1019,22 @@ dependencies = [ "log", ] +[[package]] +name = "fdt-rs" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99a40cabc11c8258822a593f5c51f2d9f4923e715ca9e2a0630cf77ae15f390b" +dependencies = [ + "endian-type-rs", + "fallible-iterator", + "memoffset 0.5.6", + "num-derive", + "num-traits", + "rustc_version 0.2.3", + "static_assertions", + "unsafe_unwrap", +] + [[package]] name = "flatten_objects" version = "0.1.0" @@ -1062,7 +1101,7 @@ checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" dependencies = [ "atomic-polyfill", "hash32", - "rustc_version", + "rustc_version 0.4.0", "spin 0.9.8", "stable_deref_trait", ] @@ -1234,6 +1273,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.9.0" @@ -1284,6 +1332,17 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -1584,13 +1643,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.18", ] [[package]] @@ -1658,12 +1726,27 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.185" @@ -1881,6 +1964,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unsafe_unwrap" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1230ec65f13e0f9b28d789da20d2d419511893ea9dac2c1f4ef67b8b14e5da80" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 1db098f12..1da430235 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ members = [ "crates/driver_net", "crates/driver_pci", "crates/driver_virtio", + "crates/dtb", "crates/flatten_objects", "crates/handler_table", "crates/kernel_guard", diff --git a/Makefile b/Makefile index b35159658..52480cde8 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ # - `MODE`: Build mode: release, debug # - `LOG:` Logging level: warn, error, info, debug, trace # - `V`: Verbose level: (empty), 1, 2 +# - `ARGS`: Command-line arguments separated by comma. Only available when feature `alloc` is enabled. +# - `ENVS`: Environment variables, separated by comma between key value pairs. Only available when feature `alloc` is enabled. # * App options: # - `A` or `APP`: Path to the application # - `FEATURES`: Features os ArceOS modules to be enabled. @@ -53,6 +55,10 @@ NET_DEV ?= user IP ?= 10.0.2.15 GW ?= 10.0.2.2 +# args and envs +ARGS ?= +ENVS ?= + # App type ifeq ($(wildcard $(APP)),) $(error Application path "$(APP)" is not valid) diff --git a/api/arceos_posix_api/src/lib.rs b/api/arceos_posix_api/src/lib.rs index 71d469dd4..3bc03b677 100644 --- a/api/arceos_posix_api/src/lib.rs +++ b/api/arceos_posix_api/src/lib.rs @@ -25,6 +25,9 @@ extern crate axruntime; #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "alloc")] +pub use axruntime::{environ, environ_iter, RX_ENVIRON}; + #[macro_use] mod utils; diff --git a/apps/c/envtest/expect_info.out b/apps/c/envtest/expect_info.out new file mode 100644 index 000000000..a6cf5d914 --- /dev/null +++ b/apps/c/envtest/expect_info.out @@ -0,0 +1,22 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started. +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... + use TLSF allocator. +Initialize kernel page table... +Initialize platform devices... +Primary CPU 0 init OK. +Running argv tests... +Argv tests run OK! +Running environ tests... +Environ tests run OK! +Shutting down... \ No newline at end of file diff --git a/apps/c/envtest/features.txt b/apps/c/envtest/features.txt new file mode 100644 index 000000000..1b71ca850 --- /dev/null +++ b/apps/c/envtest/features.txt @@ -0,0 +1,2 @@ +alloc +paging diff --git a/apps/c/envtest/main.c b/apps/c/envtest/main.c new file mode 100644 index 000000000..524aa2428 --- /dev/null +++ b/apps/c/envtest/main.c @@ -0,0 +1,21 @@ +#include +#include +#include +int main(int argc, char** argv) { + puts("Running argv tests..."); + if (argc != 3) puts("args num is wrong"); + if (strcmp(argv[0], "envtest") || strcmp(argv[1], "test1") || strcmp(argv[2], "test2")) puts("argv is wrong"); + if(argv[3] != NULL) puts("argv is wrong"); + puts("Argv tests run OK!"); + + puts("Running environ tests..."); + char *env1 = "env1", *ex1 = "ex1", *ex2 = "ex_2"; + if(setenv(env1, ex1, 1) || strcmp(ex1, getenv(env1))) puts("set new env is wrong"); + if(setenv(env1, ex2, 1) || strcmp(ex2, getenv(env1))) puts("set old env is wrong"); + if(setenv(env1, ex1, 0) || strcmp(ex2, getenv(env1))) puts("override the old env is wrong"); + if(strcmp("hello", getenv("world")) || strcmp("world", getenv("hello"))) puts("boot env is wrong"); + puts("Environ tests run OK!"); + return 0; +} + + diff --git a/apps/c/envtest/test_cmd b/apps/c/envtest/test_cmd new file mode 100644 index 000000000..c791c9159 --- /dev/null +++ b/apps/c/envtest/test_cmd @@ -0,0 +1,2 @@ +test_one "LOG=info ARGS=envtest,test1,test2 ENVS=hello=world,world=hello" "expect_info.out" +rm -f $APP/*.o diff --git a/apps/c/memtest/expect_trace.out b/apps/c/memtest/expect_trace.out index 04aaf13aa..41dca8b3f 100644 --- a/apps/c/memtest/expect_trace.out +++ b/apps/c/memtest/expect_trace.out @@ -40,4 +40,4 @@ allocated addr=0x[0-9a-f]\{16\} allocated addr=0x[0-9a-f]\{16\} allocated addr=0x[0-9a-f]\{16\} Memory tests run OK! -Shutting down... +Shutting down... \ No newline at end of file diff --git a/apps/c/memtest/memtest.c b/apps/c/memtest/memtest.c index 332303b7d..e4c3dfa77 100644 --- a/apps/c/memtest/memtest.c +++ b/apps/c/memtest/memtest.c @@ -11,7 +11,7 @@ #include #include -int main() +int main(int argc, char* argv[]) { puts("Running memory tests..."); uintptr_t *brk = (uintptr_t *)malloc(0); diff --git a/apps/c/redis/README.md b/apps/c/redis/README.md index f5c24416c..0bf76e1d8 100644 --- a/apps/c/redis/README.md +++ b/apps/c/redis/README.md @@ -1,8 +1,19 @@ # How to run? - Run: - - `make A=apps/c/redis/ LOG=error NET=y BLK=y ARCH=aarch64 SMP=4 run`(for aarch64) - - `make A=apps/c/redis/ LOG=error NET=y BLK=y ARCH=x86_64 SMP=4 run`(for x86_64) + + - for aarch64 + + ``` + make A=apps/c/redis/ LOG=error NET=y BLK=y ARCH=aarch64 SMP=4 ARGS="./redis-server,--bind,0.0.0.0,--port,5555,--save,\"\",--appendonly,no,--protected-mode,no,--ignore-warnings,ARM64-COW-BUG" run + ``` + + - for x86_64 + + ``` + make A=apps/c/redis/ LOG=error NET=y BLK=y ARCH=x86_64 SMP=4 ARGS="./redis-server,--bind,0.0.0.0,--port,5555,--save,\"\",--appendonly,no,--protected-mode,no" run + ``` + # How to test? - Use `redis-cli -p 5555` to connect to redis-server, and enjoy ArceOS-Redis world! diff --git a/apps/c/redis/redis.patch b/apps/c/redis/redis.patch index 6f4e9418b..9e95c5f9c 100644 --- a/apps/c/redis/redis.patch +++ b/apps/c/redis/redis.patch @@ -68,30 +68,10 @@ index e4f7d90..1b41e94 100644 .PHONY: clean diff --git a/src/server.c b/src/server.c -index b170cbb..f5dfde0 100644 +index b170cbb..a3960ce 100644 --- a/src/server.c +++ b/src/server.c -@@ -6854,6 +6854,19 @@ redisTestProc *getTestProcByName(const char *name) { - #endif - - int main(int argc, char **argv) { -+ const char *cmdline = "./redis-server " -+ "--bind 0.0.0.0 " -+ "--port 5555 " -+ "--save \"\" " -+ "--appendonly no " -+ "--protected-mode no " -+#if defined (__arm64__) -+ "--ignore-warnings ARM64-COW-BUG " -+#endif -+ ; -+ printf("Run Redis with: %s\n", cmdline); -+ argv = sdssplitargs(cmdline, &argc); -+ - struct timeval tv; - int j; - char config_from_stdin = 0; -@@ -6900,7 +6913,7 @@ int main(int argc, char **argv) { +@@ -6900,7 +6900,7 @@ int main(int argc, char **argv) { /* We need to initialize our libraries, and the server configuration. */ #ifdef INIT_SETPROCTITLE_REPLACEMENT diff --git a/crates/dtb/Cargo.toml b/crates/dtb/Cargo.toml new file mode 100644 index 000000000..54e10b658 --- /dev/null +++ b/crates/dtb/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dtb" +version = "0.1.0" +edition = "2021" +authors = ["Leping Wang "] +description = "Device tree basic operations" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/dtb" +documentation = "https://rcore-os.github.io/arceos/dtb/index.html" + +[dependencies] +fdt-rs = { version = "0.4.3", default-features = false } +lazy_init = { path = "../../crates/lazy_init" } \ No newline at end of file diff --git a/crates/dtb/src/lib.rs b/crates/dtb/src/lib.rs new file mode 100644 index 000000000..551b49f6f --- /dev/null +++ b/crates/dtb/src/lib.rs @@ -0,0 +1,114 @@ +//! Some useful interfaces for device tree. + +#![no_std] +use fdt_rs::{ + base::{DevTree, DevTreeNode, DevTreeProp}, + prelude::{FallibleIterator, PropReader}, +}; +use lazy_init::LazyInit; + +/// The device Tree. +static TREE: LazyInit = LazyInit::new(); + +/// Init device tree from given address. +/// # Safety +/// +/// Callers of this method the must guarantee the following: +/// +/// - The passed address is 32-bit aligned. +pub unsafe fn init(dtb: *const u8) { + TREE.init_by(DevTree::from_raw_pointer(dtb).unwrap()); +} + +/// A node on the device tree. +pub struct DeviceNode<'a>(DevTreeNode<'a, 'static>); + +/// A prop of a node. +pub struct DeviceProp<'a>(DevTreeProp<'a, 'static>); + +impl<'a> DeviceNode<'a> { + /// Find a node's prop with given name(may not exist). + pub fn find_prop(&'a self, name: &str) -> Option> { + self.0 + .props() + .filter(|p| p.name().map(|s| s == name)) + .next() + .unwrap() + .map(DeviceProp) + } + + /// Find a node's prop with given name(must exist). + pub fn prop(&'a self, name: &str) -> DeviceProp<'a> { + self.find_prop(name).unwrap() + } +} + +impl<'a> DeviceProp<'a> { + /// Assume the prop is a u32 array. Get an element. + pub fn u32(&self, index: usize) -> u32 { + self.0.u32(index).unwrap() + } + + /// Assume the prop is a u64 array. Get an element. + pub fn u64(&self, index: usize) -> u64 { + self.0.u64(index).unwrap() + } + + /// Assume the prop is a str. Get the whole str. + pub fn str(&self) -> &'static str { + self.0.str().unwrap() + } +} + +/// Find the first node with given compatible(may not exist). +pub fn compatible_node(compatible: &str) -> Option { + TREE.compatible_nodes(compatible) + .next() + .unwrap() + .map(DeviceNode) +} + +/// Find the first node with given name(may not exist). +pub fn get_node(name: &str) -> Option { + TREE.nodes() + .filter(|n| n.name().map(|s| s == name)) + .next() + .unwrap() + .map(DeviceNode) +} + +/// Do something for all devices with given type. +pub fn devices(device_type: &str, f: F) +where + F: Fn(DeviceNode), +{ + TREE.nodes() + .filter_map(|n| { + let n = DeviceNode(n); + Ok( + if n.find_prop("device_type").map(|p| p.str()) == Some(device_type) { + Some(n) + } else { + None + }, + ) + }) + .for_each(|n| { + f(n); + Ok(()) + }) + .unwrap(); +} + +/// Do something for all nodes with given compatible. +pub fn compatible_nodes(compatible: &str, mut f: F) +where + F: FnMut(DeviceNode), +{ + TREE.compatible_nodes(compatible) + .for_each(|n| { + f(DeviceNode(n)); + Ok(()) + }) + .unwrap(); +} diff --git a/modules/axhal/Cargo.toml b/modules/axhal/Cargo.toml index f29ecde07..ec2a26c3f 100644 --- a/modules/axhal/Cargo.toml +++ b/modules/axhal/Cargo.toml @@ -46,6 +46,7 @@ raw-cpuid = "11.0" [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] riscv = "0.10" sbi-rt = { version = "0.0.2", features = ["legacy"] } +dtb = {path = "../../crates/dtb" } [target.'cfg(target_arch = "aarch64")'.dependencies] aarch64-cpu = "9.3" @@ -53,6 +54,7 @@ tock-registers = "0.8" arm_gic = { path = "../../crates/arm_gic" } arm_pl011 = { path = "../../crates/arm_pl011" } dw_apb_uart = { path = "../../crates/dw_apb_uart" } +dtb = {path = "../../crates/dtb" } [build-dependencies] axconfig = { path = "../axconfig" } diff --git a/modules/axhal/src/lib.rs b/modules/axhal/src/lib.rs index 43cae84eb..0fe6ee1ca 100644 --- a/modules/axhal/src/lib.rs +++ b/modules/axhal/src/lib.rs @@ -88,3 +88,10 @@ pub use self::platform::platform_init; #[cfg(feature = "smp")] pub use self::platform::platform_init_secondary; + +/// A cmdline buf for x86_64 +/// +/// The Multiboot information structure may be placed anywhere in memory by the boot loader, +/// so we should save cmdline in a buf before this memory is set free +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub static mut COMLINE_BUF: [u8; 256] = [0; 256]; diff --git a/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs b/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs index 5040449b4..d20b007b0 100644 --- a/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs +++ b/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs @@ -40,6 +40,9 @@ pub(crate) unsafe extern "C" fn rust_entry(cpu_id: usize, dtb: usize) { crate::mem::clear_bss(); crate::arch::set_exception_vector_base(exception_vector_base as usize); crate::arch::write_page_table_root0(0.into()); // disable low address access + unsafe { + dtb::init(crate::mem::phys_to_virt(dtb.into()).as_ptr()); + } crate::cpu::init_primary(cpu_id); super::aarch64_common::pl011::init_early(); super::aarch64_common::generic_timer::init_early(); diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs b/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs index d69684c7f..fcf95e504 100644 --- a/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs +++ b/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs @@ -31,6 +31,9 @@ unsafe extern "C" fn rust_entry(cpu_id: usize, dtb: usize) { crate::mem::clear_bss(); crate::cpu::init_primary(cpu_id); crate::arch::set_trap_vector_base(trap_vector_base as usize); + unsafe { + dtb::init(crate::mem::phys_to_virt(dtb.into()).as_ptr()); + } rust_main(cpu_id, dtb); } diff --git a/modules/axhal/src/platform/x86_pc/mod.rs b/modules/axhal/src/platform/x86_pc/mod.rs index 229b90e4d..d69ff7c31 100644 --- a/modules/axhal/src/platform/x86_pc/mod.rs +++ b/modules/axhal/src/platform/x86_pc/mod.rs @@ -41,7 +41,8 @@ fn current_cpu_id() -> usize { } } -unsafe extern "C" fn rust_entry(magic: usize, _mbi: usize) { +use crate::COMLINE_BUF; +unsafe extern "C" fn rust_entry(magic: usize, mbi: usize) { // TODO: handle multiboot info if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { crate::mem::clear_bss(); @@ -49,6 +50,17 @@ unsafe extern "C" fn rust_entry(magic: usize, _mbi: usize) { self::uart16550::init(); self::dtables::init_primary(); self::time::init_early(); + // find cmdline in multiboot info and save it in COMLINE_BUF + let mbi = mbi as *const u32; + let flag = mbi.read(); + if (flag & (1 << 2)) > 0 { + let cmdline = *mbi.add(4) as *const u8; + let mut len = 0; + while cmdline.add(len).read() != 0 { + COMLINE_BUF[len] = cmdline.add(len).read(); + len += 1; + } + } rust_main(current_cpu_id(), 0); } } diff --git a/modules/axruntime/Cargo.toml b/modules/axruntime/Cargo.toml index ccd000b1d..38615f588 100644 --- a/modules/axruntime/Cargo.toml +++ b/modules/axruntime/Cargo.toml @@ -15,7 +15,7 @@ default = [] smp = ["axhal/smp"] irq = ["axhal/irq", "axtask?/irq", "percpu", "kernel_guard"] tls = ["axhal/tls", "axtask?/tls"] -alloc = ["axalloc"] +alloc = ["axalloc", "dtb"] paging = ["axhal/paging", "lazy_init"] multitask = ["axtask/multitask"] @@ -24,6 +24,7 @@ net = ["axdriver", "axnet"] display = ["axdriver", "axdisplay"] [dependencies] +cfg-if = "1.0" axhal = { path = "../axhal" } axlog = { path = "../axlog" } axconfig = { path = "../axconfig" } @@ -38,3 +39,4 @@ crate_interface = { path = "../../crates/crate_interface" } percpu = { path = "../../crates/percpu", optional = true } kernel_guard = { path = "../../crates/kernel_guard", optional = true } lazy_init = { path = "../../crates/lazy_init", optional = true } +dtb = { path = "../../crates/dtb", optional = true} diff --git a/modules/axruntime/src/env.rs b/modules/axruntime/src/env.rs new file mode 100644 index 000000000..06f89935a --- /dev/null +++ b/modules/axruntime/src/env.rs @@ -0,0 +1,81 @@ +extern crate alloc; +use alloc::vec::Vec; +use core::ffi::c_char; +use core::{ptr, usize}; + +/// argv for C main function +#[allow(non_upper_case_globals)] +pub static mut argv: *mut *mut c_char = ptr::null_mut(); + +/// Save cmdline argments +static mut RX_ARGV: Vec<*mut c_char> = Vec::new(); + +/// A pointer pointing to RX_ENVIRON +#[allow(non_upper_case_globals)] +#[no_mangle] +pub static mut environ: *mut *mut c_char = ptr::null_mut(); + +/// Save environment variables +pub static mut RX_ENVIRON: Vec<*mut c_char> = Vec::new(); + +pub(crate) unsafe fn init_argv(args: Vec<&str>) { + for arg in args { + let len = arg.len(); + let arg = arg.as_ptr(); + let buf = buf_alloc(len + 1); + for i in 0..len { + *buf.add(i) = *arg.add(i) as i8; + } + *buf.add(len) = 0; + RX_ARGV.push(buf); + } + RX_ARGV.push(ptr::null_mut()); + argv = RX_ARGV.as_mut_ptr(); +} + +/// Generate an iterator for environment variables +pub fn environ_iter() -> impl Iterator + 'static { + unsafe { + let mut ptrs = environ; + core::iter::from_fn(move || { + let ptr = ptrs.read(); + if ptr.is_null() { + None + } else { + ptrs = ptrs.add(1); + Some(ptr) + } + }) + } +} + +#[allow(dead_code)] +struct MemoryControlBlock { + size: usize, +} +const CTRL_BLK_SIZE: usize = core::mem::size_of::(); + +unsafe fn buf_alloc(size: usize) -> *mut c_char { + let layout = core::alloc::Layout::from_size_align(size + CTRL_BLK_SIZE, 8).unwrap(); + // allocate for buf to meet free function + let alloc_ptr = alloc::alloc::alloc(layout).cast::(); + assert!(!alloc_ptr.is_null(), "alloc failed"); + alloc_ptr.write(MemoryControlBlock { size }); + alloc_ptr.add(1).cast() +} + +pub(crate) fn boot_add_environ(env: &str) { + let ptr = env.as_ptr() as *const i8; + let size = env.len() + 1; + if size == 1 { + return; + } + unsafe { + let buf = buf_alloc(size); + for i in 0..size - 1 { + core::ptr::write(buf.add(i), *ptr.add(i)); + } + core::ptr::write(buf.add(size - 1), 0); + RX_ENVIRON.push(buf); + } +} diff --git a/modules/axruntime/src/lib.rs b/modules/axruntime/src/lib.rs index 56491d50c..f63a9e35c 100644 --- a/modules/axruntime/src/lib.rs +++ b/modules/axruntime/src/lib.rs @@ -41,6 +41,16 @@ mod mp; #[cfg(feature = "smp")] pub use self::mp::rust_main_secondary; +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +mod env; +#[cfg(feature = "alloc")] +pub use self::env::{argv, environ, environ_iter, RX_ENVIRON}; +#[cfg(feature = "alloc")] +use self::env::{boot_add_environ, init_argv}; +use core::ffi::{c_char, c_int}; + const LOGO: &str = r#" d8888 .d88888b. .d8888b. d88888 d88P" "Y88b d88P Y88b @@ -53,7 +63,7 @@ d88P 888 888 "Y8888P "Y8888 "Y88888P" "Y8888P" "#; extern "C" { - fn main(); + fn main(argc: c_int, argv: *mut *mut c_char); } struct LogIfImpl; @@ -101,6 +111,27 @@ fn is_init_ok() -> bool { INITED_CPUS.load(Ordering::Acquire) == axconfig::SMP } +#[cfg(feature = "alloc")] +cfg_if::cfg_if! { + if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + fn get_boot_str() -> &'static str { + let cmdline_buf: &[u8] = unsafe { &axhal::COMLINE_BUF }; + let mut len = 0; + for c in cmdline_buf.iter() { + if *c == 0 { + break; + } + len += 1; + } + core::str::from_utf8(&cmdline_buf[..len]).unwrap() + } + } else { + fn get_boot_str() -> &'static str { + dtb::get_node("chosen").unwrap().prop("bootargs").str() + } + } +} + /// The main entry point of the ArceOS runtime. /// /// It is called from the bootstrapping code in [axhal]. `cpu_id` is the ID of @@ -197,8 +228,37 @@ pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) -> ! { while !is_init_ok() { core::hint::spin_loop(); } + // environ initialization + #[cfg(feature = "alloc")] + unsafe { + use alloc::vec::Vec; + let mut boot_str = get_boot_str(); + (_, boot_str) = match boot_str.split_once(';') { + Some((a, b)) => (a, b), + None => ("", ""), + }; + let (args, envs) = match boot_str.split_once(';') { + Some((a, e)) => (a, e), + None => ("", ""), + }; + let envs: Vec<&str> = envs.split(',').collect(); + for i in envs { + boot_add_environ(i); + } + RX_ENVIRON.push(core::ptr::null_mut()); + environ = RX_ENVIRON.as_mut_ptr(); + // set up argvs + let args: Vec<&str> = args.split(',').filter(|i| !i.is_empty()).collect(); + let argc = args.len() as c_int; + init_argv(args); + + main(argc, argv); + } - unsafe { main() }; + #[cfg(not(feature = "alloc"))] + unsafe { + main(0, core::ptr::null_mut()) + }; #[cfg(feature = "multitask")] axtask::exit(0); diff --git a/scripts/make/qemu.mk b/scripts/make/qemu.mk index 3ed432614..87d52f6d0 100644 --- a/scripts/make/qemu.mk +++ b/scripts/make/qemu.mk @@ -24,7 +24,8 @@ qemu_args-aarch64 := \ -machine virt \ -kernel $(OUT_BIN) -qemu_args-y := -m 128M -smp $(SMP) $(qemu_args-$(ARCH)) +qemu_args-y := -m 128M -smp $(SMP) $(qemu_args-$(ARCH)) \ + -append ";$(ARGS);$(ENVS)" qemu_args-$(BLK) += \ -device virtio-blk-$(vdev-suffix),drive=disk0 \ diff --git a/scripts/test/app_test.sh b/scripts/test/app_test.sh index d7a1d28de..19a997ad8 100755 --- a/scripts/test/app_test.sh +++ b/scripts/test/app_test.sh @@ -123,6 +123,7 @@ if [ -z "$1" ]; then "apps/c/pthread/sleep" "apps/c/pthread/pipe" "apps/c/pthread/parallel" + "apps/c/envtest" ) else test_list="$@" diff --git a/ulib/axlibc/c/env.c b/ulib/axlibc/c/env.c deleted file mode 100644 index cbd1a9f05..000000000 --- a/ulib/axlibc/c/env.c +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright (c) [2023] [Syswonder Community] - * [Rukos] is licensed under Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. - * See the Mulan PSL v2 for more details. - */ - -#include -#include -#include -#include - -char *environ_[2] = {"dummy", NULL}; -char **environ = (char **)environ_; - -char *getenv(const char *name) -{ - size_t l = strchrnul(name, '=') - name; - if (l && !name[l] && environ) - for (char **e = environ; *e; e++) - if (!strncmp(name, *e, l) && l[*e] == '=') - return *e + l + 1; - return 0; -} - -// TODO -int setenv(const char *__name, const char *__value, int __replace) -{ - unimplemented(); - return 0; -} - -// TODO -int unsetenv(const char *__name) -{ - unimplemented(); - return 0; -} diff --git a/ulib/axlibc/c/string.c b/ulib/axlibc/c/string.c index d9418bd5e..a59a56df7 100644 --- a/ulib/axlibc/c/string.c +++ b/ulib/axlibc/c/string.c @@ -14,20 +14,6 @@ #include #include -size_t strlen(const char *s) -{ - const char *a = s; - for (; *s; s++) - ; - return s - a; -} - -size_t strnlen(const char *s, size_t n) -{ - const char *p = memchr(s, 0, n); - return p ? p - s : n; -} - int atoi(const char *s) { int n = 0, neg = 0; diff --git a/ulib/axlibc/src/env.rs b/ulib/axlibc/src/env.rs new file mode 100644 index 000000000..a90401e6c --- /dev/null +++ b/ulib/axlibc/src/env.rs @@ -0,0 +1,132 @@ +use arceos_posix_api::{environ, environ_iter, RX_ENVIRON}; +use core::ffi::{c_char, c_int, c_void}; + +use crate::malloc::{free, malloc}; +use crate::string::strlen; +unsafe fn find_env(search: *const c_char) -> Option<(usize, *mut c_char)> { + for (i, mut item) in environ_iter().enumerate() { + let mut search = search; + loop { + let end_of_query = *search == 0 || *search == b'=' as c_char; + assert_ne!(*item, 0, "environ has an item without value"); + if *item == b'=' as c_char || end_of_query { + if *item == b'=' as c_char && end_of_query { + // Both keys env here + return Some((i, item.add(1))); + } else { + break; + } + } + + if *item != *search { + break; + } + + item = item.add(1); + search = search.add(1); + } + } + None +} + +unsafe fn put_new_env(insert: *mut c_char) { + // XXX: Another problem is that `environ` can be set to any pointer, which means there is a + // chance of a memory leak. But we can check if it was the same as before, like musl does. + if environ == RX_ENVIRON.as_mut_ptr() { + *RX_ENVIRON.last_mut().unwrap() = insert; + RX_ENVIRON.push(core::ptr::null_mut()); + // Likely a no-op but is needed due to Stacked Borrows. + environ = RX_ENVIRON.as_mut_ptr(); + } else { + RX_ENVIRON.clear(); + RX_ENVIRON.extend(environ_iter()); + RX_ENVIRON.push(insert); + RX_ENVIRON.push(core::ptr::null_mut()); + environ = RX_ENVIRON.as_mut_ptr(); + } +} + +unsafe fn copy_kv( + existing: *mut c_char, + key: *const c_char, + value: *const c_char, + key_len: usize, + value_len: usize, +) { + core::ptr::copy_nonoverlapping(key, existing, key_len); + core::ptr::write(existing.add(key_len), b'=' as c_char); + core::ptr::copy_nonoverlapping(value, existing.add(key_len + 1), value_len); + core::ptr::write(existing.add(key_len + 1 + value_len), 0); +} + +/// set an environ variable +#[no_mangle] +pub unsafe extern "C" fn setenv( + key: *const c_char, + value: *const c_char, + overwrite: c_int, +) -> c_int { + let key_len = strlen(key); + let value_len = strlen(value); + if let Some((i, existing)) = find_env(key) { + if overwrite == 0 { + return 0; + } + + let existing_len = strlen(existing); + if existing_len >= value_len { + // Reuse existing element's allocation + core::ptr::copy_nonoverlapping(value, existing, value_len); + core::ptr::write(existing.add(value_len), 0); + } else { + // Reuse environ slot, but allocate a new pointer. + let ptr = malloc(key_len + 1 + value_len + 1) as *mut c_char; + copy_kv(ptr, key, value, key_len, value_len); + environ.add(i).write(ptr); + } + } else { + // Expand environ and allocate a new pointer. + let ptr = malloc(key_len + 1 + value_len + 1) as *mut c_char; + copy_kv(ptr, key, value, key_len, value_len); + put_new_env(ptr); + } + 0 +} + +/// unset an environ variable +#[no_mangle] +pub unsafe extern "C" fn unsetenv(key: *const c_char) -> c_int { + if let Some((i, _)) = find_env(key) { + if environ == RX_ENVIRON.as_mut_ptr() { + // No need to worry about updating the pointer, this does not + // reallocate in any way. And the final null is already shifted back. + let rm = RX_ENVIRON.remove(i); + free(rm as *mut c_void); + // My UB paranoia. + environ = RX_ENVIRON.as_mut_ptr(); + } else { + let len = RX_ENVIRON.len(); + for _ in 0..len { + let rm = RX_ENVIRON.pop().unwrap(); + free(rm as *mut c_void); + } + RX_ENVIRON.extend( + environ_iter() + .enumerate() + .filter(|&(j, _)| j != i) + .map(|(_, v)| v), + ); + RX_ENVIRON.push(core::ptr::null_mut()); + environ = RX_ENVIRON.as_mut_ptr(); + } + } + 0 +} + +/// get the corresponding environ variable +#[no_mangle] +pub unsafe extern "C" fn getenv(name: *const c_char) -> *mut c_char { + find_env(name) + .map(|val| val.1) + .unwrap_or(core::ptr::null_mut()) +} diff --git a/ulib/axlibc/src/lib.rs b/ulib/axlibc/src/lib.rs index b7d718580..6aacf93f7 100644 --- a/ulib/axlibc/src/lib.rs +++ b/ulib/axlibc/src/lib.rs @@ -43,7 +43,8 @@ #[cfg(feature = "alloc")] extern crate alloc; - +#[cfg(feature = "alloc")] +mod env; #[path = "."] mod ctypes { #[rustfmt::skip] @@ -83,6 +84,7 @@ mod mktime; mod rand; mod resource; mod setjmp; +mod string; mod sys; mod time; mod unistd; @@ -96,18 +98,20 @@ pub use self::mktime::mktime; pub use self::rand::{rand, random, srand}; pub use self::resource::{getrlimit, setrlimit}; pub use self::setjmp::{longjmp, setjmp}; +pub use self::string::{strlen, strnlen}; pub use self::sys::sysconf; pub use self::time::{clock_gettime, nanosleep}; pub use self::unistd::{abort, exit, getpid}; +#[cfg(feature = "alloc")] +pub use self::env::{getenv, setenv, unsetenv}; +#[cfg(feature = "fd")] +pub use self::fd_ops::{ax_fcntl, close, dup, dup2, dup3}; #[cfg(feature = "alloc")] pub use self::malloc::{free, malloc}; #[cfg(feature = "alloc")] pub use self::strftime::strftime; -#[cfg(feature = "fd")] -pub use self::fd_ops::{ax_fcntl, close, dup, dup2, dup3}; - #[cfg(feature = "fs")] pub use self::fs::{ax_open, fstat, getcwd, lseek, lstat, rename, stat}; diff --git a/ulib/axlibc/src/string.rs b/ulib/axlibc/src/string.rs new file mode 100644 index 000000000..28d479736 --- /dev/null +++ b/ulib/axlibc/src/string.rs @@ -0,0 +1,20 @@ +use crate::ctypes; +use core::ffi::c_char; +/// calculate the length of a string, excluding the terminating null byte +#[no_mangle] +pub unsafe extern "C" fn strlen(s: *const c_char) -> ctypes::size_t { + strnlen(s, ctypes::size_t::MAX) +} + +/// calculate the length of a string like strlen, but at most maxlen. +#[no_mangle] +pub unsafe extern "C" fn strnlen(s: *const c_char, size: ctypes::size_t) -> ctypes::size_t { + let mut i = 0; + while i < size { + if *s.add(i) == 0 { + break; + } + i += 1; + } + i +}