diff --git a/src/env.cpp b/src/env.cpp index 7a9b18e80..854bd6765 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -141,8 +141,6 @@ class env_node_t { static std::mutex env_lock; -static bool local_scope_exports(const env_node_t *n); - // A class wrapping up a variable stack // Currently there is only one variable stack in fish, // but we can imagine having separate (linked) stacks @@ -163,7 +161,7 @@ struct var_stack_t { void mark_changed_exported() { has_changed_exported = true; } void update_export_array_if_necessary(); - var_stack_t() : top(new env_node_t(false)) { this->global_env = this->top.get(); } + var_stack_t() : top(new env_node_t(false)), global_env(top.get()) {} // Pushes a new node onto our stack // Optionally creates a new scope for the node @@ -180,6 +178,10 @@ struct var_stack_t { // Returns the scope used for unspecified scopes. An unspecified scope is either the topmost // shadowing scope, or the global scope if none. This implements the default behavior of `set`. env_node_t *resolve_unspecified_scope(); + + private: + bool local_scope_exports(const env_node_t *n) const; + void get_exported(const env_node_t *n, var_table_t &h) const; }; void var_stack_t::push(bool new_scope) { @@ -272,11 +274,12 @@ env_node_t *var_stack_t::resolve_unspecified_scope() { return node ? node : this->global_env; } -// Get the global variable stack -static var_stack_t &vars_stack() { - static var_stack_t global_stack; - return global_stack; -} +env_stack_t::env_stack_t() : vars_(make_unique()) {} + +// Get the variable stack +var_stack_t &env_stack_t::vars_stack() { return *vars_; } + +const var_stack_t &env_stack_t::vars_stack() const { return *vars_; } /// Universal variables global instance. Initialized in env_init. static env_universal_t *s_universal_variables = NULL; @@ -616,11 +619,11 @@ static void react_to_variable_change(const wchar_t *op, const wcstring &key) { /// Universal variable callback function. This function makes sure the proper events are triggered /// when an event occurs. -static void universal_callback(const callback_data_t &cb) { +static void universal_callback(env_stack_t *stack, const callback_data_t &cb) { const wchar_t *op = cb.is_erase() ? L"ERASE" : L"SET"; react_to_variable_change(op, cb.key); - vars_stack().mark_changed_exported(); + stack->mark_changed_exported(); event_t ev = event_t::variable_event(cb.key); ev.arguments.push_back(L"VARIABLE"); @@ -650,7 +653,7 @@ static void env_set_termsize() { } /// Update the PWD variable directory from the result of getcwd(). -void env_set_pwd_from_getcwd() { +void env_stack_t::set_pwd_from_getcwd() { wcstring cwd = wgetcwd(); if (cwd.empty()) { debug(0, @@ -662,7 +665,7 @@ void env_set_pwd_from_getcwd() { /// Allow the user to override the limit on how much data the `read` command will process. /// This is primarily for testing but could be used by users in special situations. -void env_set_read_limit() { +void env_stack_t::set_read_limit() { auto read_byte_limit_var = env_get(L"fish_read_limit"); if (!read_byte_limit_var.missing_or_empty()) { size_t limit = fish_wcstoull(read_byte_limit_var->as_string().c_str()); @@ -674,7 +677,9 @@ void env_set_read_limit() { } } -wcstring env_get_pwd_slash() { +void env_stack_t::mark_changed_exported() { vars_stack().mark_changed_exported(); } + +wcstring env_stack_t::get_pwd_slash() { // Return "/" if PWD is missing. // See https://github.com/fish-shell/fish-shell/issues/5080 auto pwd_var = env_get(L"PWD"); @@ -716,13 +721,13 @@ void misc_init() { } } -static void env_universal_callbacks(const callback_data_list_t &callbacks) { +static void env_universal_callbacks(env_stack_t *stack, const callback_data_list_t &callbacks) { for (const callback_data_t &cb : callbacks) { - universal_callback(cb); + universal_callback(stack, cb); } } -void env_universal_barrier() { +void env_stack_t::universal_barrier() { ASSERT_IS_MAIN_THREAD(); if (!uvars()) return; @@ -732,7 +737,7 @@ void env_universal_barrier() { universal_notifier_t::default_notifier().post_notification(); } - env_universal_callbacks(callbacks); + env_universal_callbacks(this, callbacks); } static void handle_fish_term_change(const wcstring &op, const wcstring &var_name) { @@ -976,7 +981,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { // initialize the PWD variable if necessary // Note we may inherit a virtual PWD that doesn't match what getcwd would return; respect that. if (env_get(L"PWD").missing_or_empty()) { - env_set_pwd_from_getcwd(); + env_stack_t::principal().set_pwd_from_getcwd(); } env_set_termsize(); // initialize the terminal size variables env_set_read_limit(); // initialize the read_byte_limit @@ -1000,7 +1005,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { s_universal_variables = new env_universal_t(L""); callback_data_list_t callbacks; s_universal_variables->initialize(callbacks); - env_universal_callbacks(callbacks); + env_universal_callbacks(&env_stack_t::principal(), callbacks); // Now that the global scope is fully initialized, add a toplevel local scope. This same local // scope will persist throughout the lifetime of the fish process, and it will ensure that `set @@ -1010,7 +1015,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { /// Search all visible scopes in order for the specified key. Return the first scope in which it was /// found. -static env_node_t *env_get_node(const wcstring &key) { +env_node_t *env_stack_t::get_node(const wcstring &key) { env_node_t *env = vars_stack().top.get(); while (env != NULL) { if (env->find_entry(key)) break; @@ -1033,7 +1038,7 @@ static int set_umask(const wcstring_list_t &list_val) { /// Set a universal variable, inheriting as applicable from the given old variable. static void env_set_internal_universal(const wcstring &key, wcstring_list_t val, - env_mode_flags_t input_var_mode) { + env_mode_flags_t input_var_mode, env_stack_t *stack) { ASSERT_IS_MAIN_THREAD(); if (!uvars()) return; env_mode_flags_t var_mode = input_var_mode; @@ -1068,7 +1073,7 @@ static void env_set_internal_universal(const wcstring &key, wcstring_list_t val, uvars()->set(key, new_var); env_universal_barrier(); if (new_var.exports() || (oldvar && oldvar->exports())) { - vars_stack().mark_changed_exported(); + stack->mark_changed_exported(); } } @@ -1088,8 +1093,7 @@ static void env_set_internal_universal(const wcstring &key, wcstring_list_t val, /// * ENV_SCOPE, the variable cannot be set in the given scope. This applies to readonly/electric /// variables set from the local or universal scopes, or set as exported. /// * ENV_INVALID, the variable value was invalid. This applies only to special variables. -static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode, - wcstring_list_t val) { +int env_stack_t::set_internal(const wcstring &key, env_mode_flags_t input_var_mode, wcstring_list_t val) { ASSERT_IS_MAIN_THREAD(); env_mode_flags_t var_mode = input_var_mode; bool has_changed_old = vars_stack().has_changed_exported; @@ -1100,7 +1104,7 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode wcstring val_canonical = val.front(); path_make_canonical(val_canonical); if (val.front() != val_canonical) { - return env_set_internal(key, var_mode, {val_canonical}); + return set_internal(key, var_mode, {val_canonical}); } } @@ -1121,12 +1125,12 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode if (var_mode & ENV_UNIVERSAL) { if (uvars()) { - env_set_internal_universal(key, std::move(val), var_mode); + env_set_internal_universal(key, std::move(val), var_mode, this); } } else { // Determine the node. bool has_changed_new = false; - env_node_t *preexisting_node = env_get_node(key); + env_node_t *preexisting_node = get_node(key); maybe_t preexisting_flags{}; if (preexisting_node != NULL) { var_table_t::const_iterator result = preexisting_node->env.find(key); @@ -1157,7 +1161,7 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode } if (uvars() && uvars()->get(key)) { // Modifying an existing universal variable. - env_set_internal_universal(key, std::move(val), var_mode); + env_set_internal_universal(key, std::move(val), var_mode, this); done = true; } else { // New variable with unspecified scope @@ -1229,26 +1233,26 @@ static int env_set_internal(const wcstring &key, env_mode_flags_t input_var_mode } /// Sets the variable with the specified name to the given values. -int env_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) { - return env_set_internal(key, mode, std::move(vals)); +int env_stack_t::set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) { + return set_internal(key, mode, std::move(vals)); } /// Sets the variable with the specified name to a single value. -int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { +int env_stack_t::set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { wcstring_list_t vals; vals.push_back(std::move(val)); - return env_set_internal(key, mode, std::move(vals)); + return set_internal(key, mode, std::move(vals)); } /// Sets the variable with the specified name without any (i.e., zero) values. -int env_set_empty(const wcstring &key, env_mode_flags_t mode) { - return env_set_internal(key, mode, {}); +int env_stack_t::set_empty(const wcstring &key, env_mode_flags_t mode) { + return set_internal(key, mode, {}); } /// Attempt to remove/free the specified key/value pair from the specified map. /// /// \return zero if the variable was not found, non-zero otherwise -static bool try_remove(env_node_t *n, const wchar_t *key, int var_mode) { +bool env_stack_t::try_remove(env_node_t *n, const wchar_t *key, int var_mode) { if (n == NULL) { return false; } @@ -1256,7 +1260,7 @@ static bool try_remove(env_node_t *n, const wchar_t *key, int var_mode) { var_table_t::iterator result = n->env.find(key); if (result != n->env.end()) { if (result->second.exports()) { - vars_stack().mark_changed_exported(); + mark_changed_exported(); } n->env.erase(result); return true; @@ -1272,7 +1276,7 @@ static bool try_remove(env_node_t *n, const wchar_t *key, int var_mode) { return try_remove(n->next.get(), key, var_mode); } -int env_remove(const wcstring &key, int var_mode) { +int env_stack_t::remove(const wcstring &key, int var_mode) { ASSERT_IS_MAIN_THREAD(); env_node_t *first_node; int erased = 0; @@ -1345,7 +1349,7 @@ env_var_t::env_var_flags_t env_var_t::flags_for(const wchar_t *name) { return result; } -maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { +maybe_t env_stack_t::get(const wcstring &key, env_mode_flags_t mode) const { const bool has_scope = mode & (ENV_LOCAL | ENV_GLOBAL | ENV_UNIVERSAL); const bool search_local = !has_scope || (mode & ENV_LOCAL); const bool search_global = !has_scope || (mode & ENV_GLOBAL); @@ -1383,7 +1387,7 @@ maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { if (search_local || search_global) { scoped_lock locker(env_lock); // lock around a local region - env_node_t *env = search_local ? vars_stack().top.get() : vars_stack().global_env; + const env_node_t *env = search_local ? vars_stack().top.get() : vars_stack().global_env; while (env != NULL) { if (env == vars_stack().global_env && !search_global) { @@ -1422,11 +1426,46 @@ maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { return none(); } +/// Legacy versions. +maybe_t env_get(const wcstring &key, env_mode_flags_t mode) { + return env_stack_t::principal().get(key, mode); +} + +int env_set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) { + return env_stack_t::principal().set(key, mode, std::move(vals)); +} + +int env_set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) { + return env_stack_t::principal().set_one(key, mode, std::move(val)); +} + +int env_set_empty(const wcstring &key, env_mode_flags_t mode) { + return env_stack_t::principal().set_empty(key, mode); +} + +int env_remove(const wcstring &key, int mode) { return env_stack_t::principal().remove(key, mode); } + +void env_push(bool new_scope) { env_stack_t::principal().push(new_scope); } + +void env_pop() { env_stack_t::principal().pop(); } + +void env_universal_barrier() { env_stack_t::principal().universal_barrier(); } + +const char *const *env_export_arr() { return env_stack_t::principal().export_arr(); } + +void env_set_argv(const wchar_t *const *argv) { return env_stack_t::principal().set_argv(argv); } + +wcstring_list_t env_get_names(int flags) { return env_stack_t::principal().get_names(flags); } + +wcstring env_get_pwd_slash() { return env_stack_t::principal().get_pwd_slash(); } + +void env_set_read_limit() { return env_stack_t::principal().set_read_limit(); } + /// Returns true if the specified scope or any non-shadowed non-global subscopes contain an exported /// variable. -static bool local_scope_exports(const env_node_t *n) { +bool var_stack_t::local_scope_exports(const env_node_t *n) const { assert(n != NULL); - if (n == vars_stack().global_env) return false; + if (n == global_env) return false; if (n->exportv) return true; @@ -1435,9 +1474,9 @@ static bool local_scope_exports(const env_node_t *n) { return local_scope_exports(n->next.get()); } -void env_push(bool new_scope) { vars_stack().push(new_scope); } +void env_stack_t::push(bool new_scope) { vars_stack().push(new_scope); } -void env_pop() { vars_stack().pop(); } +void env_stack_t::pop() { vars_stack().pop(); } /// Function used with to insert keys of one table into a set::set. static void add_key_to_string_set(const var_table_t &envs, std::set *str_set, @@ -1453,7 +1492,7 @@ static void add_key_to_string_set(const var_table_t &envs, std::set *s } } -wcstring_list_t env_get_names(int flags) { +wcstring_list_t env_stack_t::get_names(int flags) { scoped_lock locker(env_lock); wcstring_list_t result; @@ -1499,11 +1538,11 @@ wcstring_list_t env_get_names(int flags) { } /// Get list of all exported variables. -static void get_exported(const env_node_t *n, var_table_t &h) { +void var_stack_t::get_exported(const env_node_t *n, var_table_t &h) const { if (!n) return; if (n->new_scope) { - get_exported(vars_stack().global_env, h); + get_exported(global_env, h); } else { get_exported(n->next.get(), h); } @@ -1569,14 +1608,14 @@ void var_stack_t::update_export_array_if_necessary() { has_changed_exported = false; } -const char *const *env_export_arr() { +const char *const *env_stack_t::export_arr() { ASSERT_IS_MAIN_THREAD(); ASSERT_IS_NOT_FORKED_CHILD(); vars_stack().update_export_array_if_necessary(); return vars_stack().export_array.get(); } -void env_set_argv(const wchar_t *const *argv) { +void env_stack_t::set_argv(const wchar_t *const *argv) { if (argv && *argv) { wcstring_list_t list; for (auto arg = argv; *arg; arg++) { @@ -1591,6 +1630,13 @@ void env_set_argv(const wchar_t *const *argv) { environment_t::~environment_t() = default; +env_stack_t::~env_stack_t() = default; + +env_stack_t &env_stack_t::principal() { + static env_stack_t s_principal; + return s_principal; +} + env_vars_snapshot_t::env_vars_snapshot_t(const wchar_t *const *keys) { ASSERT_IS_MAIN_THREAD(); wcstring key; diff --git a/src/env.h b/src/env.h index 26b186c1f..73b21a9a8 100644 --- a/src/env.h +++ b/src/env.h @@ -182,15 +182,86 @@ void env_set_argv(const wchar_t *const *argv); /// Returns all variable names. wcstring_list_t env_get_names(int flags); -/// Update the PWD variable based on the result of getcwd. -void env_set_pwd_from_getcwd(); - /// Returns the PWD with a terminating slash. wcstring env_get_pwd_slash(); /// Update the read_byte_limit variable. void env_set_read_limit(); +/// A environment stack of scopes. This is the main class that tracks fish variables. +struct var_stack_t; +class env_node_t; +class env_stack_t : public environment_t { + std::unique_ptr vars_; + + int set_internal(const wcstring &key, env_mode_flags_t var_mode, wcstring_list_t val); + + bool try_remove(env_node_t *n, const wchar_t *key, int var_mode); + env_node_t *get_node(const wcstring &key); + + var_stack_t &vars_stack(); + const var_stack_t &vars_stack() const; + + env_stack_t(); + ~env_stack_t() override; + + public: + /// Gets the variable with the specified name, or none() if it does not exist. + maybe_t get(const wcstring &key, env_mode_flags_t mode = ENV_DEFAULT) const override; + + /// Sets the variable with the specified name to the given values. + int set(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals); + + /// Sets the variable with the specified name to a single value. + int set_one(const wcstring &key, env_mode_flags_t mode, wcstring val); + + /// Sets the variable with the specified name to no values. + int set_empty(const wcstring &key, env_mode_flags_t mode); + + /// Update the PWD variable based on the result of getcwd. + void set_pwd_from_getcwd(); + + /// Remove environment variable. + /// + /// \param key The name of the variable to remove + /// \param mode should be ENV_USER if this is a remove request from the user, 0 otherwise. If + /// this is a user request, read-only variables can not be removed. The mode may also specify + /// the scope of the variable that should be erased. + /// + /// \return zero if the variable existed, and non-zero if the variable did not exist + int remove(const wcstring &key, int mode); + + /// Push the variable stack. Used for implementing local variables for functions and for-loops. + void push(bool new_scope); + + /// Pop the variable stack. Used for implementing local variables for functions and for-loops. + void pop(); + + /// Synchronizes all universal variable changes: writes everything out, reads stuff in. + void universal_barrier(); + + /// Returns an array containing all exported variables in a format suitable for execv + const char *const *export_arr(); + + /// Returns all variable names. + wcstring_list_t get_names(int flags); + + /// Sets up argv as the given null terminated array of strings. + void set_argv(const wchar_t *const *argv); + + /// Returns the PWD with a terminating slash. + wcstring get_pwd_slash(); + + /// Update the read_byte_limit variable. + void set_read_limit(); + + /// Mark that exported variables have changed. + void mark_changed_exported(); + + // Compatibility hack; access the "environment stack" from back when there was just one. + static env_stack_t &principal(); +}; + class env_vars_snapshot_t : public environment_t { std::map vars; bool is_current() const; diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index c35e3c551..0f1a430c0 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -174,7 +174,7 @@ static bool pushd(const char *path) { return false; } - env_set_pwd_from_getcwd(); + env_stack_t::principal().set_pwd_from_getcwd(); return true; } @@ -184,7 +184,7 @@ static void popd() { err(L"chdir(\"%s\") from popd() failed: errno = %d", old_cwd.c_str(), errno); } pushed_dirs.pop_back(); - env_set_pwd_from_getcwd(); + env_stack_t::principal().set_pwd_from_getcwd(); } // The odd formulation of these macros is to avoid "multiple unary operator" warnings from oclint