mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-11 07:34:32 +00:00
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:
parent
548ea1e54a
commit
33c6410809
5 changed files with 367 additions and 209 deletions
34
common.cpp
34
common.cpp
|
@ -136,6 +136,40 @@ wcstring_list_t completions_to_wcstring_list( const std::vector<completion_t> &l
|
|||
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 )
|
||||
{
|
||||
|
|
3
common.h
3
common.h
|
@ -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 );
|
||||
|
||||
/** Like fgetws2, but reads into a string */
|
||||
int fgetws2(wcstring *s, FILE *f);
|
||||
|
||||
void sort_strings( std::vector<wcstring> &strings);
|
||||
|
||||
void sort_completions( std::vector<completion_t> &strings);
|
||||
|
|
534
expand.cpp
534
expand.cpp
|
@ -16,11 +16,13 @@ parameter expansion.
|
|||
#include <pwd.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <termios.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
|
@ -121,6 +123,8 @@ parameter expansion.
|
|||
*/
|
||||
#define UNCLEAN L"$*?\\\"'({})"
|
||||
|
||||
static void remove_internal_separator( wcstring &s, bool conv );
|
||||
|
||||
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
|
||||
cmd
|
||||
*/
|
||||
static int match_pid( const wchar_t *cmd,
|
||||
const wchar_t *proc,
|
||||
int flags,
|
||||
int *offset)
|
||||
static bool match_pid( const wchar_t *cmd,
|
||||
const wchar_t *proc,
|
||||
int flags,
|
||||
int *offset)
|
||||
{
|
||||
/* Test for direct match */
|
||||
|
||||
if( wcsncmp( cmd, proc, wcslen( proc ) ) == 0 )
|
||||
/* 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( offset )
|
||||
*offset = 0;
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
Test if the commandline is a path to the command, if so we try
|
||||
to match against only the command part
|
||||
*/
|
||||
wchar_t *first_token = tok_first( cmd );
|
||||
if( first_token == 0 )
|
||||
return 0;
|
||||
|
||||
/* Get the command to match against. We're only interested in the last path component. */
|
||||
const wcstring base_cmd = wbasename(cmd);
|
||||
|
||||
bool result = string_prefixes_string(proc, base_cmd);
|
||||
if (result)
|
||||
{
|
||||
/* 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;
|
||||
wchar_t prev=0;
|
||||
wchar_t *p;
|
||||
/** Helper class for iterating over processes. The names returned have been unescaped (e.g. may include spaces) */
|
||||
#ifdef KERN_PROCARGS2
|
||||
|
||||
/*
|
||||
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 )
|
||||
{
|
||||
/* BSD / OS X process completions */
|
||||
|
||||
if( wcsncmp( start+1, proc, wcslen( proc ) ) == 0 )
|
||||
{
|
||||
if( offset )
|
||||
*offset = start+1-first_token;
|
||||
|
||||
free( first_token );
|
||||
class process_iterator_t {
|
||||
std::vector<pid_t> pids;
|
||||
size_t idx;
|
||||
|
||||
wcstring name_for_pid(pid_t pid);
|
||||
|
||||
public:
|
||||
process_iterator_t();
|
||||
bool next_process(wcstring *str, pid_t *pid);
|
||||
};
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
free( first_token );
|
||||
wcstring process_iterator_t::name_for_pid(pid_t pid)
|
||||
{
|
||||
wcstring result;
|
||||
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,
|
||||
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;
|
||||
|
||||
const job_t *j;
|
||||
|
@ -400,7 +628,7 @@ static int find_process( const wchar_t *proc,
|
|||
{
|
||||
int offset;
|
||||
|
||||
if( j->command_wcstr() == 0 )
|
||||
if( j->command_is_empty() )
|
||||
continue;
|
||||
|
||||
if( match_pid( j->command_wcstr(), proc, flags, &offset ) )
|
||||
|
@ -463,122 +691,29 @@ static int find_process( const wchar_t *proc,
|
|||
return 1;
|
||||
}
|
||||
|
||||
if( !(dir = opendir( "/proc" )))
|
||||
{
|
||||
/*
|
||||
This system does not have a /proc filesystem.
|
||||
*/
|
||||
return 1;
|
||||
}
|
||||
|
||||
pdir_name = (wchar_t *)malloc( sizeof(wchar_t)*256 );
|
||||
pfile_name = (wchar_t *)malloc( sizeof(wchar_t)*64 );
|
||||
wcscpy( pdir_name, L"/proc/" );
|
||||
|
||||
wcstring nameStr;
|
||||
while (wreaddir(dir, nameStr))
|
||||
{
|
||||
const wchar_t *name = nameStr.c_str();
|
||||
struct stat buf;
|
||||
|
||||
if( !iswnumeric( name ) )
|
||||
continue;
|
||||
|
||||
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 );
|
||||
/* Iterate over all processes */
|
||||
wcstring process_name;
|
||||
pid_t process_pid;
|
||||
process_iterator_t iterator;
|
||||
while (iterator.next_process(&process_name, &process_pid))
|
||||
{
|
||||
int offset;
|
||||
|
||||
if( match_pid( process_name.c_str(), proc, flags, &offset ) )
|
||||
{
|
||||
if( flags & ACCEPT_INCOMPLETE )
|
||||
{
|
||||
completion_allocate( out,
|
||||
process_name.c_str() + offset + wcslen(proc),
|
||||
COMPLETE_PROCESS_DESC,
|
||||
0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
out.push_back(completion_t(to_string((long)process_pid)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
@ -586,11 +721,15 @@ static int find_process( const wchar_t *proc,
|
|||
/**
|
||||
Process id expansion
|
||||
*/
|
||||
static int expand_pid( const wcstring &instr,
|
||||
static int expand_pid( const wcstring &instr_with_sep,
|
||||
int flags,
|
||||
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 )
|
||||
{
|
||||
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
|
||||
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;
|
||||
wchar_t *out=in;
|
||||
|
||||
CHECK( s, );
|
||||
|
||||
while( *in )
|
||||
{
|
||||
switch( *in )
|
||||
{
|
||||
case INTERNAL_SEPARATOR:
|
||||
in++;
|
||||
break;
|
||||
|
||||
case ANY_CHAR:
|
||||
in++;
|
||||
*out++ = conv?L'?':ANY_CHAR;
|
||||
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);
|
||||
/* Remove all instances of INTERNAL_SEPARATOR */
|
||||
str.erase(std::remove(str.begin(), str.end(), (wchar_t)INTERNAL_SEPARATOR), str.end());
|
||||
|
||||
/* If conv is true, replace all instances of ANY_CHAR with '?', ANY_STRING with '*', ANY_STRING_RECURSIVE with '*' */
|
||||
if (conv)
|
||||
{
|
||||
for (size_t idx = 0; idx < str.size(); idx++)
|
||||
{
|
||||
switch (str.at(idx))
|
||||
{
|
||||
case ANY_CHAR:
|
||||
str.at(idx) = L'?';
|
||||
break;
|
||||
case ANY_STRING:
|
||||
case ANY_STRING_RECURSIVE:
|
||||
str.at(idx) = L'*';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1542,7 +1660,7 @@ int expand_string( const wcstring &input, std::vector<completion_t> &output, exp
|
|||
wcstring next_str = in->at(i).completion;
|
||||
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();
|
||||
|
||||
if( ((flags & ACCEPT_INCOMPLETE) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
|
||||
|
|
4
expand.h
4
expand.h
|
@ -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 );
|
||||
|
||||
/**
|
||||
Testing function for getting all process names.
|
||||
*/
|
||||
std::vector<wcstring> expand_get_all_process_names(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -696,7 +696,6 @@ wchar_t *tok_first( const wchar_t *str )
|
|||
return res;
|
||||
}
|
||||
|
||||
|
||||
int tok_get_pos( tokenizer *tok )
|
||||
{
|
||||
CHECK( tok, 0 );
|
||||
|
|
Loading…
Reference in a new issue