diff --git a/common.cpp b/common.cpp index e85104fc7..b36b0a076 100644 --- a/common.cpp +++ b/common.cpp @@ -136,6 +136,40 @@ wcstring_list_t completions_to_wcstring_list( const std::vector &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 ) { diff --git a/common.h b/common.h index 774139a5b..1a85fde2c 100644 --- a/common.h +++ b/common.h @@ -206,6 +206,9 @@ wcstring_list_t completions_to_wcstring_list( const std::vector &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 &strings); void sort_completions( std::vector &strings); diff --git a/expand.cpp b/expand.cpp index 2824b9540..f0777362e 100644 --- a/expand.cpp +++ b/expand.cpp @@ -16,11 +16,13 @@ parameter expansion. #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -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 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 expand_get_all_process_names(void) +{ + wcstring name; + pid_t pid; + process_iterator_t iterator; + std::vector 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 &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 &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 &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))) || diff --git a/expand.h b/expand.h index 99993ce64..1181570c1 100644 --- a/expand.h +++ b/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 expand_get_all_process_names(void); #endif diff --git a/tokenizer.cpp b/tokenizer.cpp index be6aee43f..acc2ce699 100644 --- a/tokenizer.cpp +++ b/tokenizer.cpp @@ -696,7 +696,6 @@ wchar_t *tok_first( const wchar_t *str ) return res; } - int tok_get_pos( tokenizer *tok ) { CHECK( tok, 0 );