aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/path.cpp
diff options
context:
space:
mode:
authorGravatar ridiculousfish <corydoras@ridiculousfish.com>2015-07-24 00:50:58 -0700
committerGravatar ridiculousfish <corydoras@ridiculousfish.com>2015-07-24 00:59:27 -0700
commitb4f53143b0e05fd3061cdf2e65e17a6a2904090b (patch)
tree4785bf31f7b89fc2420aa740d9a6967dc6c6f9b1 /src/path.cpp
parent9c2fdc6da57032c4448b59de5872086eea626b74 (diff)
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
Diffstat (limited to 'src/path.cpp')
-rw-r--r--src/path.cpp407
1 files changed, 407 insertions, 0 deletions
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 <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <libgen.h>
+
+#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;
+ }
+}