From c65b06fadb27eaa4cb3b637a47ce51ef94604c3c Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Thu, 14 Nov 2024 12:20:15 +0200 Subject: [PATCH] libc: setenv/putenv/unsetenv/getenv --- test.c | 129 +++++----- userspace/lib/ygglibc/include/bits/unistd.h | 2 + userspace/lib/ygglibc/src/args.rs | 23 -- userspace/lib/ygglibc/src/env.rs | 230 ++++++++++++++++++ .../lib/ygglibc/src/headers/signal/mod.rs | 15 +- .../lib/ygglibc/src/headers/stdlib/process.rs | 39 ++- .../lib/ygglibc/src/headers/string/str.rs | 4 +- .../lib/ygglibc/src/headers/unistd/io.rs | 2 +- userspace/lib/ygglibc/src/lib.rs | 15 +- 9 files changed, 339 insertions(+), 120 deletions(-) delete mode 100644 userspace/lib/ygglibc/src/args.rs create mode 100644 userspace/lib/ygglibc/src/env.rs diff --git a/test.c b/test.c index f82ba37b..c3cfe61e 100644 --- a/test.c +++ b/test.c @@ -4,75 +4,70 @@ #include #include #include - -static void test1(void) { -#define N 128 - void *array[N] = {NULL}; - - for (size_t i = 0; i < N; ++i) { - array[i] = malloc(i * 2 + 1); - memset(array[i], 0xA3, i * 2 + 1); - } - - for (size_t i = 0; i < N; i += 2) { - free(array[i]); - array[i] = NULL; - } - - for (size_t i = 1; i < N; i += 2) { - for (size_t j = 0; j < i * 2 + 1; ++j) { - assert(((const unsigned char *) array[i])[j] == 0xA3); - } - } - - for (size_t i = 0; i < N; i += 2) { - array[i] = malloc(i * 3 + 3); - memset(array[i], 0x7D, i * 3 + 3); - } - - for (size_t i = 1; i < N; i += 2) { - for (size_t j = 0; j < i * 2 + 1; ++j) { - assert(((const unsigned char *) array[i])[j] == 0xA3); - } - } - for (size_t i = 0; i < N; i += 2) { - for (size_t j = 0; j < i * 3 + 3; ++j) { - assert(((const unsigned char *) array[i])[j] == 0x7D); - } - } - - for (size_t i = 0; i < N; ++i) { - free(array[i]); - } -#undef N -} - -static void test2(void) { -#define N 64 - void *ptr0 = NULL; - void *ptr1 = NULL; - - for (size_t i = 0; i < N; ++i) { - printf("Round #%zu\n", i); - // 2, 4, 6, ... - ptr0 = realloc(ptr0, (i + 1) * 2); - for (size_t j = 0; j < i * 2; ++j) { - assert(((const unsigned char *) ptr0)[j] == 0x12); - } - memset(ptr0, 0x12, (i + 1) * 2); - // 3, 6, 9, ... - ptr1 = realloc(ptr1, (i + 1) * 3); - for (size_t j = 0; j < i * 3; ++j) { - assert(((const unsigned char *) ptr1)[j] == 0x34); - } - memset(ptr1, 0x34, (i + 1) * 3); - } -#undef N -} +#include int main(int argc, const char **argv) { - test1(); - test2(); + char *val; + + assert(environ != NULL); + assert(*environ == NULL); + + setenv("key1", "value1", 0); + assert(environ != NULL); + assert(!strcmp(*environ, "key1=value1")); + assert(*(environ + 1) == NULL); + + setenv("key1", "value2", 0); + assert(environ != NULL); + assert(!strcmp(*environ, "key1=value1")); + assert(*(environ + 1) == NULL); + + setenv("key1", "value3", 1); + assert(environ != NULL); + assert(!strcmp(*environ, "key1=value3")); + assert(*(environ + 1) == NULL); + + setenv("key1", "longer-value", 1); + assert(environ != NULL); + assert(!strcmp(*environ, "key1=longer-value")); + assert(*(environ + 1) == NULL); + + setenv("key2", "other-value", 0); + assert(environ != NULL); + assert(!strcmp(environ[0], "key1=longer-value")); + assert(!strcmp(environ[1], "key2=other-value")); + assert(environ[2] == NULL); + + assert(!strcmp(getenv("key1"), "longer-value")); + assert(!strcmp(getenv("key2"), "other-value")); + assert(getenv("key3") == NULL); + + unsetenv("key2"); + + assert(!strcmp(getenv("key1"), "longer-value")); + assert(getenv("key2") == NULL); + assert(getenv("key3") == NULL); + + environ[0] = strdup("key2=???"); + + assert(!strcmp(getenv("key2"), "???")); + + char *value = strdup("key4=!!!"); + putenv(value); + + assert(!strcmp(getenv("key2"), "???")); + assert(!strcmp(getenv("key4"), "!!!")); + + value[6] = '@'; + + assert(!strcmp(getenv("key2"), "???")); + assert(!strcmp(getenv("key4"), "!@!")); + + unsetenv("key4"); + unsetenv("key2"); + + assert(environ != NULL); + assert(*environ == NULL); return 0; } diff --git a/userspace/lib/ygglibc/include/bits/unistd.h b/userspace/lib/ygglibc/include/bits/unistd.h index 9066c8d0..37674ac5 100644 --- a/userspace/lib/ygglibc/include/bits/unistd.h +++ b/userspace/lib/ygglibc/include/bits/unistd.h @@ -5,4 +5,6 @@ int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ...); +extern char **environ; + #endif diff --git a/userspace/lib/ygglibc/src/args.rs b/userspace/lib/ygglibc/src/args.rs deleted file mode 100644 index db5a2a89..00000000 --- a/userspace/lib/ygglibc/src/args.rs +++ /dev/null @@ -1,23 +0,0 @@ -use alloc::{ffi::CString, vec::Vec}; -use yggdrasil_rt::process::ProgramArgumentInner; - -use crate::util::PointerExt; - -pub fn handle_kernel_argument(arg: usize) -> (Vec, Vec) { - let arg_ptr: *const ProgramArgumentInner = core::ptr::with_exposed_provenance(arg); - let arg = unsafe { arg_ptr.ensure() }; - - let mut args = Vec::new(); - let mut envs = Vec::new(); - - for &arg in arg.args { - let c_arg = CString::new(arg).unwrap(); - args.push(c_arg); - } - for &env in arg.env { - let c_env = CString::new(env).unwrap(); - envs.push(c_env); - } - - (args, envs) -} diff --git a/userspace/lib/ygglibc/src/env.rs b/userspace/lib/ygglibc/src/env.rs new file mode 100644 index 00000000..c5e8380c --- /dev/null +++ b/userspace/lib/ygglibc/src/env.rs @@ -0,0 +1,230 @@ +#![allow(non_upper_case_globals)] + +use core::{ + ffi::{c_char, CStr}, + ptr::{null_mut, NonNull}, + slice::memchr, +}; + +use alloc::{ffi::CString, vec::Vec}; +use yggdrasil_rt::process::ProgramArgumentInner; + +use crate::{ + allocator::{c_alloc, c_free}, + error::EResult, + headers::{ + errno, + string::{ + mem::memcpy, + str::{strcpy, strlen}, + }, + }, + util::PointerExt, +}; + +#[no_mangle] +pub static mut environ: *mut *mut c_char = null_mut(); +static mut shadow: Vec<*mut c_char> = Vec::new(); + +unsafe fn get_key(var: NonNull) -> Option<&'static [u8]> { + let cstr = CStr::from_ptr(var.as_ptr()).to_bytes(); + let eq_pos = memchr::memchr(b'=', cstr)?; + Some(&cstr[..eq_pos]) +} + +unsafe fn entry_from_key_value(key: &[u8], value: &[u8]) -> EResult> { + let entry = c_alloc(key.len() + value.len() + 2, 1, false)?.cast::(); + memcpy(entry.as_ptr().cast(), key.as_ptr().cast(), key.len()); + memcpy( + entry.add(key.len() + 1).as_ptr().cast(), + value.as_ptr().cast(), + value.len(), + ); + entry.add(key.len()).write(b'=' as _); + entry.add(key.len() + value.len() + 1).write(0); + EResult::Ok(entry) +} + +unsafe fn entry_from_raw(raw: &[u8]) -> EResult> { + let entry = c_alloc(raw.len() + 1, 1, false)?.cast::(); + memcpy(entry.as_ptr().cast(), raw.as_ptr().cast(), raw.len()); + entry.add(raw.len()).write(0); + EResult::Ok(entry) +} + +unsafe fn reclaim_env() -> EResult<()> { + if environ == shadow.as_mut_ptr() { + // environ already points to ygglibc-owned list + return EResult::Ok(()); + } + + todo!() +} + +unsafe fn push_env(str: NonNull) -> EResult<()> { + reclaim_env(); + + if shadow.try_reserve(1).is_err() { + return EResult::Err(errno::ENOMEM); + } + + let entry = shadow.last_mut().unwrap(); + assert!(entry.is_null()); + *entry = str.as_ptr(); + + // Won't fail + shadow.push(null_mut()); + + // Fixup the pointer, shadow might've got reallocated + environ = shadow.as_mut_ptr(); + + EResult::Ok(()) +} + +unsafe fn setup_env(envs: impl IntoIterator) { + for &env in envs { + // Make a malloc()ed, NULL-terminated string + let entry = entry_from_raw(env.as_bytes()).expect("Couldn't allocate env variable"); + shadow.push(entry.as_ptr()); + } + shadow.push(null_mut()); + environ = shadow.as_mut_ptr(); +} + +pub unsafe fn get_env(name: &[u8]) -> EResult>> { + if name.is_empty() || name.contains(&b'=') { + return EResult::Err(errno::EINVAL); + } + + let Some(mut iter) = NonNull::new(environ) else { + // User set environ to NULL + return EResult::Ok(None); + }; + + while let Some(entry) = NonNull::new(iter.read()) { + let Some(key) = get_key(entry) else { continue }; + + if key == name { + // Safe: key != None means there is at least a '=' after whatever "key" points at + let value = entry.add(key.len() + 1); + return EResult::Ok(Some(value)); + } + + iter = iter.add(1); + } + + EResult::Ok(None) +} + +pub unsafe fn set_env(name: &[u8], value: NonNull, overwrite: bool) -> EResult<()> { + if name.is_empty() || name.contains(&b'=') { + return EResult::Err(errno::EINVAL); + } + + let value = CStr::from_ptr(value.as_ptr()).to_bytes(); + + if let Some(mut iter) = NonNull::new(environ) { + // Try to locate the key=value pair in the existing env + while let Some(entry) = NonNull::new(iter.read()) { + let Some(key) = get_key(entry) else { continue }; + + if key == name { + if !overwrite { + return EResult::Ok(()); + } + + // Safe: key != None means there is at least a '=' after whatever "key" points at + let old_value = entry.add(key.len() + 1); + + if strlen(old_value.as_ptr()) >= value.len() { + // Old allocation can contain `value` + memcpy( + old_value.as_ptr().cast(), + value.as_ptr().cast(), + value.len(), + ); + old_value.add(value.len()).write(0); + } else { + // Need to allocate a new entry + let new_entry = entry_from_key_value(name, value)?; + c_free(entry.cast()); + iter.write(new_entry.as_ptr()); + } + + return EResult::Ok(()); + } + + iter = iter.add(1); + } + } + + // Key not found or environ == NULL, need to push a new entry + let entry = entry_from_key_value(name, value)?; + push_env(entry) +} + +pub unsafe fn remove_env(name: &[u8]) -> EResult { + if name.is_empty() || name.contains(&b'=') { + return EResult::Err(errno::EINVAL); + } + + reclaim_env(); + for i in 0..shadow.len() { + let Some(entry) = NonNull::new(shadow[i]) else { + continue; + }; + let Some(key) = get_key(entry) else { continue }; + + if key == name { + // Pop entry at [i] + shadow.remove(i); + environ = shadow.as_mut_ptr(); + c_free(entry.cast()); + return EResult::Ok(true); + } + } + + EResult::Ok(false) +} + +pub unsafe fn put_env(str: NonNull) -> EResult<()> { + let bytes = CStr::from_ptr(str.as_ptr()).to_bytes(); + if bytes.is_empty() || !bytes.contains(&b'=') { + return EResult::Err(errno::EINVAL); + } + let name = get_key(str).unwrap(); + + if let Some(mut iter) = NonNull::new(environ) { + while let Some(entry) = NonNull::new(iter.read()) { + let Some(key) = get_key(entry) else { continue }; + + if key == name { + // Replace the entry + iter.write(str.as_ptr()); + c_free(entry.cast()); + return EResult::Ok(()); + } + + iter = iter.add(1); + } + } + + // Push a new entry + push_env(str) +} + +pub fn handle_kernel_argument(arg: usize) -> Vec { + let arg_ptr: *const ProgramArgumentInner = core::ptr::with_exposed_provenance(arg); + let arg = unsafe { arg_ptr.ensure() }; + + let mut args = Vec::new(); + + for &arg in arg.args { + let c_arg = CString::new(arg).unwrap(); + args.push(c_arg); + } + + unsafe { setup_env(arg.env) }; + + args +} diff --git a/userspace/lib/ygglibc/src/headers/signal/mod.rs b/userspace/lib/ygglibc/src/headers/signal/mod.rs index 44fbab7f..325b6404 100644 --- a/userspace/lib/ygglibc/src/headers/signal/mod.rs +++ b/userspace/lib/ygglibc/src/headers/signal/mod.rs @@ -2,6 +2,8 @@ use core::ffi::{c_char, c_int, c_long, c_void}; use yggdrasil_rt::process::Signal; +use crate::{error::CIntZeroResult, util::PointerExt}; + use super::{ sys_time::__ygg_timespec_t, sys_types::{pid_t, uid_t}, @@ -170,11 +172,12 @@ unsafe extern "C" fn raise(_signum: c_int) -> c_int { #[no_mangle] unsafe extern "C" fn sigaction( - _signum: c_int, + signum: c_int, _new: *const sigaction, _old: *mut sigaction, -) -> c_int { - todo!() +) -> CIntZeroResult { + yggdrasil_rt::debug_trace!("TODO: sigaction({})", signum); + CIntZeroResult::SUCCESS } #[no_mangle] @@ -194,8 +197,10 @@ unsafe extern "C" fn sigdelset(_mask: *mut sigset_t, _signum: c_int) -> c_int { } #[no_mangle] -unsafe extern "C" fn sigemptyset(_mask: *mut sigset_t) -> c_int { - todo!() +unsafe extern "C" fn sigemptyset(mask: *mut sigset_t) -> CIntZeroResult { + let mask = mask.ensure_mut(); + *mask = 0; + CIntZeroResult::SUCCESS } #[no_mangle] diff --git a/userspace/lib/ygglibc/src/headers/stdlib/process.rs b/userspace/lib/ygglibc/src/headers/stdlib/process.rs index 0000ac73..2efeec5f 100644 --- a/userspace/lib/ygglibc/src/headers/stdlib/process.rs +++ b/userspace/lib/ygglibc/src/headers/stdlib/process.rs @@ -1,8 +1,7 @@ -use core::ffi::{c_char, c_int}; +use core::{ffi::{c_char, c_int}, ptr::NonNull}; use crate::{ - error::{CIntZeroResult, CPtrResult}, - process, + env, error::{CIntZeroResult, CPtrResult, CResult, OptionExt}, headers::errno, process, util::{PointerExt, PointerStrExt} }; #[no_mangle] @@ -26,22 +25,34 @@ unsafe extern "C" fn exit(status: c_int) -> ! { } #[no_mangle] -unsafe extern "C" fn getenv(_name: *const c_char) -> CPtrResult { - todo!() +unsafe extern "C" fn getenv(name: *const c_char) -> CPtrResult { + let name = name.ensure_cstr().to_bytes(); + + if let Some(value) = env::get_env(name)? { + CPtrResult::success(value) + } else { + CPtrResult::ERROR + } } #[no_mangle] -unsafe extern "C" fn putenv(_value: *const c_char) -> CPtrResult { - todo!() +unsafe extern "C" fn putenv(value: *mut c_char) -> CIntZeroResult { + let value = NonNull::new(value).e_ok_or(errno::EINVAL)?; + env::put_env(value)?; + CIntZeroResult::SUCCESS } #[no_mangle] unsafe extern "C" fn setenv( - _name: *const c_char, - _value: *const c_char, - _overwrite: c_int, + name: *const c_char, + value: *const c_char, + overwrite: c_int, ) -> CIntZeroResult { - todo!() + let name = name.ensure_cstr().to_bytes(); + let value = NonNull::new(value.cast_mut()).e_ok_or(errno::EINVAL)?; + let overwrite = overwrite != 0; + env::set_env(name, value, overwrite)?; + CIntZeroResult::SUCCESS } // Deprecated @@ -56,6 +67,8 @@ unsafe extern "C" fn system(_command: *const c_char) -> CIntZeroResult { } #[no_mangle] -unsafe extern "C" fn unsetenv(_name: *const c_char) -> c_int { - todo!() +unsafe extern "C" fn unsetenv(name: *const c_char) -> CIntZeroResult { + let name = name.ensure_cstr().to_bytes(); + env::remove_env(name)?; + CIntZeroResult::SUCCESS } diff --git a/userspace/lib/ygglibc/src/headers/string/str.rs b/userspace/lib/ygglibc/src/headers/string/str.rs index b81b24af..d9d739e7 100644 --- a/userspace/lib/ygglibc/src/headers/string/str.rs +++ b/userspace/lib/ygglibc/src/headers/string/str.rs @@ -70,7 +70,7 @@ unsafe extern "C" fn strcmp(a: *const c_char, b: *const c_char) -> c_int { } #[no_mangle] -unsafe extern "C" fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char { +pub unsafe extern "C" fn strcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char { stpcpy(dst, src); dst } @@ -121,7 +121,7 @@ unsafe extern "C" fn strerror_r(e: c_int, buf: *mut c_char, n: usize) -> *mut c_ } #[no_mangle] -unsafe extern "C" fn strlen(s: *const c_char) -> usize { +pub unsafe extern "C" fn strlen(s: *const c_char) -> usize { compiler_builtins::mem::strlen(s.cast()) } diff --git a/userspace/lib/ygglibc/src/headers/unistd/io.rs b/userspace/lib/ygglibc/src/headers/unistd/io.rs index 9ffda38e..1abecdc4 100644 --- a/userspace/lib/ygglibc/src/headers/unistd/io.rs +++ b/userspace/lib/ygglibc/src/headers/unistd/io.rs @@ -45,7 +45,7 @@ unsafe extern "C" fn fsync(fd: c_int) -> CIntZeroResult { #[no_mangle] unsafe extern "C" fn isatty(fd: c_int) -> c_int { debug_trace!("TODO: isatty()"); - 0 + 1 } #[no_mangle] diff --git a/userspace/lib/ygglibc/src/lib.rs b/userspace/lib/ygglibc/src/lib.rs index 4c29f6d8..e1d001a9 100644 --- a/userspace/lib/ygglibc/src/lib.rs +++ b/userspace/lib/ygglibc/src/lib.rs @@ -14,6 +14,8 @@ #![allow(internal_features)] #![cfg_attr(not(test), no_std)] +use env::environ; + extern crate alloc; extern crate compiler_builtins; @@ -22,15 +24,15 @@ extern crate compiler_builtins; extern crate std; mod allocator; -mod args; +mod env; mod error; mod io; mod panic; mod process; +mod ssp; mod sync; mod types; mod util; -mod ssp; pub mod headers; @@ -51,23 +53,18 @@ unsafe extern "C" fn __ygglibc_entry(arg: usize) { io::init(); // Setup args - let (args, envs) = args::handle_kernel_argument(arg); + let args = env::handle_kernel_argument(arg); let mut c_args = Vec::new(); - let mut c_envs = Vec::new(); for arg in args.iter() { c_args.push(arg.as_ptr()); } - for env in envs.iter() { - c_envs.push(env.as_ptr()); - } c_args.push(null()); - c_envs.push(null()); let status = main( args.len().try_into().unwrap(), c_args.as_ptr(), - c_envs.as_ptr(), + environ.cast(), ); process::c_exit(status)