libc: setenv/putenv/unsetenv/getenv

This commit is contained in:
Mark Poliakov 2024-11-14 12:20:15 +02:00
parent e2ef677b4a
commit c65b06fadb
9 changed files with 339 additions and 120 deletions

129
test.c
View File

@ -4,75 +4,70 @@
#include <setjmp.h> #include <setjmp.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
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
}
int main(int argc, const char **argv) { int main(int argc, const char **argv) {
test1(); char *val;
test2();
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; return 0;
} }

View File

@ -5,4 +5,6 @@ int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...); int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...); int execle(const char *path, const char *arg, ...);
extern char **environ;
#endif #endif

View File

@ -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<CString>, Vec<CString>) {
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)
}

View File

@ -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<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(())
}
unsafe fn setup_env(envs: impl IntoIterator<Item = &'static &'static str>) {
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<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)
}
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 = CString::new(arg).unwrap();
args.push(c_arg);
}
unsafe { setup_env(arg.env) };
args
}

View File

@ -2,6 +2,8 @@ use core::ffi::{c_char, c_int, c_long, c_void};
use yggdrasil_rt::process::Signal; use yggdrasil_rt::process::Signal;
use crate::{error::CIntZeroResult, util::PointerExt};
use super::{ use super::{
sys_time::__ygg_timespec_t, sys_time::__ygg_timespec_t,
sys_types::{pid_t, uid_t}, sys_types::{pid_t, uid_t},
@ -170,11 +172,12 @@ unsafe extern "C" fn raise(_signum: c_int) -> c_int {
#[no_mangle] #[no_mangle]
unsafe extern "C" fn sigaction( unsafe extern "C" fn sigaction(
_signum: c_int, signum: c_int,
_new: *const sigaction, _new: *const sigaction,
_old: *mut sigaction, _old: *mut sigaction,
) -> c_int { ) -> CIntZeroResult {
todo!() yggdrasil_rt::debug_trace!("TODO: sigaction({})", signum);
CIntZeroResult::SUCCESS
} }
#[no_mangle] #[no_mangle]
@ -194,8 +197,10 @@ unsafe extern "C" fn sigdelset(_mask: *mut sigset_t, _signum: c_int) -> c_int {
} }
#[no_mangle] #[no_mangle]
unsafe extern "C" fn sigemptyset(_mask: *mut sigset_t) -> c_int { unsafe extern "C" fn sigemptyset(mask: *mut sigset_t) -> CIntZeroResult {
todo!() let mask = mask.ensure_mut();
*mask = 0;
CIntZeroResult::SUCCESS
} }
#[no_mangle] #[no_mangle]

View File

@ -1,8 +1,7 @@
use core::ffi::{c_char, c_int}; use core::{ffi::{c_char, c_int}, ptr::NonNull};
use crate::{ use crate::{
error::{CIntZeroResult, CPtrResult}, env, error::{CIntZeroResult, CPtrResult, CResult, OptionExt}, headers::errno, process, util::{PointerExt, PointerStrExt}
process,
}; };
#[no_mangle] #[no_mangle]
@ -26,22 +25,34 @@ unsafe extern "C" fn exit(status: c_int) -> ! {
} }
#[no_mangle] #[no_mangle]
unsafe extern "C" fn getenv(_name: *const c_char) -> CPtrResult<c_char> { unsafe extern "C" fn getenv(name: *const c_char) -> CPtrResult<c_char> {
todo!() let name = name.ensure_cstr().to_bytes();
if let Some(value) = env::get_env(name)? {
CPtrResult::success(value)
} else {
CPtrResult::ERROR
}
} }
#[no_mangle] #[no_mangle]
unsafe extern "C" fn putenv(_value: *const c_char) -> CPtrResult<c_char> { unsafe extern "C" fn putenv(value: *mut c_char) -> CIntZeroResult {
todo!() let value = NonNull::new(value).e_ok_or(errno::EINVAL)?;
env::put_env(value)?;
CIntZeroResult::SUCCESS
} }
#[no_mangle] #[no_mangle]
unsafe extern "C" fn setenv( unsafe extern "C" fn setenv(
_name: *const c_char, name: *const c_char,
_value: *const c_char, value: *const c_char,
_overwrite: c_int, overwrite: c_int,
) -> CIntZeroResult { ) -> 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 // Deprecated
@ -56,6 +67,8 @@ unsafe extern "C" fn system(_command: *const c_char) -> CIntZeroResult {
} }
#[no_mangle] #[no_mangle]
unsafe extern "C" fn unsetenv(_name: *const c_char) -> c_int { unsafe extern "C" fn unsetenv(name: *const c_char) -> CIntZeroResult {
todo!() let name = name.ensure_cstr().to_bytes();
env::remove_env(name)?;
CIntZeroResult::SUCCESS
} }

View File

@ -70,7 +70,7 @@ unsafe extern "C" fn strcmp(a: *const c_char, b: *const c_char) -> c_int {
} }
#[no_mangle] #[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); stpcpy(dst, src);
dst dst
} }
@ -121,7 +121,7 @@ unsafe extern "C" fn strerror_r(e: c_int, buf: *mut c_char, n: usize) -> *mut c_
} }
#[no_mangle] #[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()) compiler_builtins::mem::strlen(s.cast())
} }

View File

@ -45,7 +45,7 @@ unsafe extern "C" fn fsync(fd: c_int) -> CIntZeroResult {
#[no_mangle] #[no_mangle]
unsafe extern "C" fn isatty(fd: c_int) -> c_int { unsafe extern "C" fn isatty(fd: c_int) -> c_int {
debug_trace!("TODO: isatty()"); debug_trace!("TODO: isatty()");
0 1
} }
#[no_mangle] #[no_mangle]

View File

@ -14,6 +14,8 @@
#![allow(internal_features)] #![allow(internal_features)]
#![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_std)]
use env::environ;
extern crate alloc; extern crate alloc;
extern crate compiler_builtins; extern crate compiler_builtins;
@ -22,15 +24,15 @@ extern crate compiler_builtins;
extern crate std; extern crate std;
mod allocator; mod allocator;
mod args; mod env;
mod error; mod error;
mod io; mod io;
mod panic; mod panic;
mod process; mod process;
mod ssp;
mod sync; mod sync;
mod types; mod types;
mod util; mod util;
mod ssp;
pub mod headers; pub mod headers;
@ -51,23 +53,18 @@ unsafe extern "C" fn __ygglibc_entry(arg: usize) {
io::init(); io::init();
// Setup args // 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_args = Vec::new();
let mut c_envs = Vec::new();
for arg in args.iter() { for arg in args.iter() {
c_args.push(arg.as_ptr()); c_args.push(arg.as_ptr());
} }
for env in envs.iter() {
c_envs.push(env.as_ptr());
}
c_args.push(null()); c_args.push(null());
c_envs.push(null());
let status = main( let status = main(
args.len().try_into().unwrap(), args.len().try_into().unwrap(),
c_args.as_ptr(), c_args.as_ptr(),
c_envs.as_ptr(), environ.cast(),
); );
process::c_exit(status) process::c_exit(status)