diff --git a/src/env.cpp b/src/env.cpp index 8f8b12415..06e13fe0d 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -730,6 +730,9 @@ void env_init(const struct config_paths_t *paths /* or NULL */) { init_input(); + // Complain about invalid config paths. + path_emit_config_directory_errors(vars); + // Set up universal variables. The empty string means to use the default path. assert(s_universal_variables == NULL); s_universal_variables = new env_universal_t(L""); diff --git a/src/path.cpp b/src/path.cpp index 7037b568c..c07a8fd61 100644 --- a/src/path.cpp +++ b/src/path.cpp @@ -255,8 +255,7 @@ wcstring path_apply_working_directory(const wcstring &path, const wcstring &work /// 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) { - auto &vars = env_stack_t::globals(); + int saved_errno, env_stack_t &vars) { wcstring warning_var_name = L"_FISH_WARNED_" + which_dir; if (vars.get(warning_var_name, ENV_GLOBAL | ENV_EXPORT)) { return; @@ -278,74 +277,77 @@ static void maybe_issue_path_warning(const wcstring &which_dir, const wcstring & ignore_result(write(STDERR_FILENO, "\n", 1)); } -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; +/// The following type wraps up a user's "base" directories, corresponding (conceptually if not +/// actually) to XDG spec. +struct base_directory_t { + wcstring path{}; /// the path where we attempted to create the directory. + bool success{false}; /// whether creating the directory succeeded. + int err{0}; /// the error code if creating the directory failed. + bool used_xdg{false}; /// whether an XDG variable was used in resolving the direcotry. +}; +/// Attempt to get a base directory, creating it if necessary. If a variable named \p xdg_var is +/// set, use that directory; otherwise use the path \p non_xdg_homepath rooted in $HOME. \return the +/// result; see the base_directory_t fields. +static base_directory_t make_base_directory(const wcstring &xdg_var, + const wchar_t *non_xdg_homepath) { // 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 auto &vars = env_stack_t::globals(); + base_directory_t result{}; const auto xdg_dir = vars.get(xdg_var, ENV_GLOBAL | ENV_EXPORT); if (!xdg_dir.missing_or_empty()) { - using_xdg = true; - path = xdg_dir->as_string() + L"/fish"; - if (create_directory(path) != -1) { - path_done = true; - } else { - saved_errno = errno; - } + result.path = xdg_dir->as_string() + L"/fish"; + result.used_xdg = true; } else { const auto home = vars.get(L"HOME", ENV_GLOBAL | ENV_EXPORT); if (!home.missing_or_empty()) { - path = home->as_string() + - (which_dir == L"config" ? L"/.config/fish" : L"/.local/share/fish"); - if (create_directory(path) != -1) { - path_done = true; - } else { - saved_errno = errno; - } + result.path = home->as_string() + non_xdg_homepath; } } - if (!path_done) { - maybe_issue_path_warning(which_dir, custom_error_msg, using_xdg, xdg_var, path, - saved_errno); - path.clear(); - } - - return; + errno = 0; + result.success = !result.path.empty() && create_directory(result.path) != -1; + result.err = errno; + return result; +} + +static const base_directory_t &get_data_directory() { + static base_directory_t s_dir = make_base_directory(L"XDG_DATA_HOME", L"/.local/share/fish"); + return s_dir; +} + +static const base_directory_t &get_config_directory() { + static base_directory_t s_dir = make_base_directory(L"XDG_CONFIG_HOME", L"/.config/fish"); + return s_dir; +} + +void path_emit_config_directory_errors(env_stack_t &vars) { + const auto &data = get_data_directory(); + if (!data.success) { + maybe_issue_path_warning(L"data", _(L"Your history will not be saved."), data.used_xdg, + L"XDG_DATA_HOME", data.path, data.err, vars); + } + + const auto &config = get_config_directory(); + if (!config.success) { + maybe_issue_path_warning(L"config", _(L"Your personal settings will not be saved."), + config.used_xdg, L"XDG_CONFIG_HOME", config.path, config.err, + vars); + } } -/// Cache the config path. bool path_get_config(wcstring &path) { - 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(); + const auto &dir = get_config_directory(); + path = dir.success ? dir.path : L""; + return dir.success; } -/// Cache the data path. bool path_get_data(wcstring &path) { - 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(); + const auto &dir = get_data_directory(); + path = dir.success ? dir.path : L""; + return dir.success; } void path_make_canonical(wcstring &path) { diff --git a/src/path.h b/src/path.h index ae0cc31c0..a6074df48 100644 --- a/src/path.h +++ b/src/path.h @@ -29,6 +29,11 @@ bool path_get_config(wcstring &path); /// \return whether the directory was returned successfully bool path_get_data(wcstring &path); +/// Emit any errors if config directories are missing. +/// Use the given environment stack to ensure this only occurs once. +class env_stack_t; +void path_emit_config_directory_errors(env_stack_t &vars); + /// Finds the full path of an executable. /// /// Args: