diff options
author | ridiculousfish <corydoras@ridiculousfish.com> | 2011-12-26 19:11:54 -0800 |
---|---|---|
committer | ridiculousfish <corydoras@ridiculousfish.com> | 2011-12-26 19:11:54 -0800 |
commit | 3f16ace6784caab54fb054836ee93902e9701913 (patch) | |
tree | 6ae6170f86bd45ce7fd0dae4a4242bb8dc67c505 /wildcard.cpp | |
parent | 834ea94eb97d37c65fcbf2fcc3b69303f6fb7e24 (diff) |
Initial C++ conversion
Diffstat (limited to 'wildcard.cpp')
-rw-r--r-- | wildcard.cpp | 1202 |
1 files changed, 1202 insertions, 0 deletions
diff --git a/wildcard.cpp b/wildcard.cpp new file mode 100644 index 00000000..49f66d81 --- /dev/null +++ b/wildcard.cpp @@ -0,0 +1,1202 @@ +/** \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 <string.h> + + +#include "fallback.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 "exec.h" +#include "halloc_util.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 + +/** + The command to run to get a description from a file suffix +*/ +#define SUFFIX_CMD_STR L"mimedb 2>/dev/null -fd " + +/** + 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" ) + +/** Hashtable containing all descriptions that describe an executable */ +static hash_table_t *suffix_hash=0; + +/** + Push the specified argument to the list if an identical string is + not already in the list. This function iterates over the list, + which is quite slow if the list is large. It might make sense to + use a hashtable for this. +*/ +static 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 ); +} + + +/** + Free hash key and hash value +*/ +static void clear_hash_entry( void *key, void *data ) +{ + free( (void *)key ); + free( (void *)data ); +} + +int wildcard_has( const wchar_t *str, int internal ) +{ + wchar_t prev=0; + if( !str ) + { + debug( 2, L"Got null string on line %d of file %s", __LINE__, __FILE__ ); + return 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. +*/ +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; + } + else if( *str == 0 ) + { + /* + End of string, but not end of wildcard, and the next wildcard + element is not a '*', so this is not a match. + */ + 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, + int flags ) +{ + if( !wc || !str || !orig) + { + debug( 2, L"Got null string on line %d of file %s", __LINE__, __FILE__ ); + return 0; + } + + if( *wc == 0 && + ( ( *str != L'.') || (!is_first)) ) + { + wchar_t *out_completion = 0; + const wchar_t *out_desc = desc; + + if( !out ) + { + return 1; + } + + if( flags & COMPLETE_NO_CASE ) + { + out_completion = wcsdup( orig ); + } + else + { + out_completion = wcsdup( str ); + } + + if( wcschr( str, PROG_COMPLETE_SEP ) ) + { + /* + This completion has an embedded description, du not use the generic description + */ + wchar_t *sep; + + sep = wcschr(out_completion, PROG_COMPLETE_SEP ); + *sep = 0; + out_desc = sep + 1; + + } + else + { + if( desc_func ) + { + /* + A descripton generating function is specified, call + it. If it returns something, use that as the + description. + */ + const wchar_t *func_desc = desc_func( orig ); + if( func_desc ) + out_desc = func_desc; + } + + } + + if( out_completion ) + { + completion_allocate( out, + out_completion, + out_desc, + flags ); + } + + free ( out_completion ); + + 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, flags ); + 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, flags ); + } + else if( *wc == *str ) + { + return wildcard_complete_internal( orig, str+1, wc+1, 0, desc, desc_func, out, flags ); + } + else if( towlower(*wc) == towlower(*str) ) + { + return wildcard_complete_internal( orig, str+1, wc+1, 0, desc, desc_func, out, flags | COMPLETE_NO_CASE ); + } + 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, + int flags ) +{ + int res; + + res = wildcard_complete_internal( str, str, wc, 1, desc, desc_func, out, flags ); + + return res; +} + + +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; +} + +/** + Return a description of a file based on its suffix. This function + does not perform any caching, it directly calls the mimedb command + to do a lookup. + */ +static wchar_t *complete_get_desc_suffix_internal( const wchar_t *suff_orig ) +{ + + wchar_t *suff = wcsdup( suff_orig ); + wchar_t *cmd = wcsdupcat( SUFFIX_CMD_STR, suff ); + wchar_t *desc = 0; + array_list_t l; + + if( !suff || !cmd ) + DIE_MEM(); + + al_init( &l ); + + if( exec_subshell( cmd, &l ) != -1 ) + { + + if( al_get_count( &l )>0 ) + { + wchar_t *ln = (wchar_t *)al_get(&l, 0 ); + if( wcscmp( ln, L"unknown" ) != 0 ) + { + desc = wcsdup( ln); + /* + I have decided I prefer to have the description + begin in uppercase and the whole universe will just + have to accept it. Hah! + */ + desc[0]=towupper(desc[0]); + } + } + } + + free(cmd); + al_foreach( &l, &free ); + al_destroy( &l ); + + if( !desc ) + { + desc = wcsdup(COMPLETE_FILE_DESC); + } + + hash_put( suffix_hash, suff, desc ); + + return desc; +} + +/** + Free the suffix_hash hash table and all memory used by it. + */ +static void complete_get_desc_destroy_suffix_hash() +{ + if( suffix_hash ) + { + hash_foreach( suffix_hash, &clear_hash_entry ); + hash_destroy( suffix_hash ); + free( suffix_hash ); + } +} + + + + +/** + Use the mimedb command to look up a description for a given suffix +*/ +static const wchar_t *complete_get_desc_suffix( const wchar_t *suff_orig ) +{ + + int len; + wchar_t *suff; + wchar_t *pos; + wchar_t *tmp; + wchar_t *desc; + + len = wcslen(suff_orig ); + + if( len == 0 ) + return COMPLETE_FILE_DESC; + + if( !suffix_hash ) + { + suffix_hash = malloc( sizeof( hash_table_t) ); + if( !suffix_hash ) + DIE_MEM(); + hash_init( suffix_hash, &hash_wcs_func, &hash_wcs_cmp ); + halloc_register_function_void( global_context, &complete_get_desc_destroy_suffix_hash ); + } + + suff = wcsdup(suff_orig); + + /* + Drop characters that are commonly used as backup suffixes from the suffix + */ + for( pos=suff; *pos; pos++ ) + { + if( wcschr( L"?;#~@&", *pos ) ) + { + *pos=0; + break; + } + } + + tmp = escape( suff, 1 ); + free(suff); + suff = tmp; + desc = (wchar_t *)hash_get( suffix_hash, suff ); + + if( !desc ) + { + desc = complete_get_desc_suffix_internal( suff ); + } + + free( suff ); + + return desc; +} + + +/** + 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. +*/ + +static const wchar_t *file_get_desc( const wchar_t *filename, + int lstat_res, + struct stat lbuf, + int stat_res, + struct stat buf, + int err ) +{ + wchar_t *suffix; + + CHECK( filename, 0 ); + + if( !lstat_res ) + { + if( S_ISLNK(lbuf.st_mode)) + { + if( !stat_res ) + { + if( S_ISDIR(buf.st_mode) ) + { + return COMPLETE_DIRECTORY_SYMLINK_DESC; + } + else + { + + if( ( buf.st_mode & S_IXUSR ) || + ( buf.st_mode & S_IXGRP ) || + ( buf.st_mode & S_IXOTH ) ) + { + + if( waccess( filename, X_OK ) == 0 ) + { + /* + 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. + */ + return COMPLETE_EXEC_LINK_DESC; + } + } + } + + return COMPLETE_SYMLINK_DESC; + + } + else + { + switch( err ) + { + case ENOENT: + { + return COMPLETE_ROTTEN_SYMLINK_DESC; + } + + case ELOOP: + { + return COMPLETE_LOOP_SYMLINK_DESC; + } + } + /* + On unknown errors we do nothing. The file will be + given the default 'File' description or one based on the suffix. + */ + } + + } + else if( S_ISCHR(buf.st_mode) ) + { + return COMPLETE_CHAR_DESC; + } + else if( S_ISBLK(buf.st_mode) ) + { + return COMPLETE_BLOCK_DESC; + } + else if( S_ISFIFO(buf.st_mode) ) + { + 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 + { + if( ( buf.st_mode & S_IXUSR ) || + ( buf.st_mode & S_IXGRP ) || + ( buf.st_mode & S_IXOTH ) ) + { + + if( waccess( filename, X_OK ) == 0 ) + { + /* + 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. + */ + return COMPLETE_EXEC_DESC; + } + } + } + } + + suffix = wcsrchr( filename, L'.' ); + if( suffix != 0 && !wcsrchr( suffix, L'/' ) ) + { + return complete_get_desc_suffix( suffix ); + } + + return COMPLETE_FILE_DESC ; +} + + +/** + Add the specified filename if it matches the specified wildcard. + + If the filename matches, first get the description of the specified + filename. If this is a regular file, append the filesize to the + description. + + \param list the list to add he completion to + \param fullname the full filename of the file + \param completion the completion part of the file name + \param wc the wildcard to match against + \param is_cmd whether we are performing command completion +*/ +static void wildcard_completion_allocate( array_list_t *list, + const wchar_t *fullname, + const wchar_t *completion, + const wchar_t *wc, + int is_cmd ) +{ + const wchar_t *desc; + struct stat buf, lbuf; + static string_buffer_t *sb = 0; + + int free_completion = 0; + + int flags = 0; + int stat_res, lstat_res; + int stat_errno=0; + + long long sz; + + if( !sb ) + { + sb = sb_halloc( global_context ); + } + else + { + sb_clear( sb ); + } + + CHECK( fullname, ); + + sb_clear( sb ); + + /* + If the file is a symlink, we need to stat both the file itself + _and_ the destination file. But we try to avoid this with + non-symlinks by first doing an lstat, and if the file is not a + link we copy the results over to the regular stat buffer. + */ + if( ( lstat_res = lwstat( fullname, &lbuf ) ) ) + { + sz=-1; + stat_res = lstat_res; + } + else + { + if( S_ISLNK(lbuf.st_mode)) + { + + if( ( stat_res = wstat( fullname, &buf ) ) ) + { + sz=-1; + } + else + { + sz = (long long)buf.st_size; + } + + /* + 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; + } + else + { + stat_res = lstat_res; + memcpy( &buf, &lbuf, sizeof( struct stat ) ); + sz = (long long)buf.st_size; + } + } + + desc = file_get_desc( fullname, lstat_res, lbuf, stat_res, buf, stat_errno ); + + if( sz >= 0 && S_ISDIR(buf.st_mode) ) + { + free_completion = 1; + flags = flags | COMPLETE_NO_SPACE; + completion = wcsdupcat( completion, L"/" ); + sb_append( sb, desc ); + } + else + { + sb_append( sb, desc, L", ", (void *)0 ); + sb_format_size( sb, sz ); + } + + wildcard_complete( completion, wc, (wchar_t *)sb->buff, 0, list, flags ); + if( free_completion ) + free( (void *)completion ); +} + +/** + 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 & DIRECTORIES_ONLY ) + { + struct stat buf; + if( wstat( filename, &buf ) == -1 ) + { + return 0; + } + + if( !S_ISDIR( buf.st_mode ) ) + { + return 0; + } + } + + + if( flags & EXECUTABLES_ONLY ) + { + if ( waccess( filename, X_OK ) != 0) + return 0; + } + + return 1; +} + +/** + 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. + */ +static int wildcard_expand_internal( 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 wdirent *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( reader_interrupted() ) + { + return -1; + } + + if( !wc || !base_dir || !out) + { + debug( 2, L"Got null string on line %d of file %s", __LINE__, __FILE__ ); + return 0; + } + + 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_internal( 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)); + + if( flags & ACCEPT_INCOMPLETE ) + sb_init( &sb_desc ); + + /* + Is this segment of the wildcard the last? + */ + if( !wc_end ) + { + /* + 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=wreaddir(dir))!=0 ) + { + if( next->d_name[0] != L'.' ) + { + wchar_t *name = next->d_name; + wchar_t *long_name = make_path( base_dir, name ); + + if( test_flags( long_name, flags ) ) + { + wildcard_completion_allocate( out, + long_name, + name, + L"", + flags & EXECUTABLES_ONLY ); + } + + 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=wreaddir(dir))!=0 ) + { + wchar_t *name = next->d_name; + + 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, + 0 ) ) + { + if( test_flags( long_name, flags ) ) + { + wildcard_completion_allocate( out, + long_name, + name, + wc, + flags & EXECUTABLES_ONLY ); + + } + } + + free( long_name ); + + } + else + { + if( wildcard_match2( name, wc, 1 ) ) + { + wchar_t *long_name = make_path( base_dir, name ); + int skip = 0; + + if( is_recursive ) + { + /* + In recursive mode, we are only + interested in adding files -directories + will be added in the next pass. + */ + struct stat buf; + if( !wstat( long_name, &buf ) ) + { + skip = S_ISDIR(buf.st_mode); + } + } + + if( skip ) + { + free( long_name ); + } + else + { + al_push_check( out, long_name ); + } + res = 1; + } + } + } + } + } + + if( wc_end || is_recursive ) + { + /* + 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 + */ + long ln=MAX_FILE_LENGTH; + char * narrow_dir_string = wcs2str( dir_string ); + + /* + In recursive mode, we look through the direcotry twice. If + so, this rewind is needed. + */ + rewinddir( dir ); + + 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=wreaddir(dir))!=0 ) + { + wchar_t *name = next->d_name; + + /* + 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; + int new_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( S_ISDIR(buf.st_mode) ) + { + new_len = wcslen( new_dir ); + new_dir[new_len] = L'/'; + new_dir[new_len+1] = L'\0'; + + /* + Regular matching + */ + if( whole_match ) + { + wchar_t *new_wc = L""; + if( wc_end ) + { + new_wc=wc_end+1; + /* + Accept multiple '/' as a single direcotry separator + */ + while(*new_wc==L'/') + { + new_wc++; + } + } + + new_res = wildcard_expand_internal( new_wc, + new_dir, + flags, + out ); + + if( new_res == -1 ) + { + res = -1; + break; + } + res |= new_res; + + } + + /* + Recursive matching + */ + if( partial_match ) + { + + new_res = wildcard_expand_internal( wcschr( wc, ANY_STRING_RECURSIVE ), + new_dir, + flags | WILDCARD_RECURSIVE, + out ); + + if( new_res == -1 ) + { + res = -1; + break; + } + res |= new_res; + + } + } + } + } + } + } + + free( wc_str ); + free( new_dir ); + } + closedir( dir ); + + if( flags & ACCEPT_INCOMPLETE ) + { + sb_destroy( &sb_desc ); + } + + return res; +} + + +int wildcard_expand( const wchar_t *wc, + const wchar_t *base_dir, + int flags, + array_list_t *out ) +{ + int c = al_get_count( out ); + int res = wildcard_expand_internal( wc, base_dir, flags, out ); + int i; + + if( flags & ACCEPT_INCOMPLETE ) + { + wchar_t *wc_base=L""; + wchar_t *wc_base_ptr = wcsrchr( wc, L'/' ); + string_buffer_t sb; + + + if( wc_base_ptr ) + { + wc_base = wcsndup( wc, (wc_base_ptr-wc)+1 ); + } + + sb_init( &sb ); + + for( i=c; i<al_get_count( out ); i++ ) + { + completion_t *c = al_get( out, i ); + + if( c->flags & COMPLETE_NO_CASE ) + { + sb_clear( &sb ); + sb_printf( &sb, L"%ls%ls%ls", base_dir, wc_base, c->completion ); + + c->completion = halloc_wcsdup( out, (wchar_t *)sb.buff ); + } + } + + sb_destroy( &sb ); + + if( wc_base_ptr ) + { + free( wc_base ); + } + + } + return res; +} |