From b4f53143b0e05fd3061cdf2e65e17a6a2904090b Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 24 Jul 2015 00:50:58 -0700 Subject: Migrate source files into src/ directory This change moves source files into a src/ directory, and puts object files into an obj/ directory. The Makefile and xcode project are updated accordingly. Fixes #1866 --- src/path.cpp | 407 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 src/path.cpp (limited to 'src/path.cpp') diff --git a/src/path.cpp b/src/path.cpp new file mode 100644 index 00000000..e61ae651 --- /dev/null +++ b/src/path.cpp @@ -0,0 +1,407 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fallback.h" +#include "util.h" + +#include "common.h" +#include "env.h" +#include "wutil.h" +#include "path.h" +#include "expand.h" + +/** + Unexpected error in path_get_path() +*/ +#define MISSING_COMMAND_ERR_MSG _( L"Error while searching for command '%ls'" ) + +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) + { + struct stat buff; + if (wstat(cmd, &buff)) + { + return false; + } + + if (S_ISREG(buff.st_mode)) + { + if (out_path) + out_path->assign(cmd); + return true; + } + else + { + errno = EACCES; + return false; + } + } + else + { + return false; + } + + } + else + { + 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_path(const wcstring &cmd, wcstring *out_path, const env_vars_snapshot_t &vars) +{ + return path_get_path_core(cmd, out_path, vars.get(L"PATH")); +} + +bool path_get_path(const wcstring &cmd, wcstring *out_path) +{ + return path_get_path_core(cmd, out_path, env_get_string(L"PATH")); +} + +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_can_be_implicit_cd(const wcstring &path, wcstring *out_path, const wchar_t *wd, const env_vars_snapshot_t &vars) +{ + wcstring exp_path = path; + expand_tilde(exp_path); + + bool result = false; + if (string_prefixes_string(L"/", exp_path) || + string_prefixes_string(L"./", exp_path) || + string_prefixes_string(L"../", exp_path) || + string_suffixes_string(L"/", exp_path) || + exp_path == L"..") + { + /* These paths can be implicit cd, so see if you cd to the path. Note that a single period cannot (that's used for sourcing files anyways) */ + result = path_get_cdpath(exp_path, out_path, wd, vars); + } + return result; +} + +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 will not be saved. Please set the $XDG_CONFIG_HOME variable to a directory where the current user has write access.")); + } + return res; +} + +/* Cache the config path */ +bool path_get_config(wcstring &path) +{ + static const wcstring result = path_create_config(); + path = result; + return ! result.empty(); +} + +__attribute__((unused)) +static void replace_all(wcstring &str, const wchar_t *needle, const wchar_t *replacement) +{ + size_t needle_len = wcslen(needle); + size_t offset = 0; + while ((offset = str.find(needle, offset)) != wcstring::npos) + { + str.replace(offset, needle_len, replacement); + offset += needle_len; + } +} + +void path_make_canonical(wcstring &path) +{ + // Ignore trailing slashes, unless it's the first character + size_t len = path.size(); + while (len > 1 && path.at(len - 1) == L'/') + len--; + + // Turn runs of slashes into a single slash + size_t trailing = 0; + bool prev_was_slash = false; + for (size_t leading = 0; leading < len; leading++) + { + wchar_t c = path.at(leading); + bool is_slash = (c == '/'); + if (! prev_was_slash || ! is_slash) + { + // This is either the first slash in a run, or not a slash at all + path.at(trailing++) = c; + } + prev_was_slash = is_slash; + } + assert(trailing <= len); + if (trailing < len) + path.resize(trailing); +} + +bool paths_are_equivalent(const wcstring &p1, const wcstring &p2) +{ + if (p1 == p2) + return true; + + size_t len1 = p1.size(), len2 = p2.size(); + + // Ignore trailing slashes after the first character + while (len1 > 1 && p1.at(len1 - 1) == L'/') len1--; + while (len2 > 1 && p2.at(len2 - 1) == L'/') len2--; + + // Start walking + size_t idx1 = 0, idx2 = 0; + while (idx1 < len1 && idx2 < len2) + { + wchar_t c1 = p1.at(idx1), c2 = p2.at(idx2); + + // If the characters are different, the strings are not equivalent + if (c1 != c2) + break; + + idx1++; + idx2++; + + // If the character was a slash, walk forwards until we hit the end of the string, or a non-slash + // Note the first condition is invariant within the loop + while (c1 == L'/' && idx1 < len1 && p1.at(idx1) == L'/') idx1++; + while (c2 == L'/' && idx2 < len2 && p2.at(idx2) == L'/') idx2++; + } + + // We matched if we consumed all of the characters in both strings + return idx1 == len1 && idx2 == len2; +} + +bool path_is_valid(const wcstring &path, const wcstring &working_directory) +{ + bool path_is_valid; + /* Some special paths are always valid */ + if (path.empty()) + { + path_is_valid = false; + } + else if (path == L"." || path == L"./") + { + path_is_valid = true; + } + else if (path == L".." || path == L"../") + { + path_is_valid = (! working_directory.empty() && working_directory != L"/"); + } + else if (path.at(0) != '/') + { + /* Prepend the working directory. Note that we know path is not empty here. */ + wcstring tmp = working_directory; + tmp.append(path); + path_is_valid = (0 == waccess(tmp, F_OK)); + } + else + { + /* Simple check */ + path_is_valid = (0 == waccess(path, F_OK)); + } + return path_is_valid; +} + +bool paths_are_same_file(const wcstring &path1, const wcstring &path2) +{ + if (paths_are_equivalent(path1, path2)) + return true; + + struct stat s1, s2; + if (wstat(path1, &s1) == 0 && wstat(path2, &s2) == 0) + { + return s1.st_ino == s2.st_ino && s1.st_dev == s2.st_dev; + } + else + { + return false; + } +} -- cgit v1.2.3