2016-04-29 01:26:46 +00:00
|
|
|
// Functions for setting and getting environment variables.
|
2016-05-18 22:30:21 +00:00
|
|
|
#include "config.h" // IWYU pragma: keep
|
|
|
|
|
2019-10-13 22:50:48 +00:00
|
|
|
#include "env.h"
|
|
|
|
|
2016-04-29 01:26:46 +00:00
|
|
|
#include <errno.h>
|
2005-09-20 13:26:39 +00:00
|
|
|
#include <pwd.h>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <stddef.h>
|
2017-02-16 04:09:26 +00:00
|
|
|
#include <stdio.h>
|
2016-04-29 01:26:46 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
2016-12-23 21:08:45 +00:00
|
|
|
|
2016-04-29 01:26:46 +00:00
|
|
|
#include <algorithm>
|
2022-06-21 16:08:09 +00:00
|
|
|
#include <map>
|
2019-04-13 21:27:03 +00:00
|
|
|
#include <mutex>
|
2016-04-29 01:26:46 +00:00
|
|
|
#include <set>
|
2015-07-25 15:14:25 +00:00
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2022-04-01 19:05:27 +00:00
|
|
|
#include "abbrs.h"
|
2005-09-20 13:26:39 +00:00
|
|
|
#include "common.h"
|
2023-05-17 01:51:34 +00:00
|
|
|
#include "env_dispatch.rs.h"
|
2014-06-16 00:30:50 +00:00
|
|
|
#include "env_universal_common.h"
|
2005-10-05 22:37:08 +00:00
|
|
|
#include "event.h"
|
2016-04-29 01:26:46 +00:00
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2014-05-01 07:46:27 +00:00
|
|
|
#include "fish_version.h"
|
2019-05-27 22:56:53 +00:00
|
|
|
#include "flog.h"
|
2019-04-28 23:34:54 +00:00
|
|
|
#include "global_safety.h"
|
2016-04-29 01:26:46 +00:00
|
|
|
#include "history.h"
|
|
|
|
#include "input.h"
|
2021-05-10 02:48:43 +00:00
|
|
|
#include "kill.h"
|
2022-08-21 06:14:48 +00:00
|
|
|
#include "null_terminated_array.h"
|
2016-04-29 01:26:46 +00:00
|
|
|
#include "path.h"
|
|
|
|
#include "proc.h"
|
|
|
|
#include "reader.h"
|
2020-06-07 23:05:52 +00:00
|
|
|
#include "termsize.h"
|
2023-04-26 02:38:53 +00:00
|
|
|
#include "threads.rs.h"
|
2020-07-29 23:37:23 +00:00
|
|
|
#include "wcstringutil.h"
|
2016-04-29 01:26:46 +00:00
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2005-09-20 13:26:39 +00:00
|
|
|
|
2016-04-29 01:26:46 +00:00
|
|
|
/// Some configuration path environment variables.
|
2022-09-20 18:58:37 +00:00
|
|
|
#define FISH_DATADIR_VAR L"__fish_data_dir"
|
|
|
|
#define FISH_SYSCONFDIR_VAR L"__fish_sysconf_dir"
|
|
|
|
#define FISH_HELPDIR_VAR L"__fish_help_dir"
|
|
|
|
#define FISH_BIN_DIR L"__fish_bin_dir"
|
|
|
|
#define FISH_CONFIG_DIR L"__fish_config_dir"
|
|
|
|
#define FISH_USER_DATA_DIR L"__fish_user_data_dir"
|
2012-07-18 17:50:38 +00:00
|
|
|
|
2016-04-29 01:26:46 +00:00
|
|
|
/// At init, we read all the environment variables from this array.
|
2005-09-20 13:26:39 +00:00
|
|
|
extern char **environ;
|
|
|
|
|
2023-04-30 02:58:45 +00:00
|
|
|
// static
|
|
|
|
env_var_t env_var_t::new_ffi(EnvVar *ptr) {
|
|
|
|
assert(ptr != nullptr && "env_var_t::new_ffi called with null pointer");
|
|
|
|
return env_var_t(rust::Box<EnvVar>::from_raw(ptr));
|
|
|
|
}
|
|
|
|
|
|
|
|
wchar_t env_var_t::get_delimiter() const { return impl_->get_delimiter(); }
|
|
|
|
|
|
|
|
bool env_var_t::empty() const { return impl_->is_empty(); }
|
|
|
|
bool env_var_t::exports() const { return impl_->exports(); }
|
|
|
|
bool env_var_t::is_read_only() const { return impl_->is_read_only(); }
|
|
|
|
bool env_var_t::is_pathvar() const { return impl_->is_pathvar(); }
|
|
|
|
env_var_t::env_var_flags_t env_var_t::get_flags() const { return impl_->get_flags(); }
|
2019-04-14 21:01:23 +00:00
|
|
|
|
2023-04-30 02:58:45 +00:00
|
|
|
wcstring env_var_t::as_string() const {
|
|
|
|
wcstring res = std::move(*impl_->as_string());
|
|
|
|
return res;
|
2019-04-14 21:01:23 +00:00
|
|
|
}
|
|
|
|
|
2023-04-30 02:58:45 +00:00
|
|
|
void env_var_t::to_list(std::vector<wcstring> &out) const {
|
|
|
|
wcstring_list_ffi_t list{};
|
|
|
|
impl_->to_list(list);
|
|
|
|
out = std::move(list.vals);
|
|
|
|
}
|
2019-04-14 21:01:23 +00:00
|
|
|
|
2023-04-30 02:58:45 +00:00
|
|
|
std::vector<wcstring> env_var_t::as_list() const {
|
|
|
|
std::vector<wcstring> res = std::move(impl_->as_list()->vals);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
env_var_t &env_var_t::operator=(const env_var_t &rhs) {
|
|
|
|
this->impl_ = rhs.impl_->clone_box();
|
|
|
|
return *this;
|
|
|
|
}
|
2019-04-14 21:01:23 +00:00
|
|
|
|
2023-04-29 19:43:41 +00:00
|
|
|
env_var_t::env_var_t(const wcstring_list_ffi_t &vals, uint8_t flags)
|
|
|
|
: impl_(env_var_create(vals, flags)) {}
|
2019-04-14 21:01:23 +00:00
|
|
|
|
2023-04-30 02:58:45 +00:00
|
|
|
env_var_t::env_var_t(const env_var_t &rhs) : impl_(rhs.impl_->clone_box()) {}
|
|
|
|
|
|
|
|
bool env_var_t::operator==(const env_var_t &rhs) const { return impl_->equals(*rhs.impl_); }
|
|
|
|
|
2019-04-14 21:01:23 +00:00
|
|
|
environment_t::~environment_t() = default;
|
|
|
|
|
2023-04-29 19:43:41 +00:00
|
|
|
env_var_t::env_var_flags_t env_var_t::flags_for(const wchar_t *name) { return env_flags_for(name); }
|
|
|
|
|
2019-04-14 21:01:23 +00:00
|
|
|
wcstring environment_t::get_pwd_slash() const {
|
|
|
|
// Return "/" if PWD is missing.
|
|
|
|
// See https://github.com/fish-shell/fish-shell/issues/5080
|
2023-04-20 10:24:53 +00:00
|
|
|
auto pwd_var = get_unless_empty(L"PWD");
|
2019-04-14 21:01:23 +00:00
|
|
|
wcstring pwd;
|
2023-04-20 10:24:53 +00:00
|
|
|
if (pwd_var) {
|
2019-04-14 21:01:23 +00:00
|
|
|
pwd = pwd_var->as_string();
|
|
|
|
}
|
|
|
|
if (!string_suffixes_string(L"/", pwd)) {
|
|
|
|
pwd.push_back(L'/');
|
|
|
|
}
|
|
|
|
return pwd;
|
|
|
|
}
|
|
|
|
|
2023-04-20 10:24:53 +00:00
|
|
|
maybe_t<env_var_t> environment_t::get_unless_empty(const wcstring &key,
|
|
|
|
env_mode_flags_t mode) const {
|
|
|
|
if (auto variable = this->get(key, mode)) {
|
|
|
|
if (!variable->empty()) {
|
|
|
|
return variable;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return none();
|
|
|
|
}
|
|
|
|
|
2023-03-19 02:45:00 +00:00
|
|
|
std::unique_ptr<env_var_t> environment_t::get_or_null(wcstring const &key,
|
|
|
|
env_mode_flags_t mode) const {
|
|
|
|
auto variable = this->get(key, mode);
|
|
|
|
if (!variable.has_value()) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return make_unique<env_var_t>(variable.acquire());
|
|
|
|
}
|
|
|
|
|
2023-04-29 19:43:41 +00:00
|
|
|
null_environment_t::null_environment_t() : impl_(env_null_create()) {}
|
2019-04-14 21:01:23 +00:00
|
|
|
null_environment_t::~null_environment_t() = default;
|
2023-04-29 19:43:41 +00:00
|
|
|
|
2019-04-14 21:01:23 +00:00
|
|
|
maybe_t<env_var_t> null_environment_t::get(const wcstring &key, env_mode_flags_t mode) const {
|
2023-04-29 19:43:41 +00:00
|
|
|
if (auto *ptr = impl_->getf(key, mode)) {
|
|
|
|
return env_var_t::new_ffi(ptr);
|
|
|
|
}
|
2019-04-14 21:01:23 +00:00
|
|
|
return none();
|
|
|
|
}
|
2023-04-29 19:43:41 +00:00
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> null_environment_t::get_names(env_mode_flags_t flags) const {
|
2023-04-29 19:43:41 +00:00
|
|
|
wcstring_list_ffi_t names;
|
|
|
|
impl_->get_names(flags, names);
|
|
|
|
return std::move(names.vals);
|
2019-04-14 21:01:23 +00:00
|
|
|
}
|
|
|
|
|
2022-05-02 15:15:52 +00:00
|
|
|
/// Set up the USER and HOME variable.
|
2022-06-19 23:40:27 +00:00
|
|
|
static void setup_user(env_stack_t &vars) {
|
2022-05-02 15:15:52 +00:00
|
|
|
auto uid = geteuid();
|
2023-04-20 10:24:53 +00:00
|
|
|
auto user_var = vars.get_unless_empty(L"USER");
|
2022-05-02 15:15:52 +00:00
|
|
|
struct passwd userinfo;
|
|
|
|
struct passwd *result;
|
|
|
|
char buf[8192];
|
|
|
|
|
|
|
|
// If we have a $USER, we try to get the passwd entry for the name.
|
|
|
|
// If that has the same UID that we use, we assume the data is correct.
|
2023-04-20 10:24:53 +00:00
|
|
|
if (user_var) {
|
2023-04-01 11:50:30 +00:00
|
|
|
std::string unam_narrow = wcs2zstring(user_var->as_string());
|
2022-05-02 15:15:52 +00:00
|
|
|
int retval = getpwnam_r(unam_narrow.c_str(), &userinfo, buf, sizeof(buf), &result);
|
2017-05-18 06:07:47 +00:00
|
|
|
if (!retval && result) {
|
2022-05-02 15:15:52 +00:00
|
|
|
if (result->pw_uid == uid) {
|
|
|
|
// The uid matches but we still might need to set $HOME.
|
2023-04-20 10:24:53 +00:00
|
|
|
if (!vars.get_unless_empty(L"HOME")) {
|
2022-05-02 15:15:52 +00:00
|
|
|
if (userinfo.pw_dir) {
|
2022-06-19 23:40:27 +00:00
|
|
|
vars.set_one(L"HOME", ENV_GLOBAL | ENV_EXPORT,
|
|
|
|
str2wcstring(userinfo.pw_dir));
|
2022-05-02 15:15:52 +00:00
|
|
|
} else {
|
|
|
|
vars.set_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Either we didn't have a $USER or it had a different uid.
|
|
|
|
// We need to get the data *again* via the uid.
|
|
|
|
int retval = getpwuid_r(uid, &userinfo, buf, sizeof(buf), &result);
|
|
|
|
if (!retval && result) {
|
|
|
|
const wcstring uname = str2wcstring(userinfo.pw_name);
|
|
|
|
vars.set_one(L"USER", ENV_GLOBAL | ENV_EXPORT, uname);
|
|
|
|
// Only change $HOME if it's empty, so we allow e.g. `HOME=(mktemp -d)`.
|
|
|
|
// This is okay with common `su` and `sudo` because they set $HOME.
|
2023-04-20 10:24:53 +00:00
|
|
|
if (!vars.get_unless_empty(L"HOME")) {
|
2022-05-02 15:15:52 +00:00
|
|
|
if (userinfo.pw_dir) {
|
2022-06-19 23:40:27 +00:00
|
|
|
vars.set_one(L"HOME", ENV_GLOBAL | ENV_EXPORT, str2wcstring(userinfo.pw_dir));
|
2022-05-02 15:15:52 +00:00
|
|
|
} else {
|
|
|
|
// We cannot get $HOME. This triggers warnings for history and config.fish already,
|
|
|
|
// so it isn't necessary to warn here as well.
|
|
|
|
vars.set_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT);
|
|
|
|
}
|
2016-11-28 17:24:49 +00:00
|
|
|
}
|
2023-04-20 10:24:53 +00:00
|
|
|
} else if (!vars.get_unless_empty(L"HOME")) {
|
2022-05-02 15:15:52 +00:00
|
|
|
// If $USER is empty as well (which we tried to set above), we can't get $HOME.
|
|
|
|
vars.set_empty(L"HOME", ENV_GLOBAL | ENV_EXPORT);
|
2016-11-28 17:24:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-16 04:09:26 +00:00
|
|
|
/// Various things we need to initialize at run-time that don't really fit any of the other init
|
|
|
|
/// routines.
|
|
|
|
void misc_init() {
|
|
|
|
// If stdout is open on a tty ensure stdio is unbuffered. That's because those functions might
|
|
|
|
// be intermixed with `write()` calls and we need to ensure the writes are not reordered. See
|
|
|
|
// issue #3748.
|
|
|
|
if (isatty(STDOUT_FILENO)) {
|
|
|
|
fflush(stdout);
|
2019-11-19 02:34:50 +00:00
|
|
|
setvbuf(stdout, nullptr, _IONBF, 0);
|
2017-02-16 04:09:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-14 21:01:23 +00:00
|
|
|
/// Make sure the PATH variable contains something.
|
|
|
|
static void setup_path() {
|
|
|
|
auto &vars = env_stack_t::globals();
|
2023-04-20 10:24:53 +00:00
|
|
|
const auto path = vars.get_unless_empty(L"PATH");
|
|
|
|
if (!path) {
|
2019-04-14 21:01:23 +00:00
|
|
|
#if defined(_CS_PATH)
|
|
|
|
// _CS_PATH: colon-separated paths to find POSIX utilities
|
|
|
|
std::string cspath;
|
|
|
|
cspath.resize(confstr(_CS_PATH, nullptr, 0));
|
2021-05-24 18:19:01 +00:00
|
|
|
if (cspath.length() > 0) {
|
|
|
|
confstr(_CS_PATH, &cspath[0], cspath.length());
|
|
|
|
// remove the trailing null-terminator
|
|
|
|
cspath.resize(cspath.length() - 1);
|
|
|
|
}
|
2019-04-14 21:01:23 +00:00
|
|
|
#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));
|
|
|
|
}
|
|
|
|
}
|
2017-08-19 00:26:45 +00:00
|
|
|
|
2022-06-21 16:08:09 +00:00
|
|
|
static std::map<wcstring, wcstring> inheriteds;
|
|
|
|
|
2022-07-25 13:57:01 +00:00
|
|
|
const std::map<wcstring, wcstring> &env_get_inherited() { return inheriteds; }
|
2022-06-21 16:08:09 +00:00
|
|
|
|
2021-05-01 17:43:31 +00:00
|
|
|
void env_init(const struct config_paths_t *paths, bool do_uvars, bool default_paths) {
|
2019-05-10 01:38:18 +00:00
|
|
|
env_stack_t &vars = env_stack_t::principal();
|
2016-04-29 01:26:46 +00:00
|
|
|
// Import environment variables. Walk backwards so that the first one out of any duplicates wins
|
2017-04-04 06:16:11 +00:00
|
|
|
// (See issue #2784).
|
2016-03-06 03:07:00 +00:00
|
|
|
wcstring key, val;
|
2016-05-01 00:46:14 +00:00
|
|
|
const char *const *envp = environ;
|
2021-10-31 10:50:22 +00:00
|
|
|
int i = 0;
|
2017-08-05 22:08:39 +00:00
|
|
|
while (envp && envp[i]) i++;
|
2016-04-29 01:26:46 +00:00
|
|
|
while (i--) {
|
|
|
|
const wcstring key_and_val = str2wcstring(envp[i]); // like foo=bar
|
2012-12-19 21:31:06 +00:00
|
|
|
size_t eql = key_and_val.find(L'=');
|
2016-04-29 01:26:46 +00:00
|
|
|
if (eql == wcstring::npos) {
|
2017-08-05 22:08:39 +00:00
|
|
|
// No equal-sign found so treat it as a defined var that has no value(s).
|
2023-04-29 19:43:41 +00:00
|
|
|
if (!var_is_electric(key_and_val)) {
|
2019-05-12 02:09:22 +00:00
|
|
|
vars.set_empty(key_and_val, ENV_EXPORT | ENV_GLOBAL);
|
|
|
|
}
|
2022-06-21 16:08:09 +00:00
|
|
|
inheriteds[key] = L"";
|
2016-04-29 01:26:46 +00:00
|
|
|
} else {
|
2016-03-06 03:07:00 +00:00
|
|
|
key.assign(key_and_val, 0, eql);
|
2019-05-05 10:09:25 +00:00
|
|
|
val.assign(key_and_val, eql + 1, wcstring::npos);
|
2022-06-21 16:08:09 +00:00
|
|
|
inheriteds[key] = val;
|
2023-04-29 19:43:41 +00:00
|
|
|
if (!var_is_electric(key)) {
|
2020-10-26 02:45:45 +00:00
|
|
|
// 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.
|
|
|
|
if (key != L"fish_user_paths") {
|
|
|
|
vars.set(key, ENV_EXPORT | ENV_GLOBAL, {val});
|
|
|
|
}
|
2019-05-12 02:09:22 +00:00
|
|
|
}
|
2012-11-19 00:30:30 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
}
|
|
|
|
|
2016-04-29 01:26:46 +00:00
|
|
|
// Set the given paths in the environment, if we have any.
|
2019-11-19 02:34:50 +00:00
|
|
|
if (paths != nullptr) {
|
2018-09-14 07:36:26 +00:00
|
|
|
vars.set_one(FISH_DATADIR_VAR, ENV_GLOBAL, paths->data);
|
|
|
|
vars.set_one(FISH_SYSCONFDIR_VAR, ENV_GLOBAL, paths->sysconf);
|
|
|
|
vars.set_one(FISH_HELPDIR_VAR, ENV_GLOBAL, paths->doc);
|
|
|
|
vars.set_one(FISH_BIN_DIR, ENV_GLOBAL, paths->bin);
|
2021-05-01 17:43:31 +00:00
|
|
|
if (default_paths) {
|
2021-05-10 02:48:43 +00:00
|
|
|
wcstring scstr = paths->data;
|
2021-05-01 17:43:31 +00:00
|
|
|
scstr.append(L"/functions");
|
|
|
|
vars.set_one(L"fish_function_path", ENV_GLOBAL, scstr);
|
|
|
|
}
|
2012-07-18 17:50:38 +00:00
|
|
|
}
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2022-05-02 15:15:52 +00:00
|
|
|
// Set $USER, $HOME and $EUID
|
|
|
|
// This involves going to passwd and stuff.
|
2022-04-15 13:57:57 +00:00
|
|
|
vars.set_one(L"EUID", ENV_GLOBAL, to_string(static_cast<unsigned long long>(geteuid())));
|
2022-06-19 23:40:27 +00:00
|
|
|
setup_user(vars);
|
2014-07-25 17:42:22 +00:00
|
|
|
|
2021-01-11 17:49:15 +00:00
|
|
|
wcstring user_config_dir;
|
|
|
|
path_get_config(user_config_dir);
|
|
|
|
vars.set_one(FISH_CONFIG_DIR, ENV_GLOBAL, user_config_dir);
|
|
|
|
|
|
|
|
wcstring user_data_dir;
|
|
|
|
path_get_data(user_data_dir);
|
|
|
|
vars.set_one(FISH_USER_DATA_DIR, ENV_GLOBAL, user_data_dir);
|
|
|
|
|
|
|
|
// Set up a default PATH
|
|
|
|
setup_path();
|
|
|
|
|
|
|
|
// Set up $IFS - this used to be in share/config.fish, but really breaks if it isn't done.
|
|
|
|
vars.set_one(L"IFS", ENV_GLOBAL, L"\n \t");
|
|
|
|
|
2022-05-22 19:29:51 +00:00
|
|
|
// Ensure this var is present even before an interactive command is run so that if it is used
|
|
|
|
// in a function like `fish_prompt` or `fish_right_prompt` it is defined at the time the first
|
|
|
|
// prompt is written.
|
|
|
|
vars.set_one(L"CMD_DURATION", ENV_UNEXPORT, L"0");
|
|
|
|
|
2021-01-11 17:49:15 +00:00
|
|
|
// Set up the version variable.
|
|
|
|
wcstring version = str2wcstring(get_fish_version());
|
|
|
|
vars.set_one(L"version", ENV_GLOBAL, version);
|
|
|
|
vars.set_one(L"FISH_VERSION", ENV_GLOBAL, version);
|
|
|
|
|
|
|
|
// Set the $fish_pid variable.
|
|
|
|
vars.set_one(L"fish_pid", ENV_GLOBAL, to_string(getpid()));
|
|
|
|
|
|
|
|
// Set the $hostname variable
|
|
|
|
wcstring hostname = L"fish";
|
|
|
|
get_hostname_identifier(hostname);
|
|
|
|
vars.set_one(L"hostname", ENV_GLOBAL, hostname);
|
|
|
|
|
|
|
|
// Set up SHLVL variable. Not we can't use vars.get() because SHLVL is read-only, and therefore
|
|
|
|
// was not inherited from the environment.
|
Don't touch $SHLVL if not interactive
It's not super clear what $SHLVL is useful for, but the current
definition is essentially
"number of shells in the parent processes + 1"
which isn't *super useful*?
Bash's behavior here is a bit weird in that it increments $SHLVL
basically always, but since it auto-execs the last process it will
decrement it again, so in practice it's often not incremented.
E.g.
```
> echo $SHLVL
1
> bash -c 'echo $SHLVL; bash'
2
>> echo $SHLVL
2
```
Both bashes here end up having the same $SHLVL because this is
equivalent to `echo $SHLVL; exec bash`. Running `echo $SHLVL` and then
`bash -c 'echo $SHLVL'` in an interactive bash will have a different
result (1 and 2) because that doesn't *exec* the inner bash.
That's not something we want to get into, so what we do is increment
$SHLVL in every interactive fish. Non-interactive fish will simply
import the existing value.
That means if you had e.g. a bash that runs a fish script that ends up
opening a new fish session, you would have a $SHLVL of *2* - one for the
bash, and one for the inner fish.
We key this off is_interactive_session() (which can also be enabled
via `fish -i`) because it's easy and because `fish -i` is asking for
fish to be, in some form, "interactive".
That means most of the time $SHLVL will be "how many shells am I deep,
how often do I have to `exit`", except for when you specifically asked
for a fish to be "interactive". If that's a problem, we can rethink it.
Fixes #7864.
2021-03-29 15:35:55 +00:00
|
|
|
if (is_interactive_session()) {
|
|
|
|
wcstring nshlvl_str = L"1";
|
|
|
|
if (const char *shlvl_var = getenv("SHLVL")) {
|
2021-04-21 20:31:58 +00:00
|
|
|
// TODO: Figure out how to handle invalid numbers better. Shouldn't we issue a
|
|
|
|
// diagnostic?
|
2022-12-30 21:38:47 +00:00
|
|
|
long shlvl_i = fish_wcstol(str2wcstring(shlvl_var).c_str());
|
Don't touch $SHLVL if not interactive
It's not super clear what $SHLVL is useful for, but the current
definition is essentially
"number of shells in the parent processes + 1"
which isn't *super useful*?
Bash's behavior here is a bit weird in that it increments $SHLVL
basically always, but since it auto-execs the last process it will
decrement it again, so in practice it's often not incremented.
E.g.
```
> echo $SHLVL
1
> bash -c 'echo $SHLVL; bash'
2
>> echo $SHLVL
2
```
Both bashes here end up having the same $SHLVL because this is
equivalent to `echo $SHLVL; exec bash`. Running `echo $SHLVL` and then
`bash -c 'echo $SHLVL'` in an interactive bash will have a different
result (1 and 2) because that doesn't *exec* the inner bash.
That's not something we want to get into, so what we do is increment
$SHLVL in every interactive fish. Non-interactive fish will simply
import the existing value.
That means if you had e.g. a bash that runs a fish script that ends up
opening a new fish session, you would have a $SHLVL of *2* - one for the
bash, and one for the inner fish.
We key this off is_interactive_session() (which can also be enabled
via `fish -i`) because it's easy and because `fish -i` is asking for
fish to be, in some form, "interactive".
That means most of the time $SHLVL will be "how many shells am I deep,
how often do I have to `exit`", except for when you specifically asked
for a fish to be "interactive". If that's a problem, we can rethink it.
Fixes #7864.
2021-03-29 15:35:55 +00:00
|
|
|
if (!errno && shlvl_i >= 0) {
|
|
|
|
nshlvl_str = to_string(shlvl_i + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
vars.set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, nshlvl_str);
|
|
|
|
} else {
|
|
|
|
// If we're not interactive, simply pass the value along.
|
|
|
|
if (const char *shlvl_var = getenv("SHLVL")) {
|
|
|
|
vars.set_one(L"SHLVL", ENV_GLOBAL | ENV_EXPORT, str2wcstring(shlvl_var));
|
2021-01-11 17:49:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-17 01:03:13 +00:00
|
|
|
// initialize the PWD variable if necessary
|
2019-02-18 21:11:01 +00:00
|
|
|
// Note we may inherit a virtual PWD that doesn't match what getcwd would return; respect that
|
|
|
|
// if and only if it matches getcwd (#5647). Note we treat PWD as read-only so it was not set in
|
|
|
|
// vars.
|
2021-01-17 22:03:15 +00:00
|
|
|
//
|
|
|
|
// Also reject all paths that don't start with "/", this includes windows paths like "F:\foo".
|
|
|
|
// (see #7636)
|
2019-02-18 21:11:01 +00:00
|
|
|
const char *incoming_pwd_cstr = getenv("PWD");
|
|
|
|
wcstring incoming_pwd = incoming_pwd_cstr ? str2wcstring(incoming_pwd_cstr) : wcstring{};
|
2021-04-21 20:31:58 +00:00
|
|
|
if (!incoming_pwd.empty() && incoming_pwd.front() == L'/' &&
|
|
|
|
paths_are_same_file(incoming_pwd, L".")) {
|
2019-02-18 21:11:01 +00:00
|
|
|
vars.set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, incoming_pwd);
|
2019-01-22 21:07:55 +00:00
|
|
|
} else {
|
2018-09-11 02:17:44 +00:00
|
|
|
vars.set_pwd_from_getcwd();
|
2018-09-17 01:03:13 +00:00
|
|
|
}
|
2020-06-07 22:46:54 +00:00
|
|
|
|
|
|
|
// Initialize termsize variables.
|
2023-06-19 19:03:46 +00:00
|
|
|
auto termsize =
|
|
|
|
termsize_initialize_ffi(reinterpret_cast<const unsigned char *>(vars.get_impl_ffi()));
|
2023-04-20 10:24:53 +00:00
|
|
|
if (!vars.get_unless_empty(L"COLUMNS"))
|
2020-06-07 23:05:52 +00:00
|
|
|
vars.set_one(L"COLUMNS", ENV_GLOBAL, to_string(termsize.width));
|
2023-04-20 10:24:53 +00:00
|
|
|
if (!vars.get_unless_empty(L"LINES"))
|
2020-06-07 23:05:52 +00:00
|
|
|
vars.set_one(L"LINES", ENV_GLOBAL, to_string(termsize.height));
|
2012-11-18 10:23:22 +00:00
|
|
|
|
2016-04-29 01:26:46 +00:00
|
|
|
// Set fish_bind_mode to "default".
|
2018-09-11 02:17:44 +00:00
|
|
|
vars.set_one(FISH_BIND_MODE_VAR, ENV_GLOBAL, DEFAULT_BIND_MODE);
|
2014-07-13 20:21:06 +00:00
|
|
|
|
2019-03-26 03:18:00 +00:00
|
|
|
// Allow changes to variables to produce events.
|
2023-05-17 01:51:34 +00:00
|
|
|
env_dispatch_init_ffi(/* vars */);
|
2017-07-14 17:45:31 +00:00
|
|
|
|
2019-04-08 20:32:25 +00:00
|
|
|
init_input();
|
|
|
|
|
2019-05-02 00:31:22 +00:00
|
|
|
// Complain about invalid config paths.
|
2021-07-26 19:28:37 +00:00
|
|
|
// HACK: Assume the defaults are correct (in practice this is only --no-config anyway).
|
|
|
|
if (!default_paths) {
|
|
|
|
path_emit_config_directory_messages(vars);
|
|
|
|
}
|
2019-05-02 00:31:22 +00:00
|
|
|
|
2023-04-29 19:43:41 +00:00
|
|
|
rust_env_init(do_uvars);
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2023-04-29 19:43:41 +00:00
|
|
|
bool env_stack_t::is_principal() const { return impl_->is_principal(); }
|
2012-01-10 20:51:09 +00:00
|
|
|
|
2023-02-11 20:31:08 +00:00
|
|
|
std::vector<rust::Box<Event>> env_stack_t::universal_sync(bool always) {
|
2023-04-29 19:43:41 +00:00
|
|
|
event_list_ffi_t result;
|
|
|
|
impl_->universal_sync(always, result);
|
|
|
|
return std::move(result.events);
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2019-05-12 21:00:44 +00:00
|
|
|
statuses_t env_stack_t::get_last_statuses() const {
|
2023-04-29 19:43:41 +00:00
|
|
|
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;
|
2019-05-12 21:00:44 +00:00
|
|
|
}
|
|
|
|
|
2023-04-29 19:43:41 +00:00
|
|
|
int env_stack_t::get_last_status() const { return get_last_statuses().status; }
|
2019-05-12 21:00:44 +00:00
|
|
|
|
|
|
|
void env_stack_t::set_last_statuses(statuses_t s) {
|
2023-04-29 19:43:41 +00:00
|
|
|
return impl_->set_last_statuses(s.status, s.kill_signal, s.pipestatus);
|
2019-05-12 21:00:44 +00:00
|
|
|
}
|
|
|
|
|
2019-05-10 16:10:43 +00:00
|
|
|
/// Update the PWD variable directory from the result of getcwd().
|
2023-04-29 19:43:41 +00:00
|
|
|
void env_stack_t::set_pwd_from_getcwd() { impl_->set_pwd_from_getcwd(); }
|
2019-05-10 16:10:43 +00:00
|
|
|
|
|
|
|
maybe_t<env_var_t> env_stack_t::get(const wcstring &key, env_mode_flags_t mode) const {
|
2023-04-29 19:43:41 +00:00
|
|
|
if (auto *ptr = impl_->getf(key, mode)) {
|
|
|
|
return env_var_t::new_ffi(ptr);
|
|
|
|
}
|
|
|
|
return none();
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> env_stack_t::get_names(env_mode_flags_t flags) const {
|
2023-04-29 19:43:41 +00:00
|
|
|
wcstring_list_ffi_t names;
|
|
|
|
impl_->get_names(flags, names);
|
|
|
|
return std::move(names.vals);
|
2022-05-31 01:27:46 +00:00
|
|
|
}
|
2019-05-10 16:10:43 +00:00
|
|
|
|
2023-04-18 22:19:10 +00:00
|
|
|
int env_stack_t::set(const wcstring &key, env_mode_flags_t mode, std::vector<wcstring> vals) {
|
2023-04-29 19:43:41 +00:00
|
|
|
return static_cast<int>(impl_->set(key, mode, std::move(vals)));
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2023-03-19 03:11:18 +00:00
|
|
|
int env_stack_t::set_ffi(const wcstring &key, env_mode_flags_t mode, const void *vals,
|
|
|
|
size_t count) {
|
|
|
|
const wchar_t *const *ptr = static_cast<const wchar_t *const *>(vals);
|
2023-04-18 22:19:10 +00:00
|
|
|
return this->set(key, mode, std::vector<wcstring>(ptr, ptr + count));
|
2023-03-19 03:11:18 +00:00
|
|
|
}
|
|
|
|
|
2021-10-26 15:32:13 +00:00
|
|
|
int env_stack_t::set_one(const wcstring &key, env_mode_flags_t mode, wcstring val) {
|
2023-04-18 22:19:10 +00:00
|
|
|
std::vector<wcstring> vals;
|
2019-05-10 16:10:43 +00:00
|
|
|
vals.push_back(std::move(val));
|
2021-10-26 15:32:13 +00:00
|
|
|
return set(key, mode, std::move(vals));
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2021-10-26 15:32:13 +00:00
|
|
|
int env_stack_t::set_empty(const wcstring &key, env_mode_flags_t mode) {
|
|
|
|
return set(key, mode, {});
|
2012-02-28 23:11:46 +00:00
|
|
|
}
|
|
|
|
|
2021-10-26 15:32:13 +00:00
|
|
|
int env_stack_t::remove(const wcstring &key, int mode) {
|
2023-04-29 19:43:41 +00:00
|
|
|
return static_cast<int>(impl_->remove(key, mode));
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2021-02-14 21:15:29 +00:00
|
|
|
std::shared_ptr<owning_null_terminated_array_t> env_stack_t::export_arr() {
|
2023-04-29 19:43:41 +00:00
|
|
|
// export_array() returns a rust::Box<OwningNullTerminatedArrayRefFFI>.
|
|
|
|
// Acquire ownership.
|
|
|
|
OwningNullTerminatedArrayRefFFI *ptr = impl_->export_array();
|
|
|
|
assert(ptr && "Null pointer");
|
|
|
|
return std::make_shared<owning_null_terminated_array_t>(
|
|
|
|
rust::Box<OwningNullTerminatedArrayRefFFI>::from_raw(ptr));
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2023-05-16 23:02:18 +00:00
|
|
|
maybe_t<env_var_t> env_dyn_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);
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
2023-05-16 23:02:18 +00:00
|
|
|
return none();
|
|
|
|
}
|
2023-04-29 19:43:41 +00:00
|
|
|
|
2023-05-16 23:02:18 +00:00
|
|
|
std::vector<wcstring> env_dyn_t::get_names(env_mode_flags_t flags) const {
|
|
|
|
wcstring_list_ffi_t names;
|
|
|
|
impl_->get_names(flags, names);
|
|
|
|
return std::move(names.vals);
|
|
|
|
}
|
2023-04-29 19:43:41 +00:00
|
|
|
|
|
|
|
std::shared_ptr<environment_t> env_stack_t::snapshot() const {
|
|
|
|
auto res = std::make_shared<env_dyn_t>(impl_->snapshot());
|
|
|
|
return std::static_pointer_cast<environment_t>(res);
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
2018-09-09 19:17:31 +00:00
|
|
|
|
2023-04-29 19:43:41 +00:00
|
|
|
void env_stack_t::set_argv(std::vector<wcstring> argv) { set(L"argv", ENV_LOCAL, std::move(argv)); }
|
|
|
|
|
|
|
|
wcstring env_stack_t::get_pwd_slash() const {
|
|
|
|
std::unique_ptr<wcstring> res = impl_->get_pwd_slash();
|
|
|
|
return std::move(*res);
|
2019-05-10 16:10:43 +00:00
|
|
|
}
|
|
|
|
|
2023-04-29 19:43:41 +00:00
|
|
|
void env_stack_t::push(bool new_scope) { impl_->push(new_scope); }
|
|
|
|
|
|
|
|
void env_stack_t::pop() { impl_->pop(); }
|
|
|
|
|
2019-05-10 16:10:43 +00:00
|
|
|
env_stack_t &env_stack_t::globals() {
|
2023-04-29 19:43:41 +00:00
|
|
|
static env_stack_t s_globals(env_get_globals_ffi());
|
2019-05-10 16:10:43 +00:00
|
|
|
return s_globals;
|
2018-09-14 07:36:26 +00:00
|
|
|
}
|
|
|
|
|
2019-05-20 16:27:46 +00:00
|
|
|
const std::shared_ptr<env_stack_t> &env_stack_t::principal_ref() {
|
2023-04-29 19:43:41 +00:00
|
|
|
static const std::shared_ptr<env_stack_t> s_principal{new env_stack_t(env_get_principal_ffi())};
|
2018-09-09 19:17:31 +00:00
|
|
|
return s_principal;
|
2018-09-14 07:36:26 +00:00
|
|
|
}
|
|
|
|
|
2019-05-10 16:10:43 +00:00
|
|
|
env_stack_t::~env_stack_t() = default;
|
2023-02-11 20:31:08 +00:00
|
|
|
env_stack_t::env_stack_t(env_stack_t &&) = default;
|
2023-04-29 19:43:41 +00:00
|
|
|
env_stack_t::env_stack_t(rust::Box<EnvStackRef> imp) : impl_(std::move(imp)) {}
|
2023-02-11 20:31:08 +00:00
|
|
|
|
2018-09-28 16:49:06 +00:00
|
|
|
#if defined(__APPLE__) || defined(__CYGWIN__)
|
|
|
|
static int check_runtime_path(const char *path) {
|
2019-01-13 23:03:19 +00:00
|
|
|
UNUSED(path);
|
2018-09-28 16:49:06 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
2018-09-28 15:14:27 +00:00
|
|
|
/// Check, and create if necessary, a secure runtime path. Derived from tmux.c in tmux
|
|
|
|
/// (http://tmux.sourceforge.net/).
|
|
|
|
static int check_runtime_path(const char *path) {
|
|
|
|
// Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice appear in all copies.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
// WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
|
|
// IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
|
|
// OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
struct stat statpath;
|
|
|
|
uid_t uid = geteuid();
|
|
|
|
|
|
|
|
if (mkdir(path, S_IRWXU) != 0 && errno != EEXIST) return errno;
|
|
|
|
if (lstat(path, &statpath) != 0) return errno;
|
|
|
|
if (!S_ISDIR(statpath.st_mode) || statpath.st_uid != uid ||
|
|
|
|
(statpath.st_mode & (S_IRWXG | S_IRWXO)) != 0)
|
|
|
|
return EACCES;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/// Return the path of an appropriate runtime data directory.
|
|
|
|
wcstring env_get_runtime_path() {
|
|
|
|
wcstring result;
|
|
|
|
const char *dir = getenv("XDG_RUNTIME_DIR");
|
|
|
|
|
|
|
|
// Check that the path is actually usable. Technically this is guaranteed by the fdo spec but in
|
|
|
|
// practice it is not always the case: see #1828 and #2222.
|
2020-09-17 10:30:16 +00:00
|
|
|
if (dir != nullptr && check_runtime_path(dir) == 0) {
|
2018-09-28 15:14:27 +00:00
|
|
|
result = str2wcstring(dir);
|
|
|
|
} else {
|
|
|
|
// Don't rely on $USER being set, as setup_user() has not yet been called.
|
|
|
|
// See https://github.com/fish-shell/fish-shell/issues/5180
|
2019-01-22 16:14:33 +00:00
|
|
|
// getpeuid() can't fail, but getpwuid sure can.
|
|
|
|
auto pwuid = getpwuid(geteuid());
|
2019-11-19 02:34:50 +00:00
|
|
|
const char *uname = pwuid ? pwuid->pw_name : nullptr;
|
2018-09-28 15:14:27 +00:00
|
|
|
// /tmp/fish.user
|
2019-02-06 04:36:38 +00:00
|
|
|
std::string tmpdir = get_path_to_tmp_dir() + "/fish.";
|
2019-01-22 16:14:33 +00:00
|
|
|
if (uname) {
|
|
|
|
tmpdir.append(uname);
|
|
|
|
}
|
2018-09-28 15:14:27 +00:00
|
|
|
|
2019-01-22 16:14:33 +00:00
|
|
|
if (!uname || check_runtime_path(tmpdir.c_str()) != 0) {
|
2019-05-27 22:56:53 +00:00
|
|
|
FLOG(error, L"Runtime path not available.");
|
2019-05-30 09:54:09 +00:00
|
|
|
FLOGF(error, L"Try deleting the directory %s and restarting fish.", tmpdir.c_str());
|
2018-09-28 15:14:27 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = str2wcstring(tmpdir);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
2019-05-22 23:09:59 +00:00
|
|
|
|
|
|
|
static std::mutex s_setenv_lock{};
|
|
|
|
|
2023-05-16 19:35:14 +00:00
|
|
|
extern "C" {
|
2019-05-22 23:09:59 +00:00
|
|
|
void setenv_lock(const char *name, const char *value, int overwrite) {
|
|
|
|
scoped_lock locker(s_setenv_lock);
|
|
|
|
setenv(name, value, overwrite);
|
|
|
|
}
|
|
|
|
|
|
|
|
void unsetenv_lock(const char *name) {
|
|
|
|
scoped_lock locker(s_setenv_lock);
|
|
|
|
unsetenv(name);
|
|
|
|
}
|
2023-05-16 19:35:14 +00:00
|
|
|
}
|
2023-04-30 02:58:51 +00:00
|
|
|
|
|
|
|
wcstring_list_ffi_t get_history_variable_text_ffi(const wcstring &fish_history_val) {
|
|
|
|
wcstring_list_ffi_t out{};
|
|
|
|
std::shared_ptr<history_t> history = commandline_get_state().history;
|
|
|
|
if (!history) {
|
|
|
|
// Effective duplication of history_session_id().
|
|
|
|
wcstring session_id{};
|
|
|
|
if (fish_history_val.empty()) {
|
|
|
|
// No session.
|
|
|
|
session_id.clear();
|
|
|
|
} else if (!valid_var_name(fish_history_val)) {
|
|
|
|
session_id = L"fish";
|
|
|
|
FLOGF(error,
|
|
|
|
_(L"History session ID '%ls' is not a valid variable name. "
|
|
|
|
L"Falling back to `%ls`."),
|
|
|
|
fish_history_val.c_str(), session_id.c_str());
|
|
|
|
} else {
|
|
|
|
// Valid session.
|
|
|
|
session_id = fish_history_val;
|
|
|
|
}
|
|
|
|
history = history_t::with_name(session_id);
|
|
|
|
}
|
|
|
|
if (history) {
|
|
|
|
history->get_history(out.vals);
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
event_list_ffi_t::event_list_ffi_t() = default;
|
|
|
|
|
|
|
|
void event_list_ffi_t::push(void *event_vp) {
|
|
|
|
auto event = static_cast<Event *>(event_vp);
|
|
|
|
assert(event && "Null event");
|
|
|
|
events.push_back(rust::Box<Event>::from_raw(event));
|
|
|
|
}
|