Introduce env_stack_t

This will instance environment variable stacks.
This commit is contained in:
ridiculousfish 2018-09-09 12:17:31 -07:00
parent 391af6af0c
commit 8d7cae63ff
3 changed files with 170 additions and 53 deletions

View file

@ -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<var_stack_t>()) {}
// 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<env_var_t::env_var_flags_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_var_t> env_get(const wcstring &key, env_mode_flags_t mode) {
maybe_t<env_var_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_var_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_var_t> env_get(const wcstring &key, env_mode_flags_t mode) {
return none();
}
/// Legacy versions.
maybe_t<env_var_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<wcstring>.
static void add_key_to_string_set(const var_table_t &envs, std::set<wcstring> *str_set,
@ -1453,7 +1492,7 @@ static void add_key_to_string_set(const var_table_t &envs, std::set<wcstring> *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;

View file

@ -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<var_stack_t> 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<env_var_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<wcstring, env_var_t> vars;
bool is_current() const;

View file

@ -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