Implemented process expansion on OS X

Also fixed issue where process expansion would always fail for processes with spaces
Fixes https://github.com/fish-shell/fish-shell/issues/56
This commit is contained in:
ridiculousfish 2012-07-16 12:05:36 -07:00
parent 548ea1e54a
commit 33c6410809
5 changed files with 367 additions and 209 deletions

View file

@ -136,6 +136,40 @@ wcstring_list_t completions_to_wcstring_list( const std::vector<completion_t> &l
return strings; return strings;
} }
int fgetws2(wcstring *s, FILE *f)
{
int i=0;
wint_t c;
while( 1 )
{
errno=0;
c = getwc( f );
if( errno == EILSEQ )
{
continue;
}
switch( c )
{
/* End of line */
case WEOF:
case L'\n':
case L'\0':
return i;
/* Ignore carriage returns */
case L'\r':
break;
default:
i++;
s->push_back((wchar_t)c);
break;
}
}
}
int fgetws2( wchar_t **b, int *len, FILE *f ) int fgetws2( wchar_t **b, int *len, FILE *f )
{ {

View file

@ -206,6 +206,9 @@ wcstring_list_t completions_to_wcstring_list( const std::vector<completion_t> &c
*/ */
int fgetws2( wchar_t **buff, int *len, FILE *f ); int fgetws2( wchar_t **buff, int *len, FILE *f );
/** Like fgetws2, but reads into a string */
int fgetws2(wcstring *s, FILE *f);
void sort_strings( std::vector<wcstring> &strings); void sort_strings( std::vector<wcstring> &strings);
void sort_completions( std::vector<completion_t> &strings); void sort_completions( std::vector<completion_t> &strings);

View file

@ -16,11 +16,13 @@ parameter expansion.
#include <pwd.h> #include <pwd.h>
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/sysctl.h>
#include <termios.h> #include <termios.h>
#include <dirent.h> #include <dirent.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <signal.h> #include <signal.h>
#include <algorithm>
#include <assert.h> #include <assert.h>
#include <vector> #include <vector>
@ -121,6 +123,8 @@ parameter expansion.
*/ */
#define UNCLEAN L"$*?\\\"'({})" #define UNCLEAN L"$*?\\\"'({})"
static void remove_internal_separator( wcstring &s, bool conv );
int expand_is_clean( const wchar_t *in ) int expand_is_clean( const wchar_t *in )
{ {
@ -259,59 +263,288 @@ static int iswnumeric( const wchar_t *n )
See if the process described by \c proc matches the commandline \c See if the process described by \c proc matches the commandline \c
cmd cmd
*/ */
static int match_pid( const wchar_t *cmd, static bool match_pid( const wchar_t *cmd,
const wchar_t *proc, const wchar_t *proc,
int flags, int flags,
int *offset) int *offset)
{ {
/* Test for direct match */ /* Test for a direct match. If the proc string is empty (e.g. the user tries to complete against %), then return an offset pointing at the base command. That ensures that you don't see a bunch of dumb paths when completing against all processes. */
if( proc[0] != L'\0' && wcsncmp( cmd, proc, wcslen( proc ) ) == 0 )
if( wcsncmp( cmd, proc, wcslen( proc ) ) == 0 )
{ {
if( offset ) if( offset )
*offset = 0; *offset = 0;
return 1; return true;
} }
/* /* Get the command to match against. We're only interested in the last path component. */
Test if the commandline is a path to the command, if so we try const wcstring base_cmd = wbasename(cmd);
to match against only the command part
*/ bool result = string_prefixes_string(proc, base_cmd);
wchar_t *first_token = tok_first( cmd ); if (result)
if( first_token == 0 ) {
return 0; /* It's a match. Return the offset within the full command. */
if (offset)
*offset = wcslen(cmd) - base_cmd.size();
}
return result;
}
wchar_t *start=0; /** Helper class for iterating over processes. The names returned have been unescaped (e.g. may include spaces) */
wchar_t prev=0; #ifdef KERN_PROCARGS2
wchar_t *p;
/* /* BSD / OS X process completions */
This should be done by basename(), if it wasn't for the fact
that is does not accept wide strings
*/
for( p=first_token; *p; p++ )
{
if( *p == L'/' && prev != L'\\' )
start = p;
prev = *p;
}
if( start )
{
if( wcsncmp( start+1, proc, wcslen( proc ) ) == 0 ) class process_iterator_t {
{ std::vector<pid_t> pids;
if( offset ) size_t idx;
*offset = start+1-first_token;
wcstring name_for_pid(pid_t pid);
free( first_token );
public:
process_iterator_t();
bool next_process(wcstring *str, pid_t *pid);
};
return 1; wcstring process_iterator_t::name_for_pid(pid_t pid)
} {
} wcstring result;
free( first_token ); int mib[4], maxarg = 0, numArgs = 0;
size_t size = 0;
char *args = NULL, *stringPtr = NULL;
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
size = sizeof(maxarg);
if (sysctl(mib, 2, &maxarg, &size, NULL, 0) == -1) {
return result;
}
args = (char *)malloc( maxarg );
if ( args == NULL ) {
return result;
}
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = pid;
size = (size_t)maxarg;
if ( sysctl(mib, 3, args, &size, NULL, 0) == -1 ) {
free( args );
return result;;
}
memcpy( &numArgs, args, sizeof(numArgs) );
stringPtr = args + sizeof(numArgs);
result = str2wcstring(stringPtr);
free(args);
return result;
}
return 0; bool process_iterator_t::next_process(wcstring *out_str, pid_t *out_pid)
{
wcstring name;
pid_t pid = 0;
bool result = false;
while (idx < pids.size())
{
pid = pids.at(idx++);
name = name_for_pid(pid);
if (! name.empty())
{
result = true;
break;
}
}
if (result)
{
*out_str = name;
*out_pid = pid;
}
return result;
}
process_iterator_t::process_iterator_t() : idx(0)
{
int err;
struct kinfo_proc * result;
bool done;
static const int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
// Declaring name as const requires us to cast it when passing it to
// sysctl because the prototype doesn't include the const modifier.
size_t length;
// We start by calling sysctl with result == NULL and length == 0.
// That will succeed, and set length to the appropriate length.
// We then allocate a buffer of that size and call sysctl again
// with that buffer. If that succeeds, we're done. If that fails
// with ENOMEM, we have to throw away our buffer and loop. Note
// that the loop causes use to call sysctl with NULL again; this
// is necessary because the ENOMEM failure case sets length to
// the amount of data returned, not the amount of data that
// could have been returned.
result = NULL;
done = false;
do {
assert(result == NULL);
// Call sysctl with a NULL buffer.
length = 0;
err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1,
NULL, &length,
NULL, 0);
if (err == -1) {
err = errno;
}
// Allocate an appropriately sized buffer based on the results
// from the previous call.
if (err == 0) {
result = (struct kinfo_proc *)malloc(length);
if (result == NULL) {
err = ENOMEM;
}
}
// Call sysctl again with the new buffer. If we get an ENOMEM
// error, toss away our buffer and start again.
if (err == 0) {
err = sysctl( (int *) name, (sizeof(name) / sizeof(*name)) - 1,
result, &length,
NULL, 0);
if (err == -1) {
err = errno;
}
if (err == 0) {
done = true;
} else if (err == ENOMEM) {
assert(result != NULL);
free(result);
result = NULL;
err = 0;
}
}
} while (err == 0 && ! done);
// Clean up and establish post conditions.
if (err == 0 && result != NULL)
{
for (size_t idx = 0; idx < length / sizeof(struct kinfo_proc); idx++)
pids.push_back(result[idx].kp_proc.p_pid);
}
if (result)
free(result);
}
#else
/* /proc style process completions */
class process_iterator_t {
DIR *dir;
public:
process_iterator_t();
~process_iterator_t();
bool next_process(wcstring *out_str, pid_t *out_pid);
};
process_iterator_t::process_iterator_t(void)
{
dir = opendir( "/proc" );
}
process_iterator_t::~process_iterator_t(void)
{
if (dir)
closedir(dir);
}
bool process_iterator_t::next_process(wcstring *out_str, pid_t *out_pid)
{
wcstring cmd;
pid_t pid = 0;
while (cmd.empty())
{
wcstring name;
if (! dir || ! wreaddir(dir, name))
break;
if (!iswnumeric(name.c_str()))
continue;
wcstring path = wcstring(L"/proc/") + name;
struct stat buf;
if (wstat(path, &buf))
continue;
if( buf.st_uid != getuid() )
continue;
/* remember the pid */
pid = (long)wcstol(name.c_str(), NULL, 10);
/* the 'cmdline' file exists, it should contain the commandline */
FILE *cmdfile;
if ((cmdfile=wfopen(path + L"/cmdline", "r"))==0)
{
wcstring full_command_line;
signal_block();
fgetws2(&full_command_line, cmdfile);
signal_unblock();
/* The command line needs to be escaped */
wchar_t *first_arg = tok_first( full_command_line.c_str() );
if (first_arg)
{
cmd = first_arg;
free(first_arg);
}
}
#ifdef SunOS
else if ((cmdfile=wfopen(path + L"/psinfo", "r"))==0)
{
psinfo_t info;
if (fread(&info, sizeof(info), 1, cmdfile))
{
/* The filename is unescaped */
cmd = str2wcstring(info.pr_fname);
}
}
#endif
if (cmdfile)
fclose(cmdfile);
}
bool result = ! cmd.empty();
if (result)
{
*out_str = cmd;
*out_pid = pid;
}
return result;
}
#endif
std::vector<wcstring> expand_get_all_process_names(void)
{
wcstring name;
pid_t pid;
process_iterator_t iterator;
std::vector<wcstring> result;
while (iterator.next_process(&name, &pid))
{
result.push_back(name);
}
return result;
} }
/** /**
@ -332,11 +565,6 @@ static int find_process( const wchar_t *proc,
int flags, int flags,
std::vector<completion_t> &out ) std::vector<completion_t> &out )
{ {
DIR *dir;
wchar_t *pdir_name;
wchar_t *pfile_name;
wchar_t *cmd=0;
int sz=0;
int found = 0; int found = 0;
const job_t *j; const job_t *j;
@ -400,7 +628,7 @@ static int find_process( const wchar_t *proc,
{ {
int offset; int offset;
if( j->command_wcstr() == 0 ) if( j->command_is_empty() )
continue; continue;
if( match_pid( j->command_wcstr(), proc, flags, &offset ) ) if( match_pid( j->command_wcstr(), proc, flags, &offset ) )
@ -463,122 +691,29 @@ static int find_process( const wchar_t *proc,
return 1; return 1;
} }
if( !(dir = opendir( "/proc" ))) /* Iterate over all processes */
{ wcstring process_name;
/* pid_t process_pid;
This system does not have a /proc filesystem. process_iterator_t iterator;
*/ while (iterator.next_process(&process_name, &process_pid))
return 1; {
} int offset;
pdir_name = (wchar_t *)malloc( sizeof(wchar_t)*256 ); if( match_pid( process_name.c_str(), proc, flags, &offset ) )
pfile_name = (wchar_t *)malloc( sizeof(wchar_t)*64 ); {
wcscpy( pdir_name, L"/proc/" ); if( flags & ACCEPT_INCOMPLETE )
{
wcstring nameStr; completion_allocate( out,
while (wreaddir(dir, nameStr)) process_name.c_str() + offset + wcslen(proc),
{ COMPLETE_PROCESS_DESC,
const wchar_t *name = nameStr.c_str(); 0 );
struct stat buf; }
else
if( !iswnumeric( name ) ) {
continue; out.push_back(completion_t(to_string((long)process_pid)));
}
wcscpy( pdir_name + 6, name ); }
if( wstat( pdir_name, &buf ) ) }
{
continue;
}
if( buf.st_uid != getuid() )
{
continue;
}
wcscpy( pfile_name, pdir_name );
wcscat( pfile_name, L"/cmdline" );
if( !wstat( pfile_name, &buf ) )
{
/*
the 'cmdline' file exists, it should contain the commandline
*/
FILE *cmdfile;
if((cmdfile=wfopen( pfile_name, "r" ))==0)
{
wperror( L"fopen" );
continue;
}
signal_block();
fgetws2( &cmd, &sz, cmdfile );
signal_unblock();
fclose( cmdfile );
}
else
{
#ifdef SunOS
wcscpy( pfile_name, pdir_name );
wcscat( pfile_name, L"/psinfo" );
if( !wstat( pfile_name, &buf ) )
{
psinfo_t info;
FILE *psfile;
if((psfile=wfopen( pfile_name, "r" ))==0)
{
wperror( L"fopen" );
continue;
}
if( fread( &info, sizeof(info), 1, psfile ) )
{
if( cmd != 0 )
free( cmd );
cmd = str2wcs( info.pr_fname );
}
fclose( psfile );
}
else
#endif
{
if( cmd != 0 )
{
*cmd = 0;
}
}
}
if( cmd != 0 )
{
int offset;
if( match_pid( cmd, proc, flags, &offset ) )
{
if( flags & ACCEPT_INCOMPLETE )
{
completion_allocate( out,
cmd + offset + wcslen(proc),
COMPLETE_PROCESS_DESC,
0 );
}
else
{
if (name)
out.push_back(completion_t(name));
}
}
}
}
if( cmd != 0 )
free( cmd );
free( pdir_name );
free( pfile_name );
closedir( dir );
return 1; return 1;
} }
@ -586,11 +721,15 @@ static int find_process( const wchar_t *proc,
/** /**
Process id expansion Process id expansion
*/ */
static int expand_pid( const wcstring &instr, static int expand_pid( const wcstring &instr_with_sep,
int flags, int flags,
std::vector<completion_t> &out ) std::vector<completion_t> &out )
{ {
/* expand_string calls us with internal separators in instr...sigh */
wcstring instr = instr_with_sep;
remove_internal_separator(instr, false);
if( instr.empty() || instr.at(0) != PROCESS_EXPAND ) if( instr.empty() || instr.at(0) != PROCESS_EXPAND )
{ {
out.push_back(completion_t(instr)); out.push_back(completion_t(instr));
@ -1366,49 +1505,28 @@ void expand_tilde( wcstring &input)
Remove any internal separators. Also optionally convert wildcard characters to Remove any internal separators. Also optionally convert wildcard characters to
regular equivalents. This is done to support EXPAND_SKIP_WILDCARDS. regular equivalents. This is done to support EXPAND_SKIP_WILDCARDS.
*/ */
static void remove_internal_separator( const void *s, int conv ) static void remove_internal_separator( wcstring &str, bool conv )
{ {
wchar_t *in = (wchar_t *)s; /* Remove all instances of INTERNAL_SEPARATOR */
wchar_t *out=in; str.erase(std::remove(str.begin(), str.end(), (wchar_t)INTERNAL_SEPARATOR), str.end());
CHECK( s, ); /* If conv is true, replace all instances of ANY_CHAR with '?', ANY_STRING with '*', ANY_STRING_RECURSIVE with '*' */
if (conv)
while( *in ) {
{ for (size_t idx = 0; idx < str.size(); idx++)
switch( *in ) {
{ switch (str.at(idx))
case INTERNAL_SEPARATOR: {
in++; case ANY_CHAR:
break; str.at(idx) = L'?';
break;
case ANY_CHAR: case ANY_STRING:
in++; case ANY_STRING_RECURSIVE:
*out++ = conv?L'?':ANY_CHAR; str.at(idx) = L'*';
break; break;
}
case ANY_STRING: }
in++; }
*out++ = conv?L'*':ANY_STRING;
break;
case ANY_STRING_RECURSIVE:
in++;
*out++ = conv?L'*':ANY_STRING_RECURSIVE;
break;
default:
*out++ = *in++;
}
}
*out=0;
}
static void remove_internal_separator2( wcstring &s, int conv )
{
wchar_t *tmp = wcsdup(s.c_str());
remove_internal_separator(tmp, conv);
s = tmp;
free(tmp);
} }
@ -1542,7 +1660,7 @@ int expand_string( const wcstring &input, std::vector<completion_t> &output, exp
wcstring next_str = in->at(i).completion; wcstring next_str = in->at(i).completion;
int wc_res; int wc_res;
remove_internal_separator2( next_str, EXPAND_SKIP_WILDCARDS & flags ); remove_internal_separator( next_str, (EXPAND_SKIP_WILDCARDS & flags) ? true : false );
const wchar_t *next = next_str.c_str(); const wchar_t *next = next_str.c_str();
if( ((flags & ACCEPT_INCOMPLETE) && (!(flags & EXPAND_SKIP_WILDCARDS))) || if( ((flags & ACCEPT_INCOMPLETE) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||

View file

@ -192,5 +192,9 @@ int expand_is_clean( const wchar_t *in );
*/ */
void expand_variable_error( parser_t &parser, const wchar_t *token, int token_pos, int error_pos ); void expand_variable_error( parser_t &parser, const wchar_t *token, int token_pos, int error_pos );
/**
Testing function for getting all process names.
*/
std::vector<wcstring> expand_get_all_process_names(void);
#endif #endif

View file

@ -696,7 +696,6 @@ wchar_t *tok_first( const wchar_t *str )
return res; return res;
} }
int tok_get_pos( tokenizer *tok ) int tok_get_pos( tokenizer *tok )
{ {
CHECK( tok, 0 ); CHECK( tok, 0 );