#![allow(clippy::fn_address_comparisons)]
use super::VariableError;
use crate::ffi::variables as ffi;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::mem::MaybeUninit;
use std::os::raw::c_char;
use std::sync::atomic::AtomicBool;
use std::sync::{atomic::Ordering::SeqCst, Mutex, MutexGuard};
use std::{mem, panic};
pub trait DynamicVariable {
fn get(&mut self) -> Option<CString>;
fn set(&mut self, value: &CStr);
}
pub(super) fn bind_dynvar(
name: &str,
dynvar: Box<dyn DynamicVariable>,
) -> Result<(), VariableError> {
let name = CString::new(name).map_err(|_| VariableError::InvalidName)?;
unsafe {
let mut shell_var = ffi::bind_variable(name.as_ptr(), std::ptr::null(), 0);
if shell_var.is_null() {
return Err(VariableError::InvalidName);
}
(*shell_var).dynamic_value = read_var;
(*shell_var).assign_func = assign_var;
}
global_state().insert(name, dynvar);
Ok(())
}
static STATE_INIT: AtomicBool = AtomicBool::new(false);
type State = HashMap<CString, Box<dyn DynamicVariable>>;
fn global_state() -> MutexGuard<'static, State> {
static mut STATE: MaybeUninit<Mutex<State>> = MaybeUninit::uninit();
if !STATE_INIT.fetch_or(true, SeqCst) {
unsafe {
STATE = MaybeUninit::new(Mutex::new(State::default()));
libc::atexit(remove_all_dynvars);
}
}
match unsafe { (*STATE.as_ptr()).lock() } {
Ok(l) => l,
Err(e) => e.into_inner(),
}
}
extern "C" fn remove_all_dynvars() {
let state: State = mem::take(&mut *global_state());
STATE_INIT.store(false, SeqCst);
for (varname, _) in state {
unsafe {
let shell_var = ffi::find_variable(varname.as_ptr());
if !shell_var.is_null() && (*shell_var).dynamic_value == read_var {
ffi::unbind_variable(varname.as_ptr());
}
}
}
}
unsafe extern "C" fn read_var(shell_var: *mut ffi::ShellVar) -> *const ffi::ShellVar {
if !STATE_INIT.load(SeqCst) {
return shell_var;
}
let result = panic::catch_unwind(|| {
global_state()
.get_mut(CStr::from_ptr((*shell_var).name))
.map(|dynvar| dynvar.get())
});
let new_value = match result {
Ok(Some(v)) => v,
_ => {
crate::ffi::internal_error(b"dynamic variable unavailable\0".as_ptr().cast());
return shell_var;
}
};
libc::free((*shell_var).value.cast());
(*shell_var).value = match new_value {
Some(v) => v.into_raw(),
None => libc::calloc(1, 1).cast(),
};
shell_var
}
unsafe extern "C" fn assign_var(
shell_var: *mut ffi::ShellVar,
value: *const c_char,
_: libc::intmax_t,
_: *const c_char,
) -> *const ffi::ShellVar {
if value.is_null() {
return shell_var;
}
let result = panic::catch_unwind(|| {
global_state()
.get_mut(CStr::from_ptr((*shell_var).name))
.map(|dynvar| dynvar.set(CStr::from_ptr(value)))
});
if result.is_err() {
crate::ffi::internal_error(b"dynamic variable unavailable\0".as_ptr().cast());
}
shell_var
}