#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'" ) bool path_get_path_string(const wcstring &cmd_str, wcstring &output, const env_vars &vars) { const wchar_t * const cmd = cmd_str.c_str(); int err = ENOENT; debug( 3, L"path_get_path_string( '%ls' )", cmd ); if(wcschr( cmd, L'/' ) != 0 ) { if( waccess( cmd, X_OK )==0 ) { struct stat buff; if(wstat( cmd, &buff )) { return false; } if (S_ISREG(buff.st_mode)) { output = cmd_str; return true; } else { errno = EACCES; return false; } } else { //struct stat buff; //wstat( cmd, &buff ); return false; } } else { const wchar_t *path = vars.get(L"PATH"); if( path == 0 ) { if( contains( PREFIX L"/bin", L"/bin", L"/usr/bin" ) ) { path = L"/bin" ARRAY_SEP_STR L"/usr/bin"; } else { path = L"/bin" ARRAY_SEP_STR L"/usr/bin" ARRAY_SEP_STR PREFIX L"/bin"; } } wcstokenizer tokenizer(path, ARRAY_SEP_STR); wcstring new_cmd; while (tokenizer.next(new_cmd)) { size_t path_len = new_cmd.size(); if (path_len == 0) continue; append_path_component(new_cmd, cmd_str); if( waccess( new_cmd, X_OK )==0 ) { struct stat buff; if( wstat( new_cmd, &buff )==-1 ) { if( errno != EACCES ) { wperror( L"stat" ); } continue; } if( S_ISREG(buff.st_mode) ) { output = new_cmd; return true; } err = EACCES; } else { switch( errno ) { case ENOENT: case ENAMETOOLONG: case EACCES: case ENOTDIR: break; default: { debug( 1, MISSING_COMMAND_ERR_MSG, new_cmd.c_str() ); wperror( L"access" ); } } } } } errno = err; return false; } wchar_t *path_get_path( const wchar_t *cmd ) { int err = ENOENT; CHECK( cmd, 0 ); debug( 3, L"path_get_path( '%ls' )", cmd ); if(wcschr( cmd, L'/' ) != 0 ) { if( waccess( cmd, X_OK )==0 ) { struct stat buff; if(wstat( cmd, &buff )) { return 0; } if( S_ISREG(buff.st_mode) ) return wcsdup( cmd ); else { errno = EACCES; return 0; } } else { struct stat buff; wstat( cmd, &buff ); return 0; } } else { env_var_t path = env_get_string(L"PATH"); if( path.missing() ) { if( contains( PREFIX L"/bin", L"/bin", L"/usr/bin" ) ) { path = L"/bin" ARRAY_SEP_STR L"/usr/bin"; } else { path = L"/bin" ARRAY_SEP_STR L"/usr/bin" ARRAY_SEP_STR PREFIX L"/bin"; } } /* Allocate string long enough to hold the whole command */ wchar_t *new_cmd = (wchar_t *)calloc(wcslen(cmd)+path.size()+2, sizeof(wchar_t) ); /* We tokenize a copy of the path, since strtok modifies its arguments */ wchar_t *path_cpy = wcsdup( path.c_str() ); wchar_t *state; if( (new_cmd==0) || (path_cpy==0) ) { DIE_MEM(); } for( const wchar_t *nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state ); nxt_path != 0; nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) ) { int path_len = wcslen( nxt_path ); wcscpy( new_cmd, nxt_path ); if( new_cmd[path_len-1] != L'/' ) { new_cmd[path_len++]=L'/'; } wcscpy( &new_cmd[path_len], cmd ); if( waccess( new_cmd, X_OK )==0 ) { struct stat buff; if( wstat( new_cmd, &buff )==-1 ) { if( errno != EACCES ) { wperror( L"stat" ); } continue; } if( S_ISREG(buff.st_mode) ) { free( path_cpy ); return new_cmd; } err = EACCES; } else { switch( errno ) { case ENOENT: case ENAMETOOLONG: case EACCES: case ENOTDIR: break; default: { debug( 1, MISSING_COMMAND_ERR_MSG, new_cmd ); wperror( L"access" ); } } } } free( new_cmd ); free( path_cpy ); } errno = err; return 0; } bool path_get_path_string(const wcstring &cmd, wcstring &output) { bool success = false; wchar_t *tmp = path_get_path(cmd.c_str()); if (tmp) { output = tmp; free(tmp); success = true; } return success; } bool path_get_cdpath_string(const wcstring &dir_str, wcstring &result, const env_vars &vars) { 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 { const wchar_t *path = L"."; // Respect CDPATH env_var_t cdpath = env_get_string(L"CDPATH"); if (! cdpath.missing_or_empty()) { path = cdpath.c_str(); printf("CDPATH: %ls\n", path); } 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; } wchar_t *path_allocate_cdpath( const wcstring &dir, const wchar_t *wd ) { wchar_t *res = NULL; int err = ENOENT; if (dir.empty()) return NULL; 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 { wchar_t *path_cpy; wchar_t *state; // Respect CDPATH env_var_t path = env_get_string(L"CDPATH"); if (path.missing_or_empty()) path = L"."; //We'll change this to the wd if we have one path_cpy = wcsdup( path.c_str() ); for( const wchar_t *nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state ); nxt_path != NULL; nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) ) { if (! wcscmp(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; } wcstring expanded_path = nxt_path; expand_tilde(expanded_path); // debug( 2, L"woot %ls\n", expanded_path.c_str() ); if (expanded_path.empty()) continue; wcstring whole_path = expanded_path; append_path_component(whole_path, dir); paths.push_back(whole_path); } free( path_cpy ); } 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) ) { res = wcsdup(dir.c_str()); break; } else { err = ENOTDIR; } } } if( !res ) { errno = err; } return res; } bool path_can_get_cdpath(const wcstring &in, const wchar_t *wd) { wchar_t *tmp = path_allocate_cdpath(in, wd); bool result = (tmp != NULL); free(tmp); return result; } bool path_can_be_implicit_cd(const wcstring &path, wcstring *out_path, const wchar_t *wd) { 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) || exp_path == L"..") { /* These paths can be implicit cd. Note that a single period cannot (that's used for sourcing files anyways) */ wchar_t *cd_path = path_allocate_cdpath(exp_path, wd); if (cd_path) { /* It worked. Return the path if desired */ if (out_path) out_path->assign(cd_path); free(cd_path); result = true; } } return result; } bool path_get_config(wcstring &path) { int done = 0; 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 = 1; } } else { const env_var_t home = env_get_string( L"HOME" ); if( ! home.missing() ) { res = home + L"/.config/fish"; if( !create_directory( res ) ) { done = 1; } } } if( done ) { path = res; return true; } else { 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 false; } } 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 ) { /* Remove double slashes */ size_t size; do { size = path.size(); replace_all(path, L"//", L"/"); } while (path.size() != size); /* Remove trailing slashes, except don't remove the first one */ while (size-- > 1) { if (path.at(size) != L'/') break; } /* Now size is either -1 (if the entire string was slashes) or is the index of the last non-slash character. Either way this will set it to the correct size. */ path.resize(size+1); } 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.c_str(), F_OK)); } else { /* Simple check */ path_is_valid = (0 == waccess(path.c_str(), F_OK)); } return path_is_valid; } bool paths_are_same_file(const wcstring &path1, const wcstring &path2) { if (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; } } wcstring get_working_directory(void) { wcstring wd = L"./"; wchar_t dir_path[4096]; const wchar_t *cwd = wgetcwd( dir_path, 4096 ); if (cwd) { wd = cwd; /* Make sure the working directory ends with a slash */ if (! wd.empty() && wd.at(wd.size() - 1) != L'/') wd.push_back(L'/'); } return wd; }