230 lines
6.5 KiB
Rust

#![allow(non_upper_case_globals, static_mut_refs)]
use core::{
ffi::{c_char, CStr},
ptr::{null_mut, NonNull},
slice::memchr,
};
use alloc::{borrow::ToOwned, ffi::CString, vec::Vec};
use yggdrasil_rt::process::{thread_local, ProgramArgumentInner};
// use yggdrasil_rt::process::ProgramArgumentInner;
use crate::{
allocator::{c_alloc, c_free}, error::EResult, headers::{
errno,
string::{mem::memcpy, str::strlen},
}, thread::{self, tls}, 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<c_char>) -> 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<NonNull<c_char>> {
let entry = c_alloc(key.len() + value.len() + 2, 1, false)?.cast::<c_char>();
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<NonNull<c_char>> {
let entry = c_alloc(raw.len() + 1, 1, false)?.cast::<c_char>();
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<c_char>) -> 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(())
}
pub unsafe fn get_env(name: &[u8]) -> EResult<Option<NonNull<c_char>>> {
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<c_char>, 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<bool> {
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<c_char>) -> 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)
}
unsafe fn setup_env<'a>(envs: impl Iterator<Item = &'a CStr>) {
for env in envs {
// Make a malloc()ed, NULL-terminated string
let entry = entry_from_raw(env.to_bytes()).expect("Couldn't allocate env variable");
shadow.push(entry.as_ptr());
}
shadow.push(null_mut());
environ = shadow.as_mut_ptr();
}
pub fn handle_kernel_argument(arg: usize) -> Vec<CString> {
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 = arg.to_owned();
args.push(c_arg);
}
unsafe { setup_env(arg.envs()) };
// Setup TLS from argument
thread::init_main_thread(arg);
args
}