Better handle symlink loops in recursive wildcards (**)

https://github.com/fish-shell/fish-shell/issues/268
This commit is contained in:
ridiculousfish 2012-08-07 02:50:12 -07:00
parent 0e2a625815
commit 1e328c3546

View file

@ -18,6 +18,7 @@ wildcards using **.
#include <dirent.h> #include <dirent.h>
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <set>
#include "fallback.h" #include "fallback.h"
@ -663,6 +664,16 @@ static int test_flags( const wchar_t *filename,
return 1; return 1;
} }
/** 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);
}
/** Helper stuff to avoid symlink loops */
typedef std::pair<dev_t, ino_t> file_id_t;
/** /**
The real implementation of wildcard expansion is in this The real implementation of wildcard expansion is in this
function. Other functions are just wrappers around this one. function. Other functions are just wrappers around this one.
@ -674,7 +685,10 @@ static int test_flags( const wchar_t *filename,
static int wildcard_expand_internal( const wchar_t *wc, static int wildcard_expand_internal( const wchar_t *wc,
const wchar_t *base_dir, const wchar_t *base_dir,
expand_flags_t flags, expand_flags_t flags,
std::vector<completion_t> &out ) std::vector<completion_t> &out,
std::set<wcstring> &completion_set,
std::set<file_id_t> &visited_files
)
{ {
/* Points to the end of the current wildcard segment */ /* Points to the end of the current wildcard segment */
@ -693,7 +707,7 @@ static int wildcard_expand_internal( const wchar_t *wc,
const wchar_t *wc_recursive; const wchar_t *wc_recursive;
int is_recursive; int is_recursive;
/* Sligtly mangled version of base_dir */ /* Slightly mangled version of base_dir */
const wchar_t *dir_string; const wchar_t *dir_string;
// debug( 3, L"WILDCARD_EXPAND %ls in %ls", wc, base_dir ); // debug( 3, L"WILDCARD_EXPAND %ls in %ls", wc, base_dir );
@ -719,7 +733,7 @@ static int wildcard_expand_internal( const wchar_t *wc,
{ {
wchar_t * foo = wcsdup( wc ); wchar_t * foo = wcsdup( wc );
foo[len-1]=0; foo[len-1]=0;
int res = wildcard_expand_internal( foo, base_dir, flags, out ); int res = wildcard_expand_internal( foo, base_dir, flags, out, completion_set, visited_files );
free( foo ); free( foo );
return res; return res;
} }
@ -784,10 +798,7 @@ static int wildcard_expand_internal( const wchar_t *wc,
else else
{ {
res = 1; res = 1;
completion_t data_to_push(base_dir); insert_completion_if_missing(base_dir, out, completion_set);
if (std::find( out.begin(), out.end(), data_to_push ) == out.end()) {
out.push_back(data_to_push);
}
} }
} }
else else
@ -848,7 +859,7 @@ static int wildcard_expand_internal( const wchar_t *wc,
} }
if (! skip) if (! skip)
{ {
out.push_back(completion_t(long_name) ); insert_completion_if_missing(long_name, out, completion_set);
} }
res = 1; res = 1;
} }
@ -883,7 +894,7 @@ static int wildcard_expand_internal( const wchar_t *wc,
char * narrow_dir_string = wcs2str( dir_string ); char * narrow_dir_string = wcs2str( dir_string );
/* /*
In recursive mode, we look through the direcotry twice. If In recursive mode, we look through the directory twice. If
so, this rewind is needed. so, this rewind is needed.
*/ */
rewinddir( dir ); rewinddir( dir );
@ -957,7 +968,10 @@ static int wildcard_expand_internal( const wchar_t *wc,
if( !stat_res ) if( !stat_res )
{ {
if( S_ISDIR(buf.st_mode) ) // Insert a "file ID" into visited_files
// If the insertion fails, we've already visited this file (i.e. a symlink loop)
const file_id_t file_id(buf.st_dev, buf.st_ino);
if( S_ISDIR(buf.st_mode) && visited_files.insert(file_id).second)
{ {
size_t new_len = wcslen( new_dir ); size_t new_len = wcslen( new_dir );
new_dir[new_len] = L'/'; new_dir[new_len] = L'/';
@ -984,7 +998,9 @@ static int wildcard_expand_internal( const wchar_t *wc,
new_res = wildcard_expand_internal( new_wc, new_res = wildcard_expand_internal( new_wc,
new_dir, new_dir,
flags, flags,
out ); out,
completion_set,
visited_files );
if( new_res == -1 ) if( new_res == -1 )
{ {
@ -1004,7 +1020,9 @@ static int wildcard_expand_internal( const wchar_t *wc,
new_res = wildcard_expand_internal( wcschr( wc, ANY_STRING_RECURSIVE ), new_res = wildcard_expand_internal( wcschr( wc, ANY_STRING_RECURSIVE ),
new_dir, new_dir,
flags | WILDCARD_RECURSIVE, flags | WILDCARD_RECURSIVE,
out ); out,
completion_set,
visited_files);
if( new_res == -1 ) if( new_res == -1 )
{ {
@ -1035,7 +1053,17 @@ int wildcard_expand( const wchar_t *wc,
std::vector<completion_t> &out ) std::vector<completion_t> &out )
{ {
size_t c = out.size(); size_t c = out.size();
int res = wildcard_expand_internal( wc, base_dir, flags, out );
/* 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.
*/
std::set<wcstring> completion_set;
for (std::vector<completion_t>::const_iterator iter = out.begin(); iter != out.end(); ++iter)
{
completion_set.insert(iter->completion);
}
std::set<file_id_t> visited_files;
int res = wildcard_expand_internal( wc, base_dir, flags, out, completion_set, visited_files );
if( flags & ACCEPT_INCOMPLETE ) if( flags & ACCEPT_INCOMPLETE )
{ {
@ -1062,7 +1090,6 @@ int wildcard_expand( const wchar_t *wc,
int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_flags_t flags, std::vector<completion_t> &outputs ) int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_flags_t flags, std::vector<completion_t> &outputs )
{ {
std::vector<completion_t> lst; std::vector<completion_t> lst;
int res = wildcard_expand(wc.c_str(), base_dir.c_str(), flags, lst); int res = wildcard_expand(wc.c_str(), base_dir.c_str(), flags, lst);
outputs.insert(outputs.end(), lst.begin(), lst.end()); outputs.insert(outputs.end(), lst.begin(), lst.end());
return res; return res;