static wcstring path_create_data() { bool done = false; wcstring res; 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; } } 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; } } } 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.")); } return res; }
/// Make sure the PATH variable contains something. static void setup_path() { const env_var_t path = env_get_string(L"PATH"); if (path.missing_or_empty()) { const wchar_t *value = L"/usr/bin" ARRAY_SEP_STR L"/bin"; env_set(L"PATH", value, ENV_GLOBAL | ENV_EXPORT); } }
static void update_export_array_if_necessary(bool recalc) { ASSERT_IS_MAIN_THREAD(); if (recalc && !get_proc_had_barrier()) { set_proc_had_barrier(true); env_universal_barrier(); } if (has_changed_exported) { std::map<wcstring, wcstring> vals; debug(4, L"env_export_arr() recalc"); get_exported(top, &vals); if (uvars()) { const wcstring_list_t uni = uvars()->get_names(true, false); for (size_t i = 0; i < uni.size(); i++) { const wcstring &key = uni.at(i); const env_var_t val = uvars()->get(key); if (!val.missing() && val != ENV_NULL) { // Note that std::map::insert does NOT overwrite a value already in the map, // which we depend on here. vals.insert(std::pair<wcstring, wcstring>(key, val)); } } } std::vector<std::string> local_export_buffer; export_func(vals, local_export_buffer); export_array.set(local_export_buffer); has_changed_exported = false; } }
static wcstring path_create_config() { bool done = false; wcstring res; 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; } } 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; } } } 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; }
/// Properly sets all locale information. static void handle_locale(const wchar_t *env_var_name) { debug(2, L"handle_locale() called in response to '%ls' changing", env_var_name); const char *old_msg_locale = setlocale(LC_MESSAGES, NULL); const env_var_t val = env_get_string(env_var_name, ENV_EXPORT); const std::string &value = wcs2string(val); const std::string &name = wcs2string(env_var_name); debug(2, L"locale var %s='%s'", name.c_str(), value.c_str()); if (val.empty()) { unsetenv(name.c_str()); } else { setenv(name.c_str(), value.c_str(), 1); } char *locale = setlocale(LC_ALL, ""); fish_setlocale(); debug(2, L"handle_locale() setlocale(): '%s'", locale); const char *new_msg_locale = setlocale(LC_MESSAGES, NULL); debug(3, L"old LC_MESSAGES locale: '%s'", old_msg_locale); debug(3, L"new LC_MESSAGES locale: '%s'", new_msg_locale); #ifdef HAVE__NL_MSG_CAT_CNTR if (strcmp(old_msg_locale, new_msg_locale)) { // Make change known to GNU gettext. extern int _nl_msg_cat_cntr; _nl_msg_cat_cntr++; } #endif }
/** When fishd isn't started, this function is provided to env_universal as a callback, it tries to start up fishd. It's implementation is a bit of a hack, since it evaluates a bit of shellscript, and it might be used at times when that might not be the best idea. */ static void start_fishd() { struct passwd *pw = getpwuid(getuid()); debug(3, L"Spawning new copy of fishd"); if (!pw) { debug(0, _(L"Could not get user information")); return; } wcstring cmd = format_string(FISHD_CMD, pw->pw_name); /* Prefer the fishd in __fish_bin_dir, if exists */ const env_var_t bin_dir = env_get_string(L"__fish_bin_dir"); if (! bin_dir.missing_or_empty()) { wcstring path = bin_dir + L"/fishd"; if (waccess(path, X_OK) == 0) { /* The path command just looks like 'fishd', so insert the bin path to make it absolute */ cmd.insert(0, bin_dir + L"/"); } } parser_t &parser = parser_t::principal_parser(); parser.eval(cmd, io_chain_t(), TOP); }
/** Set up default values for various variables if not defined. */ static void env_set_defaults() { if( env_get_string(L"USER").missing() ) { struct passwd *pw = getpwuid( getuid()); wchar_t *unam = str2wcs( pw->pw_name ); env_set( L"USER", unam, ENV_GLOBAL ); free( unam ); } if( env_get_string(L"HOME").missing() ) { const env_var_t unam = env_get_string( L"USER" ); char *unam_narrow = wcs2str( unam.c_str() ); struct passwd *pw = getpwnam( unam_narrow ); wchar_t *dir = str2wcs( pw->pw_dir ); env_set( L"HOME", dir, ENV_GLOBAL ); free( dir ); free( unam_narrow ); } env_set_pwd(); }
/** Set up default values for various variables if not defined. */ static void env_set_defaults() { if (env_get_string(L"USER").missing()) { struct passwd *pw = getpwuid(getuid()); if (pw->pw_name != NULL) { const wcstring wide_name = str2wcstring(pw->pw_name); env_set(L"USER", wide_name.c_str(), ENV_GLOBAL); } } if (env_get_string(L"HOME").missing()) { 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->pw_dir != NULL) { const wcstring dir = str2wcstring(pw->pw_dir); env_set(L"HOME", dir.c_str(), ENV_GLOBAL); } free(unam_narrow); } env_set_pwd(); }
/// Insert a list of all dynamically loaded functions into the specified list. static void autoload_names(std::set<wcstring> &names, int get_hidden) { size_t i; const env_var_t path_var_wstr = env_get_string(L"fish_function_path"); if (path_var_wstr.missing()) return; const wchar_t *path_var = path_var_wstr.c_str(); wcstring_list_t path_list; tokenize_variable_array(path_var, path_list); for (i = 0; i < path_list.size(); i++) { const wcstring &ndir_str = path_list.at(i); const wchar_t *ndir = (wchar_t *)ndir_str.c_str(); DIR *dir = wopendir(ndir); if (!dir) continue; wcstring name; while (wreaddir(dir, name)) { const wchar_t *fn = name.c_str(); const wchar_t *suffix; if (!get_hidden && fn[0] == L'_') continue; suffix = wcsrchr(fn, L'.'); if (suffix && (wcscmp(suffix, L".fish") == 0)) { wcstring name(fn, suffix - fn); names.insert(name); } } closedir(dir); } }
bool autoload_t::can_load(const wcstring &cmd, const env_vars_snapshot_t &vars) { const env_var_t path_var = vars.get(env_var_name); if (path_var.missing_or_empty()) return false; std::vector<wcstring> path_list; tokenize_variable_array(path_var, path_list); return this->locate_file_and_maybe_load_it(cmd, false, false, path_list); }
void kill_add(const wcstring &str) { ASSERT_IS_MAIN_THREAD(); if (str.empty()) return; wcstring cmd; wcstring escaped_str; kill_list.push_front(str); /* Check to see if user has set the FISH_CLIPBOARD_CMD variable, and, if so, use it instead of checking the display, etc. I couldn't think of a safe way to allow overide of the echo command too, so, the command used must accept the input via stdin. */ const env_var_t clipboard_wstr = env_get_string(L"FISH_CLIPBOARD_CMD"); if (!clipboard_wstr.missing()) { escaped_str = escape(str.c_str(), ESCAPE_ALL); cmd.assign(L"echo -n "); cmd.append(escaped_str); cmd.append(clipboard_wstr); } else { /* This is for sending the kill to the X copy-and-paste buffer */ if (!has_xsel()) { return; } const env_var_t disp_wstr = env_get_string(L"DISPLAY"); if (!disp_wstr.missing()) { escaped_str = escape(str.c_str(), ESCAPE_ALL); cmd.assign(L"echo -n "); cmd.append(escaped_str); cmd.append(L" | xsel -i -b"); } } if (! cmd.empty()) { if (exec_subshell(cmd, false /* do not apply exit status */) == -1) { /* Do nothing on failiure */ } cut_buffer = escaped_str; } }
env_vars_snapshot_t::env_vars_snapshot_t(const wchar_t *const *keys) { ASSERT_IS_MAIN_THREAD(); wcstring key; for (size_t i = 0; keys[i]; i++) { key.assign(keys[i]); const env_var_t val = env_get_string(key); if (!val.missing()) { vars[key] = val; } } }
/// Properly sets all timezone information. static void handle_timezone(const wchar_t *env_var_name) { debug(2, L"handle_timezone() called in response to '%ls' changing", env_var_name); const env_var_t val = env_get_string(env_var_name, ENV_EXPORT); const std::string &value = wcs2string(val); const std::string &name = wcs2string(env_var_name); debug(2, L"timezone var %s='%s'", name.c_str(), value.c_str()); if (val.empty()) { unsetenv(name.c_str()); } else { setenv(name.c_str(), value.c_str(), 1); } tzset(); }
/// Push all curses/terminfo env vars into the global environment where they can be found by those /// libraries. static void handle_curses(const wchar_t *env_var_name) { debug(2, L"handle_curses() called in response to '%ls' changing", env_var_name); const env_var_t val = env_get_string(env_var_name, ENV_EXPORT); const std::string &name = wcs2string(env_var_name); const std::string &value = wcs2string(val); debug(2, L"curses var %s='%s'", name.c_str(), value.c_str()); if (val.empty()) { unsetenv(name.c_str()); } else { setenv(name.c_str(), value.c_str(), 1); } // TODO: Modify input_init() to allow calling it when the terminfo env vars are dynamically // changed. At the present time it can be called just once. Also, we should really only do this // if the TERM var is set. // input_init(); }
wcstring expand_escape_variable(const env_var_t &var) { wcstring buff; wcstring_list_t lst; var.to_list(lst); if (lst.size() == 0) { ; // empty list expands to nothing } else if (lst.size() == 1) { const wcstring &el = lst.at(0); if (el.find(L' ') != wcstring::npos && is_quotable(el)) { buff.append(L"'"); buff.append(el); buff.append(L"'"); } else { buff.append(escape_string(el, 1)); } } else { for (size_t j = 0; j < lst.size(); j++) { const wcstring &el = lst.at(j); if (j) buff.append(L" "); if (is_quotable(el)) { buff.append(L"'"); buff.append(el); buff.append(L"'"); } else { buff.append(escape_string(el, 1)); } } } return buff; }
/// Does this look like the escape sequence for setting a screen name. static bool is_screen_name_escape_seq(const wchar_t *code, size_t *resulting_length) { bool found = false; if (code[1] == L'k') { const env_var_t term_name = env_get_string(L"TERM"); if (!term_name.missing() && string_prefixes_string(L"screen", term_name)) { const wchar_t *const screen_name_end_sentinel = L"\x1b\\"; const wchar_t *screen_name_end = wcsstr(&code[2], screen_name_end_sentinel); if (screen_name_end == NULL) { // Consider just <esc>k to be the code. *resulting_length = 2; } else { const wchar_t *escape_sequence_end = screen_name_end + wcslen(screen_name_end_sentinel); *resulting_length = escape_sequence_end - code; } found = true; } } return found; }
/** Check the X clipboard. If it has been changed, add the new clipboard contents to the fish killring. */ static void kill_check_x_buffer() { if (!has_xsel()) return; const env_var_t disp = env_get_string(L"DISPLAY"); if (! disp.missing()) { size_t i; wcstring cmd = L"xsel -t 500 -b"; wcstring new_cut_buffer=L""; wcstring_list_t list; if (exec_subshell(cmd, list, false /* do not apply exit status */) != -1) { for (i=0; i<list.size(); i++) { wcstring next_line = escape_string(list.at(i), 0); if (i > 0) new_cut_buffer += L"\\n"; new_cut_buffer += next_line; } if (new_cut_buffer.size() > 0) { /* The buffer is inserted with backslash escapes, since we don't really like tabs, newlines, etc. anyway. */ if (cut_buffer != new_cut_buffer) { cut_buffer = new_cut_buffer; kill_list.push_front(new_cut_buffer); } } } } }
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 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", ENV_GLOBAL | ENV_EXPORT); if (!home.missing_or_empty()) { path = home + (which_dir == L"config" ? L"/.config/fish" : L"/.local/share/fish"); if (create_directory(path) != -1) { path_done = true; } else { saved_errno = errno; } } } if (!path_done) { maybe_issue_path_warning(which_dir, custom_error_msg, using_xdg, xdg_var, path, saved_errno); path.clear(); } return; }
void env_init(const struct config_paths_t *paths /* or NULL */) { /* env_read_only variables can not be altered directly by the user */ const wchar_t * const ro_keys[] = { L"status", L"history", L"version", L"_", L"LINES", L"COLUMNS", L"PWD", //L"SHLVL", // will be inserted a bit lower down L"FISH_VERSION", }; for (size_t i=0; i < sizeof ro_keys / sizeof *ro_keys; i++) { env_read_only.insert(ro_keys[i]); } /* Names of all dynamically calculated variables */ env_electric.insert(L"history"); env_electric.insert(L"status"); env_electric.insert(L"umask"); env_electric.insert(L"COLUMNS"); env_electric.insert(L"LINES"); top = new env_node_t; global_env = top; global = &top->env; /* Now the environemnt variable handling is set up, the next step is to insert valid data */ /* Import environment variables */ for (char **p = (environ ? environ : __environ); p && *p; p++) { const wcstring key_and_val = str2wcstring(*p); //like foo=bar size_t eql = key_and_val.find(L'='); if (eql == wcstring::npos) { // no equals found if (is_read_only(key_and_val) || is_electric(key_and_val)) continue; env_set(key_and_val, L"", ENV_EXPORT | ENV_GLOBAL); } else { wcstring key = key_and_val.substr(0, eql); if (is_read_only(key) || is_electric(key)) continue; wcstring val = key_and_val.substr(eql + 1); if (variable_is_colon_delimited_array(key)) { std::replace(val.begin(), val.end(), L':', ARRAY_SEP); } env_set(key, val.c_str(), ENV_EXPORT | ENV_GLOBAL); } } /* Set the given paths in the environment, if we have any */ if (paths != NULL) { env_set(FISH_DATADIR_VAR, paths->data.c_str(), ENV_GLOBAL); env_set(FISH_SYSCONFDIR_VAR, paths->sysconf.c_str(), ENV_GLOBAL); env_set(FISH_HELPDIR_VAR, paths->doc.c_str(), ENV_GLOBAL); env_set(FISH_BIN_DIR, paths->bin.c_str(), ENV_GLOBAL); } /* Set up the PATH variable */ setup_path(); /* Set up the USER variable */ if (env_get_string(L"USER").missing_or_empty()) { const struct passwd *pw = getpwuid(getuid()); if (pw && pw->pw_name) { const wcstring uname = str2wcstring(pw->pw_name); env_set(L"USER", uname.c_str(), ENV_GLOBAL | ENV_EXPORT); } } /* Set up the version variables */ wcstring version = str2wcstring(get_fish_version()); env_set(L"version", version.c_str(), ENV_GLOBAL); env_set(L"FISH_VERSION", version.c_str(), ENV_GLOBAL); /* Set up SHLVL variable */ const env_var_t shlvl_str = env_get_string(L"SHLVL"); wcstring nshlvl_str = L"1"; if (! shlvl_str.missing()) { wchar_t *end; long shlvl_i = wcstol(shlvl_str.c_str(), &end, 10); while (iswspace(*end)) ++end; /* skip trailing whitespace */ if (shlvl_i >= 0 && *end == '\0') { nshlvl_str = to_string<long>(shlvl_i + 1); } } env_set(L"SHLVL", nshlvl_str.c_str(), ENV_GLOBAL | ENV_EXPORT); env_read_only.insert(L"SHLVL"); /* Set up the HOME variable */ if (env_get_string(L"HOME").missing_or_empty()) { 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->pw_dir != NULL) { const wcstring dir = str2wcstring(pw->pw_dir); env_set(L"HOME", dir.c_str(), ENV_GLOBAL | ENV_EXPORT); } free(unam_narrow); } /* Set PWD */ env_set_pwd(); /* Set up universal variables. The empty string means to use the deafult path. */ assert(s_universal_variables == NULL); s_universal_variables = new env_universal_t(L""); s_universal_variables->load(); /* Set g_log_forks */ env_var_t log_forks = env_get_string(L"fish_log_forks"); g_log_forks = ! log_forks.missing_or_empty() && from_string<bool>(log_forks); /* Set g_use_posix_spawn. Default to true. */ env_var_t use_posix_spawn = env_get_string(L"fish_use_posix_spawn"); g_use_posix_spawn = (use_posix_spawn.missing_or_empty() ? true : from_string<bool>(use_posix_spawn)); /* Set fish_bind_mode to "default" */ env_set(FISH_BIND_MODE_VAR, DEFAULT_BIND_MODE, ENV_GLOBAL); /* Now that the global scope is fully initialized, add a toplevel local scope. This same local scope will persist throughout the lifetime of the fish process, and it will ensure that `set -l` commands run at the command-line don't affect the global scope. */ env_push(false); }
void env_init(const struct config_paths_t *paths /* or NULL */) { /* env_read_only variables can not be altered directly by the user */ const wchar_t * const ro_keys[] = { L"status", L"history", L"version", L"_", L"LINES", L"COLUMNS", L"PWD", L"SHLVL", L"FISH_VERSION", }; for (size_t i=0; i < sizeof ro_keys / sizeof *ro_keys; i++) { env_read_only.insert(ro_keys[i]); } /* HOME and USER should be writeable by root, since this can be a convenient way to install software. */ if (getuid() != 0) { env_read_only.insert(L"HOME"); env_read_only.insert(L"USER"); } /* Names of all dynamically calculated variables */ env_electric.insert(L"history"); env_electric.insert(L"status"); env_electric.insert(L"umask"); top = new env_node_t; global_env = top; global = &top->env; /* Now the environemnt variable handling is set up, the next step is to insert valid data */ /* Import environment variables */ for (char **p = (environ ? environ : __environ); p && *p; p++) { const wcstring key_and_val = str2wcstring(*p); //like foo=bar size_t eql = key_and_val.find(L'='); if (eql == wcstring::npos) { // no equals found env_set(key_and_val, L"", ENV_EXPORT); } else { wcstring key = key_and_val.substr(0, eql); wcstring val = key_and_val.substr(eql + 1); if (variable_can_be_array(val)) { std::replace(val.begin(), val.end(), L':', ARRAY_SEP); } env_set(key, val.c_str(), ENV_EXPORT | ENV_GLOBAL); } } /* Set the given paths in the environment, if we have any */ if (paths != NULL) { env_set(FISH_DATADIR_VAR, paths->data.c_str(), ENV_GLOBAL | ENV_EXPORT); env_set(FISH_SYSCONFDIR_VAR, paths->sysconf.c_str(), ENV_GLOBAL | ENV_EXPORT); env_set(FISH_HELPDIR_VAR, paths->doc.c_str(), ENV_GLOBAL | ENV_EXPORT); env_set(FISH_BIN_DIR, paths->bin.c_str(), ENV_GLOBAL | ENV_EXPORT); } /* Set up the PATH variable */ setup_path(); /* Set up the USER variable */ const struct passwd *pw = getpwuid(getuid()); if (pw && pw->pw_name) { const wcstring uname = str2wcstring(pw->pw_name); env_set(L"USER", uname.c_str(), ENV_GLOBAL | ENV_EXPORT); } /* Set up the version variables */ wcstring version = str2wcstring(FISH_BUILD_VERSION); env_set(L"version", version.c_str(), ENV_GLOBAL); env_set(L"FISH_VERSION", version.c_str(), ENV_GLOBAL); const env_var_t fishd_dir_wstr = env_get_string(L"FISHD_SOCKET_DIR"); const env_var_t user_dir_wstr = env_get_string(L"USER"); wchar_t * fishd_dir = fishd_dir_wstr.missing()?NULL:const_cast<wchar_t*>(fishd_dir_wstr.c_str()); wchar_t * user_dir = user_dir_wstr.missing()?NULL:const_cast<wchar_t*>(user_dir_wstr.c_str()); env_universal_init(fishd_dir , user_dir , &start_fishd, &universal_callback); /* Set up SHLVL variable */ const env_var_t shlvl_str = env_get_string(L"SHLVL"); wcstring nshlvl_str = L"1"; if (! shlvl_str.missing()) { long shlvl_i = wcstol(shlvl_str.c_str(), NULL, 10); if (shlvl_i >= 0) { nshlvl_str = to_string<long>(shlvl_i + 1); } } env_set(L"SHLVL", nshlvl_str.c_str(), ENV_GLOBAL | ENV_EXPORT); /* Set correct defaults for e.g. USER and HOME variables */ env_set_defaults(); /* Set g_log_forks */ env_var_t log_forks = env_get_string(L"fish_log_forks"); g_log_forks = ! log_forks.missing_or_empty() && from_string<bool>(log_forks); /* Set g_use_posix_spawn. Default to true. */ env_var_t use_posix_spawn = env_get_string(L"fish_use_posix_spawn"); g_use_posix_spawn = (use_posix_spawn.missing_or_empty() ? true : from_string<bool>(use_posix_spawn)); }
/** Properly sets all locale information */ static void handle_locale() { const env_var_t lc_all = env_get_string(L"LC_ALL"); const wcstring old_locale = wsetlocale(LC_MESSAGES, NULL); /* Array of locale constants corresponding to the local variable names defined in locale_variable */ static const int cat[] = { 0, LC_ALL, LC_COLLATE, LC_CTYPE, LC_MESSAGES, LC_MONETARY, LC_NUMERIC, LC_TIME } ; if (!lc_all.missing()) { wsetlocale(LC_ALL, lc_all.c_str()); } else { const env_var_t lang = env_get_string(L"LANG"); if (!lang.missing()) { wsetlocale(LC_ALL, lang.c_str()); } for (int i=2; locale_variable[i]; i++) { const env_var_t val = env_get_string(locale_variable[i]); if (!val.missing()) { wsetlocale(cat[i], val.c_str()); } } } const wcstring new_locale = wsetlocale(LC_MESSAGES, NULL); if (old_locale != new_locale) { /* Try to make change known to gettext. Both changing _nl_msg_cat_cntr and calling dcgettext might potentially tell some gettext implementation that the translation strings should be reloaded. We do both and hope for the best. */ extern int _nl_msg_cat_cntr; _nl_msg_cat_cntr++; fish_dcgettext("fish", "Changing language to English", LC_MESSAGES); if (get_is_interactive()) { debug(2, _(L"Changing language to English")); } } }
/** Call env_set. If this is a path variable, e.g. PATH, validate the elements. On error, print a description of the problem to stderr. */ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope, io_streams_t &streams) { size_t i; int retcode = 0; const wchar_t *val_str=NULL; if (is_path_variable(key)) { /* Fix for https://github.com/fish-shell/fish-shell/issues/199 . Return success if any path setting succeeds. */ bool any_success = false; /* Don't bother validating (or complaining about) values that are already present. When determining already-present values, use ENV_DEFAULT instead of the passed-in scope because in: set -l PATH stuff $PATH where we are temporarily shadowing a variable, we want to compare against the shadowed value, not the (missing) local value. Also don't bother to complain about relative paths, which don't start with /. */ wcstring_list_t existing_values; const env_var_t existing_variable = env_get_string(key, ENV_DEFAULT); if (! existing_variable.missing_or_empty()) tokenize_variable_array(existing_variable, existing_values); for (i=0; i< val.size() ; i++) { const wcstring &dir = val.at(i); if (!string_prefixes_string(L"/", dir) || list_contains_string(existing_values, dir)) { any_success = true; continue; } bool show_perror = false; int show_hint = 0; bool error = false; struct stat buff; if (wstat(dir, &buff)) { error = true; show_perror = true; } if (!(S_ISDIR(buff.st_mode))) { error = true; } if (!error) { any_success = true; } else { streams.err.append_format(_(BUILTIN_SET_PATH_ERROR), L"set", dir.c_str(), key); const wchar_t *colon = wcschr(dir.c_str(), L':'); if (colon && *(colon+1)) { show_hint = 1; } } if (show_perror) { builtin_wperror(L"set", streams); } if (show_hint) { streams.err.append_format(_(BUILTIN_SET_PATH_HINT), L"set", key, key, wcschr(dir.c_str(), L':')+1); } } /* Fail at setting the path if we tried to set it to something non-empty, but it wound up empty. */ if (! val.empty() && ! any_success) { return 1; } } wcstring sb; if (! val.empty()) { for (i=0; i< val.size() ; i++) { sb.append(val[i]); if (i<val.size() - 1) { sb.append(ARRAY_SEP_STR); } } val_str = sb.c_str(); } switch (env_set(key, val_str, scope | ENV_USER)) { case ENV_PERM: { streams.err.append_format(_(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key); retcode=1; break; } case ENV_SCOPE: { streams.err.append_format(_(L"%ls: Tried to set the special variable '%ls' with the wrong scope\n"), L"set", key); retcode=1; break; } case ENV_INVALID: { streams.err.append_format(_(L"%ls: Tried to set the special variable '%ls' to an invalid value\n"), L"set", key); retcode=1; break; } } return retcode; }
/// The set builtin creates, updates, and erases (removes, deletes) variables. int builtin_set(parser_t &parser, io_streams_t &streams, wchar_t **argv) { wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); // Flags to set the work mode. int local = 0, global = 0, exportv = 0; int erase = 0, list = 0, unexport = 0; int universal = 0, query = 0; bool shorten_ok = true; bool preserve_failure_exit_status = true; const int incoming_exit_status = proc_get_last_status(); // Variables used for performing the actual work. wchar_t *dest = NULL; int retcode = STATUS_CMD_OK; int scope; int slice = 0; // Variables used for parsing the argument list. This command is atypical in using the "+" // (REQUIRE_ORDER) option for flag parsing. This is not typical of most fish commands. It means // we stop scanning for flags when the first non-flag argument is seen. static const wchar_t *short_options = L"+LUeghlnqux"; static const struct woption long_options[] = {{L"export", no_argument, NULL, 'x'}, {L"global", no_argument, NULL, 'g'}, {L"local", no_argument, NULL, 'l'}, {L"erase", no_argument, NULL, 'e'}, {L"names", no_argument, NULL, 'n'}, {L"unexport", no_argument, NULL, 'u'}, {L"universal", no_argument, NULL, 'U'}, {L"long", no_argument, NULL, 'L'}, {L"query", no_argument, NULL, 'q'}, {L"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0}}; // Parse options to obtain the requested operation and the modifiers. int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'e': { erase = 1; preserve_failure_exit_status = false; break; } case 'n': { list = 1; preserve_failure_exit_status = false; break; } case 'x': { exportv = 1; break; } case 'l': { local = 1; break; } case 'g': { global = 1; break; } case 'u': { unexport = 1; break; } case 'U': { universal = 1; break; } case 'L': { shorten_ok = false; break; } case 'q': { query = 1; preserve_failure_exit_status = false; break; } case 'h': { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } // Ok, all arguments have been parsed, let's validate them. If we are checking the existance of // a variable (-q) we can not also specify scope. if (query && (erase || list)) { streams.err.append_format(BUILTIN_ERR_COMBO, cmd); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } // We can't both list and erase variables. if (erase && list) { streams.err.append_format(BUILTIN_ERR_COMBO, cmd); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } // Variables can only have one scope. if (local + global + universal > 1) { streams.err.append_format(BUILTIN_ERR_GLOCAL, cmd); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } // Variables can only have one export status. if (exportv && unexport) { streams.err.append_format(BUILTIN_ERR_EXPUNEXP, argv[0]); builtin_print_help(parser, streams, cmd, streams.err); return STATUS_INVALID_ARGS; } // Calculate the scope value for variable assignement. scope = (local ? ENV_LOCAL : 0) | (global ? ENV_GLOBAL : 0) | (exportv ? ENV_EXPORT : 0) | (unexport ? ENV_UNEXPORT : 0) | (universal ? ENV_UNIVERSAL : 0) | ENV_USER; if (query) { // Query mode. Return the number of variables that do not exist out of the specified // variables. int i; for (i = w.woptind; i < argc; i++) { wchar_t *arg = argv[i]; int slice = 0; dest = wcsdup(arg); assert(dest); if (wcschr(dest, L'[')) { slice = 1; *wcschr(dest, L'[') = 0; } if (slice) { std::vector<long> indexes; wcstring_list_t result; size_t j; env_var_t dest_str = env_get_string(dest, scope); if (!dest_str.missing()) tokenize_variable_array(dest_str, result); if (!parse_index(indexes, arg, dest, result.size(), streams)) { builtin_print_help(parser, streams, cmd, streams.err); retcode = 1; break; } for (j = 0; j < indexes.size(); j++) { long idx = indexes[j]; if (idx < 1 || (size_t)idx > result.size()) { retcode++; } } } else { if (!env_exist(arg, scope)) { retcode++; } } free(dest); } return retcode; } if (list) { // Maybe we should issue an error if there are any other arguments? print_variables(0, 0, shorten_ok, scope, streams); return STATUS_CMD_OK; } if (w.woptind == argc) { // Print values of variables. if (erase) { streams.err.append_format(_(L"%ls: Erase needs a variable name\n"), cmd); builtin_print_help(parser, streams, cmd, streams.err); retcode = STATUS_INVALID_ARGS; } else { print_variables(1, 1, shorten_ok, scope, streams); } return retcode; } dest = wcsdup(argv[w.woptind]); assert(dest); if (wcschr(dest, L'[')) { slice = 1; *wcschr(dest, L'[') = 0; } if (!valid_var_name(dest)) { streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, dest); builtin_print_help(parser, streams, argv[0], streams.err); return STATUS_INVALID_ARGS; } // Set assignment can work in two modes, either using slices or using the whole array. We detect // which mode is used here. if (slice) { // Slice mode. std::vector<long> indexes; wcstring_list_t result; const env_var_t dest_str = env_get_string(dest, scope); if (!dest_str.missing()) { tokenize_variable_array(dest_str, result); } else if (erase) { retcode = 1; } if (!retcode) { for (; w.woptind < argc; w.woptind++) { if (!parse_index(indexes, argv[w.woptind], dest, result.size(), streams)) { builtin_print_help(parser, streams, argv[0], streams.err); retcode = 1; break; } size_t idx_count = indexes.size(); size_t val_count = argc - w.woptind - 1; if (!erase) { if (val_count < idx_count) { streams.err.append_format(_(BUILTIN_SET_ARG_COUNT), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); retcode = 1; break; } if (val_count == idx_count) { w.woptind++; break; } } } } if (!retcode) { // Slice indexes have been calculated, do the actual work. if (erase) { erase_values(result, indexes); my_env_set(dest, result, scope, streams); } else { wcstring_list_t value; while (w.woptind < argc) { value.push_back(argv[w.woptind++]); } if (update_values(result, indexes, value)) { streams.err.append_format(L"%ls: ", argv[0]); streams.err.append_format(ARRAY_BOUNDS_ERR); streams.err.push_back(L'\n'); } my_env_set(dest, result, scope, streams); } } } else { w.woptind++; // No slicing. if (erase) { if (w.woptind != argc) { streams.err.append_format(_(L"%ls: Values cannot be specfied with erase\n"), argv[0]); builtin_print_help(parser, streams, argv[0], streams.err); retcode = 1; } else { retcode = env_remove(dest, scope); } } else { wcstring_list_t val; for (int i = w.woptind; i < argc; i++) val.push_back(argv[i]); retcode = my_env_set(dest, val, scope, streams); } } // Check if we are setting variables above the effective scope. See // https://github.com/fish-shell/fish-shell/issues/806 env_var_t global_dest = env_get_string(dest, ENV_GLOBAL); if (universal && !global_dest.missing()) { streams.err.append_format( _(L"%ls: Warning: universal scope selected, but a global variable '%ls' exists.\n"), L"set", dest); } free(dest); if (retcode == STATUS_CMD_OK && preserve_failure_exit_status) retcode = incoming_exit_status; return retcode; }
/* Returns the number of characters in the escape code starting at 'code' (which should initially contain \x1b) */ size_t escape_code_length(const wchar_t *code) { assert(code != NULL); /* The only escape codes we recognize start with \x1b */ if (code[0] != L'\x1b') return 0; size_t resulting_length = 0; bool found = false; if (cur_term != NULL) { /* Detect these terminfo color escapes with parameter value 0..7, all of which don't move the cursor */ char * const esc[] = { set_a_foreground, set_a_background, set_foreground, set_background, }; for (size_t p=0; p < sizeof esc / sizeof *esc && !found; p++) { if (!esc[p]) continue; for (size_t k=0; k<8; k++) { size_t len = try_sequence(tparm(esc[p],k), code); if (len) { resulting_length = len; found = true; break; } } } } if (cur_term != NULL) { /* Detect these semi-common terminfo escapes without any parameter values, all of which don't move the cursor */ char * const esc2[] = { enter_bold_mode, exit_attribute_mode, enter_underline_mode, exit_underline_mode, enter_standout_mode, exit_standout_mode, flash_screen, enter_subscript_mode, exit_subscript_mode, enter_superscript_mode, exit_superscript_mode, enter_blink_mode, enter_italics_mode, exit_italics_mode, enter_reverse_mode, enter_shadow_mode, exit_shadow_mode, enter_standout_mode, exit_standout_mode, enter_secure_mode }; for (size_t p=0; p < sizeof esc2 / sizeof *esc2 && !found; p++) { if (!esc2[p]) continue; /* Test both padded and unpadded version, just to be safe. Most versions of tparm don't actually seem to do anything these days. */ size_t len = maxi(try_sequence(tparm(esc2[p]), code), try_sequence(esc2[p], code)); if (len) { resulting_length = len; found = true; } } } if (!found) { if (code[1] == L'k') { /* This looks like the escape sequence for setting a screen name */ const env_var_t term_name = env_get_string(L"TERM"); if (!term_name.missing() && string_prefixes_string(L"screen", term_name)) { const wchar_t * const screen_name_end_sentinel = L"\x1b\\"; const wchar_t *screen_name_end = wcsstr(&code[2], screen_name_end_sentinel); if (screen_name_end != NULL) { const wchar_t *escape_sequence_end = screen_name_end + wcslen(screen_name_end_sentinel); resulting_length = escape_sequence_end - code; } else { /* Consider just <esc>k to be the code */ resulting_length = 2; } found = true; } } } if (! found) { /* Generic VT100 one byte sequence: CSI followed by something in the range @ through _ */ if (code[1] == L'[' && (code[2] >= L'@' && code[2] <= L'_')) { resulting_length = 3; found = true; } } if (! found) { /* Generic VT100 CSI-style sequence. <esc>, followed by zero or more ASCII characters NOT in the range [@,_], followed by one character in that range */ if (code[1] == L'[') { // Start at 2 to skip over <esc>[ size_t cursor = 2; for (; code[cursor] != L'\0'; cursor++) { /* Consume a sequence of ASCII characters not in the range [@, ~] */ wchar_t c = code[cursor]; /* If we're not in ASCII, just stop */ if (c > 127) break; /* If we're the end character, then consume it and then stop */ if (c >= L'@' && c <= L'~') { cursor++; break; } } /* curs now indexes just beyond the end of the sequence (or at the terminating zero) */ found = true; resulting_length = cursor; } } if (! found) { /* Generic VT100 two byte sequence: <esc> followed by something in the range @ through _ */ if (code[1] >= L'@' && code[1] <= L'_') { resulting_length = 2; found = true; } } return resulting_length; }
/** Call env_set. If this is a path variable, e.g. PATH, validate the elements. On error, print a description of the problem to stderr. */ static int my_env_set(const wchar_t *key, const wcstring_list_t &val, int scope) { size_t i; int retcode = 0; const wchar_t *val_str=NULL; if (is_path_variable(key)) { /* Fix for https://github.com/fish-shell/fish-shell/issues/199 . Return success if any path setting succeeds. */ bool any_success = false; /* Don't bother validating (or complaining about) values that are already present */ wcstring_list_t existing_values; const env_var_t existing_variable = env_get_string(key, scope); if (! existing_variable.missing_or_empty()) tokenize_variable_array(existing_variable, existing_values); for (i=0; i< val.size() ; i++) { const wcstring &dir = val.at(i); if (list_contains_string(existing_values, dir)) { any_success = true; continue; } bool show_perror = false; int show_hint = 0; bool error = false; struct stat buff; if (wstat(dir, &buff)) { error = true; show_perror = true; } if (!(S_ISDIR(buff.st_mode))) { error = true; } if (!error) { any_success = true; } else { append_format(stderr_buffer, _(BUILTIN_SET_PATH_ERROR), L"set", dir.c_str(), key); const wchar_t *colon = wcschr(dir.c_str(), L':'); if (colon && *(colon+1)) { show_hint = 1; } } if (show_perror) { builtin_wperror(L"set"); } if (show_hint) { append_format(stderr_buffer, _(BUILTIN_SET_PATH_HINT), L"set", key, key, wcschr(dir.c_str(), L':')+1); } } /* Fail at setting the path if we tried to set it to something non-empty, but it wound up empty. */ if (! val.empty() && ! any_success) { return 1; } } wcstring sb; if (! val.empty()) { for (i=0; i< val.size() ; i++) { sb.append(val[i]); if (i<val.size() - 1) { sb.append(ARRAY_SEP_STR); } } val_str = sb.c_str(); } switch (env_set(key, val_str, scope | ENV_USER)) { case ENV_PERM: { append_format(stderr_buffer, _(L"%ls: Tried to change the read-only variable '%ls'\n"), L"set", key); retcode=1; break; } case ENV_SCOPE: { append_format(stderr_buffer, _(L"%ls: Tried to set the special variable '%ls' with the wrong scope\n"), L"set", key); retcode=1; break; } case ENV_INVALID: { append_format(stderr_buffer, _(L"%ls: Tried to set the special variable '%ls' to an invalid value\n"), L"set", key); retcode=1; break; } } return retcode; }
static bool path_get_path_core(const wcstring &cmd, wcstring *out_path, const env_var_t &bin_path_var) { int err = ENOENT; debug(3, L"path_get_path( '%ls' )", cmd.c_str()); // If the command has a slash, it must be a full path. if (cmd.find(L'/') != wcstring::npos) { if (waccess(cmd, X_OK) != 0) { return false; } struct stat buff; if (wstat(cmd, &buff)) { return false; } if (S_ISREG(buff.st_mode)) { if (out_path) out_path->assign(cmd); return true; } errno = EACCES; return false; } wcstring bin_path; if (!bin_path_var.missing()) { bin_path = bin_path_var; } else { if (contains(PREFIX L"/bin", L"/bin", L"/usr/bin")) { bin_path = L"/bin" ARRAY_SEP_STR L"/usr/bin"; } else { bin_path = L"/bin" ARRAY_SEP_STR L"/usr/bin" ARRAY_SEP_STR PREFIX L"/bin"; } } wcstring nxt_path; wcstokenizer tokenizer(bin_path, ARRAY_SEP_STR); while (tokenizer.next(nxt_path)) { if (nxt_path.empty()) continue; append_path_component(nxt_path, cmd); if (waccess(nxt_path, X_OK) == 0) { struct stat buff; if (wstat(nxt_path, &buff) == -1) { if (errno != EACCES) { wperror(L"stat"); } continue; } if (S_ISREG(buff.st_mode)) { if (out_path) out_path->swap(nxt_path); return true; } err = EACCES; } else { switch (errno) { case ENOENT: case ENAMETOOLONG: case EACCES: case ENOTDIR: { break; } default: { debug(1, MISSING_COMMAND_ERR_MSG, nxt_path.c_str()); wperror(L"access"); } } } } errno = err; return false; }
void env_init(const struct config_paths_t *paths /* or NULL */) { char **p; struct passwd *pw; wchar_t *uname; wchar_t *version; /* env_read_only variables can not be altered directly by the user */ const wchar_t * const ro_keys[] = { L"status", L"history", L"version", L"_", L"LINES", L"COLUMNS", L"PWD", L"SHLVL", }; for (size_t i=0; i < sizeof ro_keys / sizeof *ro_keys; i++) { env_read_only.insert(ro_keys[i]); } /* HOME and USER should be writeable by root, since this can be a convenient way to install software. */ if( getuid() != 0 ) { env_read_only.insert(L"HOME"); env_read_only.insert(L"USER"); } /* Names of all dynamically calculated variables */ env_electric.insert(L"history"); env_electric.insert(L"status"); env_electric.insert(L"umask"); top = new env_node_t; global_env = top; global = &top->env; /* Now the environemnt variable handling is set up, the next step is to insert valid data */ /* Import environment variables */ for( p=environ?environ:__environ; p && *p; p++ ) { wchar_t *key, *val; key = str2wcs(*p); if( !key ) { continue; } val = wcschr( key, L'=' ); if( val == 0 ) { env_set( key, L"", ENV_EXPORT ); } else { *val = L'\0'; val++; //fwprintf( stderr, L"Set $%ls to %ls\n", key, val ); if (variable_can_be_array(val)) { for (size_t i=0; val[i] != L'\0'; i++) { if( val[i] == L':' ) { val[i] = ARRAY_SEP; } } } env_set( key, val, ENV_EXPORT | ENV_GLOBAL ); } free(key); } /* Set the given paths in the environment, if we have any */ if (paths != NULL) { env_set(FISH_DATADIR_VAR, paths->data.c_str(), ENV_GLOBAL | ENV_EXPORT); env_set(FISH_SYSCONFDIR_VAR, paths->sysconf.c_str(), ENV_GLOBAL | ENV_EXPORT); env_set(FISH_HELPDIR_VAR, paths->doc.c_str(), ENV_GLOBAL | ENV_EXPORT); env_set(FISH_BIN_DIR, paths->bin.c_str(), ENV_GLOBAL | ENV_EXPORT); } /* Set up the PATH variable */ setup_path(); /* Set up the USER variable */ pw = getpwuid( getuid() ); if( pw ) { uname = str2wcs( pw->pw_name ); env_set( L"USER", uname, ENV_GLOBAL | ENV_EXPORT ); free( uname ); } /* Set up the version variable */ version = str2wcs( PACKAGE_VERSION ); env_set( L"version", version, ENV_GLOBAL ); free( version ); const env_var_t fishd_dir_wstr = env_get_string( L"FISHD_SOCKET_DIR"); const env_var_t user_dir_wstr = env_get_string( L"USER" ); wchar_t * fishd_dir = fishd_dir_wstr.missing()?NULL:const_cast<wchar_t*>(fishd_dir_wstr.c_str()); wchar_t * user_dir = user_dir_wstr.missing()?NULL:const_cast<wchar_t*>(user_dir_wstr.c_str()); env_universal_init(fishd_dir , user_dir , &start_fishd, &universal_callback ); /* Set up SHLVL variable */ const env_var_t shlvl_str = env_get_string( L"SHLVL" ); wcstring nshlvl_str = L"1"; if (! shlvl_str.missing()) { long shlvl_i = wcstol(shlvl_str.c_str(), NULL, 10); if (shlvl_i >= 0) { nshlvl_str = format_string(L"%ld", 1 + shlvl_i); } } env_set(L"SHLVL", nshlvl_str.c_str(), ENV_GLOBAL | ENV_EXPORT ); /* Set correct defaults for e.g. USER and HOME variables */ env_set_defaults(); /* Set g_log_forks */ env_var_t log_forks = env_get_string(L"fish_log_forks"); g_log_forks = ! log_forks.missing_or_empty() && from_string<bool>(log_forks); /* Set g_use_posix_spawn. Default to true. */ env_var_t use_posix_spawn = env_get_string(L"fish_use_posix_spawn"); g_use_posix_spawn = (use_posix_spawn.missing_or_empty() ? true : from_string<bool>(use_posix_spawn)); }
/** Calculate the width of the specified prompt. Does some clever magic to detect common escape sequences that may be embeded in a prompt, such as color codes. */ static size_t calc_prompt_width( const wchar_t *prompt ) { size_t res = 0; size_t j, k; for( j=0; prompt[j]; j++ ) { if( prompt[j] == L'\x1b' ) { /* This is the start of an escape code. Try to guess it's width. */ size_t p; int len=0; bool found = false; /* Detect these terminfo color escapes with parameter value 0..7, all of which don't move the cursor */ char * const esc[] = { set_a_foreground, set_a_background, set_foreground, set_background, } ; /* Detect these semi-common terminfo escapes without any parameter values, all of which don't move the cursor */ char * const esc2[] = { enter_bold_mode, exit_attribute_mode, enter_underline_mode, exit_underline_mode, enter_standout_mode, exit_standout_mode, flash_screen, enter_subscript_mode, exit_subscript_mode, enter_superscript_mode, exit_superscript_mode, enter_blink_mode, enter_italics_mode, exit_italics_mode, enter_reverse_mode, enter_shadow_mode, exit_shadow_mode, enter_standout_mode, exit_standout_mode, enter_secure_mode } ; for( p=0; p < sizeof esc / sizeof *esc && !found; p++ ) { if( !esc[p] ) continue; for( k=0; k<8; k++ ) { len = try_sequence( tparm(esc[p],k), &prompt[j] ); if( len ) { j += (len-1); found = true; break; } } } // PCA for term256 support, let's just detect the escape codes directly if (! found) { len = is_term256_escape(&prompt[j]); if (len) { j += (len - 1); found = true; } } for( p=0; p < (sizeof(esc2)/sizeof(char *)) && !found; p++ ) { if( !esc2[p] ) continue; /* Test both padded and unpadded version, just to be safe. Most versions of tparm don't actually seem to do anything these days. */ len = maxi( try_sequence( tparm(esc2[p]), &prompt[j] ), try_sequence( esc2[p], &prompt[j] )); if( len ) { j += (len-1); found = true; } } if( !found ) { if( prompt[j+1] == L'k' ) { const env_var_t term_name = env_get_string( L"TERM" ); if( !term_name.missing() && wcsstr( term_name.c_str(), L"screen" ) == term_name ) { const wchar_t *end; j+=2; found = true; end = wcsstr( &prompt[j], L"\x1b\\" ); if( end ) { /* You'd thing this should be '(end-prompt)+2', in order to move j past the end of the string, but there is a 'j++' at the end of each lap, so j should always point to the last menged character, e.g. +1. */ j = (end-prompt)+1; } else { break; } } } } } else if( prompt[j] == L'\t' ) { res = next_tab_stop( res ); } else if( prompt[j] == L'\n' ) { res = 0; } else { /* Ordinary decent character. Just add width. */ res += fish_wcwidth( prompt[j] ); } } return res; }
/** The set builtin. Creates, updates and erases environment variables and environemnt variable arrays. */ static int builtin_set( parser_t &parser, wchar_t **argv ) { /** Variables used for parsing the argument list */ static const struct woption long_options[] = { { L"export", no_argument, 0, 'x' } , { L"global", no_argument, 0, 'g' } , { L"local", no_argument, 0, 'l' } , { L"erase", no_argument, 0, 'e' } , { L"names", no_argument, 0, 'n' } , { L"unexport", no_argument, 0, 'u' } , { L"universal", no_argument, 0, 'U' } , { L"long", no_argument, 0, 'L' } , { L"query", no_argument, 0, 'q' } , { L"help", no_argument, 0, 'h' } , { 0, 0, 0, 0 } } ; const wchar_t *short_options = L"+xglenuULqh"; int argc = builtin_count_args(argv); /* Flags to set the work mode */ int local = 0, global = 0, exportv = 0; int erase = 0, list = 0, unexport=0; int universal = 0, query=0; bool shorten_ok = true; /* Variables used for performing the actual work */ wchar_t *dest = 0; int retcode=0; int scope; int slice=0; int i; wchar_t *bad_char; /* Parse options to obtain the requested operation and the modifiers */ woptind = 0; while (1) { int c = wgetopt_long(argc, argv, short_options, long_options, 0); if (c == -1) { break; } switch(c) { case 0: break; case 'e': erase = 1; break; case 'n': list = 1; break; case 'x': exportv = 1; break; case 'l': local = 1; break; case 'g': global = 1; break; case 'u': unexport = 1; break; case 'U': universal = 1; break; case 'L': shorten_ok = false; break; case 'q': query = 1; break; case 'h': builtin_print_help( parser, argv[0], stdout_buffer ); return 0; case '?': builtin_unknown_option( parser, argv[0], argv[woptind-1] ); return 1; default: break; } } /* Ok, all arguments have been parsed, let's validate them */ /* If we are checking the existance of a variable (-q) we can not also specify scope */ if( query && (erase || list) ) { append_format(stderr_buffer, BUILTIN_ERR_COMBO, argv[0] ); builtin_print_help( parser, argv[0], stderr_buffer ); return 1; } /* We can't both list and erase varaibles */ if( erase && list ) { append_format(stderr_buffer, BUILTIN_ERR_COMBO, argv[0] ); builtin_print_help( parser, argv[0], stderr_buffer ); return 1; } /* Variables can only have one scope */ if( local + global + universal > 1 ) { append_format(stderr_buffer, BUILTIN_ERR_GLOCAL, argv[0] ); builtin_print_help( parser, argv[0], stderr_buffer ); return 1; } /* Variables can only have one export status */ if( exportv && unexport ) { append_format(stderr_buffer, BUILTIN_ERR_EXPUNEXP, argv[0] ); builtin_print_help( parser, argv[0], stderr_buffer ); return 1; } /* Calculate the scope value for variable assignement */ scope = (local ? ENV_LOCAL : 0) | (global ? ENV_GLOBAL : 0) | (exportv ? ENV_EXPORT : 0) | (unexport ? ENV_UNEXPORT : 0) | (universal ? ENV_UNIVERSAL:0) | ENV_USER; if( query ) { /* Query mode. Return the number of variables that do not exist out of the specified variables. */ int i; for( i=woptind; i<argc; i++ ) { wchar_t *arg = argv[i]; int slice=0; if( !(dest = wcsdup(arg))) { DIE_MEM(); } if( wcschr( dest, L'[' ) ) { slice = 1; *wcschr( dest, L'[' )=0; } if( slice ) { std::vector<long> indexes; wcstring_list_t result; size_t j; env_var_t dest_str = env_get_string(dest); if (! dest_str.missing()) tokenize_variable_array( dest_str, result ); if( !parse_index( indexes, arg, dest, result.size() ) ) { builtin_print_help( parser, argv[0], stderr_buffer ); retcode = 1; break; } for( j=0; j < indexes.size() ; j++ ) { long idx = indexes[j]; if( idx < 1 || (size_t)idx > result.size() ) { retcode++; } } } else { if( !env_exist( arg, scope ) ) { retcode++; } } free( dest ); } return retcode; } if( list ) { /* Maybe we should issue an error if there are any other arguments? */ print_variables(0, 0, shorten_ok, scope); return 0; } if( woptind == argc ) { /* Print values of variables */ if( erase ) { append_format(stderr_buffer, _(L"%ls: Erase needs a variable name\n"), argv[0] ); builtin_print_help( parser, argv[0], stderr_buffer ); retcode = 1; } else { print_variables( 1, 1, shorten_ok, scope ); } return retcode; } if( !(dest = wcsdup(argv[woptind]))) { DIE_MEM(); } if( wcschr( dest, L'[' ) ) { slice = 1; *wcschr( dest, L'[' )=0; } if( !wcslen( dest ) ) { free( dest ); append_format(stderr_buffer, BUILTIN_ERR_VARNAME_ZERO, argv[0] ); builtin_print_help( parser, argv[0], stderr_buffer ); return 1; } if( (bad_char = wcsvarname( dest ) ) ) { append_format(stderr_buffer, BUILTIN_ERR_VARCHAR, argv[0], *bad_char ); builtin_print_help( parser, argv[0], stderr_buffer ); free( dest ); return 1; } if( slice && erase && (scope != ENV_USER) ) { free( dest ); append_format(stderr_buffer, _(L"%ls: Can not specify scope when erasing array slice\n"), argv[0] ); builtin_print_help( parser, argv[0], stderr_buffer ); return 1; } /* set assignment can work in two modes, either using slices or using the whole array. We detect which mode is used here. */ if( slice ) { /* Slice mode */ int idx_count, val_count; wcstring_list_t values; std::vector<long> indexes; wcstring_list_t result; const env_var_t dest_str = env_get_string(dest); if (! dest_str.missing()) tokenize_variable_array( dest_str, result ); for( ; woptind<argc; woptind++ ) { if( !parse_index( indexes, argv[woptind], dest, result.size() ) ) { builtin_print_help( parser, argv[0], stderr_buffer ); retcode = 1; break; } val_count = argc-woptind-1; idx_count = indexes.size(); if( !erase ) { if( val_count < idx_count ) { append_format(stderr_buffer, _(BUILTIN_SET_ARG_COUNT), argv[0] ); builtin_print_help( parser, argv[0], stderr_buffer ); retcode=1; break; } if( val_count == idx_count ) { woptind++; break; } } } if( !retcode ) { /* Slice indexes have been calculated, do the actual work */ if( erase ) { erase_values(result, indexes); my_env_set( dest, result, scope); } else { wcstring_list_t value; // al_init(&value); while( woptind < argc ) { value.push_back( argv[woptind++] ); } if( update_values( result, indexes, value ) ) { append_format(stderr_buffer, L"%ls: ", argv[0] ); append_format(stderr_buffer, ARRAY_BOUNDS_ERR ); stderr_buffer.push_back(L'\n'); } my_env_set(dest, result, scope); // al_destroy( &value ); } } // al_foreach( &result, &free ); // al_destroy( &result ); // al_destroy(&indexes); // al_destroy(&values); } else { woptind++; /* No slicing */ if( erase ) { if( woptind != argc ) { append_format(stderr_buffer, _(L"%ls: Values cannot be specfied with erase\n"), argv[0] ); builtin_print_help( parser, argv[0], stderr_buffer ); retcode=1; } else { retcode = env_remove( dest, scope ); } } else { wcstring_list_t val; for( i=woptind; i<argc; i++ ) val.push_back(argv[i]); retcode = my_env_set( dest, val, scope ); } } free( dest ); return retcode; }
bool path_get_cdpath_string(const wcstring &dir_str, wcstring &result, const env_var_t &cdpath) { wchar_t *res = 0; int err = ENOENT; bool success = false; const wchar_t *const dir = dir_str.c_str(); if (dir[0] == L'/'|| (wcsncmp(dir, L"./", 2)==0)) { struct stat buf; if (wstat(dir, &buf) == 0) { if (S_ISDIR(buf.st_mode)) { result = dir_str; success = true; } else { err = ENOTDIR; } } } else { wcstring path = L"."; // Respect CDPATH env_var_t cdpath = env_get_string(L"CDPATH"); if (! cdpath.missing_or_empty()) { path = cdpath.c_str(); } wcstokenizer tokenizer(path, ARRAY_SEP_STR); wcstring next_path; while (tokenizer.next(next_path)) { expand_tilde(next_path); if (next_path.size() == 0) continue; wcstring whole_path = next_path; append_path_component(whole_path, dir); struct stat buf; if (wstat(whole_path, &buf) == 0) { if (S_ISDIR(buf.st_mode)) { result = whole_path; success = true; break; } else { err = ENOTDIR; } } else { if (lwstat(whole_path, &buf) == 0) { err = EROTTEN; } } } } if (!success) { errno = err; } return res; }