/** \file builtin_set.c Functions defining the set builtin Functions used for implementing the set builtin. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "fallback.h" #include "util.h" #include "wutil.h" #include "builtin.h" #include "env.h" #include "expand.h" #include "common.h" #include "wgetopt.h" #include "proc.h" #include "parser.h" /* We know about these buffers */ extern wcstring stdout_buffer, stderr_buffer; /** Error message for invalid path operations */ #define BUILTIN_SET_PATH_ERROR L"%ls: Warning: path component %ls may not be valid in %ls.\n" /** Hint for invalid path operation with a colon */ #define BUILTIN_SET_PATH_HINT L"%ls: Did you mean 'set %ls $%ls %ls'?\n" /** Error for mismatch between index count and elements */ #define BUILTIN_SET_ARG_COUNT L"%ls: The number of variable indexes does not match the number of values\n" /** Test if the specified variable should be subject to path validation */ static int is_path_variable(const wchar_t *env) { return contains(env, L"PATH", L"CDPATH"); } /** 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 &indexes, const wchar_t *src, const wchar_t *name, size_t var_count) { size_t len; int count = 0; const wchar_t *src_orig = src; if (src == 0) { return 0; } while (*src != L'\0' && (iswalnum(*src) || *src == L'_')) { src++; } if (*src != L'[') { append_format(stderr_buffer, _(BUILTIN_SET_ARG_COUNT), L"set"); return 0; } len = src-src_orig; if ((wcsncmp(src_orig, name, len)!=0) || (wcslen(name) != (len))) { append_format(stderr_buffer, _(L"%ls: Multiple variable names specified in single call (%ls and %.*ls)\n"), L"set", name, len, src_orig); return 0; } src++; while (iswspace(*src)) { src++; } while (*src != L']') { wchar_t *end; long l_ind; errno = 0; l_ind = wcstol(src, &end, 10); if (end==src || errno) { append_format(stderr_buffer, _(L"%ls: Invalid index starting at '%ls'\n"), L"set", src); return 0; } if (l_ind < 0) { l_ind = var_count+l_ind+1; } src = end; if (*src==L'.' && *(src+1)==L'.') { src+=2; long l_ind2 = wcstol(src, &end, 10); if (end==src || errno) { return 1; } src = end; if (l_ind2 < 0) { l_ind2 = var_count+l_ind2+1; } int direction = l_ind2 &indexes, wcstring_list_t &values) { size_t i; /* Replace values where needed */ for (i = 0; i < indexes.size(); i++) { /* The '- 1' below is because the indices in fish are one-based, but the vector uses zero-based indices */ long ind = indexes[i] - 1; const wcstring newv = values[ i ]; if (ind < 0) { return 1; } if ((size_t)ind >= list.size()) { list.resize(ind+1); } // free((void *) al_get(list, ind)); list[ ind ] = newv; } return 0; } /** Erase from a list of wcstring values at specified indexes */ static void erase_values(wcstring_list_t &list, const std::vector &indexes) { // Make a set of indexes. // This both sorts them into ascending order and removes duplicates. const std::set indexes_set(indexes.begin(), indexes.end()); // Now walk the set backwards, so we encounter larger indexes first, and remove elements at the given (1-based) indexes. std::set::const_reverse_iterator iter; for (iter = indexes_set.rbegin(); iter != indexes_set.rend(); ++iter) { long val = *iter; if (val > 0 && (size_t)val <= list.size()) { // One-based indexing! list.erase(list.begin() + val - 1); } } } /** Print the names of all environment variables in the scope, with or without shortening, with or without values, with or without escaping */ static void print_variables(int include_values, int esc, bool shorten_ok, int scope) { wcstring_list_t names = env_get_names(scope); sort(names.begin(), names.end()); for (size_t i = 0; i < names.size(); i++) { const wcstring key = names.at(i); const wcstring e_key = escape_string(key, 0); stdout_buffer.append(e_key); if (include_values) { env_var_t value = env_get_string(key, scope); if (!value.missing()) { int shorten = 0; if (shorten_ok && value.length() > 64) { shorten = 1; value.resize(60); } wcstring e_value = esc ? expand_escape_variable(value) : value; stdout_buffer.append(L" "); stdout_buffer.append(e_value); if (shorten) { stdout_buffer.push_back(ellipsis_char); } } } stdout_buffer.append(L"\n"); } } /** 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 */ 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; bool preserve_incoming_failure_exit_status = true; const int incoming_exit_status = proc_get_last_status(); /* Variables used for performing the actual work */ wchar_t *dest = 0; int retcode=0; int scope; int slice=0; int i; const wchar_t *bad_char = NULL; /* 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; preserve_incoming_failure_exit_status = false; break; case 'n': list = 1; preserve_incoming_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_incoming_failure_exit_status = false; 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 variables */ 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 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())) { 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; } /* 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 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 (; woptind