diff --git a/complete.c b/complete.c index 8dfc124f7..95206f586 100644 --- a/complete.c +++ b/complete.c @@ -65,61 +65,6 @@ These functions are used for storing and retrieving tab-completion data, as well */ #define COMPLETE_VAR_DESC_VAL _( L"Variable: %ls" ) -/** - 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" ) - -/** - The command to run to get a description from a file suffix -*/ -#define SUFFIX_CMD_STR L"mimedb 2>/dev/null -fd " - /** The maximum number of commands on which to perform description lookup. The lookup process is quite time consuming, so this should @@ -220,19 +165,13 @@ typedef struct complete_entry /** First node in the linked list of all completion entries */ static complete_entry_t *first_entry=0; -/** Hashtable containing all descriptions that describe an executable */ -static hash_table_t *suffix_hash=0; - /** Table of completions conditions that have already been tested and the corresponding test results */ static hash_table_t *condition_cache=0; - static void complete_free_entry( complete_entry_t *c ); -static void clear_hash_entry( void *key, void *data ); - /** Create a new completion entry @@ -289,14 +228,6 @@ static void complete_destroy() } first_entry = 0; - if( suffix_hash ) - { - hash_foreach( suffix_hash, &clear_hash_entry ); - hash_destroy( suffix_hash ); - free( suffix_hash ); - suffix_hash=0; - } - parse_util_load_reset( L"fish_complete_path", 0 ); } @@ -404,15 +335,6 @@ static void complete_free_entry( complete_entry_t *c ) free( c ); } -/** - Free hash key and hash value -*/ -static void clear_hash_entry( void *key, void *data ) -{ - free( (void *)key ); - free( (void *)data ); -} - /** Search for an exactly matching completion entry */ @@ -937,204 +859,6 @@ int complete_is_valid_argument( const wchar_t *str, return 1; } -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; -} - - -/** - 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 ); - } - - suff = wcsdup(suff_orig); - - 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; -} - - -const wchar_t *complete_get_desc( const wchar_t *filename ) -{ - - static string_buffer_t *get_desc_buff=0; - struct stat buf; - - CHECK( filename, 0 ); - - if( !get_desc_buff ) - { - complete_init(); - get_desc_buff = sb_halloc( global_context); - } - else - { - sb_clear( get_desc_buff ); - } - - if( lwstat( filename, &buf )==0) - { - if( S_ISCHR(buf.st_mode) ) - { - sb_printf( get_desc_buff, L"%lc%ls", COMPLETE_SEP, COMPLETE_CHAR_DESC ); - } - else if( S_ISBLK(buf.st_mode) ) - sb_printf( get_desc_buff, L"%lc%ls", COMPLETE_SEP, COMPLETE_BLOCK_DESC ); - else if( S_ISFIFO(buf.st_mode) ) - sb_printf( get_desc_buff, L"%lc%ls", COMPLETE_SEP, COMPLETE_FIFO_DESC ); - else if( S_ISLNK(buf.st_mode)) - { - struct stat buf2; - - if( wstat( filename, &buf2 ) == 0 ) - { - if( S_ISDIR(buf2.st_mode) ) - { - sb_printf( get_desc_buff, L"/%lc%ls", COMPLETE_SEP, COMPLETE_DIRECTORY_SYMLINK_DESC ); - } - else if( waccess( filename, X_OK ) == 0 ) - sb_printf( get_desc_buff, L"%lc%ls", COMPLETE_SEP, COMPLETE_EXEC_LINK_DESC ); - else - sb_printf( get_desc_buff, L"%lc%ls", COMPLETE_SEP, COMPLETE_SYMLINK_DESC ); - } - else - { - switch( errno ) - { - case ENOENT: - { - sb_printf( get_desc_buff, L"%lc%ls", COMPLETE_SEP, COMPLETE_ROTTEN_SYMLINK_DESC ); - break; - } - - case ELOOP: - { - sb_printf( get_desc_buff, L"%lc%ls", COMPLETE_SEP, COMPLETE_LOOP_SYMLINK_DESC ); - break; - } - } - /* - On unknown errors we do nothing. The file will be - given the default 'File' description. - */ - } - - } - else if( S_ISSOCK(buf.st_mode)) - sb_printf( get_desc_buff, L"%lc%ls", COMPLETE_SEP, COMPLETE_SOCKET_DESC ); - else if( S_ISDIR(buf.st_mode) ) - sb_printf( get_desc_buff, L"/%lc%ls", COMPLETE_SEP, COMPLETE_DIRECTORY_DESC ); - else if( waccess( filename, X_OK ) == 0 ) - { - sb_printf( get_desc_buff, L"%lc%ls", COMPLETE_SEP, COMPLETE_EXEC_DESC ); - } - - } - - if( wcslen((wchar_t *)get_desc_buff->buff) == 0 ) - { - wchar_t *suffix = wcsrchr( filename, L'.' ); - if( suffix != 0 && !wcsrchr( suffix, L'/' ) ) - { - sb_printf( get_desc_buff, - L"%lc%ls", - COMPLETE_SEP, - complete_get_desc_suffix( suffix ) ); - } - else - { - sb_printf( get_desc_buff, - L"%lc%ls", - COMPLETE_SEP, - COMPLETE_FILE_DESC ); - } - - } - - return (wchar_t *)get_desc_buff->buff; -} /** Copy any strings in possible_comp which have the specified prefix @@ -1177,7 +901,7 @@ static void complete_strings( array_list_t *comp_out, wchar_t *next_str = (wchar_t *)al_get( possible_comp, i ); if( next_str ) { - wildcard_complete( next_str, wc, desc, desc_func, comp_out ); + wildcard_complete( next_str, wc, desc, desc_func, comp_out, 0 ); } } @@ -1230,7 +954,7 @@ static void complete_cmd_desc( const wchar_t *cmd, array_list_t *comp ) { completion_t *c = (completion_t *)al_get( comp, i ); - if( wcscmp( c->description, COMPLETE_DIRECTORY_DESC ) != 0 ) + if( !wcslen( c->completion) || (c->completion[wcslen(c->completion)-1] != L'/' )) { skip = 0; break; diff --git a/complete.h b/complete.h index 74a4d8b25..24b51ab92 100644 --- a/complete.h +++ b/complete.h @@ -200,15 +200,6 @@ void complete( const wchar_t *cmd, array_list_t *out ); */ void complete_print( string_buffer_t *out ); -/** - Obtain a description string for the file specified by the filename. - - The returned value is a string constant and should not be freed. - - \param filename The file for which to find a description string -*/ -const wchar_t *complete_get_desc( const wchar_t *filename ); - /** Tests if the specified option is defined for the specified command */ diff --git a/wildcard.c b/wildcard.c index 50ca55b69..d80d47e30 100644 --- a/wildcard.c +++ b/wildcard.c @@ -17,6 +17,7 @@ wildcards using **. #include #include #include +#include #include "fallback.h" @@ -29,6 +30,8 @@ wildcards using **. #include "complete.h" #include "reader.h" #include "expand.h" +#include "exec.h" +#include "halloc_util.h" /** @@ -44,6 +47,64 @@ wildcards using **. */ #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, @@ -67,6 +128,15 @@ static void al_push_check( array_list_t *l, const wchar_t *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; @@ -166,7 +236,8 @@ static int wildcard_complete_internal( const wchar_t *orig, int is_first, const wchar_t *desc, const wchar_t *(*desc_func)(const wchar_t *), - array_list_t *out ) + array_list_t *out, + int flags ) { if( !wc || !str || !orig) { @@ -222,7 +293,7 @@ static int wildcard_complete_internal( const wchar_t *orig, completion_allocate( out, out_completion, out_desc, - 0 ); + flags ); } free ( out_completion ); @@ -242,7 +313,7 @@ static int wildcard_complete_internal( const wchar_t *orig, /* Try all submatches */ do { - res |= wildcard_complete_internal( orig, str, wc+1, 0, desc, desc_func, out ); + res |= wildcard_complete_internal( orig, str, wc+1, 0, desc, desc_func, out, flags ); if( res && !out ) break; } @@ -252,11 +323,11 @@ static int wildcard_complete_internal( const wchar_t *orig, } else if( *wc == ANY_CHAR ) { - return wildcard_complete_internal( orig, str+1, wc+1, 0, desc, desc_func, out ); + 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 ); + return wildcard_complete_internal( orig, str+1, wc+1, 0, desc, desc_func, out, flags ); } return 0; } @@ -265,9 +336,10 @@ 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 ) + array_list_t *out, + int flags ) { - return wildcard_complete_internal( str, str, wc, 1, desc, desc_func, out ); + return wildcard_complete_internal( str, str, wc, 1, desc, desc_func, out, flags ); } @@ -293,15 +365,246 @@ static wchar_t *make_path( const wchar_t *base_dir, const wchar_t *name ) return long_name; } + +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; +} + +static void complete_get_desc_destroy_suffix_hash() +{ + hash_foreach( suffix_hash, &clear_hash_entry ); + hash_destroy( suffix_hash ); + free( suffix_hash ); +} + + + /** - Get the description of the specified filename. If this is a regular file, append the filesize to the description. + Use the mimedb command to look up a description for a given suffix */ -static void get_desc( wchar_t *fn, string_buffer_t *sb, int is_cmd ) +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( waccess( filename, X_OK ) == 0 ) + { + 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( waccess( filename, X_OK ) == 0 ) + { + 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, + wchar_t *fullname, + wchar_t *completion, + 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; - struct stat buf; - /* This is a long long, not an off_t since we really need to know exactly how large it is when using *printf() to output it. @@ -312,28 +615,66 @@ static void get_desc( wchar_t *fn, string_buffer_t *sb, int is_cmd ) L"kB", L"MB", L"GB", L"TB", L"PB", L"EB", L"ZB", L"YB", 0 } ; - - if( !fn || !sb ) + + if( !sb ) { - debug( 2, L"Got null string on line %d of file %s", __LINE__, __FILE__ ); - return; - } - - sb_clear( sb ); - - if( wstat( fn, &buf ) ) - { - sz=-1; + sb = sb_halloc( global_context ); } else { - sz = (long long)buf.st_size; + sb_clear( sb ); } - - desc = complete_get_desc( fn ); + 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 @@ -372,6 +713,11 @@ static void get_desc( wchar_t *fn, string_buffer_t *sb, int is_cmd ) } } } + + wildcard_complete( completion, wc, (wchar_t *)sb->buff, 0, list, flags ); + + if( free_completion ) + free( completion ); } /** @@ -515,14 +861,11 @@ int wildcard_expand( const wchar_t *wc, if( test_flags( long_name, flags ) ) { - get_desc( long_name, - &sb_desc, - flags & EXECUTABLES_ONLY ); - completion_allocate( out, - name, - (wchar_t *)sb_desc.buff, - 0 ); - + wildcard_completion_allocate( out, + long_name, + name, + L"", + flags & EXECUTABLES_ONLY ); } free( long_name ); @@ -556,19 +899,17 @@ int wildcard_expand( const wchar_t *wc, wc, L"", 0, + 0, 0 ) ) { if( test_flags( long_name, flags ) ) { - get_desc( long_name, - &sb_desc, - flags & EXECUTABLES_ONLY ); + wildcard_completion_allocate( out, + long_name, + name, + wc, + flags & EXECUTABLES_ONLY ); - wildcard_complete( name, - wc, - (wchar_t *)sb_desc.buff, - 0, - out ); } } diff --git a/wildcard.h b/wildcard.h index 39e8ce6d2..917e15afd 100644 --- a/wildcard.h +++ b/wildcard.h @@ -87,9 +87,10 @@ int wildcard_has( const wchar_t *str, int internal ); Test wildcard completion */ 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 ); + const wchar_t *wc, + const wchar_t *desc, + const wchar_t *(*desc_func)(const wchar_t *), + array_list_t *out, + int flags ); #endif