restyle path module to match project style

Reduces lint errors from 30 to 21 (-30%). Line count from 597 to 481 (-19%).

Another step in resolving issue #2902.
This commit is contained in:
Kurtis Rader 2016-05-02 21:15:43 -07:00
parent 80250c0729
commit 8d6b88eb5d
2 changed files with 212 additions and 328 deletions

View File

@ -1,118 +1,90 @@
#include <wchar.h>
// Directory utilities. This library contains functions for locating configuration directories, for
// testing if a command with a given name can be found in the PATH, and various other path-related
// issues.
#include <assert.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <wchar.h>
#include <string>
#include <vector>
#include "fallback.h" // IWYU pragma: keep
#include "common.h"
#include "env.h"
#include "wutil.h" // IWYU pragma: keep
#include "path.h"
#include "expand.h"
#include "fallback.h" // IWYU pragma: keep
#include "path.h"
#include "wutil.h" // IWYU pragma: keep
/**
Unexpected error in path_get_path()
*/
#define MISSING_COMMAND_ERR_MSG _( L"Error while searching for command '%ls'" )
/// 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)
{
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)
{
// 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))
{
if (wstat(cmd, &buff)) {
return false;
}
if (S_ISREG(buff.st_mode))
{
if (out_path)
out_path->assign(cmd);
if (S_ISREG(buff.st_mode)) {
if (out_path) out_path->assign(cmd);
return true;
}
else
{
} else {
errno = EACCES;
return false;
}
}
else
{
} else {
return false;
}
}
else
{
} else {
wcstring bin_path;
if (! bin_path_var.missing())
{
if (!bin_path_var.missing()) {
bin_path = bin_path_var;
}
else
{
if (contains(PREFIX L"/bin", L"/bin", L"/usr/bin"))
{
} else {
if (contains(PREFIX L"/bin", L"/bin", L"/usr/bin")) {
bin_path = L"/bin" ARRAY_SEP_STR L"/usr/bin";
}
else
{
} 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;
while (tokenizer.next(nxt_path)) {
if (nxt_path.empty()) continue;
append_path_component(nxt_path, cmd);
if (waccess(nxt_path, X_OK)==0)
{
if (waccess(nxt_path, X_OK) == 0) {
struct stat buff;
if (wstat(nxt_path, &buff)==-1)
{
if (errno != EACCES)
{
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);
if (S_ISREG(buff.st_mode)) {
if (out_path) out_path->swap(nxt_path);
return true;
}
err = EACCES;
}
else
{
switch (errno)
{
} else {
switch (errno) {
case ENOENT:
case ENAMETOOLONG:
case EACCES:
case ENOTDIR:
case ENOTDIR: {
break;
default:
{
debug(1,
MISSING_COMMAND_ERR_MSG,
nxt_path.c_str());
}
default: {
debug(1, MISSING_COMMAND_ERR_MSG, nxt_path.c_str());
wperror(L"access");
}
}
@ -124,69 +96,53 @@ static bool path_get_path_core(const wcstring &cmd, wcstring *out_path, const en
return false;
}
bool path_get_path(const wcstring &cmd, wcstring *out_path, const env_vars_snapshot_t &vars)
{
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)
{
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)
{
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 (dir.empty()) return false;
if (wd)
{
if (wd) {
size_t len = wcslen(wd);
assert(wd[len - 1] == L'/');
}
wcstring_list_t paths;
if (dir.at(0) == L'/')
{
/* Absolute path */
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 */
} 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);
if (wd) path.append(wd);
path.append(dir);
paths.push_back(path);
}
else
{
// Respect CDPATH
} 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
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
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() );
// debug( 2, L"woot %ls\n", expanded_path.c_str() );
if (nxt_path.empty())
continue;
if (nxt_path.empty()) continue;
wcstring whole_path = nxt_path;
append_path_component(whole_path, dir);
@ -195,300 +151,245 @@ bool path_get_cdpath(const wcstring &dir, wcstring *out, const wchar_t *wd, cons
}
bool success = false;
for (wcstring_list_t::const_iterator iter = paths.begin(); iter != paths.end(); ++iter)
{
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))
{
if (wstat(dir, &buf) == 0) {
if (S_ISDIR(buf.st_mode)) {
success = true;
if (out)
out->assign(dir);
if (out) out->assign(dir);
break;
}
else
{
} else {
err = ENOTDIR;
}
}
}
if (! success)
errno = err;
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)
{
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) */
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;
}
/* 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 / */
// 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))
{
switch (path.at(0)) {
case L'/':
case HOME_DIRECTORY:
case HOME_DIRECTORY: {
prepend_wd = false;
break;
default:
}
default: {
prepend_wd = true;
break;
}
}
if (! prepend_wd)
{
/* No need to prepend the wd, so just return the path we were given */
if (!prepend_wd) {
// No need to prepend the wd, so just return the path we were given.
return path;
}
else
{
/* Remove up to one ./ */
} else {
// Remove up to one "./".
wcstring path_component = path;
if (string_prefixes_string(L"./", path_component))
{
if (string_prefixes_string(L"./", path_component)) {
path_component.erase(0, 2);
}
/* Removing leading /s */
while (string_prefixes_string(L"/", path_component))
{
// Removing leading /s.
while (string_prefixes_string(L"/", path_component)) {
path_component.erase(0, 1);
}
/* Construct and return a new path */
// Construct and return a new path.
wcstring new_path = working_directory;
append_path_component(new_path, path_component);
return new_path;
}
}
static wcstring path_create_config()
{
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())
{
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))
{
if (!create_directory(res)) {
done = true;
}
}
else
{
} else {
const env_var_t home = env_get_string(L"HOME");
if (! home.missing())
{
if (!home.missing()) {
res = home + L"/.config/fish";
if (!create_directory(res))
{
if (!create_directory(res)) {
done = true;
}
}
}
if (! done)
{
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."));
debug(0, _(L"Unable to create a configuration directory for fish. Your personal settings "
L"will not be saved. Please set the $XDG_CONFIG_HOME variable to a directory "
L"where the current user has write access."));
}
return res;
}
static wcstring path_create_data()
{
static wcstring path_create_data() {
bool done = false;
wcstring res;
const env_var_t xdg_dir = env_get_string(L"XDG_DATA_HOME");
if (! xdg_dir.missing())
{
const env_var_t xdg_dir = env_get_string(L"XDG_DATA_HOME");
if (!xdg_dir.missing()) {
res = xdg_dir + L"/fish";
if (!create_directory(res))
{
if (!create_directory(res)) {
done = true;
}
}
else
{
} else {
const env_var_t home = env_get_string(L"HOME");
if (! home.missing())
{
if (!home.missing()) {
res = home + L"/.local/share/fish";
if (!create_directory(res))
{
if (!create_directory(res)) {
done = true;
}
}
}
if (! done)
{
if (!done) {
res.clear();
debug(0, _(L"Unable to create a data directory for fish. Your history will not be saved. Please set the $XDG_DATA_HOME variable to a directory where the current user has write access."));
debug(0, _(L"Unable to create a data directory for fish. Your history will not be saved. "
L"Please set the $XDG_DATA_HOME variable to a directory where the current user "
L"has write access."));
}
return res;
}
/* Cache the config path */
bool path_get_config(wcstring &path)
{
/// Cache the config path.
bool path_get_config(wcstring &path) {
static const wcstring result = path_create_config();
path = result;
return ! result.empty();
return !result.empty();
}
/* Cache the data path */
bool path_get_data(wcstring &path)
{
/// Cache the data path.
bool path_get_data(wcstring &path) {
static const wcstring result = path_create_data();
path = result;
return ! result.empty();
return !result.empty();
}
__attribute__((unused))
static void replace_all(wcstring &str, const wchar_t *needle, const wchar_t *replacement)
{
__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)
{
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
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--;
while (len > 1 && path.at(len - 1) == L'/') len--;
// Turn runs of slashes into a single slash
// 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++)
{
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
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);
if (trailing < len) path.resize(trailing);
}
bool paths_are_equivalent(const wcstring &p1, const wcstring &p2)
{
if (p1 == p2)
return true;
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
// 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)
{
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;
// 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
// 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
// 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(const wcstring &path, const wcstring &working_directory) {
bool path_is_valid;
/* Some special paths are always valid */
if (path.empty())
{
// Some special paths are always valid.
if (path.empty()) {
path_is_valid = false;
}
else if (path == L"." || path == L"./")
{
} 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. */
} 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 */
} 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;
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)
{
if (wstat(path1, &s1) == 0 && wstat(path2, &s2) == 0) {
return s1.st_ino == s2.st_ino && s1.st_dev == s2.st_dev;
}
else
{
} else {
return false;
}
}

View File

@ -1,103 +1,86 @@
/** \file path.h
Directory utilities. This library contains functions for locating
configuration directories, for testing if a command with a given
name can be found in the PATH, and various other path-related
issues.
*/
// Directory utilities. This library contains functions for locating configuration directories, for
// testing if a command with a given name can be found in the PATH, and various other path-related
// issues.
#ifndef FISH_PATH_H
#define FISH_PATH_H
#include <stddef.h>
#include <stdbool.h>
#include <stddef.h>
#include "common.h"
#include "env.h"
/**
Return value for path_cdpath_get when locatied a rotten symlink
*/
/// Return value for path_cdpath_get when locatied a rotten symlink.
#define EROTTEN 1
/**
Returns the user configuration directory for fish. If the directory
or one of its parents doesn't exist, they are first created.
\param path The directory as an out param
\return whether the directory was returned successfully
*/
/// Returns the user configuration directory for fish. If the directory or one of its parents
/// doesn't exist, they are first created.
///
/// \param path The directory as an out param
/// \return whether the directory was returned successfully
bool path_get_config(wcstring &path);
/**
Returns the user data directory for fish. If the directory
or one of its parents doesn't exist, they are first created.
Volatile files presumed to be local to the machine,
such as the fish_history and all the generated_completions,
will be stored in this directory.
\param path The directory as an out param
\return whether the directory was returned successfully
*/
/// Returns the user data directory for fish. If the directory or one of its parents doesn't exist,
/// they are first created.
///
/// Volatile files presumed to be local to the machine, such as the fish_history and all the
/// generated_completions, will be stored in this directory.
///
/// \param path The directory as an out param
/// \return whether the directory was returned successfully
bool path_get_data(wcstring &path);
/**
Finds the full path of an executable. Returns YES if successful.
\param cmd The name of the executable.
\param output_or_NULL If non-NULL, store the full path.
\param vars The environment variables snapshot to use
\return 0 if the command can not be found, the path of the command otherwise. The result should be freed with free().
*/
bool path_get_path(const wcstring &cmd,
wcstring *output_or_NULL,
/// Finds the full path of an executable. Returns YES if successful.
///
/// \param cmd The name of the executable.
/// \param output_or_NULL If non-NULL, store the full path.
/// \param vars The environment variables snapshot to use
/// \return 0 if the command can not be found, the path of the command otherwise. The result should
/// be freed with free().
bool path_get_path(const wcstring &cmd, wcstring *output_or_NULL,
const env_vars_snapshot_t &vars = env_vars_snapshot_t::current());
/**
Returns the full path of the specified directory, using the CDPATH
variable as a list of base directories for relative paths. The
returned string is allocated using halloc and the specified
context.
If no valid path is found, null is returned and errno is set to
ENOTDIR if at least one such path was found, but it did not point
to a directory, EROTTEN if a arotten symbolic link was found, or
ENOENT if no file of the specified name was found. If both a rotten
symlink and a file are found, it is undefined which error status
will be returned.
\param dir The name of the directory.
\param out_or_NULL If non-NULL, return the path to the resolved directory
\param wd The working directory, or NULL to use the default. The working directory should have a slash appended at the end.
\param vars The environment variable snapshot to use (for the CDPATH variable)
\return 0 if the command can not be found, the path of the command otherwise. The path should be free'd with free().
*/
bool path_get_cdpath(const wcstring &dir,
wcstring *out_or_NULL,
const wchar_t *wd = NULL,
/// Returns the full path of the specified directory, using the CDPATH variable as a list of base
/// directories for relative paths. The returned string is allocated using halloc and the specified
/// context.
///
/// If no valid path is found, null is returned and errno is set to ENOTDIR if at least one such
/// path was found, but it did not point to a directory, EROTTEN if a arotten symbolic link was
/// found, or ENOENT if no file of the specified name was found. If both a rotten symlink and a file
/// are found, it is undefined which error status will be returned.
///
/// \param dir The name of the directory.
/// \param out_or_NULL If non-NULL, return the path to the resolved directory
/// \param wd The working directory, or NULL to use the default. The working directory should have a
/// slash appended at the end.
/// \param vars The environment variable snapshot to use (for the CDPATH variable)
/// \return 0 if the command can not be found, the path of the command otherwise. The path should be
/// free'd with free().
bool path_get_cdpath(const wcstring &dir, wcstring *out_or_NULL, const wchar_t *wd = NULL,
const env_vars_snapshot_t &vars = env_vars_snapshot_t::current());
/** Returns whether the path can be used for an implicit cd command; if so, also returns the path by reference (if desired). This requires it to start with one of the allowed prefixes (., .., ~) and resolve to a directory. */
bool path_can_be_implicit_cd(const wcstring &path,
wcstring *out_path = NULL,
/// Returns whether the path can be used for an implicit cd command; if so, also returns the path by
/// reference (if desired). This requires it to start with one of the allowed prefixes (., .., ~)
/// and resolve to a directory.
bool path_can_be_implicit_cd(const wcstring &path, wcstring *out_path = NULL,
const wchar_t *wd = NULL,
const env_vars_snapshot_t &vars = env_vars_snapshot_t::current());
/**
Remove double slashes and trailing slashes from a path,
e.g. transform foo//bar/ into foo/bar. The string is modified in-place.
*/
/// Remove double slashes and trailing slashes from a path, e.g. transform foo//bar/ into foo/bar.
/// The string is modified in-place.
void path_make_canonical(wcstring &path);
/** Check if two paths are equivalent, which means to ignore runs of multiple slashes (or trailing slashes) */
/// Check if two paths are equivalent, which means to ignore runs of multiple slashes (or trailing
/// slashes).
bool paths_are_equivalent(const wcstring &p1, const wcstring &p2);
bool path_is_valid(const wcstring &path, const wcstring &working_directory);
/** Returns whether the two paths refer to the same file */
/// Returns whether the two paths refer to the same file.
bool paths_are_same_file(const wcstring &path1, const wcstring &path2);
/* 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 ~) */
/// 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);
#endif