diff --git a/fish-rust/src/env/env_ffi.rs b/fish-rust/src/env/env_ffi.rs index a4ca835e2..6f6f2973e 100644 --- a/fish-rust/src/env/env_ffi.rs +++ b/fish-rust/src/env/env_ffi.rs @@ -1,8 +1,13 @@ -use super::var::{EnvVar, EnvVarFlags}; -use crate::ffi::{wchar_t, wcharz_t, wcstring_list_ffi_t}; +use super::environment::{self, EnvNull, EnvStack, EnvStackRef, Environment}; +use super::var::{ElectricVar, EnvVar, EnvVarFlags, Statuses}; +use crate::env::EnvMode; +use crate::event::Event; +use crate::ffi::{event_list_ffi_t, wchar_t, wcharz_t, wcstring_list_ffi_t}; +use crate::null_terminated_array::OwningNullTerminatedArrayRefFFI; +use crate::signal::Signal; use crate::wchar_ffi::WCharToFFI; use crate::wchar_ffi::{AsWstr, WCharFromFFI}; -use cxx::{CxxWString, UniquePtr}; +use cxx::{CxxVector, CxxWString, UniquePtr}; use std::pin::Pin; #[allow(clippy::module_inception)] @@ -20,9 +25,15 @@ mod env_ffi { } extern "C++" { + include!("env.h"); + include!("null_terminated_array.h"); include!("wutil.h"); + type event_list_ffi_t = super::event_list_ffi_t; type wcstring_list_ffi_t = super::wcstring_list_ffi_t; type wcharz_t = super::wcharz_t; + + type OwningNullTerminatedArrayRefFFI = + crate::null_terminated_array::OwningNullTerminatedArrayRefFFI; } extern "Rust" { @@ -64,6 +75,81 @@ mod env_ffi { values: &wcstring_list_ffi_t, ) -> Box; } + extern "Rust" { + type EnvNull; + #[cxx_name = "getf"] + fn getf_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar; + #[cxx_name = "get_names"] + fn get_names_ffi(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>); + #[cxx_name = "env_null_create"] + fn env_null_create_ffi() -> Box; + } + + extern "Rust" { + type Statuses; + #[cxx_name = "get_status"] + fn get_status_ffi(&self) -> i32; + + #[cxx_name = "get_pipestatus"] + fn get_pipestatus_ffi(&self) -> &Vec; + + #[cxx_name = "get_kill_signal"] + fn get_kill_signal_ffi(&self) -> i32; + } + + extern "Rust" { + #[cxx_name = "EnvDyn"] + type EnvDynFFI; + fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar; + fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>); + } + + extern "Rust" { + #[cxx_name = "EnvStackRef"] + type EnvStackRefFFI; + fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar; + fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>); + fn is_principal(&self) -> bool; + fn get_last_statuses(&self) -> Box; + fn set_last_statuses(&self, status: i32, kill_signal: i32, pipestatus: &CxxVector); + fn set( + &self, + name: &CxxWString, + flags: u16, + vals: &wcstring_list_ffi_t, + ) -> EnvStackSetResult; + fn remove(&self, name: &CxxWString, flags: u16) -> EnvStackSetResult; + fn get_pwd_slash(&self) -> UniquePtr; + fn set_pwd_from_getcwd(&self); + + fn push(&mut self, new_scope: bool); + fn pop(&mut self); + + // Returns a ``Box.into_raw()``. + fn export_array(&self) -> *mut OwningNullTerminatedArrayRefFFI; + + fn snapshot(&self) -> Box; + + // Access a variable stack that only represents globals. + // Do not push or pop from this. + fn env_get_globals_ffi() -> Box; + + // Access the principal variable stack. + fn env_get_principal_ffi() -> Box; + + fn universal_sync(&self, always: bool, out_events: Pin<&mut event_list_ffi_t>); + } + + extern "Rust" { + #[cxx_name = "var_is_electric"] + fn var_is_electric_ffi(name: &CxxWString) -> bool; + + #[cxx_name = "rust_env_init"] + fn rust_env_init_ffi(do_uvars: bool); + + #[cxx_name = "env_flags_for"] + fn env_flags_for_ffi(name: wcharz_t) -> u8; + } } pub use env_ffi::EnvStackSetResult; @@ -117,3 +203,167 @@ fn env_var_create_ffi(vals: &wcstring_list_ffi_t, flags: u8) -> Box { pub fn env_var_create_from_name_ffi(name: wcharz_t, values: &wcstring_list_ffi_t) -> Box { Box::new(EnvVar::new_from_name_vec(name.as_wstr(), values.from_ffi())) } + +fn env_null_create_ffi() -> Box { + Box::new(EnvNull::new()) +} + +/// FFI wrapper around dyn Environment. +pub struct EnvDynFFI(Box); +impl EnvDynFFI { + fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar { + EnvironmentFFI::getf_ffi(&*self.0, name, mode) + } + fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>) { + EnvironmentFFI::get_names_ffi(&*self.0, flags, out) + } +} + +/// FFI wrapper around EnvStackRef. +pub struct EnvStackRefFFI(EnvStackRef); + +impl EnvStackRefFFI { + fn getf(&self, name: &CxxWString, mode: u16) -> *mut EnvVar { + EnvironmentFFI::getf_ffi(&*self.0, name, mode) + } + fn get_names(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>) { + EnvironmentFFI::get_names_ffi(&*self.0, flags, out) + } + fn is_principal(&self) -> bool { + self.0.is_principal() + } + + fn get_pwd_slash(&self) -> UniquePtr { + self.0.get_pwd_slash().to_ffi() + } + + fn push(&self, new_scope: bool) { + self.0.push(new_scope) + } + + fn pop(&self) { + self.0.pop() + } + + fn get_last_statuses(&self) -> Box { + Box::new(self.0.get_last_statuses()) + } + + fn set_last_statuses(&self, status: i32, kill_signal: i32, pipestatus: &CxxVector) { + let statuses = Statuses { + status, + kill_signal: if kill_signal == 0 { + None + } else { + Some(Signal::new(kill_signal)) + }, + pipestatus: pipestatus.as_slice().to_vec(), + }; + self.0.set_last_statuses(statuses) + } + + fn set_pwd_from_getcwd(&self) { + self.0.set_pwd_from_getcwd() + } + + fn set(&self, name: &CxxWString, flags: u16, vals: &wcstring_list_ffi_t) -> EnvStackSetResult { + let mode = EnvMode::from_bits(flags).expect("Invalid mode bits"); + self.0.set(name.as_wstr(), mode, vals.from_ffi()) + } + + fn remove(&self, name: &CxxWString, flags: u16) -> EnvStackSetResult { + let mode = EnvMode::from_bits(flags).expect("Invalid mode bits"); + self.0.remove(name.as_wstr(), mode) + } + + fn export_array(&self) -> *mut OwningNullTerminatedArrayRefFFI { + Box::into_raw(Box::new(OwningNullTerminatedArrayRefFFI( + self.0.export_array(), + ))) + } + + fn snapshot(&self) -> Box { + Box::new(EnvDynFFI(self.0.snapshot())) + } + + fn universal_sync( + self: &EnvStackRefFFI, + always: bool, + mut out_events: Pin<&mut event_list_ffi_t>, + ) { + let events: Vec> = self.0.universal_sync(always); + for event in events { + out_events.as_mut().push(Box::into_raw(event).cast()); + } + } +} + +impl Statuses { + fn get_status_ffi(&self) -> i32 { + self.status + } + + fn get_pipestatus_ffi(&self) -> &Vec { + &self.pipestatus + } + + fn get_kill_signal_ffi(&self) -> i32 { + match self.kill_signal { + Some(sig) => sig.code(), + None => 0, + } + } +} + +fn env_get_globals_ffi() -> Box { + Box::new(EnvStackRefFFI(EnvStack::globals().clone())) +} + +fn env_get_principal_ffi() -> Box { + Box::new(EnvStackRefFFI(EnvStack::principal().clone())) +} + +// We have to implement these directly to make cxx happy, even though they're implemented in the EnvironmentFFI trait. +impl EnvNull { + pub fn getf_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar { + EnvironmentFFI::getf_ffi(self, name, mode) + } + pub fn get_names_ffi(&self, flags: u16, out: Pin<&mut wcstring_list_ffi_t>) { + EnvironmentFFI::get_names_ffi(self, flags, out) + } +} + +trait EnvironmentFFI: Environment { + /// FFI helper. + /// This returns either null, or the result of Box.into_raw(). + /// This is a workaround for the difficulty of passing an Option through FFI. + fn getf_ffi(&self, name: &CxxWString, mode: u16) -> *mut EnvVar { + match self.getf( + name.as_wstr(), + EnvMode::from_bits(mode).expect("Invalid mode bits"), + ) { + None => std::ptr::null_mut(), + Some(var) => Box::into_raw(Box::new(var)), + } + } + fn get_names_ffi(&self, mode: u16, mut out: Pin<&mut wcstring_list_ffi_t>) { + let names = self.get_names(EnvMode::from_bits(mode).expect("Invalid mode bits")); + for name in names { + out.as_mut().push(name.to_ffi()); + } + } +} + +impl EnvironmentFFI for T {} + +fn var_is_electric_ffi(name: &CxxWString) -> bool { + ElectricVar::for_name(name.as_wstr()).is_some() +} + +fn rust_env_init_ffi(do_uvars: bool) { + environment::env_init(do_uvars); +} + +fn env_flags_for_ffi(name: wcharz_t) -> u8 { + EnvVar::flags_for(name.as_wstr()).bits() +} diff --git a/src/env.cpp b/src/env.cpp index d62104da5..9e868cb7f 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -50,106 +50,11 @@ /// At init, we read all the environment variables from this array. extern char **environ; -/// The character used to delimit path variables in exporting and in string expansion. -static constexpr wchar_t PATH_ARRAY_SEP = L':'; - bool curses_initialized = false; /// Does the terminal have the "eat_newline_glitch". bool term_has_xn = false; -/// Getter for universal variables. -/// This is typically initialized in env_init(), and is considered empty before then. -static acquired_lock uvars() { - // Leaked to avoid shutdown dtor registration. - static auto const s_universal_variables = new owning_lock(); - return s_universal_variables->acquire(); -} - -/// Set when a universal variable has been modified but not yet been written to disk via sync(). -static relaxed_atomic_bool_t s_uvars_locally_modified{false}; - -/// Whether we were launched with no_config; in this case setting a uvar instead sets a global. -static relaxed_atomic_bool_t s_uvar_scope_is_global{false}; - -namespace { -struct electric_var_t { - enum { - freadonly = 1 << 0, // May not be modified by the user. - fcomputed = 1 << 1, // Value is dynamically computed. - fexports = 1 << 2, // Exported to child processes. - }; - const wchar_t *name; - uint32_t flags; - - bool readonly() const { return flags & freadonly; } - - bool computed() const { return flags & fcomputed; } - - bool exports() const { return flags & fexports; } - - static const electric_var_t *for_name(const wchar_t *name); - static const electric_var_t *for_name(const wcstring &name); -}; - -// Keep sorted alphabetically -static constexpr const electric_var_t electric_variables[] = { - {L"FISH_VERSION", electric_var_t::freadonly}, - {L"PWD", electric_var_t::freadonly | electric_var_t::fcomputed | electric_var_t::fexports}, - {L"SHLVL", electric_var_t::freadonly | electric_var_t::fexports}, - {L"_", electric_var_t::freadonly}, - {L"fish_kill_signal", electric_var_t::freadonly | electric_var_t::fcomputed}, - {L"fish_killring", electric_var_t::freadonly | electric_var_t::fcomputed}, - {L"fish_pid", electric_var_t::freadonly}, - {L"history", electric_var_t::freadonly | electric_var_t::fcomputed}, - {L"hostname", electric_var_t::freadonly}, - {L"pipestatus", electric_var_t::freadonly | electric_var_t::fcomputed}, - {L"status", electric_var_t::freadonly | electric_var_t::fcomputed}, - {L"status_generation", electric_var_t::freadonly | electric_var_t::fcomputed}, - {L"umask", electric_var_t::fcomputed}, - {L"version", electric_var_t::freadonly}, -}; -ASSERT_SORTED_BY_NAME(electric_variables); - -const electric_var_t *electric_var_t::for_name(const wchar_t *name) { - return get_by_sorted_name(name, electric_variables); -} - -const electric_var_t *electric_var_t::for_name(const wcstring &name) { - return electric_var_t::for_name(name.c_str()); -} -} // namespace - -/// Check if a variable may not be set using the set command. -static bool is_read_only(const wchar_t *key) { - if (auto ev = electric_var_t::for_name(key)) { - return ev->readonly(); - } - return false; -} - -static bool is_read_only(const wcstring &key) { return is_read_only(key.c_str()); } - -/// Return true if a variable should become a path variable by default. See #436. -static bool variable_should_auto_pathvar(const wcstring &name) { - return string_suffixes_string(L"PATH", name); -} -// This is a big dorky lock we take around everything that might read from or modify an env_node_t. -// Fine grained locking is annoying here because env_nodes may be shared between env_stacks, so each -// node would need its own lock. -static std::mutex env_lock; - -/// 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. -using export_generation_t = uint64_t; -static export_generation_t next_export_generation() { - static owning_lock s_gen; - auto val = s_gen.acquire(); - return ++*val; -} - // static env_var_t env_var_t::new_ffi(EnvVar *ptr) { assert(ptr != nullptr && "env_var_t::new_ffi called with null pointer"); @@ -185,30 +90,17 @@ env_var_t &env_var_t::operator=(const env_var_t &rhs) { return *this; } -env_var_t::env_var_flags_t env_var_t::flags_for(const wchar_t *name) { - env_var_flags_t result = 0; - if (::is_read_only(name)) result |= flag_read_only; - return result; -} +env_var_t::env_var_t(const wcstring_list_ffi_t &vals, uint8_t flags) + : impl_(env_var_create(vals, flags)) {} env_var_t::env_var_t(const env_var_t &rhs) : impl_(rhs.impl_->clone_box()) {} -env_var_t::env_var_t(std::vector vals, env_var_flags_t flags) - : impl_(env_var_create(std::move(vals), flags)) {} - -env_var_t::env_var_t(const wchar_t *name, std::vector vals) - : impl_(env_var_create_from_name(name, std::move(vals))) {} - bool env_var_t::operator==(const env_var_t &rhs) const { return impl_->equals(*rhs.impl_); } -/// \return a singleton empty list, to avoid unnecessary allocations in env_var_t. -std::shared_ptr> env_var_t::empty_list() { - static const auto s_empty_result = std::make_shared>(); - return s_empty_result; -} - environment_t::~environment_t() = default; +env_var_t::env_var_flags_t env_var_t::flags_for(const wchar_t *name) { return env_flags_for(name); } + wcstring environment_t::get_pwd_slash() const { // Return "/" if PWD is missing. // See https://github.com/fish-shell/fish-shell/issues/5080 @@ -242,15 +134,20 @@ std::unique_ptr environment_t::get_or_null(wcstring const &key, return make_unique(variable.acquire()); } +null_environment_t::null_environment_t() : impl_(env_null_create()) {} null_environment_t::~null_environment_t() = default; + maybe_t null_environment_t::get(const wcstring &key, env_mode_flags_t mode) const { - UNUSED(key); - UNUSED(mode); + if (auto *ptr = impl_->getf(key, mode)) { + return env_var_t::new_ffi(ptr); + } return none(); } + std::vector null_environment_t::get_names(env_mode_flags_t flags) const { - UNUSED(flags); - return {}; + wcstring_list_ffi_t names; + impl_->get_names(flags, names); + return std::move(names.vals); } /// Set up the USER and HOME variable. @@ -355,7 +252,7 @@ void env_init(const struct config_paths_t *paths, bool do_uvars, bool default_pa size_t eql = key_and_val.find(L'='); if (eql == wcstring::npos) { // No equal-sign found so treat it as a defined var that has no value(s). - if (!electric_var_t::for_name(key_and_val)) { + if (!var_is_electric(key_and_val)) { vars.set_empty(key_and_val, ENV_EXPORT | ENV_GLOBAL); } inheriteds[key] = L""; @@ -363,7 +260,7 @@ void env_init(const struct config_paths_t *paths, bool do_uvars, bool default_pa key.assign(key_and_val, 0, eql); val.assign(key_and_val, eql + 1, wcstring::npos); inheriteds[key] = val; - if (!electric_var_t::for_name(key)) { + if (!var_is_electric(key)) { // fish_user_paths should not be exported; attempting to re-import it from // a value we previously (due to user error) exported will cause impossibly // difficult to debug PATH problems. @@ -482,982 +379,51 @@ void env_init(const struct config_paths_t *paths, bool do_uvars, bool default_pa path_emit_config_directory_messages(vars); } - // Initialize our uvars if requested. - if (!do_uvars) { - s_uvar_scope_is_global = true; - } else { - // Set up universal variables using the default path. - callback_data_list_t callbacks; - uvars()->initialize(callbacks); - for (const callback_data_t &cb : callbacks) { - env_dispatch_var_change(cb.key, vars); - } - - // Do not import variables that have the same name and value as - // an exported universal variable. See issues #5258 and #5348. - var_table_t table = uvars()->get_table(); - for (const auto &kv : table) { - const wcstring &name = kv.first; - const env_var_t &uvar = kv.second; - if (!uvar.exports()) continue; - // Look for a global exported variable with the same name. - maybe_t global = vars.globals().get(name, ENV_GLOBAL | ENV_EXPORT); - if (global && uvar.as_string() == global->as_string()) { - vars.globals().remove(name, ENV_GLOBAL | ENV_EXPORT); - } - } - - // Import any abbreviations from uvars. - // Note we do not dynamically react to changes. - const wchar_t *const prefix = L"_fish_abbr_"; - size_t prefix_len = wcslen(prefix); - const bool from_universal = true; - auto abbrs = abbrs_get_set(); - for (const auto &kv : table) { - if (string_prefixes_string(prefix, kv.first)) { - wcstring escaped_name = kv.first.substr(prefix_len); - if (auto name = - unescape_string(escaped_name, unescape_flags_t{}, STRING_STYLE_VAR)) { - wcstring key = *name; - wcstring replacement = join_strings(kv.second.as_list(), L' '); - abbrs->add(std::move(*name), std::move(key), std::move(replacement), - abbrs_position_t::command, from_universal); - } - } - } - } + rust_env_init(do_uvars); } -static int set_umask(const std::vector &list_val) { - long mask = -1; - if (list_val.size() == 1 && !list_val.front().empty()) { - mask = fish_wcstol(list_val.front().c_str(), nullptr, 8); - } - - if (errno || mask > 0777 || mask < 0) return ENV_INVALID; - // Do not actually create a umask variable. On env_stack_t::get() it will be calculated. - umask(mask); - return ENV_OK; -} - -namespace { -struct query_t { - // Whether any scopes were specified. - bool has_scope; - - // Whether to search local, function, global, universal scopes. - bool local; - bool function; - bool global; - bool universal; - - // Whether export or unexport was specified. - bool has_export_unexport; - - // Whether to search exported and unexported variables. - bool exports; - bool unexports; - - // Whether pathvar or unpathvar was set. - bool has_pathvar_unpathvar; - bool pathvar; - bool unpathvar; - - // Whether this is a "user" set. - bool user; - - explicit query_t(env_mode_flags_t mode) { - has_scope = mode & (ENV_LOCAL | ENV_FUNCTION | ENV_GLOBAL | ENV_UNIVERSAL); - local = !has_scope || (mode & ENV_LOCAL); - function = !has_scope || (mode & ENV_FUNCTION); - global = !has_scope || (mode & ENV_GLOBAL); - universal = !has_scope || (mode & ENV_UNIVERSAL); - - has_export_unexport = mode & (ENV_EXPORT | ENV_UNEXPORT); - exports = !has_export_unexport || (mode & ENV_EXPORT); - unexports = !has_export_unexport || (mode & ENV_UNEXPORT); - - // note we don't use pathvar for searches, so these don't default to true if unspecified. - has_pathvar_unpathvar = mode & (ENV_PATHVAR | ENV_UNPATHVAR); - pathvar = mode & ENV_PATHVAR; - unpathvar = mode & ENV_UNPATHVAR; - - user = mode & ENV_USER; - } - - bool export_matches(const env_var_t &var) const { - if (has_export_unexport) { - return var.exports() ? exports : unexports; - } else { - return true; - } - } - - bool pathvar_matches(const env_var_t &var) const { - if (has_pathvar_unpathvar) { - return var.is_pathvar() ? pathvar : unpathvar; - } else { - return true; - } - } -}; - -// Struct representing one level in the function variable stack. -class env_node_t { - public: - /// Variable table. - var_table_t env; - /// 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. - const bool new_scope; - /// 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_generation_t export_gen = 0; - /// Pointer to next level. - const std::shared_ptr next; - - env_node_t(bool is_new_scope, std::shared_ptr next_scope) - : new_scope(is_new_scope), next(std::move(next_scope)) {} - - maybe_t find_entry(const wcstring &key) { - auto it = env.find(key); - if (it != env.end()) return it->second; - return none(); - } - - bool exports() const { return export_gen > 0; } - - void changed_exported() { export_gen = next_export_generation(); } -}; -} // namespace - -using env_node_ref_t = std::shared_ptr; -class env_scoped_impl_t : public environment_t, noncopyable_t { - /// A struct wrapping up parser-local variables. These are conceptually variables that differ in - /// different fish internal processes. - struct perproc_data_t { - wcstring pwd{}; - statuses_t statuses{statuses_t::just(0)}; - }; - - public: - env_scoped_impl_t(env_node_ref_t locals, env_node_ref_t globals) - : locals_(std::move(locals)), globals_(std::move(globals)) { - assert(locals_ && globals_ && "Nodes cannot be null"); - } - - maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override; - std::vector get_names(env_mode_flags_t flags) const override; - - perproc_data_t &perproc_data() { return perproc_data_; } - const perproc_data_t &perproc_data() const { return perproc_data_; } - - std::shared_ptr snapshot() const; - - ~env_scoped_impl_t() override = default; - - std::shared_ptr export_array(); - - protected: - // A linked list of scopes. - env_node_ref_t locals_{}; - - // Global scopes. There is no parent here. - env_node_ref_t globals_{}; - - // Per process data. - perproc_data_t perproc_data_{}; - - // Exported variable array used by execv. - std::shared_ptr export_array_{}; - - // 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. - std::vector export_array_generations_{}; - - private: - // These "try" methods return true on success, false on failure. On a true return, \p result is - // populated. A maybe_t> is a bridge too far. - // These may populate result with none() if a variable is present which does not match the - // query. - maybe_t try_get_computed(const wcstring &key) const; - maybe_t try_get_local(const wcstring &key) const; - maybe_t try_get_function(const wcstring &key) const; - maybe_t try_get_global(const wcstring &key) const; - maybe_t try_get_universal(const wcstring &key) const; - - /// Invoke a function on the current (nonzero) export generations, in order. - template - void enumerate_generations(const Func &func) const { - // 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 (globals_->exports()) func(globals_->export_gen); - for (auto node = locals_; node; node = node->next) { - if (node->exports()) func(node->export_gen); - } - } - - /// \return whether the current export array is empty or out-of-date. - bool export_array_needs_regeneration() const; - - /// \return a newly allocated export array. - std::shared_ptr create_export_array() const; -}; - -/// Get the exported variables into a variable table. -static void get_exported(const env_node_ref_t &n, var_table_t &table) { - if (!n) return; - - // Allow parent scopes to populate first, since we may want to overwrite those results. - get_exported(n->next, table); - - for (const auto &kv : n->env) { - const wcstring &key = kv.first; - const env_var_t &var = kv.second; - if (var.exports()) { - // Export the variable. Don't use std::map::insert here, since we need to overwrite - // existing values from previous scopes. - table[key] = var; - } else { - // We need to erase from the map if we are not exporting, since a lower scope may have - // exported. See #2132. - table.erase(key); - } - } -} - -bool env_scoped_impl_t::export_array_needs_regeneration() const { - // 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 (!export_array_) return true; - - bool mismatch = false; - auto cursor = export_array_generations_.begin(); - auto end = export_array_generations_.end(); - enumerate_generations([&](export_generation_t gen) { - if (cursor != end && *cursor == gen) { - ++cursor; - } else { - mismatch = true; - } - }); - if (cursor != end) { - mismatch = true; - } - return mismatch; -} - -std::shared_ptr env_scoped_impl_t::create_export_array() const { - FLOG(env_export, L"create_export_array() recalc"); - var_table_t vals; - get_exported(this->globals_, vals); - get_exported(this->locals_, vals); - - const std::vector uni = uvars()->get_names(true, false); - for (const wcstring &key : uni) { - auto var = uvars()->get(key); - assert(var && "Variable should be present in uvars"); - // Note that std::map::insert does NOT overwrite a value already in the map, - // which we depend on here. - // Note: Using std::move around emplace prevents the compiler from implementing - // copy elision. - vals.emplace(key, std::move(*var)); - } - - // Dorky way to add our single exported computed variable. - vals[L"PWD"] = env_var_t(L"PWD", perproc_data().pwd); - - // Construct the export list: a list of strings of the form key=value. - std::vector export_list; - export_list.reserve(vals.size()); - for (const auto &kv : vals) { - std::string str = wcs2zstring(kv.first); - str.push_back('='); - str.append(wcs2zstring(kv.second.as_string())); - export_list.push_back(std::move(str)); - } - return std::make_shared(std::move(export_list)); -} - -std::shared_ptr env_scoped_impl_t::export_array() { - ASSERT_IS_NOT_FORKED_CHILD(); - if (export_array_needs_regeneration()) { - export_array_ = create_export_array(); - - // Update our export array generations. - export_array_generations_.clear(); - enumerate_generations( - [this](export_generation_t gen) { export_array_generations_.push_back(gen); }); - } - return export_array_; -} - -maybe_t env_scoped_impl_t::try_get_computed(const wcstring &key) const { - const electric_var_t *ev = electric_var_t::for_name(key); - if (!(ev && ev->computed())) { - return none(); - } - if (key == L"PWD") { - return env_var_t(perproc_data().pwd, env_var_t::flag_export); - } else if (key == L"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(); - } - - std::shared_ptr history = commandline_get_state().history; - if (!history) { - history = history_t::with_name(history_session_id(*this)); - } - std::vector result; - if (history) history->get_history(result); - return env_var_t(L"history", std::move(result)); - } else if (key == L"fish_killring") { - return env_var_t(L"fish_killring", kill_entries()); - } else if (key == L"pipestatus") { - const auto &js = perproc_data().statuses; - std::vector result; - result.reserve(js.pipestatus.size()); - for (int i : js.pipestatus) { - result.push_back(to_string(i)); - } - return env_var_t(L"pipestatus", std::move(result)); - } else if (key == L"status") { - const auto &js = perproc_data().statuses; - return env_var_t(L"status", to_string(js.status)); - } else if (key == L"status_generation") { - auto status_generation = reader_status_count(); - return env_var_t(L"status_generation", to_string(status_generation)); - } else if (key == L"fish_kill_signal") { - const auto &js = perproc_data().statuses; - return env_var_t(L"fish_kill_signal", to_string(js.kill_signal)); - } else if (key == L"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. - mode_t guess = 022; - mode_t res = umask(guess); - if (res != guess) umask(res); - return env_var_t(L"umask", format_string(L"0%0.3o", res)); - } - // We should never get here unless the electric var list is out of sync with the above code. - DIE("unrecognized computed var name"); -} - -maybe_t env_scoped_impl_t::try_get_local(const wcstring &key) const { - maybe_t entry; - for (auto cur = locals_; cur; cur = cur->next) { - if ((entry = cur->find_entry(key))) break; - } - return entry; // this is either the entry or none() from find_entry -} - -maybe_t env_scoped_impl_t::try_get_function(const wcstring &key) const { - maybe_t entry; - auto node = locals_; - while (node->next) { - node = node->next; - // 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->new_scope) break; - } - for (auto cur = node; cur; cur = cur->next) { - if ((entry = cur->find_entry(key))) break; - } - return entry; // this is either the entry or none() from find_entry -} - -maybe_t env_scoped_impl_t::try_get_global(const wcstring &key) const { - return globals_->find_entry(key); -} - -maybe_t env_scoped_impl_t::try_get_universal(const wcstring &key) const { - return uvars()->get(key); -} - -maybe_t env_scoped_impl_t::get(const wcstring &key, env_mode_flags_t mode) const { - const query_t query(mode); - - maybe_t result; - // Computed variables are effectively global and can't be shadowed. - if (query.global) { - result = try_get_computed(key); - } - - if (!result && query.local) { - result = try_get_local(key); - } - if (!result && query.function) { - result = try_get_function(key); - } - if (!result && query.global) { - result = try_get_global(key); - } - if (!result && query.universal) { - result = try_get_universal(key); - } - // If the user requested only exported or unexported variables, enforce that here. - if (result && !query.export_matches(*result)) { - result = none(); - } - // Same for pathvars - if (result && !query.pathvar_matches(*result)) { - result = none(); - } - return result; -} - -std::vector env_scoped_impl_t::get_names(env_mode_flags_t flags) const { - const query_t query(flags); - std::set names; - - // Helper to add the names of variables from \p envs to names, respecting show_exported and - // show_unexported. - auto add_keys = [&](const var_table_t &envs) { - for (const auto &kv : envs) { - if (query.export_matches(kv.second)) { - names.insert(kv.first); - } - } - }; - - if (query.local) { - for (auto cursor = locals_; cursor != nullptr; cursor = cursor->next) { - add_keys(cursor->env); - } - } - - if (query.global) { - add_keys(globals_->env); - // Add electrics. - for (const auto &ev : electric_variables) { - if (ev.exports() ? query.exports : query.unexports) { - names.insert(ev.name); - } - } - } - - if (query.universal) { - const std::vector uni_list = uvars()->get_names(query.exports, query.unexports); - names.insert(uni_list.begin(), uni_list.end()); - } - - return {names.begin(), names.end()}; -} - -/// Recursive helper to snapshot a series of nodes. -static env_node_ref_t copy_node_chain(const env_node_ref_t &node) { - if (node == nullptr) { - return nullptr; - } - - auto next = copy_node_chain(node->next); - auto result = std::make_shared(node->new_scope, next); - // Copy over variables. - // Note assigning env is a potentially big copy. - result->export_gen = node->export_gen; - result->env = node->env; - return result; -} - -std::shared_ptr env_scoped_impl_t::snapshot() const { - auto ret = std::make_shared(copy_node_chain(locals_), globals_); - ret->perproc_data_ = this->perproc_data_; - return ret; -} - -// A struct that wraps up the result of setting or removing a variable. -namespace { -struct mod_result_t { - // The publicly visible status of the set call. - int status{ENV_OK}; - - // Whether we modified the global scope. - bool global_modified{false}; - - // Whether we modified universal variables. - bool uvar_modified{false}; - - explicit mod_result_t(int status) : status(status) {} -}; -} // namespace - -/// A mutable subclass of env_scoped_impl_t. -class env_stack_impl_t final : public env_scoped_impl_t { - public: - using env_scoped_impl_t::env_scoped_impl_t; - - /// Set a variable under the name \p key, using the given \p mode, setting its value to \p val. - mod_result_t set(const wcstring &key, env_mode_flags_t mode, std::vector val); - - /// Remove a variable under the name \p key. - mod_result_t remove(const wcstring &key, int var_mode); - - /// Push a new shadowing local scope. - void push_shadowing(); - - /// Push a new non-shadowing (inner) local scope. - void push_nonshadowing(); - - /// Pop the variable stack. - /// \return the popped node. - env_node_ref_t pop(); - - /// \return a new impl representing global variables, with a single local scope. - static std::unique_ptr create() { - static const auto s_global_node = std::make_shared(false, nullptr); - auto local = std::make_shared(false, nullptr); - return make_unique(std::move(local), s_global_node); - } - - ~env_stack_impl_t() override = default; - - private: - /// The scopes of caller functions, which are currently shadowed. - std::vector shadowed_locals_; - - /// A restricted set of variable flags. - struct var_flags_t { - // if set, whether we should become a path variable; otherwise guess based on the name. - maybe_t pathvar{}; - - // if set, the new export value; otherwise inherit any existing export value. - maybe_t exports{}; - - // whether the variable is exported by some parent. - bool parent_exports{}; - }; - - /// Find the first node in the chain starting at \p node which contains the given key \p key. - static env_node_ref_t find_in_chain(const env_node_ref_t &node, const wcstring &key) { - for (auto cursor = node; cursor; cursor = cursor->next) { - if (cursor->env.count(key)) { - return cursor; - } - } - return nullptr; - } - - /// Remove a variable from the chain \p node. - /// \return true if the variable was found and removed. - bool remove_from_chain(const env_node_ref_t &node, const wcstring &key) const { - for (auto cursor = node; cursor; cursor = cursor->next) { - auto iter = cursor->env.find(key); - if (iter != cursor->env.end()) { - if (iter->second.exports()) { - node->changed_exported(); - } - cursor->env.erase(iter); - return true; - } - } - return false; - } - - /// Try setting\p key as an electric or readonly variable. - /// \return an error code, or none() if not an electric or readonly variable. - /// \p val will not be modified upon a none() return. - maybe_t try_set_electric(const wcstring &key, const query_t &query, - std::vector &val); - - /// Set a universal value. - void set_universal(const wcstring &key, std::vector val, const query_t &query); - - /// Set a variable in a given node \p node. - void set_in_node(const env_node_ref_t &node, const wcstring &key, std::vector &&val, - const var_flags_t &flags); - - // Implement the default behavior of 'set' by finding the node for an unspecified scope. - env_node_ref_t resolve_unspecified_scope() { - for (auto cursor = locals_; cursor; cursor = cursor->next) { - if (cursor->new_scope) return cursor; - } - return globals_; - } - - /// Get a pointer to an existing variable, or nullptr. - /// This is used for inheriting pathvar and export status. - const env_var_t *find_variable(const wcstring &key) const { - env_node_ref_t node = find_in_chain(locals_, key); - if (!node) node = find_in_chain(globals_, key); - if (node) { - auto iter = node->env.find(key); - assert(iter != node->env.end() && "Node should contain key"); - return &iter->second; - } - return nullptr; - } -}; - -void env_stack_impl_t::push_nonshadowing() { - locals_ = std::make_shared(false, locals_); -} - -void env_stack_impl_t::push_shadowing() { - // Propagate local exported variables. - auto node = std::make_shared(true, nullptr); - for (auto cursor = locals_; cursor; cursor = cursor->next) { - for (const auto &var : cursor->env) { - if (var.second.exports()) { - node->env.insert(var); - node->changed_exported(); - } - } - } - this->shadowed_locals_.push_back(std::move(locals_)); - this->locals_ = std::move(node); -} - -env_node_ref_t env_stack_impl_t::pop() { - auto popped = std::move(locals_); - if (popped->next) { - // Pop the inner scope. - locals_ = popped->next; - } else { - // Exhausted the inner scopes, put back a shadowing scope. - assert(!shadowed_locals_.empty() && "Attempt to pop last local scope"); - locals_ = std::move(shadowed_locals_.back()); - shadowed_locals_.pop_back(); - } - assert(locals_ && "Attempt to pop first local scope"); - return popped; -} - -/// Apply the pathvar behavior, splitting about colons. -static std::vector colon_split(const std::vector &val) { - std::vector split_val; - split_val.reserve(val.size()); - for (const wcstring &str : val) { - vec_append(split_val, split_string(str, PATH_ARRAY_SEP)); - } - return split_val; -} - -void env_stack_impl_t::set_in_node(const env_node_ref_t &node, const wcstring &key, - std::vector &&val, const var_flags_t &flags) { - env_var_t &var = node->env[key]; - - // Use an explicit exports, or inherit from the existing variable. - bool res_exports = flags.exports.has_value() ? *flags.exports : var.exports(); - - // Pathvar is inferred from the name. If set, split our entry about colons. - bool res_pathvar = - flags.pathvar.has_value() ? *flags.pathvar : variable_should_auto_pathvar(key); - if (res_pathvar) { - val = colon_split(val); - } - - var = - var.setting_vals(std::move(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->changed_exported(); - } -} - -maybe_t env_stack_impl_t::try_set_electric(const wcstring &key, const query_t &query, - std::vector &val) { - const electric_var_t *ev = electric_var_t::for_name(key); - if (!ev) { - return none(); - } - - // If a variable is electric, it may only be set in the global scope. - if (query.has_scope && !query.global) { - return ENV_SCOPE; - } - - // If the variable is read-only, the user may not set it. - if (query.user && ev->readonly()) { - return ENV_PERM; - } - - // Be picky about exporting. - if (query.has_export_unexport) { - if (ev->exports() ? query.unexports : query.exports) { - return ENV_SCOPE; - } - } - - // Handle computed mutable electric variables. - if (key == L"umask") { - return set_umask(val); - } else if (key == L"PWD") { - assert(val.size() == 1 && "Should have exactly one element in PWD"); - wcstring &pwd = val.front(); - if (pwd != perproc_data().pwd) { - perproc_data().pwd = std::move(pwd); - globals_->changed_exported(); - } - return ENV_OK; - } - - // Decide on the mode and set it in the global scope. - var_flags_t flags{}; - flags.exports = ev->exports(); - flags.parent_exports = ev->exports(); - flags.pathvar = false; - set_in_node(globals_, key, std::move(val), flags); - return ENV_OK; -} - -/// Set a universal variable, inheriting as applicable from the given old variable. -void env_stack_impl_t::set_universal(const wcstring &key, std::vector val, - const query_t &query) { - auto oldvar = uvars()->get(key); - // Resolve whether or not to export. - bool exports = false; - if (query.has_export_unexport) { - exports = query.exports; - } else if (oldvar) { - exports = oldvar->exports(); - } - - // Resolve whether to be a path variable. - // Here we fall back to the auto-pathvar behavior. - bool pathvar = false; - if (query.has_pathvar_unpathvar) { - pathvar = query.pathvar; - } else if (oldvar) { - pathvar = oldvar->is_pathvar(); - } else { - pathvar = variable_should_auto_pathvar(key); - } - - // Split about ':' if it's a path variable. - if (pathvar) { - std::vector split_val; - for (const wcstring &str : val) { - vec_append(split_val, split_string(str, PATH_ARRAY_SEP)); - } - val = std::move(split_val); - } - - // Construct and set the new variable. - env_var_t::env_var_flags_t varflags = 0; - if (exports) varflags |= env_var_t::flag_export; - if (pathvar) varflags |= env_var_t::flag_pathvar; - env_var_t new_var{val, varflags}; - - uvars()->set(key, new_var); -} - -mod_result_t env_stack_impl_t::set(const wcstring &key, env_mode_flags_t mode, - std::vector val) { - const query_t query(mode); - // Handle electric and read-only variables. - auto ret = try_set_electric(key, query, val); - if (ret.has_value()) { - return mod_result_t{*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. - var_flags_t flags{}; - if (const env_var_t *existing = find_variable(key)) { - flags.pathvar = existing->is_pathvar(); - flags.parent_exports = existing->exports(); - } - if (query.has_export_unexport) { - flags.exports = query.exports; - } - if (query.has_pathvar_unpathvar) { - flags.pathvar = query.pathvar; - } - - mod_result_t result{ENV_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 && !s_uvar_scope_is_global) { - set_universal(key, std::move(val), query); - result.uvar_modified = true; - } else if (query.global || (query.universal && s_uvar_scope_is_global)) { - set_in_node(globals_, key, std::move(val), flags); - result.global_modified = true; - } else if (query.local) { - assert(locals_ != globals_ && "Locals should not be globals"); - set_in_node(locals_, key, std::move(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. - auto node = locals_; - while (node->next) { - node = node->next; - // 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->new_scope) break; - } - set_in_node(node, key, std::move(val), flags); - } else { - DIE("Unknown scope"); - } - } else if (env_node_ref_t node = find_in_chain(locals_, key)) { - // Existing local variable. - set_in_node(node, key, std::move(val), flags); - } else if (env_node_ref_t node = find_in_chain(globals_, key)) { - // Existing global variable. - set_in_node(node, key, std::move(val), flags); - result.global_modified = true; - } else if (uvars()->get(key)) { - // Existing universal variable. - set_universal(key, std::move(val), query); - result.uvar_modified = true; - } else { - // Unspecified scope with no existing variables. - node = resolve_unspecified_scope(); - assert(node && "Should always resolve some scope"); - set_in_node(node, key, std::move(val), flags); - result.global_modified = (node == globals_); - } - return result; -} - -mod_result_t env_stack_impl_t::remove(const wcstring &key, int mode) { - const query_t query(mode); - - // Users can't remove read-only keys. - if (query.user && is_read_only(key)) { - return mod_result_t{ENV_SCOPE}; - } - - mod_result_t result{ENV_OK}; - if (query.has_scope) { - // The user requested erasing from a particular scope. - if (query.universal) { - result.status = uvars()->remove(key) ? ENV_OK : ENV_NOT_FOUND; - result.uvar_modified = true; - } else if (query.global) { - result.status = remove_from_chain(globals_, key) ? ENV_OK : ENV_NOT_FOUND; - result.global_modified = true; - } else if (query.local) { - result.status = remove_from_chain(locals_, key) ? ENV_OK : ENV_NOT_FOUND; - } else if (query.function) { - auto node = locals_; - while (node->next) { - node = node->next; - if (node->new_scope) break; - } - result.status = remove_from_chain(node, key) ? ENV_OK : ENV_NOT_FOUND; - } else { - DIE("Unknown scope"); - } - } else if (remove_from_chain(locals_, key)) { - // pass - } else if (remove_from_chain(globals_, key)) { - result.global_modified = true; - } else if (uvars()->remove(key)) { - result.uvar_modified = true; - } else { - result.status = ENV_NOT_FOUND; - } - return result; -} +bool env_stack_t::is_principal() const { return impl_->is_principal(); } std::vector> env_stack_t::universal_sync(bool always) { - if (s_uvar_scope_is_global) return {}; - if (!always && !s_uvars_locally_modified) return {}; - s_uvars_locally_modified = false; - - callback_data_list_t callbacks; - bool changed = uvars()->sync(callbacks); - if (changed) { - universal_notifier_t::default_notifier().post_notification(); - } - // React internally to changes to special variables like LANG, and populate on-variable events. - std::vector> result; - for (const callback_data_t &cb : callbacks) { - env_dispatch_var_change(cb.key, *this); - auto evt = - cb.is_erase() ? new_event_variable_erase(cb.key) : new_event_variable_set(cb.key); - result.push_back(std::move(evt)); - } - return result; + event_list_ffi_t result; + impl_->universal_sync(always, result); + return std::move(result.events); } statuses_t env_stack_t::get_last_statuses() const { - return acquire_impl()->perproc_data().statuses; + auto statuses_ffi = impl_->get_last_statuses(); + statuses_t res{}; + res.status = statuses_ffi->get_status(); + res.kill_signal = statuses_ffi->get_kill_signal(); + auto &pipestatus = statuses_ffi->get_pipestatus(); + res.pipestatus.assign(pipestatus.begin(), pipestatus.end()); + return res; } -int env_stack_t::get_last_status() const { return acquire_impl()->perproc_data().statuses.status; } +int env_stack_t::get_last_status() const { return get_last_statuses().status; } void env_stack_t::set_last_statuses(statuses_t s) { - acquire_impl()->perproc_data().statuses = std::move(s); + return impl_->set_last_statuses(s.status, s.kill_signal, s.pipestatus); } /// Update the PWD variable directory from the result of getcwd(). -void env_stack_t::set_pwd_from_getcwd() { - wcstring cwd = wgetcwd(); - if (cwd.empty()) { - FLOG(error, - _(L"Could not determine current working directory. Is your locale set correctly?")); - return; - } - set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, cwd); -} - -env_stack_t::env_stack_t(std::unique_ptr impl) : impl_(std::move(impl)) {} - -acquired_lock env_stack_t::acquire_impl() { - return acquired_lock::from_global(env_lock, impl_.get()); -} - -acquired_lock env_stack_t::acquire_impl() const { - return acquired_lock::from_global(env_lock, impl_.get()); -} +void env_stack_t::set_pwd_from_getcwd() { impl_->set_pwd_from_getcwd(); } maybe_t env_stack_t::get(const wcstring &key, env_mode_flags_t mode) const { - return acquire_impl()->get(key, mode); + if (auto *ptr = impl_->getf(key, mode)) { + return env_var_t::new_ffi(ptr); + } + return none(); } std::vector env_stack_t::get_names(env_mode_flags_t flags) const { - return acquire_impl()->get_names(flags); + wcstring_list_ffi_t names; + impl_->get_names(flags, names); + return std::move(names.vals); } int env_stack_t::set(const wcstring &key, env_mode_flags_t mode, std::vector vals) { - // Historical behavior. - if (vals.size() == 1 && (key == L"PWD" || key == L"HOME")) { - path_make_canonical(vals.front()); - } - - // Hacky stuff around PATH and CDPATH: #3914. - // Not MANPATH; see #4158. - // Replace empties with dot. Note we ignore pathvar here. - if (key == L"PATH" || key == L"CDPATH") { - auto munged_vals = colon_split(vals); - std::replace(munged_vals.begin(), munged_vals.end(), wcstring(L""), wcstring(L".")); - vals = std::move(munged_vals); - } - - mod_result_t ret = acquire_impl()->set(key, mode, std::move(vals)); - if (ret.status == ENV_OK) { - // If we modified the global state, or we are principal, then dispatch changes. - // Important to not hold the lock here. - if (ret.global_modified || is_principal()) { - env_dispatch_var_change(key, *this); - } - } - // Mark if we modified a uvar. - if (ret.uvar_modified) { - s_uvars_locally_modified = true; - } - return ret.status; + return static_cast(impl_->set(key, mode, std::move(vals))); } int env_stack_t::set_ffi(const wcstring &key, env_mode_flags_t mode, const void *vals, @@ -1477,70 +443,69 @@ int env_stack_t::set_empty(const wcstring &key, env_mode_flags_t mode) { } int env_stack_t::remove(const wcstring &key, int mode) { - mod_result_t ret = acquire_impl()->remove(key, mode); - if (ret.status == ENV_OK) { - if (ret.global_modified || is_principal()) { - // Important to not hold the lock here. - env_dispatch_var_change(key, *this); - } - } - if (ret.uvar_modified) { - s_uvars_locally_modified = true; - } - return ret.status; + return static_cast(impl_->remove(key, mode)); } std::shared_ptr env_stack_t::export_arr() { - return acquire_impl()->export_array(); + // export_array() returns a rust::Box. + // Acquire ownership. + OwningNullTerminatedArrayRefFFI *ptr = impl_->export_array(); + assert(ptr && "Null pointer"); + return std::make_shared( + rust::Box::from_raw(ptr)); } -std::shared_ptr env_stack_t::snapshot() const { return acquire_impl()->snapshot(); } +/// Wrapper around a EnvDyn. +class env_dyn_t final : public environment_t { + public: + env_dyn_t(rust::Box impl) : impl_(std::move(impl)) {} + + maybe_t get(const wcstring &key, env_mode_flags_t mode) const { + if (auto *ptr = impl_->getf(key, mode)) { + return env_var_t::new_ffi(ptr); + } + return none(); + } + + std::vector get_names(env_mode_flags_t flags) const { + wcstring_list_ffi_t names; + impl_->get_names(flags, names); + return std::move(names.vals); + } + + private: + rust::Box impl_; +}; + +std::shared_ptr env_stack_t::snapshot() const { + auto res = std::make_shared(impl_->snapshot()); + return std::static_pointer_cast(res); +} void env_stack_t::set_argv(std::vector argv) { set(L"argv", ENV_LOCAL, std::move(argv)); } wcstring env_stack_t::get_pwd_slash() const { - wcstring pwd = acquire_impl()->perproc_data().pwd; - if (!string_suffixes_string(L"/", pwd)) { - pwd.push_back(L'/'); - } - return pwd; + std::unique_ptr res = impl_->get_pwd_slash(); + return std::move(*res); } -void env_stack_t::push(bool new_scope) { - auto impl = acquire_impl(); - if (new_scope) { - impl->push_shadowing(); - } else { - impl->push_nonshadowing(); - } -} +void env_stack_t::push(bool new_scope) { impl_->push(new_scope); } -void env_stack_t::pop() { - auto popped = acquire_impl()->pop(); - // Only dispatch variable changes if we are the principal environment. - if (this == principal_ref().get()) { - // TODO: we would like to coalesce locale / curses changes, so that we only re-initialize - // once. - for (const auto &kv : popped->env) { - env_dispatch_var_change(kv.first, *this); - } - } -} +void env_stack_t::pop() { impl_->pop(); } env_stack_t &env_stack_t::globals() { - static env_stack_t s_globals(env_stack_impl_t::create()); + static env_stack_t s_globals(env_get_globals_ffi()); return s_globals; } const std::shared_ptr &env_stack_t::principal_ref() { - static const std::shared_ptr s_principal{ - new env_stack_t(env_stack_impl_t::create())}; + static const std::shared_ptr s_principal{new env_stack_t(env_get_principal_ffi())}; return s_principal; } env_stack_t::~env_stack_t() = default; - env_stack_t::env_stack_t(env_stack_t &&) = default; +env_stack_t::env_stack_t(rust::Box imp) : impl_(std::move(imp)) {} #if defined(__APPLE__) || defined(__CYGWIN__) static int check_runtime_path(const char *path) { diff --git a/src/env.h b/src/env.h index de997cec2..8b0dc4410 100644 --- a/src/env.h +++ b/src/env.h @@ -17,10 +17,14 @@ #include "maybe.h" #include "wutil.h" +struct event_list_ffi_t; + #if INCLUDE_RUST_HEADERS #include "env/env_ffi.rs.h" #else struct EnvVar; +struct EnvNull; +struct EnvStackRef; #endif /// FFI helper for events. @@ -42,8 +46,6 @@ struct owning_null_terminated_array_t; extern size_t read_byte_limit; extern bool curses_initialized; -struct Event; - // Flags that may be passed as the 'mode' in env_stack_t::set() / environment_t::get(). enum : uint16_t { /// Default mode. Used with `env_stack_t::get()` to indicate the caller doesn't care what scope @@ -114,32 +116,23 @@ void env_init(const struct config_paths_t *paths = nullptr, bool do_uvars = true void misc_init(); /// env_var_t is an immutable value-type data structure representing the value of an environment -/// variable. +/// variable. This wraps the EnvVar type from Rust. class env_var_t { public: using env_var_flags_t = uint8_t; - - public: enum { flag_export = 1 << 0, // whether the variable is exported flag_read_only = 1 << 1, // whether the variable is read only flag_pathvar = 1 << 2, // whether the variable is a path variable }; - - // Constructors. - env_var_t() : env_var_t{std::vector{}, 0} {} + env_var_t() : env_var_t(wcstring_list_ffi_t{}, 0) {} + env_var_t(const wcstring_list_ffi_t &vals, uint8_t flags); env_var_t(const env_var_t &); env_var_t(env_var_t &&) = default; - env_var_t(std::vector vals, env_var_flags_t flags); env_var_t(wcstring val, env_var_flags_t flags) : env_var_t{std::vector{std::move(val)}, flags} {} - // Constructors that infer the flags from a name. - env_var_t(const wchar_t *name, std::vector vals); - env_var_t(const wchar_t *name, wcstring val) - : env_var_t{name, std::vector{std::move(val)}} {} - // Construct from FFI. This transfers ownership of the EnvVar, which should originate // in Box::into_raw(). static env_var_t new_ffi(EnvVar *ptr); @@ -163,33 +156,7 @@ class env_var_t { /// \return the character used when delimiting quoted expansion. wchar_t get_delimiter() const; - /// \return a copy of this variable with new values. - env_var_t setting_vals(std::vector vals) const { - return env_var_t{std::move(vals), get_flags()}; - } - - env_var_t setting_exports(bool exportv) const { - env_var_flags_t flags = get_flags(); - if (exportv) { - flags |= flag_export; - } else { - flags &= ~flag_export; - } - return env_var_t{as_list(), flags}; - } - - env_var_t setting_pathvar(bool pathvar) const { - env_var_flags_t flags = get_flags(); - if (pathvar) { - flags |= flag_pathvar; - } else { - flags &= ~flag_pathvar; - } - return env_var_t{as_list(), flags}; - } - static env_var_flags_t flags_for(const wchar_t *name); - static std::shared_ptr> empty_list(); env_var_t &operator=(const env_var_t &); env_var_t &operator=(env_var_t &&) = default; @@ -228,29 +195,22 @@ class environment_t { /// The null environment contains nothing. class null_environment_t : public environment_t { public: - null_environment_t() = default; - ~null_environment_t() override; + null_environment_t(); + ~null_environment_t(); maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override; std::vector get_names(env_mode_flags_t flags) const override; + + private: + rust::Box impl_; }; /// A mutable environment which allows scopes to be pushed and popped. -class env_stack_impl_t; class env_stack_t final : public environment_t { friend class parser_t; - /// The implementation. Do not access this directly. - std::unique_ptr impl_; - - /// All environment stacks are guarded by a global lock. - acquired_lock acquire_impl(); - acquired_lock acquire_impl() const; - - explicit env_stack_t(std::unique_ptr impl); - /// \return whether we are the principal stack. - bool is_principal() const { return this == principal_ref().get(); } + bool is_principal() const; public: ~env_stack_t() override; @@ -334,6 +294,12 @@ class env_stack_t final : public environment_t { // Access a variable stack that only represents globals. // Do not push or pop from this. static env_stack_t &globals(); + + private: + env_stack_t(rust::Box imp); + + /// The implementation. Do not access this directly. + rust::Box impl_; }; bool get_use_posix_spawn(); diff --git a/src/event.h b/src/event.h index 7602d51c4..d7ed502cc 100644 --- a/src/event.h +++ b/src/event.h @@ -2,7 +2,6 @@ // replacement. There is no logic still in here that needs to be ported to rust. #ifndef FISH_EVENT_H -#ifdef INCLUDE_RUST_HEADERS #define FISH_EVENT_H #include @@ -13,11 +12,16 @@ #include #include "common.h" +#include "cxx.h" #include "global_safety.h" #include "wutil.h" class parser_t; +#if INCLUDE_RUST_HEADERS #include "event.rs.h" +#else +struct Event; +#endif /// The process id that is used to match any process id. // TODO: Remove after porting functions.cpp @@ -34,4 +38,3 @@ void event_fire_generic(parser_t &parser, const wcstring &name, const std::vector &args = g_empty_string_list); #endif -#endif