fish-shell/wildcard.c

693 lines
14 KiB
C
Raw Normal View History

/** \file wildcard.c
Fish needs it's own globbing implementation to support
tab-expansion of globbed parameters. Also provides recursive
wildcards using **.
*/
#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"
#include "translate.h"
/**
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
/**
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
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.
\param is_first Whether files beginning with dots should not be matched against wildcards.
\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'.' )
{
return 0;
}
/* Try all submatches */
do
{
if( wildcard_match2( str, wc+1, 0 ) )
return 1;
}
while( *(str++) != 0 );
return 0;
}
if( *wc == ANY_CHAR )
{
if( is_first && *str == L'.' )
{
return 0;
}
return wildcard_match2( str+1, wc+1, 0 );
}
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 ), (void *)0);
}
else
{
/*
Append generic description to item, if the description exists
*/
if( desc && wcslen(desc) )
{
/*
Check if the description already contains a separator character, if not, prepend it
*/
if( wcschr( desc, COMPLETE_SEP ) )
new = wcsdupcat2( str, desc, (void *)0 );
else
new = wcsdupcat2( str, COMPLETE_SEP_STR, desc, (void *)0 );
}
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) )))
{
die_mem();
}
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) )
{
sb_append2( sb, desc, (void *)0 );
}
else
{
sb_append2( sb, desc, L", ", (void *)0 );
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;
}
}
}
}
/*
Test if the file specified by the given filename matches the
expansion flags specified. flags can be a combination of
EXECUTABLES_ONLY and DIRECTORIES_ONLY.
*/
static int test_flags( wchar_t *filename,
int flags )
{
if( !(flags & EXECUTABLES_ONLY) && !(flags & DIRECTORIES_ONLY) )
return 1;
struct stat buf;
if( wstat( filename, &buf ) == -1 )
{
return 1;
}
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 )
{
/* 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;
// debug( 3, L"WILDCARD_EXPAND %ls in %ls", wc, base_dir );
if( flags & ACCEPT_INCOMPLETE )
{
/*
Avoid excessive number of returned matches for wc ending with a *
*/
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;
}
}
/*
Initialize various variables
*/
dir_string = base_dir[0]==L'\0'?L".":base_dir;
if( !(dir = wopendir( dir_string )))
{
return 0;
}
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));
/*
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 );
}
if( flags & ACCEPT_INCOMPLETE )
sb_init( &sb_desc );
/*
Is this segment of the wildcard the last?
*/
if( !wc_end && !is_recursive )
{
/*
Wildcard segment is the last segment,
Insert all matching files/directories
*/
if( wc[0]=='\0' )
{
/*
The last wildcard segment is empty. Insert everything if
completing, the directory itself otherwise.
*/
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 );
al_push( out,
wcsdupcat(name, (wchar_t *)sb_desc.buff) );
}
free(name);
free( long_name );
}
}
}
else
{
res = 1;
al_push_check( out, wcsdup( base_dir ) );
}
}
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 );
/*
Test for matches before stating file, so as to minimize the number of calls to the much slower stat function
*/
if( wildcard_complete( name,
wc,
L"",
0,
0 ) )
{
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 );
al_push_check( out, long_name );
res = 1;
}
}
free( name );
}
}
}
else
{
/*
Wilcard segment is not the last segment. Recursively call
wildcard_expand for all matching subdirectories.
*/
/*
wc_str is the part of the wildcarded string from the
beginning to the first slash
*/
wchar_t *wc_str;
/*
new_dir is a scratch area containing the full path to a
file/directory we are iterating over
*/
wchar_t *new_dir;
/*
The maximum length of a file element
*/
static size_t ln=MAX_FILE_LENGTH;
char * narrow_dir_string = wcs2str( dir_string );
if( narrow_dir_string )
{
/*
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
*/
if( ln < 0 )
ln = MAX_FILE_LENGTH;
free( narrow_dir_string );
}
new_dir= malloc( sizeof(wchar_t)*(base_len+ln+2) );
wc_str = wc_end?wcsndup(wc, wc_end-wc):wcsdup(wc);
if( (!new_dir) || (!wc_str) )
{
die_mem();
}
wcscpy( new_dir, base_dir );
while( (next=readdir(dir))!=0 )
{
wchar_t *name = str2wcs(next->d_name);
if( name == 0 )
{
continue;
}
/*
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;
/*
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 )
{
int new_len;
struct stat buf;
char *dir_str;
int stat_res;
wcscpy(&new_dir[base_len], name );
dir_str = wcs2str( new_dir );
if( dir_str )
{
stat_res = stat( dir_str, &buf );
free( dir_str );
if( !stat_res )
{
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 );
}
/*
Recursive matching
*/
if( partial_match )
{
res |= wildcard_expand( wcschr( wc, ANY_STRING_RECURSIVE ),
new_dir,
flags | WILDCARD_RECURSIVE,
out );
}
}
}
}
}
free(name);
}
free( wc_str );
free( new_dir );
}
closedir( dir );
if( flags & ACCEPT_INCOMPLETE )
sb_destroy( &sb_desc );
return res;
}
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 ) )
{
free( (void *)new );
return;
}
}
al_push( l, new );
}