// If the given path looks like it's relative to the working directory, then prepend that working // directory. This operates on unescaped paths only (so a ~ means a literal ~). wcstring path_apply_working_directory(const wcstring &path, const wcstring &working_directory) { if (path.empty() || working_directory.empty()) return path; // We're going to make sure that if we want to prepend the wd, that the string has no leading // "/". bool prepend_wd = path.at(0) != L'/' && path.at(0) != HOME_DIRECTORY; if (!prepend_wd) { // No need to prepend the wd, so just return the path we were given. return path; } // Remove up to one "./". wcstring path_component = path; if (string_prefixes_string(L"./", path_component)) { path_component.erase(0, 2); } // Removing leading /s. while (string_prefixes_string(L"/", path_component)) { path_component.erase(0, 1); } // Construct and return a new path. wcstring new_path = working_directory; append_path_component(new_path, path_component); return new_path; }
// Given a start point as an absolute path, for any directory that has exactly one non-hidden // entity in it which is itself a directory, return that. The result is a relative path. For // example, if start_point is '/usr' we may return 'local/bin/'. // // The result does not have a leading slash, but does have a trailing slash if non-empty. wcstring descend_unique_hierarchy(const wcstring &start_point) { assert(!start_point.empty() && start_point.at(0) == L'/'); wcstring unique_hierarchy; wcstring abs_unique_hierarchy = start_point; bool stop_descent = false; DIR *dir; while (!stop_descent && (dir = wopendir(abs_unique_hierarchy))) { // We keep track of the single unique_entry entry. If we get more than one, it's not // unique and we stop the descent. wcstring unique_entry; bool child_is_dir; wcstring child_entry; while (wreaddir_resolving(dir, abs_unique_hierarchy, child_entry, &child_is_dir)) { if (child_entry.empty() || child_entry.at(0) == L'.') { continue; // either hidden, or . and .. entries -- skip them } else if (child_is_dir && unique_entry.empty()) { unique_entry = child_entry; // first candidate } else { // We either have two or more candidates, or the child is not a directory. We're // done. stop_descent = true; break; } } // We stop if we got two or more entries; also stop if we got zero or were interrupted if (unique_entry.empty() || interrupted()) { stop_descent = true; } if (!stop_descent) { // We have an entry in the unique hierarchy! append_path_component(unique_hierarchy, unique_entry); unique_hierarchy.push_back(L'/'); append_path_component(abs_unique_hierarchy, unique_entry); abs_unique_hierarchy.push_back(L'/'); } closedir(dir); } return unique_hierarchy; }
/** Get the configuration directory. The resulting string needs to be free'd. This is mostly the same code as path_get_config(), but had to be rewritten to avoid dragging in additional library dependencies. */ static wcstring fishd_get_config() { wchar_t *xdg_dir, *home; bool done = false; wcstring result; xdg_dir = fishd_env_get(L"XDG_CONFIG_HOME"); if (xdg_dir) { result = xdg_dir; append_path_component(result, L"/fish"); if (!create_directory(result)) { done = true; } free(xdg_dir); } else { home = fishd_env_get(L"HOME"); if (home) { result = home; append_path_component(result, L"/.config/fish"); if (!create_directory(result)) { done = 1; } free(home); } } if (! done) { /* Bad juju */ debug(0, _(L"Unable to create a configuration directory for fish. Your personal settings will not be saved. Please set the $XDG_CONFIG_HOME variable to a directory where the current user has write access.")); result.clear(); } return result; }
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 will not be saved. Please set the $XDG_CONFIG_HOME variable to a directory where the current user has write access.")); result.clear(); } return result; }
// Helper to resolve using our prefix. DIR *open_dir(const wcstring &base_dir) const { wcstring path = this->working_directory; append_path_component(path, base_dir); if (flags & EXPAND_SPECIAL_FOR_CD) { // cd operates on logical paths. // for example, cd ../<tab> should complete "without resolving symlinks". path = normalize_path(path); } else { // Other commands operate on physical paths. if (auto tmp = wrealpath(path)) { path = tmp.acquire(); } } return wopendir(path); }
void try_add_completion_result(const wcstring &filepath, const wcstring &filename, const wcstring &wildcard, const wcstring &prefix) { // This function is only for the completions case. assert(this->flags & EXPAND_FOR_COMPLETIONS); wcstring abs_path = this->working_directory; append_path_component(abs_path, filepath); // We must normalize the path to allow 'cd ..' to operate on logical paths. if (flags & EXPAND_SPECIAL_FOR_CD) abs_path = normalize_path(abs_path); size_t before = this->resolved_completions->size(); if (wildcard_test_flags_then_complete(abs_path, filename, wildcard.c_str(), this->flags, this->resolved_completions)) { // Hack. We added this completion result based on the last component of the wildcard. // Prepend our prefix to each wildcard that replaces its token. // Note that prepend_token_prefix is a no-op unless COMPLETE_REPLACES_TOKEN is set size_t after = this->resolved_completions->size(); for (size_t i = before; i < after; i++) { completion_t *c = &this->resolved_completions->at(i); if (this->has_fuzzy_ancestor && !(c->flags & COMPLETE_REPLACES_TOKEN)) { c->flags |= COMPLETE_REPLACES_TOKEN; c->prepend_token_prefix(wildcard); } c->prepend_token_prefix(prefix); } // Implement EXPAND_SPECIAL_FOR_CD_AUTOSUGGEST by descending the deepest unique // hierarchy we can, and then appending any components to each new result. // Only descend deepest unique for cd autosuggest and not for cd tab completion // (issue #4402). if (flags & EXPAND_SPECIAL_FOR_CD_AUTOSUGGEST) { wcstring unique_hierarchy = this->descend_unique_hierarchy(abs_path); if (!unique_hierarchy.empty()) { for (size_t i = before; i < after; i++) { completion_t &c = this->resolved_completions->at(i); c.completion.append(unique_hierarchy); } } } this->did_add = true; } }
/* If the given path looks like it's relative to the working directory, then prepend that working directory. This operates on unescaped paths only (so a ~ means a literal ~) */ wcstring path_apply_working_directory(const wcstring &path, const wcstring &working_directory) { if (path.empty() || working_directory.empty()) return path; /* We're going to make sure that if we want to prepend the wd, that the string has no leading / */ bool prepend_wd; switch (path.at(0)) { case L'/': case HOME_DIRECTORY: prepend_wd = false; break; default: prepend_wd = true; break; } if (! prepend_wd) { /* No need to prepend the wd, so just return the path we were given */ return path; } else { /* Remove up to one ./ */ wcstring path_component = path; if (string_prefixes_string(L"./", path_component)) { path_component.erase(0, 2); } /* Removing leading /s */ while (string_prefixes_string(L"/", path_component)) { path_component.erase(0, 1); } /* Construct and return a new path */ wcstring new_path = working_directory; append_path_component(new_path, path_component); return new_path; } }
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; }
bool path_get_cdpath(const wcstring &dir, wcstring *out, const wchar_t *wd, const env_vars_snapshot_t &env_vars) { int err = ENOENT; if (dir.empty()) return false; if (wd) { size_t len = wcslen(wd); assert(wd[len - 1] == L'/'); } wcstring_list_t paths; if (dir.at(0) == L'/') { // Absolute path. paths.push_back(dir); } else if (string_prefixes_string(L"./", dir) || string_prefixes_string(L"../", dir) || dir == L"." || dir == L"..") { // Path is relative to the working directory. wcstring path; if (wd) path.append(wd); path.append(dir); 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 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; } expand_tilde(nxt_path); // debug( 2, L"woot %ls\n", expanded_path.c_str() ); if (nxt_path.empty()) continue; wcstring whole_path = nxt_path; append_path_component(whole_path, dir); paths.push_back(whole_path); } } bool success = false; for (wcstring_list_t::const_iterator iter = paths.begin(); iter != paths.end(); ++iter) { struct stat buf; const wcstring &dir = *iter; if (wstat(dir, &buf) == 0) { if (S_ISDIR(buf.st_mode)) { success = true; if (out) out->assign(dir); break; } else { err = ENOTDIR; } } } if (!success) errno = err; return success; }
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; }
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 { // Note that PREFIX is defined in the `Makefile` and is thus defined when this module is // compiled. This ensures we always default to "/bin", "/usr/bin" and the bin dir defined // for the fish programs. Possibly with a duplicate dir if PREFIX is empty, "/", "/usr" or // "/usr/". If the PREFIX duplicates /bin or /usr/bin that is harmless other than a trivial // amount of time testing a path we've already tested. bin_path = L"/bin" ARRAY_SEP_STR L"/usr/bin" ARRAY_SEP_STR PREFIX L"/bin"; } 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(next_path, &buff) == -1) { if (errno != EACCES) { wperror(L"stat"); } continue; } if (S_ISREG(buff.st_mode)) { if (out_path) *out_path = std::move(next_path); return true; } err = EACCES; } else { switch (errno) { case ENOENT: case ENAMETOOLONG: case EACCES: case ENOTDIR: { break; } default: { debug(1, MISSING_COMMAND_ERR_MSG, next_path.c_str()); wperror(L"access"); break; } } } } errno = err; return false; }