fish-shell/src/env/environment_impl.rs
hdhoang 7682abb703 Import portable_atomic::AtomicU64 when std does not provide it
Restores support for 32-bit powerpc and mips. Fixes #10415.

Signed-off-by: Hoang Duc Hieu <code@hdhoang.space>
2024-08-11 14:50:39 +02:00

1173 lines
41 KiB
Rust

use crate::common::wcs2zstring;
use crate::env::{
is_read_only, ElectricVar, EnvMode, 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::reader::{commandline_get_state, reader_status_count};
use crate::threads::{is_forked_child, is_main_thread};
use crate::wchar::prelude::*;
use crate::wutil::fish_wcstol_radix;
use lazy_static::lazy_static;
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};
#[cfg(not(target_has_atomic = "64"))]
use portable_atomic::AtomicU64;
#[cfg(target_has_atomic = "64")]
use std::sync::atomic::AtomicU64;
use std::sync::{atomic::Ordering, Arc, Mutex, MutexGuard};
/// Getter for universal variables.
/// This is typically initialized in env_init(), and is considered empty before then.
pub fn uvars() -> MutexGuard<'static, EnvUniversal> {
use std::sync::OnceLock;
/// Universal variables instance.
static UVARS: OnceLock<Mutex<EnvUniversal>> = OnceLock::new();
UVARS
.get_or_init(|| Mutex::new(EnvUniversal::new()))
.lock()
.unwrap()
}
/// Whether we were launched with no_config; in this case setting a uvar instead sets a global.
pub static UVAR_SCOPE_IS_GLOBAL: RelaxedAtomicBool = RelaxedAtomicBool::new(false);
/// Apply the pathvar behavior, splitting about colons.
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
}
/// Return true if a variable should become a path variable by default. See #436.
fn variable_should_auto_pathvar(name: &wstr) -> bool {
name.ends_with("PATH")
}
/// We cache our null-terminated export list. However an exported variable may change for lots of
/// reasons: popping a scope, a modified universal variable, etc. We thus have a monotone counter.
/// Every time an exported variable changes in a node, it acquires the next generation. 0 is a
/// sentinel that indicates that the node contains no exported variables.
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;
}
// Do not actually create a umask variable. On env_stack_t::get() it will be calculated.
// SAFETY: umask cannot fail.
unsafe { libc::umask(mask as libc::mode_t) };
EnvStackSetResult::Ok
}
/// A query for environment variables.
struct Query {
/// Whether any scopes were specified.
pub has_scope: bool,
/// Whether to search local, function, global, universal scopes.
pub local: bool,
pub function: bool,
pub global: bool,
pub universal: bool,
/// Whether export or unexport was specified.
pub has_export_unexport: bool,
/// Whether to search exported and unexported variables.
pub exports: bool,
pub unexports: bool,
/// Whether pathvar or unpathvar was set.
pub has_pathvar_unpathvar: bool,
pub pathvar: bool,
pub unpathvar: bool,
/// Whether this is a "user" set.
pub user: bool,
}
impl Query {
/// Creates a `Query` from env mode flags.
fn new(mode: EnvMode) -> Self {
let has_scope = mode
.intersects(EnvMode::LOCAL | EnvMode::FUNCTION | EnvMode::GLOBAL | EnvMode::UNIVERSAL);
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),
// note we don't use pathvar for searches, so these don't default to true if unspecified.
has_pathvar_unpathvar: mode.intersects(EnvMode::PATHVAR | EnvMode::UNPATHVAR),
pathvar: mode.contains(EnvMode::PATHVAR),
unpathvar: mode.contains(EnvMode::UNPATHVAR),
user: mode.contains(EnvMode::USER),
}
}
/// Returns whether an environment variable matches the query's export criteria.
fn export_matches(&self, var: &EnvVar) -> bool {
if self.has_export_unexport {
if var.exports() {
self.exports
} else {
self.unexports
}
} else {
true
}
}
/// Returns whether an environment variable matches the query's path variable criteria.
fn pathvar_matches(&self, var: &EnvVar) -> bool {
if self.has_pathvar_unpathvar {
if var.is_pathvar() {
self.pathvar
} else {
self.unpathvar
}
} else {
true
}
}
}
// Struct representing one level in the function variable stack.
struct EnvNode {
// Variable table.
env: VarTable,
/// Does this node imply a new variable scope? If yes, all non-global variables below this one
/// in the stack are invisible. If new_scope is set for the global variable node, the universe
/// will explode.
new_scope: bool,
/// The export generation. If this is nonzero, then we contain a variable that is exported to
/// subshells, or redefines a variable to not be exported.
export_gen: ExportGeneration,
/// Next scope to search. This is None if this node establishes a new scope.
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();
}
}
/// EnvNodeRef is a reference to an EnvNode. It may be shared between different environments.
/// The type Arc<RefCell<...>> may look suspicious, but all accesses to the EnvNode are protected by a global lock.
#[derive(Clone)]
struct EnvNodeRef(Arc<RefCell<EnvNode>>);
impl Deref for EnvNodeRef {
type Target = RefCell<EnvNode>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl EnvNodeRef {
fn new(is_new_scope: bool, next: Option<EnvNodeRef>) -> EnvNodeRef {
// Accesses are protected by the global lock.
#[allow(unknown_lints)]
#[allow(clippy::arc_with_non_send_sync)]
EnvNodeRef(Arc::new(RefCell::new(EnvNode {
env: VarTable::new(),
new_scope: is_new_scope,
export_gen: 0,
next,
})))
}
/// Return whether this points at the same value as another node.
fn ptr_eq(&self, other: &EnvNodeRef) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
/// Cover over find_entry.
fn find_entry(&self, key: &wstr) -> Option<EnvVar> {
self.borrow().find_entry(key)
}
/// Cover over next.
fn next(&self) -> Option<EnvNodeRef> {
self.borrow().next.clone()
}
/// Helper to get an iterator over the chain of EnvNodeRefs.
fn iter(&self) -> EnvNodeIter {
EnvNodeIter::new(self.clone())
}
}
// Safety: in order to do anything with an EnvNodeRef, the caller must be holding ENV_LOCK.
unsafe impl Sync for EnvNodeRef {}
/// Helper to iterate over a chain of EnvNodeRefs.
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
}
}
lazy_static! {
// All accesses to the EnvNode are protected by a global lock.
static ref GLOBAL_NODE: EnvNodeRef = EnvNodeRef::new(false, None);
}
/// Recursive helper to snapshot a series of nodes.
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(RefCell::new(new_node)))
}
/// A struct wrapping up parser-local variables. These are conceptually variables that differ in
/// different fish internal processes.
#[derive(Default, Clone)]
struct PerprocData {
pwd: WString,
statuses: Statuses,
}
#[derive(Clone)]
pub struct EnvScopedImpl {
// A linked list of scopes.
locals: EnvNodeRef,
// Global scopes. There is no parent here.
globals: EnvNodeRef,
// Per process data.
perproc_data: PerprocData,
// Exported variable array used by execv.
export_array: Option<Arc<OwningNullTerminatedArray>>,
// Cached list of export generations corresponding to the above export_array.
// If this differs from the current export generations then we need to regenerate the array.
export_array_generations: Vec<ExportGeneration>,
}
impl EnvScopedImpl {
/// Creates a new `EnvScopedImpl` with the specified local and global scopes.
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.is_none() || !ev.unwrap().computed() {
return None;
}
if key == "PWD" {
Some(EnvVar::new(
self.perproc_data.pwd.clone(),
EnvVarFlags::EXPORT,
))
} else if key == "history" {
// Big hack. We only allow getting the history on the main thread. Note that history_t
// may ask for an environment variable, so don't take the lock here (we don't need it).
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)
});
return 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" {
// note umask() is an absurd API: you call it to set the value and it returns the old
// value. Thus we have to call it twice, to reset the value. The env_lock protects
// against races. Guess what the umask is; if we guess right we don't need to reset it.
let guess: libc::mode_t = 0o022;
// Safety: umask cannot error.
let res: libc::mode_t = unsafe { libc::umask(guess) };
if res != guess {
unsafe { libc::umask(res) };
}
Some(EnvVar::new_from_name(L!("umask"), sprintf!("0%0.3o", res)))
} else {
// We should never get here unless the electric var list is out of sync with the above code.
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;
// The first node that introduces a new scope is ours.
// If this doesn't happen, we go on until we've reached the
// topmost local scope.
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::new(mode);
let mut result: Option<EnvVar> = None;
// Computed variables are effectively global and can't be shadowed.
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 the user requested only exported or unexported variables, enforce that here.
if result.is_some() && !query.export_matches(result.as_ref().unwrap()) {
result = None;
}
// Same for pathvars
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::new(flags);
let mut names: HashSet<WString> = HashSet::new();
// Helper to add the names of variables from `envs` to names, respecting show_exported and
// show_unexported.
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);
// Add electrics.
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()
}
/// Slightly optimized implementation.
pub fn get_pwd_slash(&self) -> WString {
// Return "/" if PWD is missing.
// See https://github.com/fish-shell/fish-shell/issues/5080
let mut pwd;
pwd = self.perproc_data.pwd.clone();
if !pwd.ends_with('/') {
pwd.push('/');
}
pwd
}
/// Return a copy of self, with copied locals but shared globals.
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(),
})
}
}
/// Export array implementations.
impl EnvScopedImpl {
/// Invoke a function on the current (nonzero) export generations, in order.
fn enumerate_generations<F>(&self, mut func: F)
where
F: FnMut(u64),
{
// Our uvars generation count doesn't come from next_export_generation(), so always supply
// it even if it's 0.
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);
}
}
}
/// Return whether the current export array is empty or out-of-date.
fn export_array_needs_regeneration(&self) -> bool {
// Check if our export array is stale. If we don't have one, it's obviously stale. Otherwise,
// compare our cached generations with the current generations. If they don't match exactly then
// our generation list is stale.
if self.export_array.is_none() {
return true;
}
let mut cursor = self.export_array_generations.iter().fuse();
let mut mismatch = false;
self.enumerate_generations(|gen| {
if cursor.next().cloned() != Some(gen) {
mismatch = true;
}
});
if cursor.next().is_some() {
mismatch = true;
}
return mismatch;
}
/// Get the exported variables into a variable table.
fn get_exported(n: &EnvNodeRef, table: &mut VarTable) {
let n = n.borrow();
// Allow parent scopes to populate first, since we may want to overwrite those results.
if let Some(next) = n.next.as_ref() {
Self::get_exported(next, table);
}
for (key, var) in n.env.iter() {
if var.exports() {
// Export the variable. Note this overwrites existing values from previous scopes.
table.insert(key.clone(), var.clone());
} else {
// We need to erase from the map if we are not exporting, since a lower scope may have
// exported. See #2132.
table.remove(key);
}
}
}
/// Return a newly allocated export array.
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);
let uni = uvars().get_names(true, false);
for key in uni {
let var = uvars().get(&key).unwrap();
// Only insert if not already present, as uvars have lowest precedence.
// TODO: a longstanding bug is that an unexported local variable will not mask an exported uvar.
vals.entry(key).or_insert(var);
}
// Dorky way to add our single exported computed variable.
vals.insert(
L!("PWD").to_owned(),
EnvVar::new_from_name(L!("PWD"), self.perproc_data.pwd.clone()),
);
// Construct the export list: a list of strings of the form key=value.
let mut export_list: Vec<CString> = Vec::with_capacity(vals.len());
for (key, val) in vals.into_iter() {
let mut str = key;
str.push('=');
str.push_utfstr(&val.as_string());
export_list.push(wcs2zstring(&str));
}
return Arc::new(OwningNullTerminatedArray::new(export_list));
}
// Exported variable array used by execv.
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());
// Have to pull this into a local to satisfy the borrow checker.
let mut generations = std::mem::take(&mut self.export_array_generations);
self.enumerate_generations(|gen| generations.push(gen));
self.export_array_generations = generations;
}
return self.export_array.as_ref().unwrap().clone();
}
}
#[derive(Copy, Clone, Default)]
/// A restricted set of variable flags.
struct VarFlags {
/// If set, whether the variable should be a path variable; otherwise guess based on the name.
pub pathvar: Option<bool>,
/// If set, the new export value; otherwise inherit any existing export value.
pub exports: Option<bool>,
/// Whether the variable is exported by some parent.
pub parent_exports: bool,
}
#[derive(Copy, Clone, Default)]
pub struct ModResult {
/// The publicly visible status of the set call.
pub status: EnvStackSetResult,
/// Whether the global scope was modified.
pub global_modified: bool,
/// Whether universal variables were modified.
pub uvar_modified: bool,
}
impl ModResult {
/// Creates a `ModResult` with a given status.
fn new(status: EnvStackSetResult) -> Self {
ModResult {
status,
..Default::default()
}
}
}
/// A mutable "subclass" of EnvScopedImpl.
#[derive(Clone)]
pub struct EnvStackImpl {
pub base: EnvScopedImpl,
/// The scopes of caller functions, which are currently shadowed.
shadowed_locals: Vec<EnvNodeRef>,
}
impl EnvStackImpl {
/// Return a new impl representing global variables, with a single local scope.
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(),
})
}
/// Set a variable under the name `key`, using the given `mode`, setting its value to `val`.
pub fn set(&mut self, key: &wstr, mode: EnvMode, mut val: Vec<WString>) -> ModResult {
let query = Query::new(mode);
// Handle electric and read-only variables.
if let Some(ret) = self.try_set_electric(key, &query, &mut val) {
return ModResult::new(ret);
}
// Resolve as much of our flags as we can. Note these contain maybes, and we may defer the final
// decision until the set_in_node call. Also note that we only inherit pathvar, not export. For
// example, if you have a global exported variable, a local variable with the same name will not
// automatically be exported. But if you have a global pathvar, a local variable with the same
// name will be a pathvar. This is historical.
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 {
// The user requested a particular scope.
// If we don't have uvars, fall back to using globals.
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);
} 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 {
// "Function" scope is:
// Either the topmost local scope of the nearest function,
// or the top-level local scope if no function exists.
//
// This is distinct from the unspecified scope,
// which is the global scope if no function exists.
let mut node = self.base.locals.clone();
while node.next().is_some() {
node = node.next().unwrap();
// The first node that introduces a new scope is ours.
// If this doesn't happen, we go on until we've reached the
// topmost local scope.
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) {
// Existing local variable.
Self::set_in_node(&mut node, key, val, flags);
} else if let Some(mut node) = Self::find_in_chain(&self.base.globals, key) {
// Existing global variable.
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() {
// Existing universal variable.
self.set_universal(key, val, query);
result.uvar_modified = true;
} else {
// Unspecified scope with no existing variables.
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
}
/// Remove a variable under the name `key`.
pub fn remove(&mut self, key: &wstr, mode: EnvMode) -> ModResult {
let query = Query::new(mode);
// Users can't remove read-only keys.
if query.user && is_read_only(key) {
return ModResult::new(EnvStackSetResult::Scope);
}
// Helper to invoke remove_from_chain and map a false return to not found.
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 {
// The user requested erasing from a particular scope.
if query.universal {
if uvars().remove(key) {
result.status = EnvStackSetResult::Ok;
} else {
result.status = EnvStackSetResult::NotFound;
}
// Note we have historically set this even if the uvar is not found.
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 node.next().is_some() {
node = node.next().unwrap();
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) {
// pass
} 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
}
/// Push a new shadowing local scope.
pub fn push_shadowing(&mut self) {
// Propagate local exported variables.
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();
// Do NOT overwrite existing values, since we go from inner scopes outwards.
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);
}
/// Push a new non-shadowing (inner) local scope.
pub fn push_nonshadowing(&mut self) {
self.base.locals = EnvNodeRef::new(false, Some(self.base.locals.clone()));
}
/// Pop the variable stack.
/// Return a list of the names of variables which were modified.
/// TODO: We return the variable names because we may need to dispatch changes,
/// for example if there is a local change to LC_ALL; but that is rare. How can
/// we avoid these copies in the common case?
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 {
// Exhausted the inner scopes, put back a shadowing scope.
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
}
/// Find the first node in the chain starting at `node` which contains the given key `key`.
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
}
/// Remove a variable from the chain `node`.
/// Return true if the variable was found and removed.
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
}
/// Try setting `key` as an electric or readonly variable, whose value is provided by reference in `val`.
/// Return an error code, or NOne if not an electric or readonly variable.
/// `val` will not be modified upon a None return.
fn try_set_electric(
&mut self,
key: &wstr,
query: &Query,
val: &mut Vec<WString>,
) -> Option<EnvStackSetResult> {
// Do nothing if not electric.
let ev = ElectricVar::for_name(key)?;
// If a variable is electric, it may only be set in the global scope.
if query.has_scope && !query.global {
return Some(EnvStackSetResult::Scope);
}
// If the variable is read-only, the user may not set it.
if query.user && ev.readonly() {
return Some(EnvStackSetResult::Perm);
}
// Be picky about exporting.
if query.has_export_unexport {
let matches = if ev.exports() {
query.exports
} else {
query.unexports
};
if !matches {
return Some(EnvStackSetResult::Scope);
}
}
// Handle computed mutable electric variables.
if key == "umask" {
return Some(set_umask(val));
} else if key == "PWD" {
assert!(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);
}
// Claim the value.
let val = std::mem::take(val);
// Decide on the mode and set it in the global scope.
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);
return Some(EnvStackSetResult::Ok);
}
/// Set a universal variable, inheriting as applicable from the given old variable.
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();
// Resolve whether or not to export.
let mut exports = false;
if query.has_export_unexport {
exports = query.exports;
} else if oldvar.is_some() {
exports = oldvar.unwrap().exports();
}
// Resolve whether to be a path variable.
// Here we fall back to the auto-pathvar behavior.
let pathvar;
if query.has_pathvar_unpathvar {
pathvar = query.pathvar;
} else if oldvar.is_some() {
pathvar = oldvar.unwrap().is_pathvar();
} else {
pathvar = variable_should_auto_pathvar(key);
}
// Split about ':' if it's a path variable.
if pathvar {
val = colon_split(&val);
}
// Construct and set the new variable.
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);
}
/// Set a variable in a given node `node`.
fn set_in_node(node: &mut EnvNodeRef, key: &wstr, mut val: Vec<WString>, flags: VarFlags) {
// Read the var from the node. In C++ this was node->env[key] which establishes a default.
let mut node_ref = node.borrow_mut();
let var = node_ref.env.entry(key.to_owned()).or_default();
// Use an explicit exports, or inherit from the existing variable.
let res_exports = match flags.exports {
Some(exports) => exports,
None => var.exports(),
};
// Pathvar is inferred from the name. If set, split our entry about colons.
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);
// Perhaps mark that this node contains an exported variable, or shadows an exported variable.
// If so regenerate the export list.
if res_exports || flags.parent_exports {
node_ref.changed_exported();
}
}
// Implement the default behavior of 'set' by finding the node for an unspecified scope.
fn resolve_unspecified_scope(&mut self) -> EnvNodeRef {
for cursor in self.base.locals.iter() {
if cursor.borrow().new_scope {
return cursor;
}
}
return self.base.globals.clone();
}
/// Get an existing variable, or None.
/// This is used for inheriting pathvar and export status.
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()
}
}
// This is a big dorky lock we take around everything. Everything exported from this module should be
// wrapped in an EnvMutexGuard using this lock.
// Fine grained locking is annoying here because nodes may be shared between stacks, so each
// node would need its own lock, and each stack would need to take all the locks before any operation.
static ENV_LOCK: Mutex<()> = Mutex::new(());
/// Like MutexGuard but for our global lock.
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 {
// Safety: we hold the global lock.
unsafe { &*self.value }
}
}
impl<'a, T: 'a> DerefMut for EnvMutexGuard<'a, T> {
fn deref_mut(&mut self) -> &'a mut T {
// Safety: we hold the global lock.
unsafe { &mut *self.value }
}
}
// Like Mutex, but references the global lock.
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();
// Safety: we have the global lock.
let value = unsafe { &mut *self.inner.get() };
EnvMutexGuard {
_guard: guard,
value,
_phantom: PhantomData,
}
}
}
// Safety: we use a global lock.
unsafe impl<T> Sync for EnvMutex<T> {}
unsafe impl<T> Send for EnvMutex<T> {}
#[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!("")]
);
}