diff --git a/src/env.cpp b/src/env.cpp index 77e2e8635..3f98f3504 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -334,7 +334,7 @@ static bool variable_is_colon_delimited_array(const wcstring &str) { } /// Set up the USER variable. -static void setup_user(bool force=false) { +static void setup_user(bool force) { if (env_get_string(L"USER").missing_or_empty() || force) { const struct passwd *pw = getpwuid(getuid()); if (pw && pw->pw_name) { @@ -404,7 +404,7 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { // Set up the USER and PATH variables setup_path(); - setup_user(); + setup_user(false); // Set up the version variable. wcstring version = str2wcstring(get_fish_version()); @@ -429,7 +429,8 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { const env_var_t unam = env_get_string(L"USER"); char *unam_narrow = wcs2str(unam.c_str()); struct passwd *pw = getpwnam(unam_narrow); - if (pw == NULL) { // Maybe USER is set but it's bogus. Reset USER from the db and try again. + if (pw == NULL) { + // Maybe USER is set but it's bogus. Reset USER from the db and try again. setup_user(true); const env_var_t unam = env_get_string(L"USER"); unam_narrow = wcs2str(unam.c_str()); @@ -747,9 +748,12 @@ env_var_t env_get_string(const wcstring &key, env_mode_flags_t mode) { // that in env_set(). if (is_electric(key)) { if (!search_global) return env_var_t::missing_var(); - // 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 (key == L"history" && is_main_thread()) { + 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 env_var_t::missing_var(); + } env_var_t result; history_t *history = reader_get_history(); @@ -767,7 +771,8 @@ env_var_t env_get_string(const wcstring &key, env_mode_flags_t mode) { } else if (key == L"umask") { return format_string(L"0%0.3o", get_umask()); } - // We should never get here unless the electric var list is out of sync. + // We should never get here unless the electric var list is out of sync with the above code. + DIE("unerecognized electric var name"); } if (search_local || search_global) { diff --git a/src/env_universal_common.cpp b/src/env_universal_common.cpp index f10ceaa71..955a9196e 100644 --- a/src/env_universal_common.cpp +++ b/src/env_universal_common.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -34,6 +33,7 @@ #include "env.h" #include "env_universal_common.h" #include "fallback.h" // IWYU pragma: keep +#include "path.h" #include "utf8.h" #include "util.h" #include "wutil.h" @@ -70,7 +70,6 @@ "# This file is automatically generated by the fish.\n# Do NOT edit it directly, your " \ "changes will be overwritten.\n" -static wcstring fishd_get_config(); static wcstring get_machine_identifier(); static bool get_hostname_identifier(wcstring *result); @@ -83,12 +82,10 @@ static wcstring vars_filename_in_directory(const wcstring &wdir) { return result; } -static const wcstring &default_vars_path() { - // Oclint complains about this being a "redundant local variable"; however it isn't because the - // assignment to a static var is needed to keep the object from being deleted when this function - // returns. - static wcstring cached_result = vars_filename_in_directory(fishd_get_config()); //!OCLINT - return cached_result; +static const wcstring default_vars_path() { + wcstring path; + path_get_config(path); + return vars_filename_in_directory(path); } /// Check, and create if necessary, a secure runtime path Derived from tmux.c in tmux @@ -166,18 +163,6 @@ static bool match(const wchar_t *msg, const wchar_t *cmd) { return true; } -static void report_error(int err_code, const wchar_t *err_format, ...) { - va_list va; - va_start(va, err_format); - const wcstring err_text = vformat_string(err_format, va); - va_end(va); - - if (!err_text.empty()) { - fwprintf(stderr, L"%ls: ", err_text.c_str()); - } - fwprintf(stderr, L"%s\n", strerror(err_code)); -} - /// The universal variable format has some funny escaping requirements; here we try to be safe. static bool is_universal_safe_to_encode_directly(wchar_t c) { if (c < 32 || c > 128) return false; @@ -479,9 +464,9 @@ bool env_universal_t::write_to_fd(int fd, const wcstring &path) { // Flush if this is the last iteration or we exceed a page. if (iter == vars.end() || contents.size() >= 4096) { if (write_loop(fd, contents.data(), contents.size()) < 0) { - int err = errno; - report_error(err, L"Unable to write to universal variables file '%ls'", - path.c_str()); + const char *error = strerror(errno); + debug(0, _(L"Unable to write to universal variables file '%ls': %s"), path.c_str(), + error); success = false; break; } @@ -499,45 +484,13 @@ bool env_universal_t::write_to_fd(int fd, const wcstring &path) { bool env_universal_t::move_new_vars_file_into_place(const wcstring &src, const wcstring &dst) { int ret = wrename(src, dst); if (ret != 0) { - int err = errno; - report_error(err, L"Unable to rename file from '%ls' to '%ls'", src.c_str(), dst.c_str()); + const char *error = strerror(errno); + debug(0, _(L"Unable to rename file from '%ls' to '%ls': %s"), src.c_str(), dst.c_str(), + error); } return ret == 0; } -static wcstring fishd_get_config() { - bool done = false; - wcstring result; - - env_var_t xdg_dir = env_get_string(L"XDG_CONFIG_HOME", ENV_GLOBAL | ENV_EXPORT); - if (!xdg_dir.missing_or_empty()) { - result = xdg_dir; - append_path_component(result, L"/fish"); - if (!create_directory(result)) { - done = true; - } - } else { - env_var_t home = env_get_string(L"HOME", ENV_GLOBAL | ENV_EXPORT); - if (!home.missing_or_empty()) { - result = home; - append_path_component(result, L"/.config/fish"); - if (!create_directory(result)) { - done = 1; - } - } - } - - if (!done) { - // Bad juju. - debug(0, _(L"Unable to create a configuration directory for fish. Your personal settings " - L"will not be saved. Please set the $XDG_CONFIG_HOME variable to a directory " - L"where the current user has write access.")); - result.clear(); - } - - return result; -} - bool env_universal_t::load() { scoped_lock locker(lock); callback_data_list_t callbacks; @@ -566,12 +519,11 @@ bool env_universal_t::open_temporary_file(const wcstring &directory, wcstring *o // Create and open a temporary file for writing within the given directory. Try to create a // temporary file, up to 10 times. We don't use mkstemps because we want to open it CLO_EXEC. // This should almost always succeed on the first try. - assert(!string_suffixes_string(L"/", directory)); + assert(!string_suffixes_string(L"/", directory)); //!OCLINT(multiple unary operator) bool success = false; - int saved_errno = 0; + int saved_errno; const wcstring tmp_name_template = directory + L"/fishd.tmp.XXXXXX"; - wcstring tmp_name; for (size_t attempt = 0; attempt < 10 && !success; attempt++) { char *narrow_str = wcs2str(tmp_name_template.c_str()); @@ -592,7 +544,8 @@ bool env_universal_t::open_temporary_file(const wcstring &directory, wcstring *o } if (!success) { - report_error(saved_errno, L"Unable to open temporary file '%ls'", out_path->c_str()); + const char *error = strerror(saved_errno); + debug(0, _(L"Unable to open temporary file '%ls': %s"), out_path->c_str(), error); } return success; } @@ -614,13 +567,12 @@ bool env_universal_t::open_and_acquire_lock(const wcstring &path, int *out_fd) { for (;;) { int fd = wopen_cloexec(path, flags, 0644); if (fd < 0) { - int err = errno; - if (err == EINTR) { + if (errno == EINTR) { /* Signal; try again */ continue; } #ifdef O_EXLOCK - else if (err == ENOTSUP || err == EOPNOTSUPP) { + else if (errno == ENOTSUP || errno == EOPNOTSUPP) { // Filesystem probably does not support locking. Clear the flag and try again. Note // that we try taking the lock via flock anyways. Note that on Linux the two errno // symbols have the same value but on BSD they're different. @@ -630,7 +582,9 @@ bool env_universal_t::open_and_acquire_lock(const wcstring &path, int *out_fd) { } #endif else { - report_error(err, L"Unable to open universal variable file '%ls'", path.c_str()); + const char *error = strerror(errno); + debug(0, _(L"Unable to open universal variable file '%ls': %s"), path.c_str(), + error); break; } } @@ -1013,8 +967,8 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t { bool errored = false; int fd = shm_open(path, O_RDWR | O_CREAT, 0600); if (fd < 0) { - int err = errno; - report_error(err, L"Unable to open shared memory with path '%s'", path); + const char *error = strerror(errno); + debug(0, _(L"Unable to open shared memory with path '%s': %s"), path, error); errored = true; } @@ -1023,8 +977,9 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t { if (!errored) { struct stat buf = {}; if (fstat(fd, &buf) < 0) { - int err = errno; - report_error(err, L"Unable to fstat shared memory object with path '%s'", path); + const char *error = strerror(errno); + debug(0, _(L"Unable to fstat shared memory object with path '%s': %s"), path, + error); errored = true; } size = buf.st_size; @@ -1033,8 +988,8 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t { // Set the size, if it's too small. bool set_size = !errored && size < (off_t)sizeof(universal_notifier_shmem_t); if (set_size && ftruncate(fd, sizeof(universal_notifier_shmem_t)) < 0) { - int err = errno; - report_error(err, L"Unable to truncate shared memory object with path '%s'", path); + const char *error = strerror(errno); + debug(0, _(L"Unable to truncate shared memory object with path '%s': %s"), path, error); errored = true; } @@ -1043,9 +998,9 @@ class universal_notifier_shmem_poller_t : public universal_notifier_t { void *addr = mmap(NULL, sizeof(universal_notifier_shmem_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { - int err = errno; - report_error(err, L"Unable to memory map shared memory object with path '%s'", - path); + const char *error = strerror(errno); + debug(0, _(L"Unable to memory map shared memory object with path '%s': %s"), path, + error); this->region = NULL; } else { this->region = static_cast(addr); @@ -1242,13 +1197,14 @@ class universal_notifier_named_pipe_t : public universal_notifier_t { if (fd < 0) { // Maybe open failed, maybe mkfifo failed. - int err = errno; // We explicitly do NOT report an error for ENOENT or EACCESS. This works around #1955, // where $XDG_RUNTIME_DIR may get a bogus value under success. - if (err != ENOENT && err != EPERM) { - report_error( - err, L"Unable to make or open a FIFO for universal variables with path '%ls'", - vars_path.c_str()); + if (errno != ENOENT && errno != EPERM) { + const char *error = strerror(errno); + debug( + 0, + _(L"Unable to make or open a FIFO for universal variables with path '%ls': %s"), + vars_path.c_str(), error); } pipe_fd = -1; } else { @@ -1424,8 +1380,8 @@ static universal_notifier_t::notifier_strategy_t fetch_default_strategy_from_env } } if (i >= opt_count) { - fprintf(stderr, "Warning: unrecognized value for %s: '%s'\n", - UNIVERSAL_NOTIFIER_ENV_NAME, var); + fprintf(stderr, "Warning: unrecognized value for %s: '%s'\n", UNIVERSAL_NOTIFIER_ENV_NAME, + var); fprintf(stderr, "Warning: valid values are "); for (size_t j = 0; j < opt_count; j++) { fprintf(stderr, "%s%s", j > 0 ? ", " : "", options[j].name); diff --git a/src/env_universal_common.h b/src/env_universal_common.h index 1b0ae4904..d574b6132 100644 --- a/src/env_universal_common.h +++ b/src/env_universal_common.h @@ -1,5 +1,6 @@ #ifndef FISH_ENV_UNIVERSAL_COMMON_H #define FISH_ENV_UNIVERSAL_COMMON_H +#include "config.h" #include #include diff --git a/src/input.cpp b/src/input.cpp index 971a6a9c8..db1725623 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -2,8 +2,11 @@ #include "config.h" #include +#include +#include #include #include +#include #if HAVE_NCURSES_H #include #elif HAVE_NCURSES_CURSES_H @@ -16,8 +19,7 @@ #elif HAVE_NCURSES_TERM_H #include #endif -#include -#include + #include #include #include @@ -372,6 +374,7 @@ int input_init() { } else { debug(0, _(L"Using fallback terminal type '%ls'"), DEFAULT_TERM); } + putc('\n', stderr); } input_terminfo_init(); diff --git a/src/path.cpp b/src/path.cpp index e4775cec5..44b30a61f 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -5,9 +5,12 @@ #include #include +#include +#include #include #include #include + #include #include @@ -213,78 +216,101 @@ wcstring path_apply_working_directory(const wcstring &path, const wcstring &work return new_path; } -static wcstring path_create_config() { - bool done = false; - wcstring res; +/// We separate this from path_create() for two reasons. First it's only caused if there is a +/// problem, and thus is not central to the behavior of that function. Second, we only want to issue +/// the message once. If the current shell starts a new fish shell (e.g., by running `fish -c` from +/// a function) we don't want that subshell to issue the same warnings. +static void maybe_issue_path_warning(const wcstring &which_dir, const wcstring &custom_error_msg, + bool using_xdg, const wcstring &xdg_var, const wcstring &path, + int saved_errno) { + wcstring warning_var_name = L"_FISH_WARNED_" + which_dir; + if (env_exist(warning_var_name.c_str(), ENV_GLOBAL | ENV_EXPORT)) { + return; + } + env_set(warning_var_name, L"1", ENV_GLOBAL | ENV_EXPORT); - const env_var_t xdg_dir = env_get_string(L"XDG_CONFIG_HOME"); - if (!xdg_dir.missing()) { - res = xdg_dir + L"/fish"; - if (!create_directory(res)) { - done = true; - } + debug(0, custom_error_msg.c_str()); + if (path.empty()) { + debug(0, _(L"Unable to locate the %ls directory."), which_dir.c_str()); + debug(0, _(L"Please set the %ls or HOME environment variable " + L"before starting fish."), + xdg_var.c_str()); } else { - const env_var_t home = env_get_string(L"HOME"); - if (!home.missing()) { - res = home + L"/.config/fish"; - if (!create_directory(res)) { - done = true; - } - } + const wchar_t *env_var = using_xdg ? xdg_var.c_str() : L"HOME"; + debug(0, _(L"Unable to locate %ls directory derived from $%ls: '%ls'."), which_dir.c_str(), + env_var, path.c_str()); + debug(0, _(L"The error was '%s'."), strerror(saved_errno)); + debug(0, _(L"Please set $%ls to a directory where you have write access."), env_var); } - - if (!done) { - res.clear(); - - debug(0, _(L"Unable to create a configuration directory for fish. Your personal settings " - L"will not be saved. Please set the $XDG_CONFIG_HOME variable to a directory " - L"where the current user has write access.")); - } - return res; + putc('\n', stderr); } -static wcstring path_create_data() { - bool done = false; - wcstring res; +static void path_create(wcstring &path, const wcstring &xdg_var, const wcstring &which_dir, + const wcstring &custom_error_msg) { + bool path_done = false; + bool using_xdg = false; + int saved_errno = 0; - const env_var_t xdg_dir = env_get_string(L"XDG_DATA_HOME"); - if (!xdg_dir.missing()) { - res = xdg_dir + L"/fish"; - if (!create_directory(res)) { - done = true; + // The vars we fetch must be exported. Allowing them to be universal doesn't make sense and + // allowing that creates a lock inversion that deadlocks the shell since we're called before + // uvars are available. + const env_var_t xdg_dir = env_get_string(xdg_var, ENV_GLOBAL | ENV_EXPORT); + if (!xdg_dir.missing_or_empty()) { + using_xdg = true; + path = xdg_dir + L"/fish"; + if (create_directory(path) != -1) { + path_done = true; + } else { + saved_errno = errno; } } else { - const env_var_t home = env_get_string(L"HOME"); - if (!home.missing()) { - res = home + L"/.local/share/fish"; - if (!create_directory(res)) { - done = true; + const env_var_t home = env_get_string(L"HOME", ENV_GLOBAL | ENV_EXPORT); + if (!home.missing_or_empty()) { + path = home + L"/.config/fish"; + if (create_directory(path) != -1) { + path_done = true; + } else { + saved_errno = errno; } } } - if (!done) { - res.clear(); - - debug(0, _(L"Unable to create a data directory for fish. Your history will not be saved. " - L"Please set the $XDG_DATA_HOME variable to a directory where the current user " - L"has write access.")); + if (!path_done) { + maybe_issue_path_warning(which_dir, custom_error_msg, using_xdg, xdg_var, path, + saved_errno); + path.clear(); } - return res; + + return; } /// Cache the config path. bool path_get_config(wcstring &path) { - static const wcstring result = path_create_config(); - path = result; - return !result.empty(); + static bool config_path_done = false; + static wcstring config_path(L""); + + if (!config_path_done) { + path_create(config_path, L"XDG_CONFIG_HOME", L"config", + _(L"Your personal settings will not be saved.")); + config_path_done = true; + } + + path = config_path; + return !config_path.empty(); } /// Cache the data path. bool path_get_data(wcstring &path) { - static const wcstring result = path_create_data(); - path = result; - return !result.empty(); + static bool data_path_done = false; + static wcstring data_path(L""); + + if (!data_path_done) { + data_path_done = true; + path_create(data_path, L"XDG_DATA_HOME", L"data", _(L"Your history will not be saved.")); + } + + path = data_path; + return !data_path.empty(); } void path_make_canonical(wcstring &path) {