2005-09-20 13:26:39 +00:00
/** \file wildcard.c
2005-11-29 14:33:52 +00:00
2006-01-04 12:51:02 +00:00
Fish needs it ' s own globbing implementation to support
tab - expansion of globbed parameters . Also provides recursive
wildcards using * * .
2005-09-20 13:26:39 +00:00
*/
2015-07-25 15:14:25 +00:00
# include "config.h" // IWYU pragma: keep
2005-09-20 13:26:39 +00:00
# include <stdlib.h>
# include <wchar.h>
# include <unistd.h>
# include <sys/stat.h>
# include <dirent.h>
# include <errno.h>
2007-02-25 09:05:24 +00:00
# include <string.h>
2012-08-07 09:50:12 +00:00
# include <set>
2015-07-25 15:14:25 +00:00
# include <assert.h>
# include <stddef.h>
# include <wctype.h>
# include <string>
# include <utility>
2006-02-28 13:17:16 +00:00
# include "fallback.h"
2005-09-20 13:26:39 +00:00
# include "wutil.h"
# include "common.h"
# include "wildcard.h"
# include "complete.h"
# include "reader.h"
# include "expand.h"
2012-02-10 00:06:24 +00:00
# include <map>
2005-09-20 13:26:39 +00:00
2007-02-25 09:05:24 +00:00
/**
Description for generic executable
*/
# define COMPLETE_EXEC_DESC _( L"Executable" )
/**
Description for link to executable
*/
# define COMPLETE_EXEC_LINK_DESC _( L"Executable link" )
/**
Description for regular file
*/
# define COMPLETE_FILE_DESC _( L"File" )
/**
Description for character device
*/
# define COMPLETE_CHAR_DESC _( L"Character device" )
/**
Description for block device
*/
# define COMPLETE_BLOCK_DESC _( L"Block device" )
/**
Description for fifo buffer
*/
# define COMPLETE_FIFO_DESC _( L"Fifo" )
/**
Description for symlink
*/
# define COMPLETE_SYMLINK_DESC _( L"Symbolic link" )
/**
Description for symlink
*/
# define COMPLETE_DIRECTORY_SYMLINK_DESC _( L"Symbolic link to directory" )
/**
Description for Rotten symlink
*/
# define COMPLETE_ROTTEN_SYMLINK_DESC _( L"Rotten symbolic link" )
/**
Description for symlink loop
*/
# define COMPLETE_LOOP_SYMLINK_DESC _( L"Symbolic link loop" )
/**
Description for socket files
*/
# define COMPLETE_SOCKET_DESC _( L"Socket" )
/**
Description for directories
*/
# define COMPLETE_DIRECTORY_DESC _( L"Directory" )
2015-08-03 22:51:27 +00:00
/* Finds an internal (ANY_STRING, etc.) style wildcard, or wcstring::npos */
static size_t wildcard_find ( const wchar_t * wc )
{
for ( size_t i = 0 ; wc [ i ] ! = L ' \0 ' ; i + + )
{
if ( wc [ i ] = = ANY_CHAR | | wc [ i ] = = ANY_STRING | | wc [ i ] = = ANY_STRING_RECURSIVE )
{
return i ;
}
}
return wcstring : : npos ;
}
2014-08-24 21:28:31 +00:00
// Implementation of wildcard_has. Needs to take the length to handle embedded nulls (#1631)
static bool wildcard_has_impl ( const wchar_t * str , size_t len , bool internal )
2005-09-20 13:26:39 +00:00
{
2014-08-24 21:28:31 +00:00
assert ( str ! = NULL ) ;
const wchar_t * end = str + len ;
2012-11-19 00:30:30 +00:00
if ( internal )
2012-11-18 10:23:22 +00:00
{
2014-08-24 21:28:31 +00:00
for ( ; str < end ; str + + )
2012-11-19 00:30:30 +00:00
{
if ( ( * str = = ANY_CHAR ) | | ( * str = = ANY_STRING ) | | ( * str = = ANY_STRING_RECURSIVE ) )
2013-05-25 22:41:18 +00:00
return 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
wchar_t prev = 0 ;
2014-08-24 21:28:31 +00:00
for ( ; str < end ; str + + )
2012-11-19 00:30:30 +00:00
{
if ( ( ( * str = = L ' * ' ) | | ( * str = = L ' ? ' ) ) & & ( prev ! = L ' \\ ' ) )
2013-05-25 22:41:18 +00:00
return true ;
2012-11-19 00:30:30 +00:00
prev = * str ;
}
2012-11-18 10:23:22 +00:00
}
2013-05-25 22:41:18 +00:00
return false ;
2005-09-20 13:26:39 +00:00
}
2014-08-24 21:28:31 +00:00
bool wildcard_has ( const wchar_t * str , bool internal )
{
assert ( str ! = NULL ) ;
return wildcard_has_impl ( str , wcslen ( str ) , internal ) ;
}
bool wildcard_has ( const wcstring & str , bool internal )
{
return wildcard_has_impl ( str . data ( ) , str . size ( ) , internal ) ;
}
2005-09-20 13:26:39 +00:00
/**
Check whether the string str matches the wildcard string wc .
2012-11-18 10:23:22 +00:00
2005-09-20 13:26:39 +00:00
\ param str String to be matched .
\ param wc The wildcard .
2012-11-18 10:23:22 +00:00
\ param is_first Whether files beginning with dots should not be matched against wildcards .
2005-09-20 13:26:39 +00:00
*/
2015-08-04 07:32:34 +00:00
static enum fuzzy_match_type_t wildcard_match_internal ( const wchar_t * str , const wchar_t * wc , bool leading_dots_fail_to_match , bool is_first , enum fuzzy_match_type_t max_type )
2005-09-20 13:26:39 +00:00
{
2012-11-19 00:30:30 +00:00
if ( * str = = 0 & & * wc = = 0 )
2015-08-01 23:02:46 +00:00
{
/* We're done */
2015-08-04 07:32:34 +00:00
return fuzzy_match_exact ;
2015-08-01 23:02:46 +00:00
}
2012-11-18 10:23:22 +00:00
2015-08-04 07:32:34 +00:00
/* Hackish fix for #270 . Prevent wildcards from matching . or .., but we must still allow literal matches. */
2013-04-20 19:41:02 +00:00
if ( leading_dots_fail_to_match & & is_first & & contains ( str , L " . " , L " .. " ) )
2012-10-16 01:16:47 +00:00
{
/* The string is '.' or '..'. Return true if the wildcard exactly matches. */
2015-08-04 07:32:34 +00:00
return wcscmp ( str , wc ) ? fuzzy_match_none : fuzzy_match_exact ;
}
/* Hackish fuzzy match support */
if ( 0 & & ! wildcard_has ( wc , true ) )
{
const string_fuzzy_match_t match = string_fuzzy_match_string ( wc , str ) ;
return match . type < = max_type ? match . type : fuzzy_match_none ;
2012-10-16 01:16:47 +00:00
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( * wc = = ANY_STRING | | * wc = = ANY_STRING_RECURSIVE )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
/* Ignore hidden file */
2013-04-20 19:41:02 +00:00
if ( leading_dots_fail_to_match & & is_first & & * str = = L ' . ' )
2012-11-19 00:30:30 +00:00
{
2015-08-04 07:32:34 +00:00
return fuzzy_match_none ;
2012-11-19 00:30:30 +00:00
}
2015-08-01 23:02:46 +00:00
/* Common case of * at the end. In that case we can early out since we know it will match. */
if ( wc [ 1 ] = = L ' \0 ' )
{
2015-08-04 07:32:34 +00:00
return fuzzy_match_exact ;
2015-08-01 23:02:46 +00:00
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/* Try all submatches */
do
{
2015-08-04 07:32:34 +00:00
enum fuzzy_match_type_t subresult = wildcard_match_internal ( str , wc + 1 , leading_dots_fail_to_match , false , max_type ) ;
if ( subresult ! = fuzzy_match_none )
return subresult ;
} while ( * str + + ! = 0 ) ;
return fuzzy_match_none ;
2012-11-19 00:30:30 +00:00
}
else if ( * str = = 0 )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
/*
End of string , but not end of wildcard , and the next wildcard
element is not a ' * ' , so this is not a match .
*/
2015-08-04 07:32:34 +00:00
return fuzzy_match_none ;
2012-11-18 10:23:22 +00:00
}
2015-08-01 23:02:46 +00:00
else if ( * wc = = ANY_CHAR )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
if ( is_first & & * str = = L ' . ' )
{
2015-08-04 07:32:34 +00:00
return fuzzy_match_none ;
2012-11-19 00:30:30 +00:00
}
2012-11-18 10:23:22 +00:00
2015-08-04 07:32:34 +00:00
return wildcard_match_internal ( str + 1 , wc + 1 , leading_dots_fail_to_match , false , max_type ) ;
2012-11-19 00:30:30 +00:00
}
2015-08-01 23:02:46 +00:00
else if ( * wc = = * str )
{
2015-08-04 07:32:34 +00:00
return wildcard_match_internal ( str + 1 , wc + 1 , leading_dots_fail_to_match , false , max_type ) ;
2015-08-01 23:02:46 +00:00
}
2012-11-18 10:23:22 +00:00
2015-08-04 07:32:34 +00:00
return fuzzy_match_none ;
2005-09-20 13:26:39 +00:00
}
2015-08-03 22:51:27 +00:00
/* This does something horrible refactored from an even more horrible function */
static wcstring resolve_description ( wcstring * completion , const wchar_t * explicit_desc , wcstring ( * desc_func ) ( const wcstring & ) )
2005-09-20 13:26:39 +00:00
{
2015-08-03 22:51:27 +00:00
size_t complete_sep_loc = completion - > find ( PROG_COMPLETE_SEP ) ;
if ( complete_sep_loc ! = wcstring : : npos )
2012-11-18 10:23:22 +00:00
{
2015-08-03 22:51:27 +00:00
/* This completion has an embedded description, do not use the generic description */
const wcstring description = completion - > substr ( complete_sep_loc + 1 ) ;
completion - > resize ( complete_sep_loc ) ;
return description ;
2012-11-18 10:23:22 +00:00
}
2015-08-03 22:51:27 +00:00
else
2012-11-18 10:23:22 +00:00
{
2015-08-03 22:51:27 +00:00
const wcstring func_result = ( desc_func ? desc_func ( * completion ) : wcstring ( ) ) ;
if ( ! func_result . empty ( ) )
2012-11-19 00:30:30 +00:00
{
2015-08-03 22:51:27 +00:00
return func_result ;
}
else
{
return explicit_desc ? explicit_desc : L " " ;
2012-11-19 00:30:30 +00:00
}
2013-05-25 22:41:18 +00:00
}
2015-08-03 22:51:27 +00:00
}
2013-06-02 08:14:26 +00:00
2015-08-03 22:51:27 +00:00
/* A transient parameter pack needed by wildcard_complete.f */
struct wc_complete_pack_t
{
const wcstring & orig ; // the original string, transient
const wchar_t * desc ; // literal description
wcstring ( * desc_func ) ( const wcstring & ) ; // function for generating descriptions
expand_flags_t expand_flags ;
wc_complete_pack_t ( const wcstring & str ) : orig ( str ) { }
} ;
2015-08-04 05:09:25 +00:00
/* Weirdly specific and non-reusable helper function that makes its one call site much clearer */
static bool has_prefix_match ( const std : : vector < completion_t > * comps , size_t first )
{
if ( comps ! = NULL )
{
const size_t after_count = comps - > size ( ) ;
for ( size_t j = first ; j < after_count ; j + + )
{
if ( comps - > at ( j ) . match . type < = fuzzy_match_prefix )
{
return true ;
}
}
}
return false ;
}
2015-08-03 22:51:27 +00:00
/**
Matches the string against the wildcard , and if the wildcard is a
possible completion of the string , the remainder of the string is
inserted into the out vector .
We ignore ANY_STRING_RECURSIVE here . The consequence is that you cannot
tab complete * * wildcards . This is historic behavior .
*/
static bool wildcard_complete_internal ( const wchar_t * str ,
const wchar_t * wc ,
const wc_complete_pack_t & params ,
complete_flags_t flags ,
std : : vector < completion_t > * out ,
bool is_first_call = false )
{
assert ( str ! = NULL ) ;
assert ( wc ! = NULL ) ;
/* Maybe early out for hidden files. We require that the wildcard match these exactly (i.e. a dot); ANY_STRING not allowed */
if ( is_first_call & & str [ 0 ] = = L ' . ' & & wc [ 0 ] ! = L ' . ' )
2013-06-02 08:14:26 +00:00
{
2015-08-03 22:51:27 +00:00
return false ;
}
/* Locate the next wildcard character position, e.g. ANY_CHAR or ANY_STRING */
size_t next_wc_char_pos = wildcard_find ( wc ) ;
2015-08-04 00:17:15 +00:00
/* Maybe we have no more wildcards at all. This includes the empty string. */
2015-08-03 22:51:27 +00:00
if ( next_wc_char_pos = = wcstring : : npos )
{
string_fuzzy_match_t match = string_fuzzy_match_string ( wc , str ) ;
/* If we're allowing fuzzy match, any match is OK. Otherwise we require a prefix match. */
bool match_acceptable ;
if ( params . expand_flags & EXPAND_FUZZY_MATCH )
2012-11-19 00:30:30 +00:00
{
2015-08-03 22:51:27 +00:00
match_acceptable = match . type ! = fuzzy_match_none ;
}
else
{
match_acceptable = match_type_shares_prefix ( match . type ) ;
2012-11-19 00:30:30 +00:00
}
2015-08-03 22:51:27 +00:00
2015-08-04 05:09:25 +00:00
if ( match_acceptable & & out ! = NULL )
2015-08-03 22:51:27 +00:00
{
/* Wildcard complete */
bool full_replacement = match_type_requires_full_replacement ( match . type ) | | ( flags & COMPLETE_REPLACES_TOKEN ) ;
/* If we are not replacing the token, be careful to only store the part of the string after the wildcard */
assert ( ! full_replacement | | wcslen ( wc ) < = wcslen ( str ) ) ;
wcstring out_completion = full_replacement ? params . orig : str + wcslen ( wc ) ;
wcstring out_desc = resolve_description ( & out_completion , params . desc , params . desc_func ) ;
/* Note: out_completion may be empty if the completion really is empty, e.g. tab-completing 'foo' when a file 'foo' exists. */
complete_flags_t local_flags = flags | ( full_replacement ? COMPLETE_REPLACES_TOKEN : 0 ) ;
append_completion ( out , out_completion , out_desc , local_flags , match ) ;
}
return match_acceptable ;
2013-05-25 22:41:18 +00:00
}
2015-08-03 22:51:27 +00:00
else if ( next_wc_char_pos > 0 )
2013-05-25 22:41:18 +00:00
{
2015-08-03 22:51:27 +00:00
/* Here we have a non-wildcard prefix. Note that we don't do fuzzy matching for stuff before a wildcard, so just do case comparison and then recurse. */
if ( wcsncmp ( str , wc , next_wc_char_pos ) = = 0 )
2012-11-19 00:30:30 +00:00
{
2015-08-03 22:51:27 +00:00
// Normal match
2015-08-04 05:09:25 +00:00
return wildcard_complete_internal ( str + next_wc_char_pos , wc + next_wc_char_pos , params , flags , out ) ;
2015-08-03 22:51:27 +00:00
}
else if ( wcsncasecmp ( str , wc , next_wc_char_pos ) = = 0 )
{
// Case insensitive match
2015-08-04 05:09:25 +00:00
return wildcard_complete_internal ( str + next_wc_char_pos , wc + next_wc_char_pos , params , flags | COMPLETE_REPLACES_TOKEN , out ) ;
2012-11-19 00:30:30 +00:00
}
else
{
2015-08-03 22:51:27 +00:00
// No match
2015-08-04 05:09:25 +00:00
return false ;
2012-11-19 00:30:30 +00:00
}
2015-08-04 05:09:25 +00:00
assert ( 0 & & " Unreachable code reached " ) ;
2012-11-19 00:30:30 +00:00
}
2015-08-03 22:51:27 +00:00
else
2012-11-19 00:30:30 +00:00
{
2015-08-03 22:51:27 +00:00
/* Our first character is a wildcard. */
assert ( next_wc_char_pos = = 0 ) ;
switch ( wc [ 0 ] )
2012-11-19 00:30:30 +00:00
{
2015-08-03 22:51:27 +00:00
case ANY_CHAR :
2013-09-11 09:33:44 +00:00
{
2015-08-03 22:51:27 +00:00
if ( str [ 0 ] = = L ' \0 ' )
{
return false ;
}
else
{
return wildcard_complete_internal ( str + 1 , wc + 1 , params , flags , out ) ;
}
break ;
}
case ANY_STRING :
{
2015-08-04 05:09:25 +00:00
/* Try all submatches. #929: if the recursive call gives us a prefix match, just stop. This is sloppy - what we really want to do is say, once we've seen a match of a particular type, ignore all matches of that type further down the string, such that the wildcard produces the "minimal match.". */
2015-08-03 22:51:27 +00:00
bool has_match = false ;
2015-08-04 05:09:25 +00:00
for ( size_t i = 0 ; str [ i ] ! = L ' \0 ' ; i + + )
2013-09-11 09:33:44 +00:00
{
2015-08-04 05:09:25 +00:00
const size_t before_count = out ? out - > size ( ) : 0 ;
2015-08-03 22:51:27 +00:00
if ( wildcard_complete_internal ( str + i , wc + 1 , params , flags , out ) )
2013-09-11 09:33:44 +00:00
{
2015-08-04 05:09:25 +00:00
/* We found a match */
2015-08-03 22:51:27 +00:00
has_match = true ;
2015-08-04 05:09:25 +00:00
/* If out is NULL, we don't care about the actual matches. If out is not NULL but we have a prefix match, stop there. */
if ( out = = NULL | | has_prefix_match ( out , before_count ) )
2015-08-03 22:51:27 +00:00
{
2015-08-04 05:09:25 +00:00
break ;
2015-08-03 22:51:27 +00:00
}
2013-09-11 09:33:44 +00:00
}
}
2015-08-03 22:51:27 +00:00
return has_match ;
2013-09-11 09:33:44 +00:00
}
2015-08-03 22:51:27 +00:00
case ANY_STRING_RECURSIVE :
/* We don't even try with this one */
return false ;
default :
assert ( 0 & & " Unreachable code reached " ) ;
return false ;
2012-11-19 00:30:30 +00:00
}
}
2015-08-03 22:51:27 +00:00
assert ( 0 & & " Unreachable code reached " ) ;
2005-09-20 13:26:39 +00:00
}
2012-05-09 09:33:42 +00:00
bool wildcard_complete ( const wcstring & str ,
2012-11-19 00:30:30 +00:00
const wchar_t * wc ,
const wchar_t * desc ,
wcstring ( * desc_func ) ( const wcstring & ) ,
2015-07-28 01:45:47 +00:00
std : : vector < completion_t > * out ,
2013-05-25 22:41:18 +00:00
expand_flags_t expand_flags ,
complete_flags_t flags )
2005-09-20 13:26:39 +00:00
{
2015-08-04 05:09:25 +00:00
// Note out may be NULL
assert ( wc ! = NULL ) ;
2015-08-03 22:51:27 +00:00
wc_complete_pack_t params ( str ) ;
params . desc = desc ;
params . desc_func = desc_func ;
params . expand_flags = expand_flags ;
return wildcard_complete_internal ( str . c_str ( ) , wc , params , flags , out , true /* first call */ ) ;
2005-09-20 13:26:39 +00:00
}
2013-04-20 19:41:02 +00:00
bool wildcard_match ( const wcstring & str , const wcstring & wc , bool leading_dots_fail_to_match )
2005-09-20 13:26:39 +00:00
{
2015-08-04 07:32:34 +00:00
enum fuzzy_match_type_t match = wildcard_match_internal ( str . c_str ( ) , wc . c_str ( ) , leading_dots_fail_to_match , true /* first */ , fuzzy_match_exact ) ;
return match ! = fuzzy_match_none ;
2005-09-20 13:26:39 +00:00
}
/**
2015-08-04 00:17:15 +00:00
Obtain a description string for the file specified by the filename .
The returned value is a string constant and should not be free ' d .
\ param filename The file for which to find a description string
\ param lstat_res The result of calling lstat on the file
\ param lbuf The struct buf output of calling lstat on the file
\ param stat_res The result of calling stat on the file
\ param buf The struct buf output of calling stat on the file
\ param err The errno value after a failed stat call on the file .
*/
2007-02-25 09:05:24 +00:00
2012-11-19 00:30:30 +00:00
static wcstring file_get_desc ( const wcstring & filename ,
int lstat_res ,
2015-07-20 09:34:57 +00:00
const struct stat & lbuf ,
2012-11-19 00:30:30 +00:00
int stat_res ,
struct stat buf ,
int err )
2007-02-25 09:05:24 +00:00
{
2015-08-04 00:17:15 +00:00
2012-11-19 00:30:30 +00:00
if ( ! lstat_res )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
if ( S_ISLNK ( lbuf . st_mode ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
if ( ! stat_res )
{
if ( S_ISDIR ( buf . st_mode ) )
{
return COMPLETE_DIRECTORY_SYMLINK_DESC ;
}
else
{
2015-08-04 00:17:15 +00:00
if ( buf . st_mode & ( S_IXUSR | S_IXGRP | S_IXOTH ) )
2012-11-19 00:30:30 +00:00
{
2015-08-04 00:17:15 +00:00
2012-11-19 00:30:30 +00:00
if ( waccess ( filename , X_OK ) = = 0 )
{
/*
2015-08-04 00:17:15 +00:00
Weird group permissions and other such
issues make it non - trivial to find out
if we can actually execute a file using
the result from stat . It is much safer
to use the access function , since it
tells us exactly what we want to know .
*/
2012-11-19 00:30:30 +00:00
return COMPLETE_EXEC_LINK_DESC ;
}
}
}
2015-08-04 00:17:15 +00:00
2012-11-19 00:30:30 +00:00
return COMPLETE_SYMLINK_DESC ;
2015-08-04 00:17:15 +00:00
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
else
{
switch ( err )
{
2012-11-19 08:31:03 +00:00
case ENOENT :
{
return COMPLETE_ROTTEN_SYMLINK_DESC ;
}
2015-08-04 00:17:15 +00:00
2012-11-19 08:31:03 +00:00
case ELOOP :
{
return COMPLETE_LOOP_SYMLINK_DESC ;
}
2012-11-19 00:30:30 +00:00
}
/*
2015-08-04 00:17:15 +00:00
On unknown errors we do nothing . The file will be
given the default ' File ' description or one based on the suffix .
*/
2012-11-19 00:30:30 +00:00
}
2015-08-04 00:17:15 +00:00
2012-11-19 00:30:30 +00:00
}
else if ( S_ISCHR ( buf . st_mode ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
return COMPLETE_CHAR_DESC ;
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
else if ( S_ISBLK ( buf . st_mode ) )
{
return COMPLETE_BLOCK_DESC ;
}
else if ( S_ISFIFO ( buf . st_mode ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
return COMPLETE_FIFO_DESC ;
}
else if ( S_ISSOCK ( buf . st_mode ) )
{
return COMPLETE_SOCKET_DESC ;
}
else if ( S_ISDIR ( buf . st_mode ) )
{
return COMPLETE_DIRECTORY_DESC ;
}
else
{
2015-08-04 00:17:15 +00:00
if ( buf . st_mode & ( S_IXUSR | S_IXGRP | S_IXGRP ) )
2012-11-19 00:30:30 +00:00
{
2015-08-04 00:17:15 +00:00
2012-11-19 00:30:30 +00:00
if ( waccess ( filename , X_OK ) = = 0 )
{
/*
2015-08-04 00:17:15 +00:00
Weird group permissions and other such issues
make it non - trivial to find out if we can
actually execute a file using the result from
stat . It is much safer to use the access
function , since it tells us exactly what we want
to know .
*/
2012-11-19 00:30:30 +00:00
return COMPLETE_EXEC_DESC ;
}
}
2012-11-18 10:23:22 +00:00
}
}
2015-08-04 00:17:15 +00:00
2012-11-19 00:30:30 +00:00
return COMPLETE_FILE_DESC ;
2007-02-25 09:05:24 +00:00
}
2015-08-04 00:17:15 +00:00
/** Test if the given file is an executable (if EXECUTABLES_ONLY) or directory (if DIRECTORIES_ONLY).
If it matches , call wildcard_complete ( ) with some description that we make up .
Note that the filename came from a readdir ( ) call , so we know it exists .
*/
static bool wildcard_test_flags_then_complete ( const wcstring & filepath ,
const wcstring & filename ,
const wchar_t * wc ,
expand_flags_t expand_flags ,
std : : vector < completion_t > * out )
2015-08-04 05:09:25 +00:00
{
/* Check if it will match before stat() */
if ( ! wildcard_complete ( filename , wc , NULL , NULL , NULL , expand_flags , 0 ) )
{
return false ;
}
2015-08-04 00:17:15 +00:00
struct stat lstat_buf = { } , stat_buf = { } ;
int stat_res = - 1 ;
int stat_errno = 0 ;
int lstat_res = lwstat ( filepath , & lstat_buf ) ;
if ( lstat_res < 0 )
{
/* lstat failed */
2012-11-18 10:23:22 +00:00
}
else
{
2015-08-04 00:17:15 +00:00
if ( S_ISLNK ( lstat_buf . st_mode ) )
2012-11-19 00:30:30 +00:00
{
2015-08-04 00:17:15 +00:00
stat_res = wstat ( filepath , & stat_buf ) ;
if ( stat_res < 0 )
2012-11-19 00:30:30 +00:00
{
2015-08-04 00:17:15 +00:00
/*
In order to differentiate between e . g . rotten symlinks
and symlink loops , we also need to know the error status of wstat .
*/
stat_errno = errno ;
2012-11-19 00:30:30 +00:00
}
}
else
{
2015-08-04 00:17:15 +00:00
stat_buf = lstat_buf ;
2012-11-19 00:30:30 +00:00
stat_res = lstat_res ;
}
2012-11-18 10:23:22 +00:00
}
2015-08-04 00:17:15 +00:00
const long long file_size = stat_res = = 0 ? stat_buf . st_size : 0 ;
const bool is_directory = stat_res = = 0 & & S_ISDIR ( stat_buf . st_mode ) ;
const bool is_executable = stat_res = = 0 & & S_ISREG ( stat_buf . st_mode ) ;
if ( expand_flags & DIRECTORIES_ONLY )
2012-11-19 00:30:30 +00:00
{
2015-08-04 00:17:15 +00:00
if ( ! is_directory )
2012-08-16 01:20:44 +00:00
{
2015-08-04 00:17:15 +00:00
return false ;
2012-08-16 01:20:44 +00:00
}
2012-11-19 00:30:30 +00:00
}
2015-08-04 00:17:15 +00:00
if ( expand_flags & EXECUTABLES_ONLY )
2012-11-18 10:23:22 +00:00
{
2015-08-04 00:17:15 +00:00
if ( ! is_executable | | waccess ( filepath , X_OK ) ! = 0 )
2012-11-19 00:30:30 +00:00
{
2013-05-25 22:41:18 +00:00
return false ;
2012-11-19 00:30:30 +00:00
}
2012-11-18 10:23:22 +00:00
}
2015-08-04 00:17:15 +00:00
/* Compute the description */
bool wants_desc = ! ( expand_flags & EXPAND_NO_DESCRIPTIONS ) ;
wcstring desc ;
if ( wants_desc )
2012-11-19 00:30:30 +00:00
{
2015-08-04 00:17:15 +00:00
desc = file_get_desc ( filepath , lstat_res , lstat_buf , stat_res , stat_buf , stat_errno ) ;
if ( file_size > = 0 )
2014-09-20 07:26:10 +00:00
{
2015-08-04 00:17:15 +00:00
if ( ! desc . empty ( ) )
desc . append ( L " , " ) ;
desc . append ( format_size ( file_size ) ) ;
2014-09-20 07:26:10 +00:00
}
2012-11-19 00:30:30 +00:00
}
2015-08-04 00:17:15 +00:00
/* Append a / if this is a directory */
if ( is_directory )
{
return wildcard_complete ( filename + L ' / ' , wc , desc . c_str ( ) , NULL , out , expand_flags , COMPLETE_NO_SPACE ) ;
}
else
{
return wildcard_complete ( filename , wc , desc . c_str ( ) , NULL , out , expand_flags , 0 ) ;
}
2012-08-07 09:50:12 +00:00
}
2015-08-01 23:02:46 +00:00
class wildcard_expander_t
{
2015-08-04 00:17:15 +00:00
/* The original string we are expanding */
const wcstring original_base ;
/* Original wildcard we are expanding. */
const wchar_t * const original_wildcard ;
2015-08-04 06:34:56 +00:00
/* the set of items we have resolved, used to efficiently avoid duplication */
2015-08-01 23:02:46 +00:00
std : : set < wcstring > completion_set ;
/* the set of file IDs we have visited, used to avoid symlink loops */
std : : set < file_id_t > visited_files ;
/* flags controlling expansion */
const expand_flags_t flags ;
/* resolved items get inserted into here. This is transient of course. */
2015-08-04 06:34:56 +00:00
std : : vector < completion_t > * resolved_completions ;
2015-08-01 23:02:46 +00:00
/* whether we have been interrupted */
bool did_interrupt ;
/* whether we have successfully added any completions */
bool did_add ;
/* We are a trailing slash - expand at the end */
void expand_trailing_slash ( const wcstring & base_dir ) ;
/* Given a directory base_dir, which is opened as base_dir_fp, expand an intermediate segment of the wildcard.
Treat ANY_STRING_RECURSIVE as ANY_STRING .
wc_segment is the wildcard segment for this directory
wc_remainder is the wildcard for subdirectories
*/
void expand_intermediate_segment ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc_segment , const wchar_t * wc_remainder ) ;
/* Given a directory base_dir, which is opened as base_dir_fp, expand the last segment of the wildcard.
Treat ANY_STRING_RECURSIVE as ANY_STRING .
wc is the wildcard segment to use for matching
wc_remainder is the wildcard for subdirectories
*/
void expand_last_segment ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc ) ;
2015-08-03 22:58:37 +00:00
/* Indicate whether we should cancel wildcard expansion. This latches 'interrupt' */
bool interrupted ( )
{
if ( ! did_interrupt )
{
did_interrupt = ( is_main_thread ( ) ? reader_interrupted ( ) : reader_thread_job_is_stale ( ) ) ;
}
return did_interrupt ;
}
2015-08-04 06:34:56 +00:00
void add_expansion_result ( const wcstring & result )
{
/* This function is only for the non-completions case */
assert ( ! ( this - > flags & EXPAND_FOR_COMPLETIONS ) ) ;
if ( this - > completion_set . insert ( result ) . second )
{
append_completion ( this - > resolved_completions , result ) ;
this - > did_add = true ;
}
}
2015-08-04 06:58:20 +00:00
void try_add_completion_result ( const wcstring & filepath , const wcstring & filename , const wcstring & wildcard )
2015-08-04 06:34:56 +00:00
{
/* This function is only for the completions case */
assert ( this - > flags & EXPAND_FOR_COMPLETIONS ) ;
2015-08-04 06:46:41 +00:00
size_t before = this - > resolved_completions - > size ( ) ;
2015-08-04 06:58:20 +00:00
if ( wildcard_test_flags_then_complete ( filepath , filename , wildcard . c_str ( ) , this - > flags , this - > resolved_completions ) )
2015-08-04 06:34:56 +00:00
{
2015-08-04 06:46:41 +00:00
/* Hack. We added this completion result based on the last component of the wildcard.
Prepend all prior components of the wildcard to each completion that replaces its token . */
2015-08-04 06:58:20 +00:00
size_t wc_len = wildcard . size ( ) ;
size_t orig_wc_len = wcslen ( this - > original_wildcard ) ;
assert ( wc_len < = orig_wc_len ) ;
const wcstring wc_base ( this - > original_wildcard , orig_wc_len - wc_len ) ;
2015-08-04 06:46:41 +00:00
size_t after = this - > resolved_completions - > size ( ) ;
for ( size_t i = before ; i < after ; i + + )
{
completion_t & c = this - > resolved_completions - > at ( i ) ;
c . prepend_token_prefix ( wc_base ) ;
c . prepend_token_prefix ( this - > original_base ) ;
}
2015-08-04 06:34:56 +00:00
this - > did_add = true ;
}
}
2015-08-01 23:02:46 +00:00
/* Helper to resolve an empty base directory */
static DIR * open_dir ( const wcstring & base_dir )
{
return wopendir ( base_dir . empty ( ) ? L " . " : base_dir ) ;
}
public :
2015-08-04 00:17:15 +00:00
wildcard_expander_t ( const wcstring & orig_base , const wchar_t * orig_wc , expand_flags_t f , std : : vector < completion_t > * r ) :
original_base ( orig_base ) ,
original_wildcard ( orig_wc ) ,
flags ( f ) ,
2015-08-04 06:34:56 +00:00
resolved_completions ( r ) ,
2015-08-04 00:17:15 +00:00
did_interrupt ( false ) ,
did_add ( false )
2015-08-01 23:02:46 +00:00
{
2015-08-04 06:34:56 +00:00
assert ( resolved_completions ! = NULL ) ;
2015-08-01 23:02:46 +00:00
/* Insert initial completions into our set to avoid duplicates */
2015-08-04 06:34:56 +00:00
for ( std : : vector < completion_t > : : const_iterator iter = resolved_completions - > begin ( ) ; iter ! = resolved_completions - > end ( ) ; + + iter )
2015-08-01 23:02:46 +00:00
{
this - > completion_set . insert ( iter - > completion ) ;
}
}
/* Do wildcard expansion. This is recursive. */
void expand ( const wcstring & base_dir , const wchar_t * wc ) ;
2015-08-03 22:58:37 +00:00
int status_code ( ) const
2015-08-01 23:02:46 +00:00
{
2015-08-03 22:58:37 +00:00
if ( this - > did_interrupt )
2015-08-01 23:02:46 +00:00
{
2015-08-03 22:58:37 +00:00
return - 1 ;
}
else
{
return this - > did_add ? 1 : 0 ;
2015-08-01 23:02:46 +00:00
}
}
} ;
void wildcard_expander_t : : expand_trailing_slash ( const wcstring & base_dir )
{
if ( interrupted ( ) )
{
return ;
}
2015-08-03 23:36:10 +00:00
if ( ! ( flags & EXPAND_FOR_COMPLETIONS ) )
2015-08-01 23:02:46 +00:00
{
/* Trailing slash and not accepting incomplete, e.g. `echo /tmp/`. Insert this file if it exists. */
if ( waccess ( base_dir , F_OK ) )
{
2015-08-04 06:34:56 +00:00
this - > add_expansion_result ( base_dir ) ;
2015-08-01 23:02:46 +00:00
}
}
else
{
/* Trailing slashes and accepting incomplete, e.g. `echo /tmp/<tab>`. Everything is added. */
DIR * dir = open_dir ( base_dir ) ;
if ( dir )
{
wcstring next ;
while ( wreaddir ( dir , next ) & & ! interrupted ( ) )
{
if ( ! next . empty ( ) & & next . at ( 0 ) ! = L ' . ' )
{
2015-08-04 06:34:56 +00:00
this - > try_add_completion_result ( base_dir + next , next , L " " ) ;
2015-08-01 23:02:46 +00:00
}
}
closedir ( dir ) ;
}
}
}
void wildcard_expander_t : : expand_intermediate_segment ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc_segment , const wchar_t * wc_remainder )
{
wcstring name_str ;
while ( ! interrupted ( ) & & wreaddir ( base_dir_fp , name_str ) )
{
/* Note that it's critical we ignore leading dots here, else we may descend into . and .. */
if ( ! wildcard_match ( name_str , wc_segment , true ) )
{
/* Doesn't match the wildcard for this segment, skip it */
continue ;
}
wcstring full_path = base_dir + name_str ;
struct stat buf ;
if ( 0 ! = wstat ( full_path , & buf ) | | ! S_ISDIR ( buf . st_mode ) )
{
/* We either can't stat it, or we did but it's not a directory */
continue ;
}
const file_id_t file_id = file_id_t : : file_id_from_stat ( & buf ) ;
if ( ! this - > visited_files . insert ( file_id ) . second )
{
/* Symlink loop! This directory was already visited, so skip it */
continue ;
}
/* We made it through. Perform normal wildcard expansion on this new directory, starting at our tail_wc, which includes the ANY_STRING_RECURSIVE guy. */
full_path . push_back ( L ' / ' ) ;
this - > expand ( full_path , wc_remainder ) ;
}
}
void wildcard_expander_t : : expand_last_segment ( const wcstring & base_dir , DIR * base_dir_fp , const wcstring & wc )
{
wcstring name_str ;
while ( wreaddir ( base_dir_fp , name_str ) )
{
2015-08-03 23:36:10 +00:00
if ( flags & EXPAND_FOR_COMPLETIONS )
2015-08-01 23:02:46 +00:00
{
2015-08-04 06:58:20 +00:00
this - > try_add_completion_result ( base_dir + name_str , name_str , wc ) ;
2015-08-01 23:02:46 +00:00
}
else
{
2015-08-03 00:58:37 +00:00
// Normal wildcard expansion, not for completions
2015-08-01 23:02:46 +00:00
if ( wildcard_match ( name_str , wc , true /* skip files with leading dots */ ) )
{
2015-08-04 06:34:56 +00:00
this - > add_expansion_result ( base_dir + name_str ) ;
2015-08-01 23:02:46 +00:00
}
}
}
}
/**
The real implementation of wildcard expansion is in this
function . Other functions are just wrappers around this one .
This function traverses the relevant directory tree looking for
matches , and recurses when needed to handle wildcrards spanning
multiple components and recursive wildcards .
Because this function calls itself recursively with substrings ,
it ' s important that the parameters be raw pointers instead of wcstring ,
which would be too expensive to construct for all substrings .
Args :
base_dir : the " working directory " against which the wildcard is to be resolved
wc : the wildcard string itself , e . g . foo * bar / baz ( where * is acutally ANY_CHAR )
*/
void wildcard_expander_t : : expand ( const wcstring & base_dir , const wchar_t * wc )
{
assert ( wc ! = NULL ) ;
if ( interrupted ( ) )
{
return ;
}
/* Get the current segment and compute interesting properties about it. */
const size_t wc_len = wcslen ( wc ) ;
const wchar_t * const next_slash = wcschr ( wc , L ' / ' ) ;
const bool is_last_segment = ( next_slash = = NULL ) ;
const size_t wc_segment_len = next_slash ? next_slash - wc : wc_len ;
const wcstring wc_segment = wcstring ( wc , wc_segment_len ) ;
const bool segment_has_wildcards = wildcard_has ( wc_segment , true /* internal, i.e. look for ANY_CHAR instead of ? */ ) ;
if ( wc_segment . empty ( ) )
{
/* Handle empty segment */
assert ( ! segment_has_wildcards ) ;
if ( is_last_segment )
{
this - > expand_trailing_slash ( base_dir ) ;
}
else
{
/* Multiple adjacent slashes in the wildcard. Just skip them. */
this - > expand ( base_dir , next_slash + 1 ) ;
}
}
else if ( ! segment_has_wildcards & & ! is_last_segment )
{
/* Literal intermediate match. Note that we may not be able to actually read the directory (#2099) */
assert ( next_slash ! = NULL ) ;
2015-08-04 07:32:34 +00:00
const wchar_t * wc_remainder = next_slash ;
while ( * wc_remainder = = L ' / ' )
{
wc_remainder + + ;
}
2015-08-01 23:02:46 +00:00
/* This just trumps everything */
2015-08-04 07:32:34 +00:00
size_t before = this - > resolved_completions - > size ( ) ;
this - > expand ( base_dir + wc_segment + L ' / ' , wc_remainder ) ;
if ( this - > resolved_completions - > size ( ) = = before )
{
/* Nothing was found with the literal match. Try a fuzzy match (#94). */
DIR * base_dir_fd = open_dir ( base_dir ) ;
if ( base_dir_fd ! = NULL )
{
this - > expand_intermediate_segment ( base_dir , base_dir_fd , wc_segment , wc_remainder ) ;
closedir ( base_dir_fd ) ;
}
}
2015-08-01 23:02:46 +00:00
}
else
{
assert ( ! wc_segment . empty ( ) & & ( segment_has_wildcards | | is_last_segment ) ) ;
DIR * dir = open_dir ( base_dir ) ;
if ( dir )
{
if ( is_last_segment )
{
/* Last wildcard segment, nonempty wildcard */
this - > expand_last_segment ( base_dir , dir , wc_segment ) ;
}
else
{
/* Not the last segment, nonempty wildcard */
assert ( next_slash ! = NULL ) ;
const wchar_t * wc_remainder = next_slash ;
while ( * wc_remainder = = L ' / ' )
{
wc_remainder + + ;
}
this - > expand_intermediate_segment ( base_dir , dir , wc_segment , wc_remainder ) ;
}
/* Recursive wildcards require special handling */
size_t asr_idx = wc_segment . find ( ANY_STRING_RECURSIVE ) ;
if ( asr_idx ! = wcstring : : npos )
{
2015-08-03 00:34:05 +00:00
/* Construct a "head + any" wildcard for matching stuff in this directory, and an "any + tail" wildcard for matching stuff in subdirectories. Note that the ANY_STRING_RECURSIVE character is present in both the head and the tail. */
const wcstring head_any ( wc_segment , 0 , asr_idx + 1 ) ;
const wchar_t * any_tail = wc + asr_idx ;
assert ( head_any . at ( head_any . size ( ) - 1 ) = = ANY_STRING_RECURSIVE ) ;
assert ( any_tail [ 0 ] = = ANY_STRING_RECURSIVE ) ;
2015-08-01 23:02:46 +00:00
rewinddir ( dir ) ;
2015-08-03 00:34:05 +00:00
this - > expand_intermediate_segment ( base_dir , dir , head_any , any_tail ) ;
2015-08-01 23:02:46 +00:00
}
closedir ( dir ) ;
}
}
}
2007-03-24 19:07:38 +00:00
2015-07-28 01:45:47 +00:00
static int wildcard_expand ( const wchar_t * wc ,
2015-08-01 23:52:22 +00:00
const wcstring & base_dir ,
2015-07-28 01:45:47 +00:00
expand_flags_t flags ,
std : : vector < completion_t > * out )
2007-03-24 19:07:38 +00:00
{
2015-08-04 07:32:34 +00:00
assert ( out ! = NULL ) ;
2015-08-04 00:17:15 +00:00
wildcard_expander_t expander ( base_dir , wc , flags , out ) ;
2015-08-01 23:52:22 +00:00
expander . expand ( base_dir , wc ) ;
2015-08-03 22:58:37 +00:00
return expander . status_code ( ) ;
2007-03-24 19:07:38 +00:00
}
2011-12-27 03:18:46 +00:00
2015-07-28 01:45:47 +00:00
int wildcard_expand_string ( const wcstring & wc , const wcstring & base_dir , expand_flags_t flags , std : : vector < completion_t > * output )
2011-12-27 03:18:46 +00:00
{
2015-07-28 01:45:47 +00:00
assert ( output ! = NULL ) ;
2015-08-01 23:02:46 +00:00
/* Hackish fix for 1631. We are about to call c_str(), which will produce a string truncated at any embedded nulls. We could fix this by passing around the size, etc. However embedded nulls are never allowed in a filename, so we just check for them and return 0 (no matches) if there is an embedded null. */
2014-08-24 21:28:31 +00:00
if ( wc . find ( L ' \0 ' ) ! = wcstring : : npos )
{
return 0 ;
}
2015-08-01 23:52:22 +00:00
return wildcard_expand ( wc . c_str ( ) , base_dir , flags , output ) ;
2011-12-27 03:18:46 +00:00
}