mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-29 13:23:53 +08:00
Reorganize env.cpp
Group functions together more logically
This commit is contained in:
parent
1d464da698
commit
69655ef5c1
622
src/env.cpp
622
src/env.cpp
|
@ -54,6 +54,150 @@ bool curses_initialized = false;
|
||||||
/// Does the terminal have the "eat_newline_glitch".
|
/// Does the terminal have the "eat_newline_glitch".
|
||||||
bool term_has_xn = false;
|
bool term_has_xn = false;
|
||||||
|
|
||||||
|
/// Universal variables global instance. Initialized in env_init.
|
||||||
|
static env_universal_t *s_universal_variables = NULL;
|
||||||
|
|
||||||
|
/// Getter for universal variables.
|
||||||
|
static env_universal_t *uvars() { return s_universal_variables; }
|
||||||
|
|
||||||
|
void env_universal_barrier() { env_stack_t::principal().universal_barrier(); }
|
||||||
|
|
||||||
|
// A typedef for a set of constant strings. Note our sets are typically on the order of 6 elements,
|
||||||
|
// so we don't bother to sort them.
|
||||||
|
using string_set_t = const wchar_t *const[];
|
||||||
|
|
||||||
|
/// Check if a variable may not be set using the set command.
|
||||||
|
static bool is_read_only(const wcstring &key) {
|
||||||
|
static const string_set_t env_read_only = {
|
||||||
|
L"PWD", L"SHLVL", L"history", L"pipestatus", L"status", L"version",
|
||||||
|
L"FISH_VERSION", L"fish_pid", L"hostname", L"_", L"fish_private_mode"};
|
||||||
|
return contains(env_read_only, key) || (in_private_mode() && key == L"fish_history");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Table of variables whose value is dynamically calculated, such as umask, status, etc.
|
||||||
|
static const string_set_t env_electric = {L"history", L"pipestatus", L"status", L"umask"};
|
||||||
|
|
||||||
|
static bool is_electric(const wcstring &key) { return contains(env_electric, key); }
|
||||||
|
|
||||||
|
/// \return a the value of a variable for \p key, which must be electric (computed).
|
||||||
|
static maybe_t<env_var_t> get_electric(const wcstring &key, const environment_t &vars) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
history_t *history = reader_get_history();
|
||||||
|
if (!history) {
|
||||||
|
history = &history_t::history_with_name(history_session_id(vars));
|
||||||
|
}
|
||||||
|
wcstring_list_t result;
|
||||||
|
if (history) history->get_history(result);
|
||||||
|
return env_var_t(L"history", result);
|
||||||
|
} else if (key == L"pipestatus") {
|
||||||
|
const auto js = proc_get_last_statuses();
|
||||||
|
wcstring_list_t 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") {
|
||||||
|
return env_var_t(L"status", to_string(proc_get_last_status()));
|
||||||
|
} 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. TODO: lock!
|
||||||
|
mode_t res = umask(0);
|
||||||
|
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 electric var name");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Some env vars contain a list of paths where an empty path element is equivalent to ".".
|
||||||
|
/// Unfortunately that convention causes problems for fish scripts. So this function replaces the
|
||||||
|
/// empty path element with an explicit ".". See issue #3914.
|
||||||
|
void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars) {
|
||||||
|
const auto paths = vars.get(var_name);
|
||||||
|
if (paths.missing_or_empty()) return;
|
||||||
|
|
||||||
|
// See if there's any empties.
|
||||||
|
const wcstring empty = wcstring();
|
||||||
|
const wcstring_list_t &strs = paths->as_list();
|
||||||
|
if (contains(strs, empty)) {
|
||||||
|
// Copy the list and replace empties with L"."
|
||||||
|
wcstring_list_t newstrs = strs;
|
||||||
|
std::replace(newstrs.begin(), newstrs.end(), empty, wcstring(L"."));
|
||||||
|
int retval = vars.set(var_name, ENV_DEFAULT | ENV_USER, std::move(newstrs));
|
||||||
|
if (retval != ENV_OK) {
|
||||||
|
debug(0, L"fix_colon_delimited_var failed unexpectedly with retval %d", retval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wcstring_list_t &env_var_t::as_list() const { return *vals_; }
|
||||||
|
|
||||||
|
wchar_t env_var_t::get_delimiter() const {
|
||||||
|
return is_pathvar() ? PATH_ARRAY_SEP : NONPATH_ARRAY_SEP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a string representation of the var.
|
||||||
|
wcstring env_var_t::as_string() const { return join_strings(*vals_, get_delimiter()); }
|
||||||
|
|
||||||
|
void env_var_t::to_list(wcstring_list_t &out) const { out = *vals_; }
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// \return a singleton empty list, to avoid unnecessary allocations in env_var_t.
|
||||||
|
std::shared_ptr<const wcstring_list_t> env_var_t::empty_list() {
|
||||||
|
static const auto result = std::make_shared<const wcstring_list_t>();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
environment_t::~environment_t() = default;
|
||||||
|
|
||||||
|
wcstring environment_t::get_pwd_slash() const {
|
||||||
|
// Return "/" if PWD is missing.
|
||||||
|
// See https://github.com/fish-shell/fish-shell/issues/5080
|
||||||
|
auto pwd_var = get(L"PWD");
|
||||||
|
wcstring pwd;
|
||||||
|
if (!pwd_var.missing_or_empty()) {
|
||||||
|
pwd = pwd_var->as_string();
|
||||||
|
}
|
||||||
|
if (!string_suffixes_string(L"/", pwd)) {
|
||||||
|
pwd.push_back(L'/');
|
||||||
|
}
|
||||||
|
return pwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
null_environment_t::null_environment_t() = default;
|
||||||
|
null_environment_t::~null_environment_t() = default;
|
||||||
|
maybe_t<env_var_t> null_environment_t::get(const wcstring &key, env_mode_flags_t mode) const {
|
||||||
|
UNUSED(key);
|
||||||
|
UNUSED(mode);
|
||||||
|
return none();
|
||||||
|
}
|
||||||
|
wcstring_list_t null_environment_t::get_names(int flags) const {
|
||||||
|
UNUSED(flags);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
// Struct representing one level in the function variable stack.
|
// Struct representing one level in the function variable stack.
|
||||||
// Only our variable stack should create and destroy these
|
// Only our variable stack should create and destroy these
|
||||||
class env_node_t {
|
class env_node_t {
|
||||||
|
@ -79,18 +223,11 @@ class env_node_t {
|
||||||
return none();
|
return none();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using env_node_ref_t = std::shared_ptr<env_node_t>;
|
using env_node_ref_t = std::shared_ptr<env_node_t>;
|
||||||
|
|
||||||
// This is a big dorky lock we take around everything that might modify an env_node_t. Fine grained
|
// A class wrapping up a variable stack.
|
||||||
// locking is annoying here because env_nodes may be shared between env_stacks, so each node would
|
// This forms a linekd list of env_node_t, and also maintains the export array for exported
|
||||||
// need its own lock.
|
// environment variables.
|
||||||
static std::mutex env_lock;
|
|
||||||
|
|
||||||
// A class wrapping up a variable stack
|
|
||||||
// Currently there is only one variable stack in fish,
|
|
||||||
// but we can imagine having separate (linked) stacks
|
|
||||||
// if we introduce multiple threads of execution
|
|
||||||
struct var_stack_t {
|
struct var_stack_t {
|
||||||
var_stack_t(var_stack_t &&) = default;
|
var_stack_t(var_stack_t &&) = default;
|
||||||
|
|
||||||
|
@ -188,33 +325,131 @@ struct var_stack_t {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Universal variables global instance. Initialized in env_init.
|
/// Get list of all exported variables.
|
||||||
static env_universal_t *s_universal_variables = NULL;
|
void var_stack_t::get_exported(const env_node_t *n, var_table_t &h) const {
|
||||||
|
if (!n) return;
|
||||||
|
|
||||||
/// Getter for universal variables.
|
if (n->new_scope) {
|
||||||
static env_universal_t *uvars() { return s_universal_variables; }
|
get_exported(global_env.get(), h);
|
||||||
|
} else {
|
||||||
|
get_exported(n->next.get(), h);
|
||||||
|
}
|
||||||
|
|
||||||
// A typedef for a set of constant strings. Note our sets are typically on the order of 6 elements,
|
var_table_t::const_iterator iter;
|
||||||
// so we don't bother to sort them.
|
for (iter = n->env.begin(); iter != n->env.end(); ++iter) {
|
||||||
using string_set_t = const wchar_t *const[];
|
const wcstring &key = iter->first;
|
||||||
|
const env_var_t var = iter->second;
|
||||||
|
|
||||||
/// Check if a variable may not be set using the set command.
|
if (var.exports()) {
|
||||||
static bool is_read_only(const wcstring &key) {
|
// Export the variable. Don't use std::map::insert here, since we need to overwrite
|
||||||
static const string_set_t env_read_only = {
|
// existing values from previous scopes.
|
||||||
L"PWD", L"SHLVL", L"history", L"pipestatus", L"status", L"version",
|
h[key] = var;
|
||||||
L"FISH_VERSION", L"fish_pid", L"hostname", L"_", L"fish_private_mode"};
|
} else {
|
||||||
return contains(env_read_only, key) || (in_private_mode() && key == L"fish_history");
|
// We need to erase from the map if we are not exporting, since a lower scope may have
|
||||||
|
// exported. See #2132.
|
||||||
|
h.erase(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if a variable should become a path variable by default. See #436.
|
/// Returns true if the specified scope or any non-shadowed non-global subscopes contain an exported
|
||||||
static bool variable_should_auto_pathvar(const wcstring &name) {
|
/// variable.
|
||||||
return string_suffixes_string(L"PATH", name);
|
bool var_stack_t::local_scope_exports(const env_node_ref_t &n) const {
|
||||||
|
assert(n != nullptr);
|
||||||
|
if (n == global_env) return false;
|
||||||
|
|
||||||
|
if (n->exportv) return true;
|
||||||
|
|
||||||
|
if (n->new_scope) return false;
|
||||||
|
|
||||||
|
return local_scope_exports(n->next);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Table of variables whose value is dynamically calculated, such as umask, status, etc.
|
void var_stack_t::update_export_array_if_necessary() {
|
||||||
static const string_set_t env_electric = {L"history", L"pipestatus", L"status", L"umask"};
|
if (!this->has_changed_exported()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static bool is_electric(const wcstring &key) { return contains(env_electric, key); }
|
debug(4, L"export_arr() recalc");
|
||||||
|
var_table_t vals;
|
||||||
|
get_exported(this->top.get(), vals);
|
||||||
|
|
||||||
|
if (uvars()) {
|
||||||
|
const wcstring_list_t uni = uvars()->get_names(true, false);
|
||||||
|
for (const wcstring &key : uni) {
|
||||||
|
auto var = uvars()->get(key);
|
||||||
|
|
||||||
|
if (!var.missing_or_empty()) {
|
||||||
|
// Note that std::map::insert does NOT overwrite a value already in the map,
|
||||||
|
// which we depend on here.
|
||||||
|
vals.insert(std::pair<wcstring, env_var_t>(key, *var));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the export list: a list of strings of the form key=value.
|
||||||
|
std::vector<std::string> export_list;
|
||||||
|
export_list.reserve(vals.size());
|
||||||
|
for (const auto &kv : vals) {
|
||||||
|
std::string str = wcs2string(kv.first);
|
||||||
|
str.push_back('=');
|
||||||
|
str.append(wcs2string(kv.second.as_string()));
|
||||||
|
export_list.push_back(std::move(str));
|
||||||
|
}
|
||||||
|
export_array.emplace(export_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the variable stack
|
||||||
|
var_stack_t &env_scoped_t::vars_stack() { return *vars_; }
|
||||||
|
const var_stack_t &env_scoped_t::vars_stack() const { return *vars_; }
|
||||||
|
|
||||||
|
// Read a variable respecting the given mode.
|
||||||
|
maybe_t<env_var_t> env_scoped_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);
|
||||||
|
const bool search_universal = !has_scope || (mode & ENV_UNIVERSAL);
|
||||||
|
|
||||||
|
const bool search_exported = (mode & ENV_EXPORT) || !(mode & ENV_UNEXPORT);
|
||||||
|
const bool search_unexported = (mode & ENV_UNEXPORT) || !(mode & ENV_EXPORT);
|
||||||
|
|
||||||
|
// Make the assumption that electric keys can't be shadowed elsewhere, since we currently block
|
||||||
|
// that in env_stack_t::set().
|
||||||
|
if (is_electric(key)) {
|
||||||
|
if (!search_global) return none();
|
||||||
|
return get_electric(key, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search_local || search_global) {
|
||||||
|
scoped_lock locker(env_lock); // lock around a local region
|
||||||
|
env_node_ref_t env = search_local ? vars_stack().top : vars_stack().global_env;
|
||||||
|
|
||||||
|
while (env != NULL) {
|
||||||
|
if (env == vars_stack().global_env && !search_global) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var_table_t::const_iterator result = env->env.find(key);
|
||||||
|
if (result != env->env.end()) {
|
||||||
|
const env_var_t &var = result->second;
|
||||||
|
if (var.exports() ? search_exported : search_unexported) {
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env = vars_stack().next_scope_to_search(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Okay, we couldn't find a local or global var given the requirements. If there is a matching
|
||||||
|
// universal var return that.
|
||||||
|
if (search_universal && uvars()) {
|
||||||
|
auto var = uvars()->get(key);
|
||||||
|
if (var && (var->exports() ? search_exported : search_unexported)) {
|
||||||
|
return var;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return none();
|
||||||
|
}
|
||||||
|
|
||||||
env_scoped_t::env_scoped_t() : env_scoped_t(make_unique<var_stack_t>()) {}
|
env_scoped_t::env_scoped_t() : env_scoped_t(make_unique<var_stack_t>()) {}
|
||||||
env_scoped_t::env_scoped_t(std::unique_ptr<var_stack_t> vars) : vars_(std::move(vars)) {}
|
env_scoped_t::env_scoped_t(std::unique_ptr<var_stack_t> vars) : vars_(std::move(vars)) {}
|
||||||
|
@ -234,66 +469,6 @@ void env_stack_t::universal_barrier() {
|
||||||
env_universal_callbacks(this, callbacks);
|
env_universal_callbacks(this, callbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the variable stack
|
|
||||||
var_stack_t &env_scoped_t::vars_stack() { return *vars_; }
|
|
||||||
|
|
||||||
const var_stack_t &env_scoped_t::vars_stack() const { return *vars_; }
|
|
||||||
|
|
||||||
/// Return the current umask value.
|
|
||||||
static mode_t get_umask() {
|
|
||||||
mode_t res;
|
|
||||||
res = umask(0);
|
|
||||||
umask(res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Some env vars contain a list of paths where an empty path element is equivalent to ".".
|
|
||||||
/// Unfortunately that convention causes problems for fish scripts. So this function replaces the
|
|
||||||
/// empty path element with an explicit ".". See issue #3914.
|
|
||||||
void fix_colon_delimited_var(const wcstring &var_name, env_stack_t &vars) {
|
|
||||||
const auto paths = vars.get(var_name);
|
|
||||||
if (paths.missing_or_empty()) return;
|
|
||||||
|
|
||||||
// See if there's any empties.
|
|
||||||
const wcstring empty = wcstring();
|
|
||||||
const wcstring_list_t &strs = paths->as_list();
|
|
||||||
if (contains(strs, empty)) {
|
|
||||||
// Copy the list and replace empties with L"."
|
|
||||||
wcstring_list_t newstrs = strs;
|
|
||||||
std::replace(newstrs.begin(), newstrs.end(), empty, wcstring(L"."));
|
|
||||||
int retval = vars.set(var_name, ENV_DEFAULT | ENV_USER, std::move(newstrs));
|
|
||||||
if (retval != ENV_OK) {
|
|
||||||
debug(0, L"fix_colon_delimited_var failed unexpectedly with retval %d", retval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ensure the content of the magic path env vars is reasonable. Specifically, that empty path
|
|
||||||
/// elements are converted to explicit "." to make the vars easier to use in fish scripts.
|
|
||||||
static void init_path_vars() {
|
|
||||||
// Do not replace empties in MATHPATH - see #4158.
|
|
||||||
fix_colon_delimited_var(L"PATH", env_stack_t::globals());
|
|
||||||
fix_colon_delimited_var(L"CDPATH", env_stack_t::globals());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make sure the PATH variable contains something.
|
|
||||||
static void setup_path() {
|
|
||||||
auto &vars = env_stack_t::globals();
|
|
||||||
const auto path = vars.get(L"PATH");
|
|
||||||
if (path.missing_or_empty()) {
|
|
||||||
#if defined(_CS_PATH)
|
|
||||||
// _CS_PATH: colon-separated paths to find POSIX utilities
|
|
||||||
std::string cspath;
|
|
||||||
cspath.resize(confstr(_CS_PATH, nullptr, 0));
|
|
||||||
confstr(_CS_PATH, &cspath[0], cspath.length());
|
|
||||||
#else
|
|
||||||
std::string cspath = "/usr/bin:/bin"; // I doubt this is even necessary
|
|
||||||
#endif
|
|
||||||
vars.set_one(L"PATH", ENV_GLOBAL | ENV_EXPORT, str2wcstring(cspath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If they don't already exist initialize the `COLUMNS` and `LINES` env vars to reasonable
|
/// If they don't already exist initialize the `COLUMNS` and `LINES` env vars to reasonable
|
||||||
/// defaults. They will be updated later by the `get_current_winsize()` function if they need to be
|
/// defaults. They will be updated later by the `get_current_winsize()` function if they need to be
|
||||||
/// adjusted.
|
/// adjusted.
|
||||||
|
@ -319,20 +494,6 @@ void env_stack_t::set_pwd_from_getcwd() {
|
||||||
|
|
||||||
void env_stack_t::mark_changed_exported() { vars_stack().mark_changed_exported(); }
|
void env_stack_t::mark_changed_exported() { vars_stack().mark_changed_exported(); }
|
||||||
|
|
||||||
wcstring environment_t::get_pwd_slash() const {
|
|
||||||
// Return "/" if PWD is missing.
|
|
||||||
// See https://github.com/fish-shell/fish-shell/issues/5080
|
|
||||||
auto pwd_var = get(L"PWD");
|
|
||||||
wcstring pwd;
|
|
||||||
if (!pwd_var.missing_or_empty()) {
|
|
||||||
pwd = pwd_var->as_string();
|
|
||||||
}
|
|
||||||
if (!string_suffixes_string(L"/", pwd)) {
|
|
||||||
pwd.push_back(L'/');
|
|
||||||
}
|
|
||||||
return pwd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set up the USER variable.
|
/// Set up the USER variable.
|
||||||
static void setup_user(bool force) {
|
static void setup_user(bool force) {
|
||||||
auto &vars = env_stack_t::globals();
|
auto &vars = env_stack_t::globals();
|
||||||
|
@ -360,6 +521,30 @@ void misc_init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure the content of the magic path env vars is reasonable. Specifically, that empty path
|
||||||
|
/// elements are converted to explicit "." to make the vars easier to use in fish scripts.
|
||||||
|
static void init_path_vars() {
|
||||||
|
// Do not replace empties in MATHPATH - see #4158.
|
||||||
|
fix_colon_delimited_var(L"PATH", env_stack_t::globals());
|
||||||
|
fix_colon_delimited_var(L"CDPATH", env_stack_t::globals());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make sure the PATH variable contains something.
|
||||||
|
static void setup_path() {
|
||||||
|
auto &vars = env_stack_t::globals();
|
||||||
|
const auto path = vars.get(L"PATH");
|
||||||
|
if (path.missing_or_empty()) {
|
||||||
|
#if defined(_CS_PATH)
|
||||||
|
// _CS_PATH: colon-separated paths to find POSIX utilities
|
||||||
|
std::string cspath;
|
||||||
|
cspath.resize(confstr(_CS_PATH, nullptr, 0));
|
||||||
|
confstr(_CS_PATH, &cspath[0], cspath.length());
|
||||||
|
#else
|
||||||
|
std::string cspath = "/usr/bin:/bin"; // I doubt this is even necessary
|
||||||
|
#endif
|
||||||
|
vars.set_one(L"PATH", ENV_GLOBAL | ENV_EXPORT, str2wcstring(cspath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void env_init(const struct config_paths_t *paths /* or NULL */) {
|
void env_init(const struct config_paths_t *paths /* or NULL */) {
|
||||||
env_stack_t &vars = env_stack_t::globals();
|
env_stack_t &vars = env_stack_t::globals();
|
||||||
|
@ -802,124 +987,6 @@ int env_stack_t::remove(const wcstring &key, int var_mode) {
|
||||||
return erased ? ENV_OK : ENV_NOT_FOUND;
|
return erased ? ENV_OK : ENV_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
const wcstring_list_t &env_var_t::as_list() const { return *vals_; }
|
|
||||||
|
|
||||||
wchar_t env_var_t::get_delimiter() const {
|
|
||||||
return is_pathvar() ? PATH_ARRAY_SEP : NONPATH_ARRAY_SEP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a string representation of the var.
|
|
||||||
wcstring env_var_t::as_string() const { return join_strings(*vals_, get_delimiter()); }
|
|
||||||
|
|
||||||
void env_var_t::to_list(wcstring_list_t &out) const { out = *vals_; }
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// \return a singleton empty list, to avoid unnecessary allocations in env_var_t.
|
|
||||||
std::shared_ptr<const wcstring_list_t> env_var_t::empty_list() {
|
|
||||||
static const auto result = std::make_shared<const wcstring_list_t>();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// \return a the value of a variable for \p key, which must be electric (computed).
|
|
||||||
static maybe_t<env_var_t> get_electric(const wcstring &key, const environment_t &vars) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
history_t *history = reader_get_history();
|
|
||||||
if (!history) {
|
|
||||||
history = &history_t::history_with_name(history_session_id(vars));
|
|
||||||
}
|
|
||||||
wcstring_list_t result;
|
|
||||||
if (history) history->get_history(result);
|
|
||||||
return env_var_t(L"history", result);
|
|
||||||
} else if (key == L"pipestatus") {
|
|
||||||
const auto js = proc_get_last_statuses();
|
|
||||||
wcstring_list_t 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") {
|
|
||||||
return env_var_t(L"status", to_string(proc_get_last_status()));
|
|
||||||
} else if (key == L"umask") {
|
|
||||||
return env_var_t(L"umask", format_string(L"0%0.3o", get_umask()));
|
|
||||||
}
|
|
||||||
// We should never get here unless the electric var list is out of sync with the above code.
|
|
||||||
DIE("unrecognized electric var name");
|
|
||||||
}
|
|
||||||
|
|
||||||
maybe_t<env_var_t> env_scoped_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);
|
|
||||||
const bool search_universal = !has_scope || (mode & ENV_UNIVERSAL);
|
|
||||||
|
|
||||||
const bool search_exported = (mode & ENV_EXPORT) || !(mode & ENV_UNEXPORT);
|
|
||||||
const bool search_unexported = (mode & ENV_UNEXPORT) || !(mode & ENV_EXPORT);
|
|
||||||
|
|
||||||
// Make the assumption that electric keys can't be shadowed elsewhere, since we currently block
|
|
||||||
// that in env_stack_t::set().
|
|
||||||
if (is_electric(key)) {
|
|
||||||
if (!search_global) return none();
|
|
||||||
return get_electric(key, *this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search_local || search_global) {
|
|
||||||
scoped_lock locker(env_lock); // lock around a local region
|
|
||||||
env_node_ref_t env = search_local ? vars_stack().top : vars_stack().global_env;
|
|
||||||
|
|
||||||
while (env != NULL) {
|
|
||||||
if (env == vars_stack().global_env && !search_global) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var_table_t::const_iterator result = env->env.find(key);
|
|
||||||
if (result != env->env.end()) {
|
|
||||||
const env_var_t &var = result->second;
|
|
||||||
if (var.exports() ? search_exported : search_unexported) {
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
env = vars_stack().next_scope_to_search(env);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Okay, we couldn't find a local or global var given the requirements. If there is a matching
|
|
||||||
// universal var return that.
|
|
||||||
if (search_universal && uvars()) {
|
|
||||||
auto var = uvars()->get(key);
|
|
||||||
if (var && (var->exports() ? search_exported : search_unexported)) {
|
|
||||||
return var;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return none();
|
|
||||||
}
|
|
||||||
|
|
||||||
void env_universal_barrier() {env_stack_t::principal().universal_barrier(); }
|
|
||||||
|
|
||||||
/// Returns true if the specified scope or any non-shadowed non-global subscopes contain an exported
|
|
||||||
/// variable.
|
|
||||||
bool var_stack_t::local_scope_exports(const env_node_ref_t &n) const {
|
|
||||||
assert(n != nullptr);
|
|
||||||
if (n == global_env) return false;
|
|
||||||
|
|
||||||
if (n->exportv) return true;
|
|
||||||
|
|
||||||
if (n->new_scope) return false;
|
|
||||||
|
|
||||||
return local_scope_exports(n->next);
|
|
||||||
}
|
|
||||||
|
|
||||||
void env_stack_t::push(bool new_scope) { vars_stack().push(new_scope); }
|
void env_stack_t::push(bool new_scope) { vars_stack().push(new_scope); }
|
||||||
|
|
||||||
void env_stack_t::pop() {
|
void env_stack_t::pop() {
|
||||||
|
@ -941,20 +1008,6 @@ void env_stack_t::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,
|
|
||||||
bool show_exported, bool show_unexported) {
|
|
||||||
var_table_t::const_iterator iter;
|
|
||||||
for (iter = envs.begin(); iter != envs.end(); ++iter) {
|
|
||||||
const env_var_t &var = iter->second;
|
|
||||||
|
|
||||||
if ((var.exports() && show_exported) || (!var.exports() && show_unexported)) {
|
|
||||||
// Insert this key.
|
|
||||||
str_set->insert(iter->first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wcstring_list_t env_scoped_t::get_names(int flags) const {
|
wcstring_list_t env_scoped_t::get_names(int flags) const {
|
||||||
scoped_lock locker(env_lock);
|
scoped_lock locker(env_lock);
|
||||||
|
|
||||||
|
@ -968,6 +1021,17 @@ wcstring_list_t env_scoped_t::get_names(int flags) const {
|
||||||
const bool show_exported = (flags & ENV_EXPORT) || !(flags & ENV_UNEXPORT);
|
const bool show_exported = (flags & ENV_EXPORT) || !(flags & ENV_UNEXPORT);
|
||||||
const bool show_unexported = (flags & ENV_UNEXPORT) || !(flags & ENV_EXPORT);
|
const bool show_unexported = (flags & ENV_UNEXPORT) || !(flags & ENV_EXPORT);
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
const env_var_t &var = kv.second;
|
||||||
|
if (var.exports() ? show_exported : show_unexported) {
|
||||||
|
names.insert(kv.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (!show_local && !show_global && !show_universal) {
|
if (!show_local && !show_global && !show_universal) {
|
||||||
show_local = show_universal = show_global = 1;
|
show_local = show_universal = show_global = 1;
|
||||||
}
|
}
|
||||||
|
@ -975,8 +1039,7 @@ wcstring_list_t env_scoped_t::get_names(int flags) const {
|
||||||
if (show_local) {
|
if (show_local) {
|
||||||
while (n) {
|
while (n) {
|
||||||
if (n == vars_stack().global_env) break;
|
if (n == vars_stack().global_env) break;
|
||||||
|
add_keys(n->env);
|
||||||
add_key_to_string_set(n->env, &names, show_exported, show_unexported);
|
|
||||||
if (n->new_scope)
|
if (n->new_scope)
|
||||||
break;
|
break;
|
||||||
else
|
else
|
||||||
|
@ -985,7 +1048,7 @@ wcstring_list_t env_scoped_t::get_names(int flags) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (show_global) {
|
if (show_global) {
|
||||||
add_key_to_string_set(vars_stack().global_env->env, &names, show_exported, show_unexported);
|
add_keys(vars_stack().global_env->env);
|
||||||
if (show_unexported) {
|
if (show_unexported) {
|
||||||
result.insert(result.end(), std::begin(env_electric), std::end(env_electric));
|
result.insert(result.end(), std::begin(env_electric), std::end(env_electric));
|
||||||
}
|
}
|
||||||
|
@ -1000,76 +1063,6 @@ wcstring_list_t env_scoped_t::get_names(int flags) const {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get list of all exported variables.
|
|
||||||
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(global_env.get(), h);
|
|
||||||
} else {
|
|
||||||
get_exported(n->next.get(), h);
|
|
||||||
}
|
|
||||||
|
|
||||||
var_table_t::const_iterator iter;
|
|
||||||
for (iter = n->env.begin(); iter != n->env.end(); ++iter) {
|
|
||||||
const wcstring &key = iter->first;
|
|
||||||
const env_var_t var = iter->second;
|
|
||||||
|
|
||||||
if (var.exports()) {
|
|
||||||
// Export the variable. Don't use std::map::insert here, since we need to overwrite
|
|
||||||
// existing values from previous scopes.
|
|
||||||
h[key] = var;
|
|
||||||
} else {
|
|
||||||
// We need to erase from the map if we are not exporting, since a lower scope may have
|
|
||||||
// exported. See #2132.
|
|
||||||
h.erase(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a map from key to value, return a vector of strings of the form key=value
|
|
||||||
static std::vector<std::string> get_export_list(const var_table_t &envs) {
|
|
||||||
std::vector<std::string> result;
|
|
||||||
result.reserve(envs.size());
|
|
||||||
for (const auto &kv : envs) {
|
|
||||||
std::string ks = wcs2string(kv.first);
|
|
||||||
std::string vs = wcs2string(kv.second.as_string());
|
|
||||||
// Create and append a string of the form ks=vs
|
|
||||||
std::string str;
|
|
||||||
str.reserve(ks.size() + 1 + vs.size());
|
|
||||||
str.append(ks);
|
|
||||||
str.append("=");
|
|
||||||
str.append(vs);
|
|
||||||
result.push_back(std::move(str));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void var_stack_t::update_export_array_if_necessary() {
|
|
||||||
if (!this->has_changed_exported()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug(4, L"export_arr() recalc");
|
|
||||||
var_table_t vals;
|
|
||||||
get_exported(this->top.get(), vals);
|
|
||||||
|
|
||||||
if (uvars()) {
|
|
||||||
const wcstring_list_t uni = uvars()->get_names(true, false);
|
|
||||||
for (const wcstring &key : uni) {
|
|
||||||
auto var = uvars()->get(key);
|
|
||||||
|
|
||||||
if (!var.missing_or_empty()) {
|
|
||||||
// Note that std::map::insert does NOT overwrite a value already in the map,
|
|
||||||
// which we depend on here.
|
|
||||||
vals.insert(std::pair<wcstring, env_var_t>(key, *var));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export_array.emplace(get_export_list(vals));
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *const *env_stack_t::export_arr() {
|
const char *const *env_stack_t::export_arr() {
|
||||||
ASSERT_IS_MAIN_THREAD();
|
ASSERT_IS_MAIN_THREAD();
|
||||||
ASSERT_IS_NOT_FORKED_CHILD();
|
ASSERT_IS_NOT_FORKED_CHILD();
|
||||||
|
@ -1090,22 +1083,9 @@ void env_stack_t::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() = default;
|
||||||
env_stack_t::env_stack_t(env_stack_t &&) = default;
|
env_stack_t::env_stack_t(env_stack_t &&) = default;
|
||||||
|
|
||||||
null_environment_t::null_environment_t() = default;
|
|
||||||
null_environment_t::~null_environment_t() = default;
|
|
||||||
maybe_t<env_var_t> null_environment_t::get(const wcstring &key, env_mode_flags_t mode) const {
|
|
||||||
UNUSED(key);
|
|
||||||
UNUSED(mode);
|
|
||||||
return none();
|
|
||||||
}
|
|
||||||
wcstring_list_t null_environment_t::get_names(int flags) const {
|
|
||||||
UNUSED(flags);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
env_stack_t env_stack_t::make_principal() {
|
env_stack_t env_stack_t::make_principal() {
|
||||||
const env_stack_t &gl = env_stack_t::globals();
|
const env_stack_t &gl = env_stack_t::globals();
|
||||||
std::unique_ptr<var_stack_t> dup_stack = make_unique<var_stack_t>(gl.vars_stack().clone());
|
std::unique_ptr<var_stack_t> dup_stack = make_unique<var_stack_t>(gl.vars_stack().clone());
|
||||||
|
|
Loading…
Reference in New Issue
Block a user