use crate::*;
use crate::options_::*;
pub type environ = rb_head<environ_entry>;
RB_GENERATE!(environ, environ_entry, entry, discr_entry, environ_cmp);
pub fn environ_cmp(envent1: &environ_entry, envent2: &environ_entry) -> std::cmp::Ordering {
unsafe {
i32_to_ordering(libc::strcmp(
transmute_ptr(envent1.name),
transmute_ptr(envent2.name),
))
}
}
pub fn environ_create() -> NonNull<environ> {
unsafe {
let env = xcalloc1::<environ>();
rb_init(env);
NonNull::new_unchecked(env)
}
}
pub unsafe fn environ_free(env: *mut environ) {
unsafe {
for envent in rb_foreach(env).map(NonNull::as_ptr) {
rb_remove(env, envent);
free_(transmute_ptr((*envent).name));
free_(transmute_ptr((*envent).value));
free_(envent);
}
free_(env);
}
}
pub unsafe fn environ_first(env: *mut environ) -> *mut environ_entry {
unsafe { rb_min(env) }
}
pub unsafe fn environ_next(envent: *mut environ_entry) -> *mut environ_entry {
unsafe { rb_next(envent) }
}
pub unsafe fn environ_copy(srcenv: *mut environ, dstenv: *mut environ) {
unsafe {
for envent in rb_foreach(srcenv).map(NonNull::as_ptr) {
if let Some(value) = (*envent).value {
environ_set!(
dstenv,
(*envent).name.unwrap().as_ptr(),
(*envent).flags,
"{}",
_s(value.as_ptr()),
);
} else {
environ_clear(dstenv, transmute_ptr((*envent).name));
}
}
}
}
pub unsafe fn environ_find(env: *mut environ, name: *const u8) -> *mut environ_entry {
let mut envent: MaybeUninit<environ_entry> = MaybeUninit::uninit();
let envent = envent.as_mut_ptr();
unsafe {
(*envent).name = NonNull::new(name.cast_mut());
}
unsafe { rb_find(env, envent) }
}
macro_rules! environ_set {
($env:expr, $name:expr, $flags:expr, $fmt:literal $(, $args:expr)* $(,)?) => {
crate::environ_::environ_set_($env, $name, $flags, format_args!($fmt $(, $args)*))
};
}
pub(crate) use environ_set;
pub unsafe fn environ_set_(
env: *mut environ,
name: *const u8,
flags: environ_flags,
args: std::fmt::Arguments,
) {
unsafe {
let mut envent = environ_find(env, name);
let mut s = args.to_string();
s.push('\0');
let s = NonNull::new(s.leak().as_mut_ptr().cast());
if !envent.is_null() {
(*envent).flags = flags;
free_(transmute_ptr((*envent).value));
(*envent).value = s;
} else {
envent = Box::leak(Box::new(environ_entry {
name : Some(xstrdup(name).cast()),
value: s,
flags,
entry: rb_entry::default(),
}));
rb_insert(env, envent);
}
}
}
pub unsafe fn environ_clear(env: *mut environ, name: *const u8) {
unsafe {
let mut envent = environ_find(env, name);
if !envent.is_null() {
free_(transmute_ptr((*envent).value));
(*envent).value = None;
} else {
envent = Box::leak(Box::new(environ_entry {
name : Some(xstrdup(name).cast()),
value: None,
flags: environ_flags::empty(),
entry: rb_entry::default(),
}));
rb_insert(env, envent);
}
}
}
pub unsafe fn environ_put(env: *mut environ, var: *const u8, flags: environ_flags) {
unsafe {
let mut value = libc::strchr(var, b'=' as c_int);
if value.is_null() {
return;
}
value = value.add(1);
let name: *mut u8 = xstrdup(var).cast().as_ptr();
*name.add(libc::strcspn(name, c!("="))) = b'\0';
environ_set!(env, name, flags, "{}", _s(value));
free_(name);
}
}
pub unsafe fn environ_unset(env: *mut environ, name: *const u8) {
unsafe {
let envent = environ_find(env, name);
if envent.is_null() {
return;
}
rb_remove(env, envent);
free_(transmute_ptr((*envent).name));
free_(transmute_ptr((*envent).value));
free_(envent);
}
}
pub unsafe fn environ_update(oo: *mut options, src: *mut environ, dst: *mut environ) {
unsafe {
let mut found;
let o = options_get(&mut *oo, "update-environment");
if o.is_null() {
return;
}
let mut a = options_array_first(o);
while !a.is_null() {
let ov = options_array_item_value(a);
found = false;
for envent in rb_foreach(src).map(NonNull::as_ptr) {
if libc::fnmatch((*ov).string, transmute_ptr((*envent).name), 0) == 0 {
environ_set!(
dst,
transmute_ptr((*envent).name),
environ_flags::empty(),
"{}",
_s(transmute_ptr((*envent).value)),
);
found = true;
}
}
if !found {
environ_clear(dst, (*ov).string);
}
a = options_array_next(a);
}
}
}
pub unsafe fn environ_push(env: *mut environ) {
unsafe {
environ = xcalloc_::<*mut u8>(1).as_ptr();
for envent in rb_foreach(env).map(NonNull::as_ptr) {
if (*envent).value.is_some()
&& *(*envent).name.unwrap().as_ptr() != b'\0'
&& !(*envent).flags.intersects(ENVIRON_HIDDEN)
{
std::env::set_var(
cstr_to_str(transmute_ptr((*envent).name)),
cstr_to_str(transmute_ptr((*envent).value)),
);
}
}
}
}
macro_rules! environ_log {
($env:expr, $fmt:literal $(, $args:expr)* $(,)?) => {
crate::environ_::environ_log_($env, format_args!($fmt $(, $args)*))
};
}
pub(crate) use environ_log;
pub unsafe fn environ_log_(env: *mut environ, args: std::fmt::Arguments) {
unsafe {
let prefix = args.to_string();
for envent in rb_foreach(env).map(NonNull::as_ptr) {
if (*envent).value.is_some() && *(*envent).name.unwrap().as_ptr() != b'\0' {
log_debug!(
"{}{}={}",
prefix,
_s(transmute_ptr((*envent).name)),
_s(transmute_ptr((*envent).value))
);
}
}
}
}
pub unsafe fn environ_for_session(s: *mut session, no_term: c_int) -> *mut environ {
let env: *mut environ = environ_create().as_ptr();
unsafe {
environ_copy(GLOBAL_ENVIRON, env);
if !s.is_null() {
environ_copy((*s).environ, env);
}
if no_term == 0 {
let value = options_get_string_(GLOBAL_OPTIONS, "default-terminal");
environ_set!(env, c!("TERM"), environ_flags::empty(), "{}", _s(value));
environ_set!(
env,
c!("TERM_PROGRAM"),
environ_flags::empty(),
"{}",
"tmux"
);
environ_set!(
env,
c!("TERM_PROGRAM_VERSION"),
environ_flags::empty(),
"{}",
getversion()
);
}
#[cfg(feature = "systemd")]
{
environ_clear(env, c!("LISTEN_PID"));
environ_clear(env, c!("LISTEN_FDS"));
environ_clear(env, c!("LISTEN_FDNAMES"));
}
let idx = if !s.is_null() { (*s).id as i32 } else { -1 };
environ_set!(
env,
c!("TMUX"),
environ_flags::empty(),
"{},{},{}",
_s(SOCKET_PATH),
std::process::id(),
idx,
);
env
}
}
#[cfg(test)]
mod tests {
use super::*;
unsafe fn make_entry(name: *const u8) -> environ_entry {
environ_entry {
name: Some(unsafe { xstrdup(name) }),
value: None,
flags: environ_flags::empty(),
entry: rb_entry::default(),
}
}
unsafe fn free_entry(e: &environ_entry) {
unsafe {
free_(transmute_ptr(e.name));
free_(transmute_ptr(e.value));
}
}
unsafe fn value_of(env: *mut environ, name: *const u8) -> Option<String> {
unsafe {
let e = environ_find(env, name);
if e.is_null() || (*e).value.is_none() {
None
} else {
Some(cstr_to_str(transmute_ptr((*e).value)).to_string())
}
}
}
unsafe fn names_in_order(env: *mut environ) -> Vec<String> {
unsafe {
let mut out = Vec::new();
let mut e = environ_first(env);
while !e.is_null() {
out.push(cstr_to_str(transmute_ptr((*e).name)).to_string());
e = environ_next(e);
}
out
}
}
#[test]
fn set_then_find_returns_value() {
unsafe {
let env = environ_create().as_ptr();
assert!(environ_find(env, crate::c!("FOO")).is_null());
environ_set!(env, crate::c!("FOO"), environ_flags::empty(), "{}", "bar");
let e = environ_find(env, crate::c!("FOO"));
assert!(!e.is_null());
assert_eq!(value_of(env, crate::c!("FOO")).as_deref(), Some("bar"));
environ_free(env);
}
}
#[test]
fn set_existing_name_overwrites_value() {
unsafe {
let env = environ_create().as_ptr();
environ_set!(env, crate::c!("FOO"), environ_flags::empty(), "{}", "one");
environ_set!(env, crate::c!("FOO"), environ_flags::empty(), "{}", "two");
assert_eq!(value_of(env, crate::c!("FOO")).as_deref(), Some("two"));
assert_eq!(names_in_order(env), vec!["FOO".to_string()]);
environ_free(env);
}
}
#[test]
fn put_parses_name_equals_value() {
unsafe {
let env = environ_create().as_ptr();
environ_put(env, crate::c!("PATH=/usr/bin"), environ_flags::empty());
assert_eq!(value_of(env, crate::c!("PATH")).as_deref(), Some("/usr/bin"));
environ_put(env, crate::c!("NOEQUALS"), environ_flags::empty());
assert!(environ_find(env, crate::c!("NOEQUALS")).is_null());
environ_free(env);
}
}
#[test]
fn next_iterates_in_sorted_order() {
unsafe {
let env = environ_create().as_ptr();
environ_set!(env, crate::c!("CHARLIE"), environ_flags::empty(), "{}", "3");
environ_set!(env, crate::c!("ALPHA"), environ_flags::empty(), "{}", "1");
environ_set!(env, crate::c!("BRAVO"), environ_flags::empty(), "{}", "2");
assert_eq!(
names_in_order(env),
vec!["ALPHA".to_string(), "BRAVO".to_string(), "CHARLIE".to_string()]
);
environ_free(env);
}
}
#[test]
fn clear_keeps_entry_but_nulls_value() {
unsafe {
let env = environ_create().as_ptr();
environ_set!(env, crate::c!("FOO"), environ_flags::empty(), "{}", "bar");
environ_clear(env, crate::c!("FOO"));
let e = environ_find(env, crate::c!("FOO"));
assert!(!e.is_null());
assert!((*e).value.is_none());
environ_free(env);
}
}
#[test]
fn unset_removes_entry() {
unsafe {
let env = environ_create().as_ptr();
environ_set!(env, crate::c!("FOO"), environ_flags::empty(), "{}", "bar");
assert!(!environ_find(env, crate::c!("FOO")).is_null());
environ_unset(env, crate::c!("FOO"));
assert!(environ_find(env, crate::c!("FOO")).is_null());
assert!(names_in_order(env).is_empty());
environ_free(env);
}
}
#[test]
fn copy_duplicates_entries() {
unsafe {
let src = environ_create().as_ptr();
let dst = environ_create().as_ptr();
environ_set!(src, crate::c!("FOO"), environ_flags::empty(), "{}", "bar");
environ_set!(src, crate::c!("BAZ"), environ_flags::empty(), "{}", "qux");
environ_copy(src, dst);
assert_eq!(value_of(dst, crate::c!("FOO")).as_deref(), Some("bar"));
assert_eq!(value_of(dst, crate::c!("BAZ")).as_deref(), Some("qux"));
environ_set!(dst, crate::c!("FOO"), environ_flags::empty(), "{}", "changed");
assert_eq!(value_of(src, crate::c!("FOO")).as_deref(), Some("bar"));
assert_eq!(value_of(dst, crate::c!("FOO")).as_deref(), Some("changed"));
environ_free(src);
environ_free(dst);
}
}
#[test]
fn cmp_orders_by_name() {
unsafe {
let a = make_entry(crate::c!("AAA"));
let b = make_entry(crate::c!("BBB"));
let a2 = make_entry(crate::c!("AAA"));
assert_eq!(environ_cmp(&a, &b), std::cmp::Ordering::Less);
assert_eq!(environ_cmp(&b, &a), std::cmp::Ordering::Greater);
assert_eq!(environ_cmp(&a, &a2), std::cmp::Ordering::Equal);
free_entry(&a);
free_entry(&b);
free_entry(&a2);
}
}
}