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
*/
# include "config.h"
2012-01-16 16:56:47 +00:00
# include <algorithm>
2005-09-20 13:26:39 +00:00
# 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>
2007-02-25 09:05:24 +00:00
# include <string.h>
2012-08-07 09:50:12 +00:00
# include <set>
2005-09-20 13:26:39 +00:00
2006-02-28 13:17:16 +00:00
# include "fallback.h"
2005-09-20 13:26:39 +00:00
# include "util.h"
2006-02-28 13:17:16 +00:00
2005-09-20 13:26:39 +00:00
# include "wutil.h"
# include "complete.h"
# include "common.h"
# include "wildcard.h"
# include "complete.h"
# include "reader.h"
# include "expand.h"
2007-02-25 09:05:24 +00:00
# include "exec.h"
2012-02-10 00:06:24 +00:00
# include <map>
2005-09-20 13:26:39 +00:00
2005-11-30 15:33:03 +00: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-29 16:52:02 +00:00
/**
The maximum length of a filename token . This is a fallback value ,
2012-11-18 10:23:22 +00:00
an attempt to find the true value using patchconf is always made .
2005-11-29 16:52:02 +00:00
*/
# define MAX_FILE_LENGTH 1024
2007-02-25 09:05:24 +00:00
/**
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 */
2012-02-10 00:06:24 +00:00
static std : : map < wcstring , wcstring > suffix_map ;
2006-02-19 01:54:38 +00:00
2007-02-25 09:05:24 +00:00
2012-11-19 00:30:30 +00:00
int wildcard_has ( const wchar_t * str , int internal )
2005-09-20 13:26:39 +00:00
{
2012-11-19 00:30:30 +00:00
if ( ! str )
{
debug ( 2 , L " Got null string on line %d of file %s " , __LINE__ , __FILE__ ) ;
return 0 ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( internal )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
for ( ; * str ; str + + )
{
if ( ( * str = = ANY_CHAR ) | | ( * str = = ANY_STRING ) | | ( * str = = ANY_STRING_RECURSIVE ) )
return 1 ;
}
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 ;
for ( ; * str ; str + + )
{
if ( ( ( * str = = L ' * ' ) | | ( * str = = L ' ? ' ) ) & & ( prev ! = L ' \\ ' ) )
return 1 ;
prev = * str ;
}
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
return 0 ;
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
*/
2012-10-16 01:16:47 +00:00
static bool wildcard_match2 ( const wchar_t * str ,
2012-11-19 00:30:30 +00:00
const wchar_t * wc ,
bool is_first )
2005-09-20 13:26:39 +00:00
{
2012-11-19 00:30:30 +00:00
if ( * str = = 0 & & * wc = = 0 )
return true ;
2012-11-18 10:23:22 +00:00
2012-10-16 01:16:47 +00:00
/* Hackish fix for https://github.com/fish-shell/fish-shell/issues/270. Prevent wildcards from matching . or .., but we must still allow literal matches. */
if ( is_first & & contains ( str , L " . " , L " .. " ) )
{
/* The string is '.' or '..'. Return true if the wildcard exactly matches. */
return ! wcscmp ( str , wc ) ;
}
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 */
if ( is_first & & * str = = L ' . ' )
{
return false ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/* Try all submatches */
do
{
if ( wildcard_match2 ( str , wc + 1 , false ) )
return true ;
}
while ( * ( str + + ) ! = 0 ) ;
return false ;
}
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 .
*/
return false ;
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
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 ' . ' )
{
return false ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
return wildcard_match2 ( str + 1 , wc + 1 , false ) ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( * wc = = * str )
return wildcard_match2 ( str + 1 , wc + 1 , false ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
return false ;
2005-09-20 13:26:39 +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
2012-02-21 18:47:21 +00:00
inserted into the out vector .
2005-09-20 13:26:39 +00:00
*/
2012-11-18 10:23:22 +00:00
static bool wildcard_complete_internal ( const wcstring & orig ,
2012-11-19 00:30:30 +00:00
const wchar_t * str ,
const wchar_t * wc ,
bool is_first ,
const wchar_t * desc ,
wcstring ( * desc_func ) ( const wcstring & ) ,
std : : vector < completion_t > & out ,
int flags )
2005-09-20 13:26:39 +00:00
{
2012-11-19 00:30:30 +00:00
if ( ! wc | | ! str | | orig . empty ( ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
debug ( 2 , L " Got null string on line %d of file %s " , __LINE__ , __FILE__ ) ;
return 0 ;
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
if ( * wc = = 0 & &
( ( str [ 0 ] ! = L ' . ' ) | | ( ! is_first ) ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
wcstring out_completion ;
wcstring out_desc = ( desc ? desc : L " " ) ;
if ( flags & COMPLETE_NO_CASE )
{
out_completion = orig ;
}
else
{
out_completion = str ;
}
2007-04-20 19:34:30 +00:00
2012-05-09 09:33:42 +00:00
size_t complete_sep_loc = out_completion . find ( PROG_COMPLETE_SEP ) ;
if ( complete_sep_loc ! = wcstring : : npos )
2012-11-19 00:30:30 +00:00
{
/* This completion has an embedded description, do not use the generic description */
2012-05-09 09:33:42 +00:00
out_desc . assign ( out_completion , complete_sep_loc + 1 , out_completion . size ( ) - complete_sep_loc - 1 ) ;
2012-11-18 10:23:22 +00:00
out_completion . resize ( complete_sep_loc ) ;
2012-11-19 00:30:30 +00:00
}
else
{
if ( desc_func )
{
/*
A description generating function is specified , call
it . If it returns something , use that as the
description .
*/
wcstring func_desc = desc_func ( orig ) ;
if ( ! func_desc . empty ( ) )
out_desc = func_desc ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
}
2012-11-18 10:23:22 +00:00
2012-08-20 20:09:21 +00:00
/* Note: out_completion may be empty if the completion really is empty, e.g. tab-completing 'foo' when a file 'foo' exists. */
append_completion ( out , out_completion , out_desc , flags ) ;
2012-11-19 00:30:30 +00:00
return true ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( * wc = = ANY_STRING )
{
bool res = false ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/* Ignore hidden file */
if ( is_first & & str [ 0 ] = = L ' . ' )
return false ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/* Try all submatches */
do
{
res = wildcard_complete_internal ( orig , str , wc + 1 , 0 , desc , desc_func , out , flags ) ;
if ( res )
break ;
}
while ( * str + + ! = 0 ) ;
return res ;
}
else if ( * wc = = ANY_CHAR )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
return wildcard_complete_internal ( orig , str + 1 , wc + 1 , 0 , desc , desc_func , out , flags ) ;
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
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 false ;
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 & ) ,
std : : vector < completion_t > & out ,
int flags )
2005-09-20 13:26:39 +00:00
{
2012-11-19 00:30:30 +00:00
bool res ;
res = wildcard_complete_internal ( str , str . c_str ( ) , wc , true , desc , desc_func , out , flags ) ;
return res ;
2005-09-20 13:26:39 +00:00
}
2012-11-19 00:30:30 +00:00
bool wildcard_match ( const wcstring & str , const wcstring & wc )
2005-09-20 13:26:39 +00:00
{
2012-11-19 00:30:30 +00:00
return wildcard_match2 ( str . c_str ( ) , wc . c_str ( ) , true ) ;
2005-09-20 13:26:39 +00:00
}
/**
2012-11-18 10:23:22 +00:00
Creates a path from the specified directory and filename .
2005-09-20 13:26:39 +00:00
*/
2012-11-19 00:30:30 +00:00
static wcstring make_path ( const wcstring & base_dir , const wcstring & name )
{
2012-02-14 06:44:29 +00:00
return base_dir + name ;
2005-09-20 13:26:39 +00:00
}
2008-01-13 16:47:47 +00:00
/**
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 .
*/
2012-11-19 00:30:30 +00:00
static wcstring complete_get_desc_suffix_internal ( const wcstring & suff )
2007-02-25 09:05:24 +00:00
{
2012-02-10 00:06:24 +00:00
wcstring cmd = wcstring ( SUFFIX_CMD_STR ) + suff ;
2012-11-18 10:23:22 +00:00
2011-12-28 20:36:47 +00:00
wcstring_list_t lst ;
wcstring desc ;
2012-11-18 10:23:22 +00:00
2013-01-31 23:57:08 +00:00
if ( exec_subshell ( cmd , lst , false /* do not apply exit status */ ) ! = - 1 )
2012-11-18 10:23:22 +00:00
{
2013-02-16 10:38:13 +00:00
if ( ! lst . empty ( ) )
2012-11-19 00:30:30 +00:00
{
2011-12-28 20:36:47 +00:00
const wcstring & ln = lst . at ( 0 ) ;
2012-11-19 00:30:30 +00:00
if ( ln . size ( ) > 0 & & ln ! = L " unknown " )
{
desc = 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 ] ) ;
}
}
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
if ( desc . empty ( ) )
{
desc = COMPLETE_FILE_DESC ;
}
2012-11-18 10:23:22 +00:00
2012-02-10 00:06:24 +00:00
suffix_map [ suff ] = desc . c_str ( ) ;
2012-11-19 00:30:30 +00:00
return desc ;
2007-02-25 09:05:24 +00:00
}
2006-06-20 00:50:10 +00:00
/**
2007-02-25 09:05:24 +00:00
Use the mimedb command to look up a description for a given suffix
2006-06-20 00:50:10 +00:00
*/
2012-11-19 00:30:30 +00:00
static wcstring complete_get_desc_suffix ( const wchar_t * suff_orig )
2005-09-20 13:26:39 +00:00
{
2007-02-25 09:05:24 +00:00
2012-11-19 00:30:30 +00:00
size_t len ;
wchar_t * suff ;
wchar_t * pos ;
wchar_t * tmp ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
len = wcslen ( suff_orig ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( len = = 0 )
return COMPLETE_FILE_DESC ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
suff = wcsdup ( suff_orig ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/*
Drop characters that are commonly used as backup suffixes from the suffix
*/
for ( pos = suff ; * pos ; pos + + )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
if ( wcschr ( L " ?;#~@& " , * pos ) )
{
* pos = 0 ;
break ;
}
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
tmp = escape ( suff , 1 ) ;
free ( suff ) ;
suff = tmp ;
2012-11-18 10:23:22 +00:00
2012-02-10 00:06:24 +00:00
std : : map < wcstring , wcstring > : : iterator iter = suffix_map . find ( suff ) ;
wcstring desc ;
2012-11-19 00:30:30 +00:00
if ( iter ! = suffix_map . end ( ) )
{
2012-02-10 00:06:24 +00:00
desc = iter - > second ;
2012-11-19 00:30:30 +00:00
}
else
{
desc = complete_get_desc_suffix_internal ( suff ) ;
2012-02-10 00:06:24 +00:00
}
2007-02-25 09:05:24 +00:00
2012-11-19 00:30:30 +00:00
free ( suff ) ;
2007-02-25 09:05:24 +00:00
2012-11-19 00:30:30 +00:00
return desc ;
2007-02-25 09:05:24 +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
2012-11-18 10:23:22 +00:00
\ 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 ,
struct stat lbuf ,
int stat_res ,
struct stat buf ,
int err )
2007-02-25 09:05:24 +00:00
{
2012-11-19 00:30:30 +00:00
const wchar_t * suffix ;
2012-11-18 10:23:22 +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
{
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
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 ;
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 ;
}
2012-11-18 10:23:22 +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
}
/*
On unknown errors we do nothing . The file will be
given the default ' File ' description or one based on the suffix .
*/
}
2012-11-18 10:23:22 +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
{
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 ;
}
}
2012-11-18 10:23:22 +00:00
}
}
2012-11-19 00:30:30 +00:00
suffix = wcsrchr ( filename . c_str ( ) , L ' . ' ) ;
if ( suffix ! = 0 & & ! wcsrchr ( suffix , L ' / ' ) )
{
return complete_get_desc_suffix ( suffix ) ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
return COMPLETE_FILE_DESC ;
2007-02-25 09:05:24 +00:00
}
/**
2012-11-18 10:23:22 +00:00
Add the specified filename if it matches the specified wildcard .
2007-02-25 09:05:24 +00:00
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
*/
2012-11-19 00:30:30 +00:00
static void wildcard_completion_allocate ( std : : vector < completion_t > & list ,
const wcstring & fullname ,
const wcstring & completion ,
const wchar_t * wc ,
expand_flags_t expand_flags )
2007-02-25 09:05:24 +00:00
{
2012-11-19 00:30:30 +00:00
struct stat buf , lbuf ;
2012-02-09 18:14:06 +00:00
wcstring sb ;
2012-11-19 00:30:30 +00:00
wcstring munged_completion ;
int flags = 0 ;
int stat_res , lstat_res ;
int stat_errno = 0 ;
long long sz ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/*
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 ) ) )
{
/* lstat failed! */
2012-11-18 10:23:22 +00:00
sz = - 1 ;
2012-11-19 00:30:30 +00:00
stat_res = lstat_res ;
2012-11-18 10:23:22 +00:00
}
else
{
2012-11-19 00:30:30 +00:00
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 ;
}
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
bool wants_desc = ! ( expand_flags & EXPAND_NO_DESCRIPTIONS ) ;
wcstring desc ;
2012-08-16 01:20:44 +00:00
if ( wants_desc )
2013-02-16 10:38:13 +00:00
desc = file_get_desc ( fullname , lstat_res , lbuf , stat_res , buf , stat_errno ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( sz > = 0 & & S_ISDIR ( buf . st_mode ) )
{
flags = flags | COMPLETE_NO_SPACE ;
2012-05-09 09:33:42 +00:00
munged_completion = completion ;
munged_completion . push_back ( L ' / ' ) ;
2012-08-16 01:20:44 +00:00
if ( wants_desc )
sb . append ( desc ) ;
2012-11-19 00:30:30 +00:00
}
else
{
2012-08-16 01:20:44 +00:00
if ( wants_desc )
{
if ( ! desc . empty ( ) )
{
sb . append ( desc ) ;
sb . append ( L " , " ) ;
}
sb . append ( format_size ( sz ) ) ;
}
2012-11-19 00:30:30 +00:00
}
2012-11-18 10:23:22 +00:00
2012-05-09 09:33:42 +00:00
const wcstring & completion_to_use = munged_completion . empty ( ) ? completion : munged_completion ;
2012-11-19 00:30:30 +00:00
wildcard_complete ( completion_to_use , wc , sb . c_str ( ) , NULL , list , flags ) ;
2005-09-20 13:26:39 +00:00
}
2006-06-20 00:50:10 +00:00
/**
2005-11-29 14:33:52 +00:00
Test if the file specified by the given filename matches the
2005-12-07 15:57:17 +00:00
expansion flags specified . flags can be a combination of
2005-11-29 14:33:52 +00:00
EXECUTABLES_ONLY and DIRECTORIES_ONLY .
*/
2012-11-19 00:30:30 +00:00
static int test_flags ( const wchar_t * filename ,
int flags )
2005-09-20 13:26:39 +00:00
{
2012-11-19 00:30:30 +00:00
if ( flags & DIRECTORIES_ONLY )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
struct stat buf ;
if ( wstat ( filename , & buf ) = = - 1 )
{
return 0 ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( ! S_ISDIR ( buf . st_mode ) )
{
return 0 ;
}
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
if ( flags & EXECUTABLES_ONLY )
{
if ( waccess ( filename , X_OK ) ! = 0 )
return 0 ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
return 1 ;
2005-09-20 13:26:39 +00:00
}
2012-08-07 09:50:12 +00:00
/** Appends a completion to the completion list, if the string is missing from the set. */
static void insert_completion_if_missing ( const wcstring & str , std : : vector < completion_t > & out , std : : set < wcstring > & completion_set )
{
if ( completion_set . insert ( str ) . second )
append_completion ( out , str ) ;
}
2008-01-13 16:47:47 +00:00
/**
The real implementation of wildcard expansion is in this
function . Other functions are just wrappers around this one .
2005-09-20 13:26:39 +00:00
2008-01-13 16:47:47 +00:00
This function traverses the relevant directory tree looking for
matches , and recurses when needed to handle wildcrards spanning
2012-10-16 01:16:47 +00:00
multiple components and recursive wildcards .
2012-11-18 10:23:22 +00:00
2012-10-16 01:16:47 +00:00
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 .
2008-01-13 16:47:47 +00:00
*/
2012-11-19 00:30:30 +00:00
static int wildcard_expand_internal ( const wchar_t * wc ,
const wchar_t * base_dir ,
expand_flags_t flags ,
std : : vector < completion_t > & out ,
std : : set < wcstring > & completion_set ,
std : : set < file_id_t > & visited_files
2012-08-07 09:50:12 +00:00
)
2005-09-20 13:26:39 +00:00
{
2012-08-07 09:50:12 +00:00
2012-11-19 00:30:30 +00:00
/* Points to the end of the current wildcard segment */
const wchar_t * wc_end ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/* Variables for traversing a directory */
DIR * dir ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/* The result returned */
int res = 0 ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/* Length of the directory to search in */
size_t base_len ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/* Variables for testing for presense of recursive wildcards */
const wchar_t * wc_recursive ;
bool is_recursive ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/* Slightly mangled version of base_dir */
const wchar_t * dir_string ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
// debug( 3, L"WILDCARD_EXPAND %ls in %ls", wc, base_dir );
2012-11-18 10:23:22 +00:00
2013-02-05 20:18:22 +00:00
if ( is_main_thread ( ) ? reader_interrupted ( ) : reader_thread_job_is_stale ( ) )
2012-11-19 00:30:30 +00:00
{
return - 1 ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( ! wc | | ! base_dir )
{
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 *
*/
size_t 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 , completion_set , visited_files ) ;
free ( foo ) ;
return res ;
}
}
2012-11-18 10:23:22 +00:00
/*
2012-11-19 00:30:30 +00:00
Initialize various variables
2012-11-18 10:23:22 +00:00
*/
2012-11-19 00:30:30 +00:00
dir_string = base_dir [ 0 ] = = L ' \0 ' ? L " . " : base_dir ;
if ( ! ( dir = wopendir ( dir_string ) ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
return 0 ;
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
wc_end = wcschr ( wc , L ' / ' ) ;
base_len = wcslen ( base_dir ) ;
2012-11-18 10:23:22 +00:00
/*
2012-11-19 00:30:30 +00:00
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 ) ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/*
Is this segment of the wildcard the last ?
2012-11-18 10:23:22 +00:00
*/
2012-11-19 00:30:30 +00:00
if ( ! wc_end )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
/*
Wildcard segment is the last segment ,
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
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 )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
wcstring next ;
while ( wreaddir ( dir , next ) )
{
if ( next [ 0 ] ! = L ' . ' )
{
wcstring long_name = make_path ( base_dir , next ) ;
if ( test_flags ( long_name . c_str ( ) , flags ) )
{
wildcard_completion_allocate ( out ,
long_name ,
next ,
L " " ,
flags ) ;
}
}
}
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
res = 1 ;
insert_completion_if_missing ( base_dir , out , completion_set ) ;
2012-11-18 10:23:22 +00:00
}
}
else
{
2012-11-19 00:30:30 +00:00
/*
This is the last wildcard segment , and it is not empty . Match files / directories .
*/
wcstring next ;
while ( wreaddir ( dir , next ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
const wchar_t * const name = next . c_str ( ) ;
if ( flags & ACCEPT_INCOMPLETE )
{
const wcstring long_name = make_path ( base_dir , next ) ;
/*
Test for matches before stating file , so as to minimize the number of calls to the much slower stat function
*/
std : : vector < completion_t > test ;
if ( wildcard_complete ( name ,
wc ,
L " " ,
0 ,
test ,
0 ) )
{
if ( test_flags ( long_name . c_str ( ) , flags ) )
{
wildcard_completion_allocate ( out ,
long_name ,
name ,
wc ,
flags ) ;
}
}
}
else
{
if ( wildcard_match2 ( name , wc , true ) )
{
const wcstring long_name = make_path ( base_dir , next ) ;
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 )
{
2012-08-07 09:50:12 +00:00
insert_completion_if_missing ( long_name , out , completion_set ) ;
2012-11-19 00:30:30 +00:00
}
res = 1 ;
}
}
2012-11-18 10:23:22 +00:00
}
}
}
2012-11-19 00:30:30 +00:00
if ( wc_end | | is_recursive )
{
/*
Wilcard segment is not the last segment . Recursively call
wildcard_expand for all matching subdirectories .
*/
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/*
wc_str is the part of the wildcarded string from the
beginning to the first slash
*/
wchar_t * wc_str ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/*
new_dir is a scratch area containing the full path to a
file / directory we are iterating over
*/
wchar_t * new_dir ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/*
The maximum length of a file element
*/
long ln = MAX_FILE_LENGTH ;
char * narrow_dir_string = wcs2str ( dir_string ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
/*
In recursive mode , we look through the directory twice . If
so , this rewind is needed .
*/
rewinddir ( dir ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
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 = ( wchar_t * ) malloc ( sizeof ( wchar_t ) * ( base_len + ln + 2 ) ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
wc_str = wc_end ? wcsndup ( wc , wc_end - wc ) : wcsdup ( wc ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( ( ! new_dir ) | | ( ! wc_str ) )
{
DIE_MEM ( ) ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
wcscpy ( new_dir , base_dir ) ;
2012-11-18 10:23:22 +00:00
2011-12-27 03:18:46 +00:00
wcstring next ;
2012-11-19 00:30:30 +00:00
while ( wreaddir ( dir , next ) )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
const wchar_t * name = next . c_str ( ) ;
/*
Test if the file / directory name matches the whole
wildcard element , i . e . regular matching .
*/
int whole_match = wildcard_match2 ( name , wc_str , true ) ;
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 )
{
const wchar_t * end = wcschr ( wc , ANY_STRING_RECURSIVE ) ;
wchar_t * wc_sub = wcsndup ( wc , end - wc + 1 ) ;
partial_match = wildcard_match2 ( name , wc_sub , true ) ;
free ( wc_sub ) ;
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( whole_match | | partial_match )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
struct stat buf ;
char * dir_str ;
int stat_res ;
int new_res ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
wcscpy ( & new_dir [ base_len ] , name ) ;
dir_str = wcs2str ( new_dir ) ;
2007-01-09 16:47:05 +00:00
2012-11-19 00:30:30 +00:00
if ( dir_str )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
stat_res = stat ( dir_str , & buf ) ;
free ( dir_str ) ;
2007-03-24 19:07:38 +00:00
2012-11-19 00:30:30 +00:00
if ( ! stat_res )
{
// Insert a "file ID" into visited_files
// If the insertion fails, we've already visited this file (i.e. a symlink loop)
// If we're not recursive, insert anyways (in case we loop back around in a future recursive segment), but continue on; the idea being that literal path components should still work
const file_id_t file_id ( buf . st_dev , buf . st_ino ) ;
if ( S_ISDIR ( buf . st_mode ) & & ( visited_files . insert ( file_id ) . second | | ! is_recursive ) )
{
size_t new_len = wcslen ( new_dir ) ;
new_dir [ new_len ] = L ' / ' ;
new_dir [ new_len + 1 ] = L ' \0 ' ;
/*
Regular matching
*/
if ( whole_match )
{
const 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 ,
completion_set ,
visited_files ) ;
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 ,
completion_set ,
visited_files ) ;
if ( new_res = = - 1 )
{
res = - 1 ;
break ;
}
res | = new_res ;
}
}
}
2012-11-18 10:23:22 +00:00
}
}
}
2012-11-19 00:30:30 +00:00
free ( wc_str ) ;
free ( new_dir ) ;
}
closedir ( dir ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
return res ;
2005-09-20 13:26:39 +00:00
}
2007-03-24 19:07:38 +00:00
2012-11-19 00:30:30 +00:00
int wildcard_expand ( const wchar_t * wc ,
const wchar_t * base_dir ,
expand_flags_t flags ,
std : : vector < completion_t > & out )
2007-03-24 19:07:38 +00:00
{
2012-11-19 00:30:30 +00:00
size_t c = out . size ( ) ;
2012-11-18 10:23:22 +00:00
2012-08-07 09:50:12 +00:00
/* Make a set of used completion strings so we can do fast membership tests inside wildcard_expand_internal. Otherwise wildcards like '**' are very slow, because we end up with an N^2 membership test.
*/
2012-11-19 00:30:30 +00:00
std : : set < wcstring > completion_set ;
for ( std : : vector < completion_t > : : const_iterator iter = out . begin ( ) ; iter ! = out . end ( ) ; + + iter )
{
2012-08-07 09:50:12 +00:00
completion_set . insert ( iter - > completion ) ;
2012-11-19 00:30:30 +00:00
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
std : : set < file_id_t > visited_files ;
int res = wildcard_expand_internal ( wc , base_dir , flags , out , completion_set , visited_files ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( flags & ACCEPT_INCOMPLETE )
2012-11-18 10:23:22 +00:00
{
2012-11-19 00:30:30 +00:00
wcstring wc_base ;
const wchar_t * wc_base_ptr = wcsrchr ( wc , L ' / ' ) ;
if ( wc_base_ptr )
{
2012-02-24 20:13:35 +00:00
wc_base = wcstring ( wc , ( wc_base_ptr - wc ) + 1 ) ;
2012-11-19 00:30:30 +00:00
}
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
for ( size_t i = c ; i < out . size ( ) ; i + + )
{
completion_t & c = out . at ( i ) ;
2012-11-18 10:23:22 +00:00
2012-11-19 00:30:30 +00:00
if ( c . flags & COMPLETE_NO_CASE )
{
c . completion = format_string ( L " %ls%ls%ls " , base_dir , wc_base . c_str ( ) , c . completion . c_str ( ) ) ;
}
}
2012-11-18 10:23:22 +00:00
}
2012-11-19 00:30:30 +00:00
return res ;
2007-03-24 19:07:38 +00:00
}
2011-12-27 03:18:46 +00:00
2012-11-19 00:30:30 +00:00
int wildcard_expand_string ( const wcstring & wc , const wcstring & base_dir , expand_flags_t flags , std : : vector < completion_t > & outputs )
2011-12-27 03:18:46 +00:00
{
2012-08-16 01:20:44 +00:00
// PCA: not convinced this temporary variable is really necessary
2012-01-16 16:56:47 +00:00
std : : vector < completion_t > lst ;
2012-08-16 01:20:44 +00:00
int res = wildcard_expand ( wc . c_str ( ) , base_dir . c_str ( ) , flags , lst ) ;
2012-02-14 06:44:29 +00:00
outputs . insert ( outputs . end ( ) , lst . begin ( ) , lst . end ( ) ) ;
2011-12-27 03:18:46 +00:00
return res ;
}