2005-09-20 23:26:39 +10:00
/** \file wildcard.c
2005-11-30 00:33:52 +10:00
Fish needs it ' s own globbing implementation to support
tab - expansion of globbed parameters . Also provides recursive
wildcards using * * .
2005-09-20 23:26:39 +10:00
*/
# include "config.h"
# include <stdlib.h>
# include <stdio.h>
# include <limits.h>
# include <wchar.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <dirent.h>
# include <errno.h>
# include "util.h"
# include "wutil.h"
# include "complete.h"
# include "common.h"
# include "wildcard.h"
# include "complete.h"
# include "reader.h"
# include "expand.h"
2005-12-01 01:33:03 +10:00
/**
This flag is set in the flags parameter of wildcard_expand if the
call is part of a recursiv wildcard search . It is used to make sure
that the contents of subdirectories are only searched once .
*/
# define WILDCARD_RECURSIVE 64
2005-11-30 02:52:02 +10:00
/**
The maximum length of a filename token . This is a fallback value ,
an attempt to find the true value using patchconf is always made .
*/
# define MAX_FILE_LENGTH 1024
2005-09-20 23:26:39 +10:00
int wildcard_has ( const wchar_t * str , int internal )
{
wchar_t prev = 0 ;
if ( internal )
{
for ( ; * str ; str + + )
{
if ( ( * str = = ANY_CHAR ) | | ( * str = = ANY_STRING ) | | ( * str = = ANY_STRING_RECURSIVE ) )
return 1 ;
prev = * str ;
}
}
else
{
for ( ; * str ; str + + )
{
if ( ( ( * str = = L ' * ' ) | | ( * str = = L ' ? ' ) ) & & ( prev ! = L ' \\ ' ) )
return 1 ;
prev = * str ;
}
}
return 0 ;
}
/**
Check whether the string str matches the wildcard string wc .
\ param str String to be matched .
\ param wc The wildcard .
2005-12-08 02:06:47 +10:00
\ param is_first Whether files beginning with dots should not be matched against wildcards .
2005-09-20 23:26:39 +10:00
\ param wc_unescaped Whether the unescaped special character ANY_CHAR abd ANY_STRING should be used instead of ' ? ' and ' * ' for wildcard matching
*/
static int wildcard_match2 ( const wchar_t * str ,
const wchar_t * wc ,
int is_first )
{
if ( * str = = 0 & & * wc = = 0 )
return 1 ;
if ( * wc = = ANY_STRING | | * wc = = ANY_STRING_RECURSIVE )
{
/* Ignore hidden file */
if ( is_first & & * str = = L ' . ' )
2005-12-01 01:33:03 +10:00
{
2005-09-20 23:26:39 +10:00
return 0 ;
2005-12-01 01:33:03 +10:00
}
2005-09-20 23:26:39 +10:00
/* Try all submatches */
do
{
if ( wildcard_match2 ( str , wc + 1 , 0 ) )
return 1 ;
}
while ( * ( str + + ) ! = 0 ) ;
return 0 ;
}
2005-12-01 01:33:03 +10:00
2005-09-20 23:26:39 +10:00
if ( * wc = = ANY_CHAR )
2005-12-01 01:33:03 +10:00
{
if ( is_first & & * str = = L ' . ' )
{
return 0 ;
}
2005-09-20 23:26:39 +10:00
return wildcard_match2 ( str + 1 , wc + 1 , 0 ) ;
2005-12-01 01:33:03 +10:00
}
2005-09-20 23:26:39 +10:00
if ( * wc = = * str )
return wildcard_match2 ( str + 1 , wc + 1 , 0 ) ;
return 0 ;
}
/**
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 array_list_t .
*/
static int wildcard_complete_internal ( const wchar_t * orig ,
const wchar_t * str ,
const wchar_t * wc ,
int is_first ,
const wchar_t * desc ,
const wchar_t * ( * desc_func ) ( const wchar_t * ) ,
array_list_t * out )
{
if ( * wc = = 0 & &
( ( * str ! = L ' . ' ) | | ( ! is_first ) ) )
{
if ( ! out )
return 1 ;
wchar_t * new ;
if ( wcschr ( str , PROG_COMPLETE_SEP ) )
{
/*
This completion has an embedded description , du not use the generic description
*/
wchar_t * sep ;
new = wcsdup ( str ) ;
sep = wcschr ( new , PROG_COMPLETE_SEP ) ;
* sep = COMPLETE_SEP ;
}
else if ( desc_func )
{
/*
A descripton generating function is specified , use it
*/
new = wcsdupcat2 ( str , COMPLETE_SEP_STR , desc_func ( orig ) , 0 ) ;
}
else
{
/*
Append generic description to item , if the description exists
*/
if ( desc & & wcslen ( desc ) > 1 )
new = wcsdupcat ( str , desc ) ;
else
new = wcsdup ( str ) ;
}
if ( new )
{
al_push ( out , new ) ;
}
return 1 ;
}
if ( * wc = = ANY_STRING )
{
int res = 0 ;
/* Ignore hidden file */
if ( is_first & & str [ 0 ] = = L ' . ' )
return 0 ;
/* Try all submatches */
do
{
res | = wildcard_complete_internal ( orig , str , wc + 1 , 0 , desc , desc_func , out ) ;
if ( res & & ! out )
break ;
}
while ( * str + + ! = 0 ) ;
return res ;
}
else if ( * wc = = ANY_CHAR )
{
return wildcard_complete_internal ( orig , str + 1 , wc + 1 , 0 , desc , desc_func , out ) ;
}
else if ( * wc = = * str )
{
return wildcard_complete_internal ( orig , str + 1 , wc + 1 , 0 , desc , desc_func , out ) ;
}
return 0 ;
}
int wildcard_complete ( const wchar_t * str ,
const wchar_t * wc ,
const wchar_t * desc ,
const wchar_t * ( * desc_func ) ( const wchar_t * ) ,
array_list_t * out )
{
return wildcard_complete_internal ( str , str , wc , 1 , desc , desc_func , out ) ;
}
int wildcard_match ( const wchar_t * str , const wchar_t * wc )
{
return wildcard_match2 ( str , wc , 1 ) ;
}
/**
Creates a path from the specified directory and filename .
*/
static wchar_t * make_path ( const wchar_t * base_dir , const wchar_t * name )
{
wchar_t * long_name ;
int base_len = wcslen ( base_dir ) ;
if ( ! ( long_name = malloc ( sizeof ( wchar_t ) * ( base_len + wcslen ( name ) + 1 ) ) ) )
{
2005-11-30 00:33:52 +10:00
die_mem ( ) ;
2005-09-20 23:26:39 +10:00
}
wcscpy ( long_name , base_dir ) ;
wcscpy ( & long_name [ base_len ] , name ) ;
return long_name ;
}
void get_desc ( wchar_t * fn , string_buffer_t * sb , int is_cmd )
{
const wchar_t * desc ;
struct stat buf ;
off_t sz ;
wchar_t * sz_name [ ] =
{
L " kB " , L " MB " , L " GB " , L " TB " , L " PB " , L " EB " , L " ZB " , L " YB " , 0
}
;
sb_clear ( sb ) ;
if ( wstat ( fn , & buf ) )
{
sz = - 1 ;
}
else
{
sz = buf . st_size ;
}
desc = complete_get_desc ( fn ) ;
if ( sz > = 0 & & S_ISDIR ( buf . st_mode ) )
{
2005-10-01 04:28:26 +10:00
sb_append2 ( sb , desc , ( void * ) 0 ) ;
2005-09-20 23:26:39 +10:00
}
else
{
2005-10-01 04:28:26 +10:00
sb_append2 ( sb , desc , L " , " , ( void * ) 0 ) ;
2005-09-20 23:26:39 +10:00
if ( sz < 0 )
{
sb_append ( sb , L " unknown " ) ;
}
else if ( sz < 1 )
{
sb_append ( sb , L " empty " ) ;
}
else if ( sz < 1024 )
{
sb_printf ( sb , L " %dB " , sz ) ;
}
else
{
int i ;
for ( i = 0 ; sz_name [ i ] ; i + + )
{
if ( sz < ( 1024 * 1024 ) | | ! sz_name [ i + 1 ] )
{
int isz = sz / 1024 ;
if ( isz > 9 )
sb_printf ( sb , L " %d%ls " , isz , sz_name [ i ] ) ;
else
sb_printf ( sb , L " %.1f%ls " , ( double ) sz / 1024 , sz_name [ i ] ) ;
break ;
}
sz / = 1024 ;
}
}
}
}
2005-11-30 00:33:52 +10:00
/*
Test if the file specified by the given filename matches the
2005-12-08 01:57:17 +10:00
expansion flags specified . flags can be a combination of
2005-11-30 00:33:52 +10:00
EXECUTABLES_ONLY and DIRECTORIES_ONLY .
*/
2005-09-20 23:26:39 +10:00
static int test_flags ( wchar_t * filename ,
int flags )
{
if ( ! ( flags & EXECUTABLES_ONLY ) & & ! ( flags & DIRECTORIES_ONLY ) )
return 1 ;
struct stat buf ;
2005-12-26 08:00:44 +10:00
if ( wstat ( filename , & buf ) = = - 1 )
{
return 1 ;
}
2005-09-20 23:26:39 +10:00
if ( S_IFDIR & buf . st_mode )
return 1 ;
if ( flags & EXECUTABLES_ONLY )
return ( waccess ( filename , X_OK ) = = 0 ) ;
return 0 ;
}
int wildcard_expand ( const wchar_t * wc ,
const wchar_t * base_dir ,
int flags ,
array_list_t * out )
{
2005-11-30 02:52:02 +10:00
2005-11-30 00:33:52 +10:00
/* Points to the end of the current wildcard segment */
wchar_t * wc_end ;
/* Variables for traversing a directory */
struct dirent * next ;
DIR * dir ;
/* The result returned */
int res = 0 ;
/* Length of the directory to search in */
int base_len ;
/* Variables for testing for presense of recursive wildcards */
wchar_t * wc_recursive ;
int is_recursive ;
/* Sligtly mangled version of base_dir */
const wchar_t * dir_string ;
/* Description for completions */
string_buffer_t sb_desc ;
2005-12-08 02:06:47 +10:00
// debug( 3, L"WILDCARD_EXPAND %ls in %ls", wc, base_dir );
2005-09-20 23:26:39 +10:00
if ( flags & ACCEPT_INCOMPLETE )
{
2005-11-30 00:33:52 +10:00
/*
Avoid excessive number of returned matches for wc ending with a *
*/
2005-09-20 23:26:39 +10:00
int len = wcslen ( wc ) ;
if ( len & & ( wc [ len - 1 ] = = ANY_STRING ) )
{
wchar_t * foo = wcsdup ( wc ) ;
foo [ len - 1 ] = 0 ;
int res = wildcard_expand ( foo , base_dir , flags , out ) ;
free ( foo ) ;
return res ;
}
}
2005-11-30 00:33:52 +10:00
/*
Initialize various variables
*/
2005-09-20 23:26:39 +10:00
2005-11-30 00:33:52 +10:00
dir_string = base_dir [ 0 ] = = L ' \0 ' ? L " . " : base_dir ;
2005-09-20 23:26:39 +10:00
if ( ! ( dir = wopendir ( dir_string ) ) )
{
return 0 ;
}
2005-11-30 00:33:52 +10:00
wc_end = wcschr ( wc , L ' / ' ) ;
base_len = wcslen ( base_dir ) ;
/*
Test for recursive match string in current segment
*/
wc_recursive = wcschr ( wc , ANY_STRING_RECURSIVE ) ;
is_recursive = ( wc_recursive & & ( ! wc_end | | wc_recursive < wc_end ) ) ;
2005-12-01 01:33:03 +10:00
/*
This makes sure that the base
directory of the recursive search is
also searched for matching files .
*/
if ( is_recursive & & ( wc_end = = ( wc + 1 ) ) & & ! ( flags & WILDCARD_RECURSIVE ) )
{
wildcard_expand ( wc_end + 1 ,
base_dir ,
flags ,
out ) ;
}
2005-11-30 00:33:52 +10:00
if ( flags & ACCEPT_INCOMPLETE )
sb_init ( & sb_desc ) ;
/*
Is this segment of the wildcard the last ?
*/
if ( ! wc_end & & ! is_recursive )
2005-09-20 23:26:39 +10:00
{
/*
2005-11-30 00:33:52 +10:00
Wildcard segment is the last segment ,
2005-09-20 23:26:39 +10:00
Insert all matching files / directories
*/
if ( wc [ 0 ] = = ' \0 ' )
{
/*
2005-11-30 00:33:52 +10:00
The last wildcard segment is empty . Insert everything if
completing , the directory itself otherwise .
2005-09-20 23:26:39 +10:00
*/
if ( flags & ACCEPT_INCOMPLETE )
{
while ( ( next = readdir ( dir ) ) ! = 0 )
{
if ( next - > d_name [ 0 ] ! = ' . ' )
{
wchar_t * name = str2wcs ( next - > d_name ) ;
if ( name = = 0 )
{
continue ;
}
wchar_t * long_name = make_path ( base_dir , name ) ;
if ( test_flags ( long_name , flags ) )
{
get_desc ( long_name ,
& sb_desc ,
flags & EXECUTABLES_ONLY ) ;
2005-11-30 00:33:52 +10:00
al_push ( out ,
2005-09-20 23:26:39 +10:00
wcsdupcat ( name , ( wchar_t * ) sb_desc . buff ) ) ;
}
free ( name ) ;
free ( long_name ) ;
}
}
}
else
{
res = 1 ;
2005-12-01 01:33:03 +10:00
al_push_check ( out , wcsdup ( base_dir ) ) ;
2005-09-20 23:26:39 +10:00
}
}
else
{
/*
This is the last wildcard segment , and it is not empty . Match files / directories .
*/
while ( ( next = readdir ( dir ) ) ! = 0 )
{
wchar_t * name = str2wcs ( next - > d_name ) ;
if ( name = = 0 )
{
continue ;
}
if ( flags & ACCEPT_INCOMPLETE )
{
wchar_t * long_name = make_path ( base_dir , name ) ;
2005-11-30 00:33:52 +10:00
2005-09-20 23:26:39 +10:00
/*
2005-11-30 00:33:52 +10:00
Test for matches before stating file , so as to minimize the number of calls to the much slower stat function
2005-09-20 23:26:39 +10:00
*/
if ( wildcard_complete ( name ,
wc ,
L " " ,
0 ,
0 ) )
2005-12-01 01:33:03 +10:00
{
2005-09-20 23:26:39 +10:00
if ( test_flags ( long_name , flags ) )
{
get_desc ( long_name ,
& sb_desc ,
flags & EXECUTABLES_ONLY ) ;
wildcard_complete ( name ,
wc ,
( wchar_t * ) sb_desc . buff ,
0 ,
out ) ;
}
}
free ( long_name ) ;
}
else
{
if ( wildcard_match2 ( name , wc , 1 ) )
{
wchar_t * long_name = make_path ( base_dir , name ) ;
2005-12-01 01:33:03 +10:00
al_push_check ( out , long_name ) ;
2005-09-20 23:26:39 +10:00
res = 1 ;
}
}
free ( name ) ;
}
}
}
else
{
/*
2005-12-01 01:33:03 +10:00
Wilcard segment is not the last segment . Recursively call
wildcard_expand for all matching subdirectories .
2005-09-20 23:26:39 +10:00
*/
2005-11-30 02:52:02 +10:00
/*
wc_str is the part of the wildcarded string from the
beginning to the first slash
*/
2005-09-20 23:26:39 +10:00
wchar_t * wc_str ;
2005-11-30 02:52:02 +10:00
/*
2005-12-01 01:33:03 +10:00
new_dir is a scratch area containing the full path to a
file / directory we are iterating over
2005-11-30 02:52:02 +10:00
*/
2005-09-20 23:26:39 +10:00
wchar_t * new_dir ;
2005-11-30 02:52:02 +10:00
/*
The maximum length of a file element
*/
static size_t ln = MAX_FILE_LENGTH ;
2005-09-20 23:26:39 +10:00
char * narrow_dir_string = wcs2str ( dir_string ) ;
if ( narrow_dir_string )
{
2005-11-30 02:52:02 +10:00
/*
Find out how long the filename can be in a worst case
scenario
*/
ln = pathconf ( narrow_dir_string , _PC_NAME_MAX ) ;
/*
If not specified , use som large number as fallback
*/
2005-09-20 23:26:39 +10:00
if ( ln < 0 )
2005-11-30 02:52:02 +10:00
ln = MAX_FILE_LENGTH ;
2005-09-20 23:26:39 +10:00
free ( narrow_dir_string ) ;
}
new_dir = malloc ( sizeof ( wchar_t ) * ( base_len + ln + 2 ) ) ;
2005-11-30 00:33:52 +10:00
wc_str = wc_end ? wcsndup ( wc , wc_end - wc ) : wcsdup ( wc ) ;
2005-11-30 02:52:02 +10:00
2005-09-20 23:26:39 +10:00
if ( ( ! new_dir ) | | ( ! wc_str ) )
{
2005-11-30 00:33:52 +10:00
die_mem ( ) ;
2005-09-20 23:26:39 +10:00
}
2005-11-24 01:35:03 +10:00
2005-11-30 00:33:52 +10:00
wcscpy ( new_dir , base_dir ) ;
2005-09-20 23:26:39 +10:00
while ( ( next = readdir ( dir ) ) ! = 0 )
{
wchar_t * name = str2wcs ( next - > d_name ) ;
if ( name = = 0 )
{
continue ;
}
2005-11-30 00:33:52 +10:00
/*
Test if the file / directory name matches the whole
wildcard element , i . e . regular matching .
*/
int whole_match = wildcard_match2 ( name , wc_str , 1 ) ;
int partial_match = 0 ;
2005-09-20 23:26:39 +10:00
2005-11-30 00:33:52 +10:00
/*
If we are doing recursive matching , also check if this
directory matches the part up to the recusrive
wildcard , if so , then we can search all subdirectories
for matches .
*/
if ( is_recursive )
{
wchar_t * end = wcschr ( wc , ANY_STRING_RECURSIVE ) ;
wchar_t * wc_sub = wcsndup ( wc , end - wc + 1 ) ;
partial_match = wildcard_match2 ( name , wc_sub , 1 ) ;
free ( wc_sub ) ;
}
if ( whole_match | | partial_match )
2005-09-20 23:26:39 +10:00
{
int new_len ;
struct stat buf ;
2005-11-30 00:33:52 +10:00
char * dir_str ;
2005-09-20 23:26:39 +10:00
int stat_res ;
2005-11-30 00:33:52 +10:00
wcscpy ( & new_dir [ base_len ] , name ) ;
dir_str = wcs2str ( new_dir ) ;
if ( dir_str )
2005-09-20 23:26:39 +10:00
{
2005-12-01 01:33:03 +10:00
stat_res = stat ( dir_str , & buf ) ;
2005-11-30 00:33:52 +10:00
free ( dir_str ) ;
2005-11-24 01:35:03 +10:00
2005-11-30 00:33:52 +10:00
if ( ! stat_res )
2005-09-20 23:26:39 +10:00
{
2005-11-30 00:33:52 +10:00
if ( buf . st_mode & S_IFDIR )
{
new_len = wcslen ( new_dir ) ;
new_dir [ new_len ] = L ' / ' ;
new_dir [ new_len + 1 ] = L ' \0 ' ;
/*
Regular matching
*/
if ( whole_match )
{
res | = wildcard_expand ( wc_end ? wc_end + 1 : L " " ,
new_dir ,
flags ,
out ) ;
2005-12-01 01:33:03 +10:00
2005-11-30 00:33:52 +10:00
}
/*
Recursive matching
*/
if ( partial_match )
{
res | = wildcard_expand ( wcschr ( wc , ANY_STRING_RECURSIVE ) ,
new_dir ,
2005-12-01 01:33:03 +10:00
flags | WILDCARD_RECURSIVE ,
2005-11-30 00:33:52 +10:00
out ) ;
}
}
2005-09-20 23:26:39 +10:00
}
2005-11-30 00:33:52 +10:00
}
2005-09-20 23:26:39 +10:00
}
2005-11-30 00:33:52 +10:00
free ( name ) ;
2005-09-20 23:26:39 +10:00
}
2005-11-30 00:33:52 +10:00
2005-09-20 23:26:39 +10:00
free ( wc_str ) ;
free ( new_dir ) ;
}
closedir ( dir ) ;
2005-11-30 00:33:52 +10:00
if ( flags & ACCEPT_INCOMPLETE )
sb_destroy ( & sb_desc ) ;
2005-09-20 23:26:39 +10:00
return res ;
}
2005-12-01 01:33:03 +10:00
void al_push_check ( array_list_t * l , const wchar_t * new )
{
int i ;
for ( i = 0 ; i < al_get_count ( l ) ; i + + )
{
if ( ! wcscmp ( al_get ( l , i ) , new ) )
{
2005-12-10 10:36:07 +10:00
free ( ( void * ) new ) ;
2005-12-01 01:33:03 +10:00
return ;
}
}
al_push ( l , new ) ;
}