use crate::env::{
is_read_only, ElectricVar, EnvMode, EnvSetMode, EnvStackSetResult, EnvVar, EnvVarFlags,
Statuses, VarTable, ELECTRIC_VARIABLES, PATH_ARRAY_SEP,
};
use crate::env_universal_common::EnvUniversal;
use crate::flog::flog;
use crate::global_safety::RelaxedAtomicBool;
use crate::history::{history_session_id_from_var, History};
use crate::kill::kill_entries;
use crate::null_terminated_array::OwningNullTerminatedArray;
use crate::portable_atomic::AtomicU64;
use crate::prelude::*;
use crate::reader::{commandline_get_state, reader_status_count};
use crate::threads::{is_forked_child, is_main_thread};
use crate::wutil::fish_wcstol_radix;
use fish_widestring::wcs2zstring;
use nix::sys::stat::{umask, Mode};
use std::cell::{RefCell, UnsafeCell};
use std::collections::HashSet;
use std::ffi::CString;
use std::marker::PhantomData;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::sync::LazyLock;
use std::sync::{atomic::Ordering, Arc, Mutex, MutexGuard};
pub fn uvars() -> MutexGuard<'static, EnvUniversal> {
use std::sync::OnceLock;
static UVARS: OnceLock<Mutex<EnvUniversal>> = OnceLock::new();
UVARS
.get_or_init(|| Mutex::new(EnvUniversal::new()))
.lock()
.unwrap()
}
pub static UVAR_SCOPE_IS_GLOBAL: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
pub fn colon_split<T: AsRef<wstr>>(val: &[T]) -> Vec<WString> {
let mut split_val = Vec::new();
for str in val.iter() {
split_val.extend(str.as_ref().split(PATH_ARRAY_SEP).map(|s| s.to_owned()));
}
split_val
}
fn variable_should_auto_pathvar(name: &wstr) -> bool {
name.ends_with("PATH") || name == "LANGUAGE"
}
type ExportGeneration = u64;
fn next_export_generation() -> ExportGeneration {
static GEN: AtomicU64 = AtomicU64::new(0);
1 + GEN.fetch_add(1, Ordering::Relaxed)
}
fn set_umask(list_val: &[WString]) -> EnvStackSetResult {
if list_val.len() != 1 || list_val[0].is_empty() {
return EnvStackSetResult::Invalid;
}
let Ok(mask) = fish_wcstol_radix(&list_val[0], 8) else {
return EnvStackSetResult::Invalid;
};
#[allow(
unused_comparisons,
clippy::manual_range_contains,
clippy::absurd_extreme_comparisons
)]
if mask > 0o777 || mask < 0 {
return EnvStackSetResult::Invalid;
}
umask(Mode::from_bits(mask as libc::mode_t).unwrap());
EnvStackSetResult::Ok
}
struct Query {
pub has_scope: bool,
pub local: bool,
pub function: bool,
pub global: bool,
pub universal: bool,
pub has_export_unexport: bool,
pub exports: bool,
pub unexports: bool,
pub has_pathvar_unpathvar: bool,
pub pathvar: bool,
pub unpathvar: bool,
pub user: bool,
}
impl From<EnvMode> for Query {
fn from(mode: EnvMode) -> Self {
Self::new(mode, false)
}
}
impl From<EnvSetMode> for Query {
fn from(mode: EnvSetMode) -> Self {
Self::new(mode.mode, mode.user)
}
}
impl Query {
fn new(mode: EnvMode, user: bool) -> Self {
let has_scope = mode.intersects(EnvMode::ANY_SCOPE);
let has_export_unexport = mode.intersects(EnvMode::EXPORT | EnvMode::UNEXPORT);
Query {
has_scope,
local: !has_scope || mode.contains(EnvMode::LOCAL),
function: !has_scope || mode.contains(EnvMode::FUNCTION),
global: !has_scope || mode.contains(EnvMode::GLOBAL),
universal: !has_scope || mode.contains(EnvMode::UNIVERSAL),
has_export_unexport,
exports: !has_export_unexport || mode.contains(EnvMode::EXPORT),
unexports: !has_export_unexport || mode.contains(EnvMode::UNEXPORT),
has_pathvar_unpathvar: mode.intersects(EnvMode::PATHVAR | EnvMode::UNPATHVAR),
pathvar: mode.contains(EnvMode::PATHVAR),
unpathvar: mode.contains(EnvMode::UNPATHVAR),
user,
}
}
fn export_matches(&self, var: &EnvVar) -> bool {
if self.has_export_unexport {
if var.exports() {
self.exports
} else {
self.unexports
}
} else {
true
}
}
fn pathvar_matches(&self, var: &EnvVar) -> bool {
if self.has_pathvar_unpathvar {
if var.is_pathvar() {
self.pathvar
} else {
self.unpathvar
}
} else {
true
}
}
}
struct EnvNode {
env: VarTable,
new_scope: bool,
export_gen: ExportGeneration,
next: Option<EnvNodeRef>,
}
impl EnvNode {
fn find_entry(&self, key: &wstr) -> Option<EnvVar> {
self.env.get(key).cloned()
}
fn exports(&self) -> bool {
self.export_gen > 0
}
fn changed_exported(&mut self) {
self.export_gen = next_export_generation();
}
}
struct EnvNodeSyncCell(RefCell<EnvNode>);
impl EnvNodeSyncCell {
fn new(node: EnvNode) -> Self {
Self(RefCell::new(node))
}
}
unsafe impl Sync for EnvNodeSyncCell {}
#[derive(Clone)]
struct EnvNodeRef(Arc<EnvNodeSyncCell>);
impl Deref for EnvNodeRef {
type Target = RefCell<EnvNode>;
fn deref(&self) -> &Self::Target {
&self.0 .0
}
}
impl EnvNodeRef {
fn new(is_new_scope: bool, next: Option<EnvNodeRef>) -> EnvNodeRef {
#[allow(unknown_lints)]
#[allow(clippy::arc_with_non_send_sync)]
EnvNodeRef(Arc::new(EnvNodeSyncCell::new(EnvNode {
env: VarTable::new(),
new_scope: is_new_scope,
export_gen: 0,
next,
})))
}
fn ptr_eq(&self, other: &EnvNodeRef) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
fn find_entry(&self, key: &wstr) -> Option<EnvVar> {
self.borrow().find_entry(key)
}
fn next(&self) -> Option<EnvNodeRef> {
self.borrow().next.clone()
}
fn iter(&self) -> EnvNodeIter {
EnvNodeIter::new(self.clone())
}
}
struct EnvNodeIter {
current: Option<EnvNodeRef>,
}
impl EnvNodeIter {
fn new(start: EnvNodeRef) -> EnvNodeIter {
EnvNodeIter {
current: Some(start),
}
}
}
impl Iterator for EnvNodeIter {
type Item = EnvNodeRef;
fn next(&mut self) -> Option<EnvNodeRef> {
let current: Option<EnvNodeRef> = self.current.take();
if let Some(ref current) = current {
self.current = current.next();
}
current
}
}
static GLOBAL_NODE: LazyLock<EnvNodeRef> = LazyLock::new(|| EnvNodeRef::new(false, None));
fn copy_node_chain(node: &EnvNodeRef) -> EnvNodeRef {
let next = node.next().as_ref().map(copy_node_chain);
let node = node.borrow();
let new_node = EnvNode {
env: node.env.clone(),
export_gen: node.export_gen,
new_scope: node.new_scope,
next,
};
#[allow(unknown_lints)]
#[allow(clippy::arc_with_non_send_sync)]
EnvNodeRef(Arc::new(EnvNodeSyncCell::new(new_node)))
}
#[derive(Default, Clone)]
struct PerprocData {
pwd: WString,
statuses: Statuses,
}
#[derive(Clone)]
pub struct EnvScopedImpl {
locals: EnvNodeRef,
globals: EnvNodeRef,
perproc_data: PerprocData,
export_array: Option<Arc<OwningNullTerminatedArray>>,
export_array_generations: Vec<ExportGeneration>,
}
impl EnvScopedImpl {
fn new(locals: EnvNodeRef, globals: EnvNodeRef) -> Self {
EnvScopedImpl {
locals,
globals,
perproc_data: PerprocData::default(),
export_array: None,
export_array_generations: Vec::new(),
}
}
pub fn get_last_statuses(&self) -> &Statuses {
&self.perproc_data.statuses
}
pub fn set_last_statuses(&mut self, s: Statuses) {
self.perproc_data.statuses = s;
}
fn try_get_computed(&self, key: &wstr) -> Option<EnvVar> {
let ev = ElectricVar::for_name(key)?;
if !ev.computed() {
return None;
}
if key == "PWD" {
Some(EnvVar::new(
self.perproc_data.pwd.clone(),
EnvVarFlags::EXPORT,
))
} else if key == "history" {
if !is_main_thread() {
return None;
}
let history = commandline_get_state(true).history.unwrap_or_else(|| {
let fish_history_var = self.getf(L!("fish_history"), EnvMode::default());
let session_id = history_session_id_from_var(fish_history_var);
History::with_name(&session_id)
});
Some(EnvVar::new_from_name_vec(
L!("history"),
history.get_history(),
))
} else if key == "fish_killring" {
Some(EnvVar::new_from_name_vec(
L!("fish_killring"),
kill_entries(),
))
} else if key == "pipestatus" {
let js = &self.perproc_data.statuses;
let mut result = Vec::with_capacity(js.pipestatus.len());
for i in &js.pipestatus {
result.push(i.to_wstring());
}
Some(EnvVar::new_from_name_vec(L!("pipestatus"), result))
} else if key == "status" {
let js = &self.perproc_data.statuses;
Some(EnvVar::new_from_name(L!("status"), js.status.to_wstring()))
} else if key == "status_generation" {
let status_generation = reader_status_count();
Some(EnvVar::new_from_name(
L!("status_generation"),
status_generation.to_wstring(),
))
} else if key == "fish_kill_signal" {
let js = &self.perproc_data.statuses;
let signal = js.kill_signal.map_or(0, |ks| ks.code());
Some(EnvVar::new_from_name(
L!("fish_kill_signal"),
signal.to_wstring(),
))
} else if key == "umask" {
let guess = Mode::S_IWGRP | Mode::S_IWOTH;
let res = umask(guess);
if res != guess {
umask(res);
}
Some(EnvVar::new_from_name(
L!("umask"),
sprintf!("0%0.3o", res.bits()),
))
} else {
panic!("Unrecognized computed var name {}", key);
}
}
fn try_get_local(&self, key: &wstr) -> Option<EnvVar> {
for cur in self.locals.iter() {
let entry = cur.find_entry(key);
if entry.is_some() {
return entry;
}
}
None
}
fn try_get_function(&self, key: &wstr) -> Option<EnvVar> {
let mut entry = None;
let mut node = self.locals.clone();
while let Some(next_node) = node.next() {
node = next_node;
if node.borrow().new_scope {
break;
}
}
for cur in node.iter() {
entry = cur.find_entry(key);
if entry.is_some() {
break;
}
}
entry
}
fn try_get_global(&self, key: &wstr) -> Option<EnvVar> {
self.globals.find_entry(key)
}
fn try_get_universal(&self, key: &wstr) -> Option<EnvVar> {
return uvars().get(key);
}
pub fn getf(&self, key: &wstr, mode: EnvMode) -> Option<EnvVar> {
let query = Query::from(mode);
let mut result: Option<EnvVar> = None;
if query.global {
result = self.try_get_computed(key);
}
if result.is_none() && query.local {
result = self.try_get_local(key);
}
if result.is_none() && query.function {
result = self.try_get_function(key);
}
if result.is_none() && query.global {
result = self.try_get_global(key);
}
if result.is_none() && query.universal {
result = self.try_get_universal(key);
}
if result.is_some() && !query.export_matches(result.as_ref().unwrap()) {
result = None;
}
if result.is_some() && !query.pathvar_matches(result.as_ref().unwrap()) {
result = None;
}
result
}
pub fn get_names(&self, flags: EnvMode) -> Vec<WString> {
let query = Query::from(flags);
let mut names: HashSet<WString> = HashSet::new();
let add_keys = |envs: &VarTable, names: &mut HashSet<WString>| {
for (key, val) in envs.iter() {
if query.export_matches(val) {
names.insert(key.clone());
}
}
};
if query.local {
for cur in self.locals.iter() {
add_keys(&cur.borrow().env, &mut names);
}
}
if query.global {
add_keys(&self.globals.borrow().env, &mut names);
for ev in ELECTRIC_VARIABLES {
let matches = if ev.exports() {
query.exports
} else {
query.unexports
};
if matches {
names.insert(WString::from(ev.name));
}
}
}
if query.universal {
let uni_list = uvars().get_names(query.exports, query.unexports);
names.extend(uni_list);
}
names.into_iter().collect()
}
pub fn get_pwd_slash(&self) -> WString {
let mut pwd;
pwd = self.perproc_data.pwd.clone();
if !pwd.ends_with('/') {
pwd.push('/');
}
pwd
}
pub fn snapshot(&self) -> EnvMutex<Self> {
EnvMutex::new(EnvScopedImpl {
locals: copy_node_chain(&self.locals),
globals: self.globals.clone(),
perproc_data: self.perproc_data.clone(),
export_array: None,
export_array_generations: Vec::new(),
})
}
}
impl EnvScopedImpl {
fn enumerate_generations<F>(&self, mut func: F)
where
F: FnMut(u64),
{
func(uvars().get_export_generation());
if self.globals.borrow().exports() {
func(self.globals.borrow().export_gen);
}
for node in self.locals.iter() {
if node.borrow().exports() {
func(node.borrow().export_gen);
}
}
}
fn export_array_needs_regeneration(&self) -> bool {
if self.export_array.is_none() {
return true;
}
let mut cursor = self.export_array_generations.iter().fuse();
let mut mismatch = false;
self.enumerate_generations(|r#gen| {
if cursor.next().copied() != Some(r#gen) {
mismatch = true;
}
});
if cursor.next().is_some() {
mismatch = true;
}
mismatch
}
fn get_exported(n: &EnvNodeRef, table: &mut VarTable) {
let n = n.borrow();
if let Some(next) = n.next.as_ref() {
Self::get_exported(next, table);
}
for (key, var) in n.env.iter() {
if var.exports() {
table.insert(key.clone(), var.clone());
} else {
table.remove(key);
}
}
}
fn create_export_array(&self) -> Arc<OwningNullTerminatedArray> {
flog!(env_export, "create_export_array() recalc");
let mut vals = VarTable::new();
Self::get_exported(&self.globals, &mut vals);
Self::get_exported(&self.locals, &mut vals);
for (key, var) in uvars().get_table() {
if var.exports() {
vals.entry(key.clone()).or_insert(var.clone());
}
}
vals.insert(
L!("PWD").to_owned(),
EnvVar::new_from_name(L!("PWD"), self.perproc_data.pwd.clone()),
);
let mut export_list: Vec<CString> = Vec::with_capacity(vals.len());
for (key, val) in vals {
let mut str = key;
str.push('=');
str.push_utfstr(&val.as_string());
export_list.push(wcs2zstring(&str));
}
Arc::new(OwningNullTerminatedArray::new(export_list))
}
pub fn export_array(&mut self) -> Arc<OwningNullTerminatedArray> {
assert!(!is_forked_child());
if self.export_array_needs_regeneration() {
self.export_array = Some(self.create_export_array());
let mut generations = std::mem::take(&mut self.export_array_generations);
self.enumerate_generations(|r#gen| generations.push(r#gen));
self.export_array_generations = generations;
}
self.export_array.as_ref().unwrap().clone()
}
}
#[derive(Copy, Clone, Default)]
struct VarFlags {
pub pathvar: Option<bool>,
pub exports: Option<bool>,
pub parent_exports: bool,
}
#[derive(Copy, Clone, Default)]
pub struct ModResult {
pub status: EnvStackSetResult,
pub global_modified: bool,
pub uvar_modified: bool,
}
impl ModResult {
fn new(status: EnvStackSetResult) -> Self {
ModResult {
status,
..Default::default()
}
}
}
#[derive(Clone)]
pub struct EnvStackImpl {
pub base: EnvScopedImpl,
shadowed_locals: Vec<EnvNodeRef>,
}
impl EnvStackImpl {
pub fn new() -> EnvMutex<EnvStackImpl> {
let globals = GLOBAL_NODE.clone();
let locals = EnvNodeRef::new(false, None);
let base = EnvScopedImpl::new(locals, globals);
EnvMutex::new(EnvStackImpl {
base,
shadowed_locals: Vec::new(),
})
}
pub fn set(&mut self, key: &wstr, mode: EnvSetMode, mut val: Vec<WString>) -> ModResult {
let query = Query::from(mode);
if let Some(ret) = self.try_set_electric(key, &query, &mut val) {
return ModResult::new(ret);
}
let mut flags = VarFlags::default();
if let Some(existing) = self.find_variable(key) {
flags.pathvar = Some(existing.is_pathvar());
flags.parent_exports = existing.exports();
}
if query.has_export_unexport {
flags.exports = Some(query.exports);
}
if query.has_pathvar_unpathvar {
flags.pathvar = Some(query.pathvar);
}
let mut result = ModResult::new(EnvStackSetResult::Ok);
if query.has_scope {
if query.universal && !UVAR_SCOPE_IS_GLOBAL.load() {
self.set_universal(key, val, query);
result.uvar_modified = true;
} else if query.global || (query.universal && UVAR_SCOPE_IS_GLOBAL.load()) {
Self::set_in_node(&mut self.base.globals, key, val, flags);
result.global_modified = true;
} else if query.local {
assert!(
!self.base.locals.ptr_eq(&self.base.globals),
"Locals should not be globals"
);
Self::set_in_node(&mut self.base.locals, key, val, flags);
} else if query.function {
let mut node = self.base.locals.clone();
while let Some(next_node) = node.next() {
node = next_node;
if node.borrow().new_scope {
break;
}
}
Self::set_in_node(&mut node, key, val, flags);
} else {
panic!("Unknown scope");
}
} else if let Some(mut node) = Self::find_in_chain(&self.base.locals, key) {
Self::set_in_node(&mut node, key, val, flags);
} else if let Some(mut node) = Self::find_in_chain(&self.base.globals, key) {
Self::set_in_node(&mut node, key, val, flags);
result.global_modified = true;
} else if !UVAR_SCOPE_IS_GLOBAL.load() && uvars().get(key).is_some() {
self.set_universal(key, val, query);
result.uvar_modified = true;
} else {
let mut node = self.resolve_unspecified_scope();
Self::set_in_node(&mut node, key, val, flags);
result.global_modified = node.ptr_eq(&self.base.globals);
}
result
}
pub fn remove(&mut self, key: &wstr, mode: EnvSetMode) -> ModResult {
let query = Query::from(mode);
if query.user && is_read_only(key) {
return ModResult::new(EnvStackSetResult::Scope);
}
fn remove_from_chain(node: &mut EnvNodeRef, key: &wstr) -> EnvStackSetResult {
if EnvStackImpl::remove_from_chain(node, key) {
EnvStackSetResult::Ok
} else {
EnvStackSetResult::NotFound
}
}
let mut result = ModResult::new(EnvStackSetResult::Ok);
if query.has_scope {
if query.universal {
if uvars().remove(key) {
result.status = EnvStackSetResult::Ok;
} else {
result.status = EnvStackSetResult::NotFound;
}
result.uvar_modified = true;
} else if query.global {
result.status = remove_from_chain(&mut self.base.globals, key);
result.global_modified = true;
} else if query.local {
result.status = remove_from_chain(&mut self.base.locals, key);
} else if query.function {
let mut node = self.base.locals.clone();
while let Some(next_node) = node.next() {
node = next_node;
if node.borrow().new_scope {
break;
}
}
result.status = remove_from_chain(&mut node, key);
} else {
panic!("Unknown scope");
}
} else if Self::remove_from_chain(&mut self.base.locals, key) {
} else if Self::remove_from_chain(&mut self.base.globals, key) {
result.global_modified = true;
} else if uvars().remove(key) {
result.uvar_modified = true;
} else {
result.status = EnvStackSetResult::NotFound;
}
result
}
pub fn push_shadowing(&mut self) {
let node = EnvNodeRef::new(true, None);
for cursor in self.base.locals.iter() {
for (key, val) in cursor.borrow().env.iter() {
if val.exports() {
let mut node_ref = node.borrow_mut();
if !node_ref.env.contains_key(key) {
node_ref.env.insert(key.clone(), val.clone());
}
node_ref.changed_exported();
}
}
}
let old_locals = mem::replace(&mut self.base.locals, node);
self.shadowed_locals.push(old_locals);
}
pub fn push_nonshadowing(&mut self) {
self.base.locals = EnvNodeRef::new(false, Some(self.base.locals.clone()));
}
pub fn pop(&mut self) -> Vec<WString> {
let popped: EnvNodeRef;
if let Some(next) = self.base.locals.next() {
popped = mem::replace(&mut self.base.locals, next);
} else {
if let Some(shadowed) = self.shadowed_locals.pop() {
popped = mem::replace(&mut self.base.locals, shadowed);
} else {
panic!("Attempt to pop last local scope")
}
}
let var_names = popped.borrow().env.keys().cloned().collect();
var_names
}
fn find_in_chain(node: &EnvNodeRef, key: &wstr) -> Option<EnvNodeRef> {
#[allow(clippy::manual_find)]
for cur in node.iter() {
if cur.borrow().env.contains_key(key) {
return Some(cur);
}
}
None
}
fn remove_from_chain(node: &mut EnvNodeRef, key: &wstr) -> bool {
for cur in node.iter() {
let mut cur_ref = cur.borrow_mut();
if let Some(var) = cur_ref.env.remove(key) {
if var.exports() {
cur_ref.changed_exported();
}
return true;
}
}
false
}
fn try_set_electric(
&mut self,
key: &wstr,
query: &Query,
val: &mut Vec<WString>,
) -> Option<EnvStackSetResult> {
let ev = ElectricVar::for_name(key)?;
if query.has_scope && !query.global {
return Some(EnvStackSetResult::Scope);
}
if query.user && ev.readonly() {
return Some(EnvStackSetResult::Perm);
}
if query.has_export_unexport {
let matches = if ev.exports() {
query.exports
} else {
query.unexports
};
if !matches {
return Some(EnvStackSetResult::Scope);
}
}
if key == "umask" {
return Some(set_umask(val));
} else if key == "PWD" {
assert_eq!(val.len(), 1, "Should have exactly one element in PWD");
let pwd = val.pop().unwrap();
if pwd != self.base.perproc_data.pwd {
self.base.perproc_data.pwd = pwd;
self.base.globals.borrow_mut().changed_exported();
}
return Some(EnvStackSetResult::Ok);
}
let val = std::mem::take(val);
let flags = VarFlags {
exports: Some(ev.exports()),
parent_exports: ev.exports(),
pathvar: Some(false),
};
Self::set_in_node(&mut self.base.globals, key, val, flags);
Some(EnvStackSetResult::Ok)
}
fn set_universal(&mut self, key: &wstr, mut val: Vec<WString>, query: Query) {
let mut locked_uvars = uvars();
let oldvar = locked_uvars.get(key);
let oldvar = oldvar.as_ref();
let mut exports = false;
if query.has_export_unexport {
exports = query.exports;
} else if let Some(v) = oldvar {
exports = v.exports();
}
let pathvar;
if query.has_pathvar_unpathvar {
pathvar = query.pathvar;
} else if let Some(v) = oldvar {
pathvar = v.is_pathvar();
} else {
pathvar = variable_should_auto_pathvar(key);
}
if pathvar {
val = colon_split(&val);
}
let mut varflags = EnvVarFlags::empty();
varflags.set(EnvVarFlags::EXPORT, exports);
varflags.set(EnvVarFlags::PATHVAR, pathvar);
let new_var = EnvVar::new_vec(val, varflags);
locked_uvars.set(key, new_var);
}
fn set_in_node(node: &mut EnvNodeRef, key: &wstr, mut val: Vec<WString>, flags: VarFlags) {
let mut node_ref = node.borrow_mut();
let var = node_ref.env.entry(key.to_owned()).or_default();
let res_exports = match flags.exports {
Some(exports) => exports,
None => var.exports(),
};
let res_pathvar = match flags.pathvar {
Some(pathvar) => pathvar,
None => variable_should_auto_pathvar(key),
};
if res_pathvar {
val = colon_split(&val);
}
*var = var
.setting_vals(val)
.setting_exports(res_exports)
.setting_pathvar(res_pathvar);
if res_exports || flags.parent_exports {
node_ref.changed_exported();
}
}
fn resolve_unspecified_scope(&mut self) -> EnvNodeRef {
for cursor in self.base.locals.iter() {
if cursor.borrow().new_scope {
return cursor;
}
}
self.base.globals.clone()
}
fn find_variable(&self, key: &wstr) -> Option<EnvVar> {
let mut node = Self::find_in_chain(&self.base.locals, key);
if node.is_none() {
node = Self::find_in_chain(&self.base.globals, key);
}
if let Some(node) = node {
let iter = node.borrow().env.get(key).cloned();
assert!(iter.is_some(), "Node should contain key");
return iter;
}
None
}
pub fn getf(&self, key: &wstr, mode: EnvMode) -> Option<EnvVar> {
self.base.getf(key, mode)
}
pub fn get_names(&self, flags: EnvMode) -> Vec<WString> {
self.base.get_names(flags)
}
pub fn get_pwd_slash(&self) -> WString {
self.base.get_pwd_slash()
}
}
static ENV_LOCK: Mutex<()> = Mutex::new(());
pub struct EnvMutexGuard<'a, T: 'a> {
_guard: MutexGuard<'static, ()>,
value: *mut T,
_phantom: PhantomData<&'a T>,
}
impl<'a, T: 'a> Deref for EnvMutexGuard<'a, T> {
type Target = T;
fn deref(&self) -> &'a T {
unsafe { &*self.value }
}
}
impl<'a, T: 'a> DerefMut for EnvMutexGuard<'a, T> {
fn deref_mut(&mut self) -> &'a mut T {
unsafe { &mut *self.value }
}
}
pub struct EnvMutex<T> {
inner: UnsafeCell<T>,
}
impl<T> EnvMutex<T> {
pub fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
}
}
pub fn lock(&self) -> EnvMutexGuard<'_, T> {
let guard = ENV_LOCK.lock().unwrap();
let value = unsafe { &mut *self.inner.get() };
EnvMutexGuard {
_guard: guard,
value,
_phantom: PhantomData,
}
}
}
unsafe impl<T> Sync for EnvMutex<T> {}
unsafe impl<T> Send for EnvMutex<T> {}
#[cfg(test)]
mod tests {
use super::colon_split;
use crate::prelude::*;
#[test]
fn test_colon_split() {
assert_eq!(colon_split(&[L!("foo")]), &[L!("foo")]);
assert_eq!(
colon_split(&[L!("foo:bar:baz")]),
&[L!("foo"), L!("bar"), L!("baz")]
);
assert_eq!(
colon_split(&[L!("foo:bar"), L!("baz")]),
&[L!("foo"), L!("bar"), L!("baz")]
);
assert_eq!(
colon_split(&[L!("foo:bar"), L!("baz")]),
&[L!("foo"), L!("bar"), L!("baz")]
);
assert_eq!(
colon_split(&[L!("1:"), L!("2:"), L!(":3:")]),
&[L!("1"), L!(""), L!("2"), L!(""), L!(""), L!("3"), L!("")]
);
}
}