empty CDPATH elements are equivalent to "."

In the process of fixing the issue I decided it didn't make sense to
have two, incompatible, ways of converting variable strings to arrays.
Especially since the one I'm removing does not return empty array elements.

Fixes #2106
This commit is contained in:
Kurtis Rader 2017-03-16 20:59:04 -07:00
parent 570a6430ad
commit ae0321778f
6 changed files with 50 additions and 75 deletions

View file

@ -9,6 +9,7 @@
- The `string` command now supports a `repeat` subcommand with the obvious behavior (#3864).
- The `functions --metadata --verbose` output now includes the function description (#597).
- Completions for `helm` added (#3829).
- Empty CDPATH elements are now equivalent to "." (#2106).
---

View file

@ -1919,22 +1919,6 @@ scoped_rwlock::~scoped_rwlock() {
}
}
wcstokenizer::wcstokenizer(const wcstring &s, const wcstring &separator)
: buffer(), str(), state(), sep(separator) {
buffer = wcsdup(s.c_str());
str = buffer;
state = NULL;
}
bool wcstokenizer::next(wcstring &result) {
wchar_t *tmp = wcstok(str, sep.c_str(), &state);
str = NULL;
if (tmp) result = tmp;
return tmp != NULL;
}
wcstokenizer::~wcstokenizer() { free(buffer); }
template <typename CharType_t>
static CharType_t **make_null_terminated_array_helper(
const std::vector<std::basic_string<CharType_t> > &argv) {

View file

@ -640,21 +640,6 @@ class scoped_push {
}
};
/// Wrapper around wcstok.
class wcstokenizer {
wchar_t *buffer, *str, *state;
const wcstring sep;
// No copying.
wcstokenizer &operator=(const wcstokenizer &);
wcstokenizer(const wcstokenizer &);
public:
wcstokenizer(const wcstring &s, const wcstring &separator);
bool next(wcstring &result);
~wcstokenizer();
};
/// Appends a path component, with a / if necessary.
void append_path_component(wcstring &path, const wcstring &component);

View file

@ -1403,7 +1403,8 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector<
// mostly the same. There's the following differences:
//
// 1. An empty CDPATH should be treated as '.', but an empty PATH should be left empty
// (no commands can be found).
// (no commands can be found). Also, an empty element of CDPATH is treated as '.' for
// consistency with POSIX shells.
//
// 2. PATH is only "one level," while CDPATH is multiple levels. That is, input like
// 'foo/bar' should resolve against CDPATH, but not PATH.
@ -1423,9 +1424,14 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector<
if (paths.missing_or_empty()) paths = for_cd ? L"." : L"";
// Tokenize it into directories.
wcstokenizer tokenizer(paths, ARRAY_SEP_STR);
wcstring next_path;
while (tokenizer.next(next_path)) {
std::vector<wcstring> pathsv;
tokenize_variable_array(paths, pathsv);
for (auto next_path : pathsv) {
if (next_path.empty()) {
if (!for_cd) continue;
next_path = L".";
}
// Ensure that we use the working directory for relative cdpaths like ".".
effective_working_dirs.push_back(
path_apply_working_directory(next_path, working_dir));
@ -1574,29 +1580,29 @@ bool expand_abbreviation(const wcstring &src, wcstring *output) {
if (src.empty()) return false;
// Get the abbreviations. Return false if we have none.
env_var_t var = env_get_string(USER_ABBREVIATIONS_VARIABLE_NAME);
if (var.missing_or_empty()) return false;
env_var_t abbrs = env_get_string(USER_ABBREVIATIONS_VARIABLE_NAME);
if (abbrs.missing_or_empty()) return false;
bool result = false;
wcstring line;
wcstokenizer tokenizer(var, ARRAY_SEP_STR);
while (tokenizer.next(line)) {
// Line is expected to be of the form 'foo=bar' or 'foo bar'. Parse out the first = or
// space. Silently skip on failure (no equals, or equals at the end or beginning). Try to
std::vector<wcstring> abbrsv;
tokenize_variable_array(abbrs, abbrsv);
for (auto abbr : abbrsv) {
// Abbreviation is expected to be of the form 'foo=bar' or 'foo bar'. Parse out the first =
// or space. Silently skip on failure (no equals, or equals at the end or beginning). Try to
// avoid copying any strings until we are sure this is a match.
size_t equals_pos = line.find(L'=');
size_t space_pos = line.find(L' ');
size_t equals_pos = abbr.find(L'=');
size_t space_pos = abbr.find(L' ');
size_t separator = mini(equals_pos, space_pos);
if (separator == wcstring::npos || separator == 0 || separator + 1 == line.size()) continue;
if (separator == wcstring::npos || separator == 0 || separator + 1 == abbr.size()) continue;
// Find the character just past the end of the command. Walk backwards, skipping spaces.
size_t cmd_end = separator;
while (cmd_end > 0 && iswspace(line.at(cmd_end - 1))) cmd_end--;
while (cmd_end > 0 && iswspace(abbr.at(cmd_end - 1))) cmd_end--;
// See if this command matches.
if (line.compare(0, cmd_end, src) == 0) {
if (abbr.compare(0, cmd_end, src) == 0) {
// Success. Set output to everything past the end of the string.
if (output != NULL) output->assign(line, separator + 1, wcstring::npos);
if (output != NULL) output->assign(abbr, separator + 1, wcstring::npos);
result = true;
break;

View file

@ -216,9 +216,10 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d
if (cdpath.missing_or_empty()) cdpath = L".";
// Tokenize it into directories.
wcstokenizer tokenizer(cdpath, ARRAY_SEP_STR);
wcstring next_path;
while (tokenizer.next(next_path)) {
std::vector<wcstring> pathsv;
tokenize_variable_array(cdpath, pathsv);
for (auto next_path : pathsv) {
if (next_path.empty()) next_path = L".";
// Ensure that we use the working directory for relative cdpaths like ".".
directories.push_back(path_apply_working_directory(next_path, working_directory));
}

View file

@ -58,21 +58,21 @@ static bool path_get_path_core(const wcstring &cmd, wcstring *out_path,
}
}
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) {
std::vector<wcstring> pathsv;
tokenize_variable_array(bin_path, pathsv);
for (auto next_path : pathsv) {
if (next_path.empty()) continue;
append_path_component(next_path, cmd);
if (waccess(next_path, X_OK) == 0) {
struct stat buff;
if (wstat(nxt_path, &buff) == -1) {
if (wstat(next_path, &buff) == -1) {
if (errno != EACCES) {
wperror(L"stat");
}
continue;
}
if (S_ISREG(buff.st_mode)) {
if (out_path) *out_path = std::move(nxt_path);
if (out_path) *out_path = std::move(next_path);
return true;
}
err = EACCES;
@ -85,7 +85,7 @@ static bool path_get_path_core(const wcstring &cmd, wcstring *out_path,
break;
}
default: {
debug(1, MISSING_COMMAND_ERR_MSG, nxt_path.c_str());
debug(1, MISSING_COMMAND_ERR_MSG, next_path.c_str());
wperror(L"access");
break;
}
@ -128,24 +128,22 @@ bool path_get_cdpath(const wcstring &dir, wcstring *out, const wchar_t *wd,
paths.push_back(path);
} else {
// Respect CDPATH.
env_var_t path = env_vars.get(L"CDPATH");
if (path.missing_or_empty()) path = L"."; // we'll change this to the wd if we have one
env_var_t cdpaths = env_vars.get(L"CDPATH");
if (cdpaths.missing_or_empty()) cdpaths = L".";
wcstring nxt_path;
wcstokenizer tokenizer(path, ARRAY_SEP_STR);
while (tokenizer.next(nxt_path)) {
if (nxt_path == L"." && wd != NULL) {
// nxt_path is just '.', and we have a working directory, so use the wd instead.
// TODO: if nxt_path starts with ./ we need to replace the . with the wd.
nxt_path = wd;
std::vector<wcstring> cdpathsv;
tokenize_variable_array(cdpaths, cdpathsv);
for (auto next_path : cdpathsv) {
if (next_path.empty()) next_path = L".";
if (next_path == L"." && wd != NULL) {
// next_path is just '.', and we have a working directory, so use the wd instead.
// TODO: if next_path starts with ./ we need to replace the . with the wd.
next_path = wd;
}
expand_tilde(nxt_path);
expand_tilde(next_path);
if (next_path.empty()) continue;
// debug( 2, L"woot %ls\n", expanded_path.c_str() );
if (nxt_path.empty()) continue;
wcstring whole_path = nxt_path;
wcstring whole_path = next_path;
append_path_component(whole_path, dir);
paths.push_back(whole_path);
}