2006-10-19 11:50:23 +00:00
# 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'" )
2012-07-21 03:39:31 +00:00
static bool path_get_path_core ( const wcstring & cmd , wcstring * out_path , const env_var_t & bin_path_var )
2006-10-19 11:50:23 +00:00
{
2012-11-19 00:30:30 +00:00
int err = ENOENT ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
debug ( 3 , L " path_get_path( '%ls' ) " , cmd . c_str ( ) ) ;
2012-11-18 10:23:22 +00:00
2012-07-20 22:01:56 +00:00
/* If the command has a slash, it must be a full path */
2012-11-19 00:30:30 +00:00
if ( cmd . find ( L ' / ' ) ! = wcstring : : npos )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
if ( waccess ( cmd , X_OK ) = = 0 )
{
struct stat buff ;
if ( wstat ( cmd , & buff ) )
{
return false ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( S_ISREG ( buff . st_mode ) )
2012-07-20 22:01:56 +00:00
{
2012-11-19 00:30:30 +00:00
if ( out_path )
2012-07-21 03:39:31 +00:00
out_path - > assign ( cmd ) ;
2012-07-20 22:01:56 +00:00
return true ;
}
2012-11-19 00:30:30 +00:00
else
{
errno = EACCES ;
return false ;
}
}
else
{
struct stat buff ;
wstat ( cmd , & buff ) ;
return false ;
}
2012-11-18 10:23:22 +00:00
}
else
{
2012-07-21 03:39:31 +00:00
wcstring bin_path ;
2012-11-19 00:30:30 +00:00
if ( ! bin_path_var . missing ( ) )
2012-07-21 03:39:31 +00:00
{
bin_path = bin_path_var ;
}
else
2012-11-19 00:30:30 +00:00
{
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 " ;
}
}
2012-11-18 10:23:22 +00:00
2012-07-20 22:01:56 +00:00
wcstring nxt_path ;
2012-07-21 03:39:31 +00:00
wcstokenizer tokenizer ( bin_path , ARRAY_SEP_STR ) ;
2012-11-19 00:30:30 +00:00
while ( tokenizer . next ( nxt_path ) )
{
2012-07-20 22:01:56 +00:00
if ( nxt_path . empty ( ) )
continue ;
append_path_component ( nxt_path , cmd ) ;
2012-11-19 00:30:30 +00:00
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 ) )
{
2012-07-21 03:39:31 +00:00
if ( out_path )
out_path - > swap ( nxt_path ) ;
2012-11-19 00:30:30 +00:00
return true ;
}
err = EACCES ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
}
else
{
switch ( errno )
{
2012-11-19 08:31:03 +00:00
case ENOENT :
case ENAMETOOLONG :
case EACCES :
case ENOTDIR :
break ;
default :
{
debug ( 1 ,
MISSING_COMMAND_ERR_MSG ,
nxt_path . c_str ( ) ) ;
wperror ( L " access " ) ;
}
2012-11-19 00:30:30 +00:00
}
}
2012-11-18 10:23:22 +00:00
}
}
2012-11-19 00:30:30 +00:00
errno = err ;
return false ;
2006-10-19 11:50:23 +00:00
}
2012-07-21 03:39:31 +00:00
bool path_get_path ( const wcstring & cmd , wcstring * out_path , const env_vars_snapshot_t & vars )
2012-02-08 06:44:10 +00:00
{
2012-07-21 05:11:05 +00:00
return path_get_path_core ( cmd , out_path , vars . get ( L " PATH " ) ) ;
2012-02-08 06:44:10 +00:00
}
2012-07-21 03:39:31 +00:00
bool path_get_path ( const wcstring & cmd , wcstring * out_path )
2012-07-20 22:01:56 +00:00
{
2012-07-21 03:39:31 +00:00
return path_get_path_core ( cmd , out_path , env_get_string ( L " PATH " ) ) ;
2012-07-20 22:01:56 +00:00
}
2006-10-19 11:50:23 +00:00
2012-07-21 05:11:05 +00:00
bool path_get_cdpath_string ( const wcstring & dir_str , wcstring & result , const env_var_t & cdpath )
2011-12-27 03:18:46 +00:00
{
2012-11-19 00:30:30 +00:00
wchar_t * res = 0 ;
int err = ENOENT ;
2011-12-27 03:18:46 +00:00
bool success = false ;
2012-11-18 10:23:22 +00:00
2011-12-27 03:18:46 +00:00
const wchar_t * const dir = dir_str . c_str ( ) ;
2012-11-19 00:30:30 +00:00
if ( dir [ 0 ] = = L ' / ' | | ( wcsncmp ( dir , L " ./ " , 2 ) = = 0 ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
struct stat buf ;
if ( wstat ( dir , & buf ) = = 0 )
{
if ( S_ISDIR ( buf . st_mode ) )
{
result = dir_str ;
2011-12-27 03:18:46 +00:00
success = true ;
2012-11-19 00:30:30 +00:00
}
else
{
err = ENOTDIR ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
}
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
else
{
2012-11-18 10:23:22 +00:00
2012-07-21 05:11:05 +00:00
wcstring path = L " . " ;
2012-11-18 10:23:22 +00:00
2012-05-05 21:30:20 +00:00
// Respect CDPATH
env_var_t cdpath = env_get_string ( L " CDPATH " ) ;
2012-11-19 00:30:30 +00:00
if ( ! cdpath . missing_or_empty ( ) )
{
2012-05-05 21:30:20 +00:00
path = cdpath . c_str ( ) ;
}
2012-11-18 10:23:22 +00:00
2011-12-27 03:18:46 +00:00
wcstokenizer tokenizer ( path , ARRAY_SEP_STR ) ;
wcstring next_path ;
while ( tokenizer . next ( next_path ) )
{
expand_tilde ( next_path ) ;
if ( next_path . size ( ) = = 0 ) continue ;
2012-11-18 10:23:22 +00:00
2011-12-27 03:18:46 +00:00
wcstring whole_path = next_path ;
append_path_component ( whole_path , dir ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
struct stat buf ;
if ( wstat ( whole_path , & buf ) = = 0 )
{
if ( S_ISDIR ( buf . st_mode ) )
{
2011-12-27 03:18:46 +00:00
result = whole_path ;
success = true ;
2012-11-19 00:30:30 +00:00
break ;
}
else
{
err = ENOTDIR ;
}
}
else
{
if ( lwstat ( whole_path , & buf ) = = 0 )
{
err = EROTTEN ;
}
}
2011-12-27 03:18:46 +00:00
}
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( ! success )
{
errno = err ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
return res ;
2011-12-27 03:18:46 +00:00
}
2012-07-21 05:11:05 +00:00
bool path_get_cdpath ( const wcstring & dir , wcstring * out , const wchar_t * wd , const env_vars_snapshot_t & env_vars )
2006-10-19 11:50:23 +00:00
{
2012-11-19 00:30:30 +00:00
int err = ENOENT ;
if ( dir . empty ( ) )
return false ;
2012-11-18 10:23:22 +00:00
2012-07-21 05:11:05 +00:00
if ( wd )
{
2012-02-19 02:54:36 +00:00
size_t len = wcslen ( wd ) ;
assert ( wd [ len - 1 ] = = L ' / ' ) ;
}
2012-11-18 10:23:22 +00:00
2012-02-19 02:54:36 +00:00
wcstring_list_t paths ;
2012-11-19 00:30:30 +00:00
if ( dir . at ( 0 ) = = L ' / ' )
{
2012-02-19 02:54:36 +00:00
/* Absolute path */
paths . push_back ( dir ) ;
2012-11-19 00:30:30 +00:00
}
else if ( string_prefixes_string ( L " ./ " , dir ) | |
string_prefixes_string ( L " ../ " , dir ) | |
dir = = L " . " | | dir = = L " .. " )
{
2012-02-19 02:54:36 +00:00
/* Path is relative to the working directory */
wcstring path ;
if ( wd )
path . append ( wd ) ;
path . append ( dir ) ;
paths . push_back ( path ) ;
2012-11-19 00:30:30 +00:00
}
else
{
2012-05-05 21:30:20 +00:00
// Respect CDPATH
2012-07-21 05:11:05 +00:00
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
2012-02-19 02:54:36 +00:00
2012-07-21 05:11:05 +00:00
wcstring nxt_path ;
wcstokenizer tokenizer ( path , ARRAY_SEP_STR ) ;
while ( tokenizer . next ( nxt_path ) )
2012-02-19 02:54:36 +00:00
{
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( nxt_path = = L " . " & & wd ! = NULL )
{
2012-05-06 21:53:19 +00:00
// 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 ;
}
2012-07-21 05:11:05 +00:00
expand_tilde ( nxt_path ) ;
2006-10-19 11:50:23 +00:00
2012-11-18 10:23:22 +00:00
// debug( 2, L"woot %ls\n", expanded_path.c_str() );
2006-10-19 11:50:23 +00:00
2012-07-21 05:11:05 +00:00
if ( nxt_path . empty ( ) )
2012-02-19 02:54:36 +00:00
continue ;
2012-11-18 10:23:22 +00:00
2012-07-21 05:11:05 +00:00
wcstring whole_path = nxt_path ;
2012-05-09 09:33:42 +00:00
append_path_component ( whole_path , dir ) ;
2012-02-19 02:54:36 +00:00
paths . push_back ( whole_path ) ;
}
}
2012-11-18 10:23:22 +00:00
2012-07-21 05:11:05 +00:00
bool success = false ;
2012-11-19 00:30:30 +00:00
for ( wcstring_list_t : : const_iterator iter = paths . begin ( ) ; iter ! = paths . end ( ) ; + + iter )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
struct stat buf ;
const wcstring & dir = * iter ;
if ( wstat ( dir , & buf ) = = 0 )
{
if ( S_ISDIR ( buf . st_mode ) )
{
2012-07-21 05:11:05 +00:00
success = true ;
if ( out )
out - > assign ( dir ) ;
2012-02-19 02:54:36 +00:00
break ;
2012-11-19 00:30:30 +00:00
}
else
{
err = ENOTDIR ;
}
}
2012-11-18 10:23:22 +00:00
}
2012-07-21 05:11:05 +00:00
if ( ! success )
2012-11-19 00:30:30 +00:00
errno = err ;
2012-07-21 05:11:05 +00:00
return success ;
2006-10-19 11:50:23 +00:00
}
2012-07-21 05:11:05 +00:00
bool path_can_be_implicit_cd ( const wcstring & path , wcstring * out_path , const wchar_t * wd , const env_vars_snapshot_t & vars )
2012-06-02 21:04:25 +00:00
{
wcstring exp_path = path ;
expand_tilde ( exp_path ) ;
2012-11-18 10:23:22 +00:00
2012-06-02 21:04:25 +00:00
bool result = false ;
if ( string_prefixes_string ( L " / " , exp_path ) | |
2012-11-19 00:30:30 +00:00
string_prefixes_string ( L " ./ " , exp_path ) | |
string_prefixes_string ( L " ../ " , exp_path ) | |
2013-10-01 06:25:13 +00:00
string_suffixes_string ( L " / " , exp_path ) | |
2012-11-19 00:30:30 +00:00
exp_path = = L " .. " )
2012-06-02 21:04:25 +00:00
{
2012-07-21 05:11:05 +00:00
/* 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 ) ;
2012-06-02 21:04:25 +00:00
}
return result ;
}
2012-02-08 19:48:51 +00:00
2012-12-03 10:25:08 +00:00
static wcstring path_create_config ( )
2012-02-06 00:42:24 +00:00
{
2012-12-03 10:25:08 +00:00
bool done = false ;
2012-11-19 00:30:30 +00:00
wcstring res ;
const env_var_t xdg_dir = env_get_string ( L " XDG_CONFIG_HOME " ) ;
if ( ! xdg_dir . missing ( ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
res = xdg_dir + L " /fish " ;
if ( ! create_directory ( res ) )
{
2012-12-03 10:25:08 +00:00
done = true ;
2012-11-19 00:30:30 +00:00
}
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
else
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
const env_var_t home = env_get_string ( L " HOME " ) ;
if ( ! home . missing ( ) )
{
res = home + L " /.config/fish " ;
if ( ! create_directory ( res ) )
{
2012-12-03 10:25:08 +00:00
done = true ;
2012-11-19 00:30:30 +00:00
}
}
2012-11-18 10:23:22 +00:00
}
2012-12-03 10:25:08 +00:00
if ( ! done )
2012-11-19 00:30:30 +00:00
{
2012-12-03 10:25:08 +00:00
res . clear ( ) ;
2012-11-19 00:30:30 +00:00
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. " ) ) ;
}
2012-12-03 10:25:08 +00:00
return res ;
}
2012-02-06 00:42:24 +00:00
2012-12-03 10:25:08 +00:00
/* Cache the config path */
bool path_get_config ( wcstring & path )
{
static const wcstring result = path_create_config ( ) ;
path = result ;
return ! result . empty ( ) ;
2006-10-19 11:50:23 +00:00
}
2013-08-31 22:01:02 +00:00
__attribute__ ( ( unused ) )
2012-02-08 05:23:12 +00:00
static void replace_all ( wcstring & str , const wchar_t * needle , const wchar_t * replacement )
2007-05-10 19:11:28 +00:00
{
2012-02-08 05:23:12 +00:00
size_t needle_len = wcslen ( needle ) ;
size_t offset = 0 ;
2012-11-19 00:30:30 +00:00
while ( ( offset = str . find ( needle , offset ) ) ! = wcstring : : npos )
2012-02-08 05:23:12 +00:00
{
str . replace ( offset , needle_len , replacement ) ;
offset + = needle_len ;
}
}
2007-05-10 19:11:28 +00:00
2012-11-19 00:30:30 +00:00
void path_make_canonical ( wcstring & path )
2012-02-08 05:23:12 +00:00
{
2013-08-28 01:26:22 +00:00
// 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 + + )
2012-11-19 00:30:30 +00:00
{
2013-08-28 01:26:22 +00:00
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 ) ;
}
2012-11-18 10:23:22 +00:00
2013-08-28 01:26:22 +00:00
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 )
2012-11-19 00:30:30 +00:00
{
2013-08-28 01:26:22 +00:00
wchar_t c1 = p1 . at ( idx1 ) , c2 = p2 . at ( idx2 ) ;
// If the characters are different, the strings are not equivalent
if ( c1 ! = c2 )
2012-02-08 05:23:12 +00:00
break ;
2013-08-28 01:26:22 +00:00
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 + + ;
2012-02-08 05:23:12 +00:00
}
2013-08-28 01:26:22 +00:00
// We matched if we consumed all of the characters in both strings
return idx1 = = len1 & & idx2 = = len2 ;
2007-05-10 19:11:28 +00:00
}
2012-02-19 02:54:36 +00:00
bool path_is_valid ( const wcstring & path , const wcstring & working_directory )
{
bool path_is_valid ;
/* Some special paths are always valid */
2012-11-19 00:30:30 +00:00
if ( path . empty ( ) )
{
2012-02-19 02:54:36 +00:00
path_is_valid = false ;
2012-11-19 00:30:30 +00:00
}
else if ( path = = L " . " | | path = = L " ./ " )
{
2012-02-19 02:54:36 +00:00
path_is_valid = true ;
2012-11-19 00:30:30 +00:00
}
else if ( path = = L " .. " | | path = = L " ../ " )
{
2012-02-19 02:54:36 +00:00
path_is_valid = ( ! working_directory . empty ( ) & & working_directory ! = L " / " ) ;
2012-11-19 00:30:30 +00:00
}
else if ( path . at ( 0 ) ! = ' / ' )
{
2012-02-19 02:54:36 +00:00
/* Prepend the working directory. Note that we know path is not empty here. */
wcstring tmp = working_directory ;
tmp . append ( path ) ;
2012-07-21 05:11:05 +00:00
path_is_valid = ( 0 = = waccess ( tmp , F_OK ) ) ;
2012-11-19 00:30:30 +00:00
}
else
{
2012-02-19 02:54:36 +00:00
/* Simple check */
2012-07-21 05:11:05 +00:00
path_is_valid = ( 0 = = waccess ( path , F_OK ) ) ;
2012-02-19 02:54:36 +00:00
}
return path_is_valid ;
}
2012-11-19 00:30:30 +00:00
bool paths_are_same_file ( const wcstring & path1 , const wcstring & path2 )
{
2013-08-28 01:26:22 +00:00
if ( paths_are_equivalent ( path1 , path2 ) )
2012-02-19 05:56:30 +00:00
return true ;
2012-11-18 10:23:22 +00:00
2012-02-19 05:56:30 +00:00
struct stat s1 , s2 ;
2012-11-19 00:30:30 +00:00
if ( wstat ( path1 , & s1 ) = = 0 & & wstat ( path2 , & s2 ) = = 0 )
{
2012-02-19 05:56:30 +00:00
return s1 . st_ino = = s2 . st_ino & & s1 . st_dev = = s2 . st_dev ;
2012-11-19 00:30:30 +00:00
}
else
{
2012-02-19 05:56:30 +00:00
return false ;
}
}