deleted no longer necessary codes due to removing process expansion.

This commit is contained in:
slama 2018-03-24 17:34:58 +09:00 committed by ridiculousfish
parent c0f832a743
commit d88866ccf7

View File

@ -54,33 +54,9 @@
#include "tokenizer.h"
#endif
/// Description for child process.
#define COMPLETE_CHILD_PROCESS_DESC _(L"Child process")
/// Description for non-child process.
#define COMPLETE_PROCESS_DESC _(L"Process")
/// Description for long job.
#define COMPLETE_JOB_DESC _(L"Job")
/// Description for short job. The job command is concatenated.
#define COMPLETE_JOB_DESC_VAL _(L"Job: %ls")
/// Description for the shells own pid.
#define COMPLETE_SELF_DESC _(L"Shell process")
/// Description for the shells own pid.
#define COMPLETE_LAST_DESC _(L"Last background job")
/// String in process expansion denoting ourself.
#define SELF_STR L"self"
/// String in process expansion denoting last background job.
#define LAST_STR L"last"
/// Characters which make a string unclean if they are the first character of the string. See \c
/// expand_is_clean().
#define UNCLEAN_FIRST L"~%"
#define UNCLEAN_FIRST L"~"
/// Unclean characters. See \c expand_is_clean().
#define UNCLEAN L"$*?\\\"'({})"
@ -201,370 +177,6 @@ wcstring expand_escape_variable(const env_var_t &var) {
return buff;
}
/// Tests if all characters in the wide string are numeric.
static int iswnumeric(const wchar_t *n) {
for (; *n; n++) {
if (*n < L'0' || *n > L'9') {
return 0;
}
}
return 1;
}
/// See if the process described by \c proc matches the commandline \c cmd.
static bool match_pid(const wcstring &cmd, const wchar_t *proc, size_t *offset) {
// 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.c_str(), proc, wcslen(proc)) == 0) {
if (offset) *offset = 0;
return true;
}
// 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);
// It's a match. Return the offset within the full command.
if (result && offset) *offset = cmd.size() - base_cmd.size();
return result;
}
/// Helper class for iterating over processes. The names returned have been unescaped (e.g. may
/// include spaces).
#ifdef KERN_PROCARGS2
// BSD / OS X process completions.
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);
};
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) 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;
}
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() { dir = opendir("/proc"); }
process_iterator_t::~process_iterator_t() {
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 = fish_wcstoi(name.c_str());
if (errno || pid < 0) {
debug(1, _(L"Unexpected failure to convert pid '%ls' to integer\n"), name.c_str());
}
// The 'cmdline' file exists, it should contain the commandline.
FILE *cmdfile;
if ((cmdfile = wfopen(path + L"/cmdline", "r"))) {
wcstring full_command_line;
fgetws2(&full_command_line, cmdfile);
// The command line needs to be escaped.
cmd = tok_first(full_command_line);
}
#ifdef SunOS
else if ((cmdfile = wfopen(path + L"/psinfo", "r"))) {
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
/// The following function is invoked on the main thread, because the job list is not thread safe.
/// It should search the job list for something matching the given proc, and then return true to
/// stop the search, false to continue it.
static bool find_job(const wchar_t *proc, expand_flags_t flags,
std::vector<completion_t> *completions) {
ASSERT_IS_MAIN_THREAD();
bool found = false;
// If we are not doing tab completion, we first check for the single '%' character, because an
// empty string will pass the numeric check below. But if we are doing tab completion, we want
// all of the job IDs as completion options, not just the last job backgrounded, so we pass this
// first block in favor of the second.
if (wcslen(proc) == 0 && !(flags & EXPAND_FOR_COMPLETIONS)) {
// This is an empty job expansion: '%'. It expands to the last job backgrounded.
job_iterator_t jobs;
while (const job_t *j = jobs.next()) {
if (!j->command_is_empty()) {
append_completion(completions, to_string<long>(j->pgid));
break;
}
}
// You don't *really* want to flip a coin between killing the last process backgrounded and
// all processes, do you? Let's not try other match methods with the solo '%' syntax.
found = true;
} else if (iswnumeric(proc)) {
// This is a numeric job string, like '%2'.
if (flags & EXPAND_FOR_COMPLETIONS) {
job_iterator_t jobs;
while (const job_t *j = jobs.next()) {
wchar_t jid[16];
if (j->command_is_empty()) continue;
swprintf(jid, 16, L"%d", j->job_id);
if (wcsncmp(proc, jid, wcslen(proc)) == 0) {
wcstring desc_buff = format_string(COMPLETE_JOB_DESC_VAL, j->command_wcstr());
append_completion(completions, jid + wcslen(proc), desc_buff, 0);
}
}
} else {
int jid = fish_wcstoi(proc);
if (!errno && jid > 0) {
const job_t *j = job_get(jid);
if (j && !j->command_is_empty()) {
append_completion(completions, to_string<long>(j->pgid));
}
}
}
// Stop here so you can't match a random process name when you're just trying to use job
// control.
found = true;
}
if (found) {
return found;
}
job_iterator_t jobs;
while (const job_t *j = jobs.next()) {
if (j->command_is_empty()) continue;
size_t offset = 0;
if (match_pid(j->command(), proc, &offset)) {
if (flags & EXPAND_FOR_COMPLETIONS) {
append_completion(completions, j->command_wcstr() + offset + wcslen(proc),
COMPLETE_JOB_DESC, 0);
} else {
append_completion(completions, to_string<long>(j->pgid));
found = 1;
}
}
}
if (found) {
return found;
}
jobs.reset();
while (const job_t *j = jobs.next()) {
if (j->command_is_empty()) continue;
for (const process_ptr_t &p : j->processes) {
if (p->actual_cmd.empty()) continue;
size_t offset = 0;
if (match_pid(p->actual_cmd, proc, &offset)) {
if (flags & EXPAND_FOR_COMPLETIONS) {
append_completion(completions, wcstring(p->actual_cmd, offset + wcslen(proc)),
COMPLETE_CHILD_PROCESS_DESC, 0);
} else {
append_completion(completions, to_string<long>(p->pid), L"", 0);
found = 1;
}
}
}
}
return found;
}
/// Searches for a job with the specified job id, or a job or process which has the string \c proc
/// as a prefix of its commandline. Appends the name of the process as a completion in 'out'.
///
/// Otherwise, any job matching the specified string is matched, and the job pgid is returned. If no
/// job matches, all child processes are searched. If no child processes match, and <tt>fish</tt>
/// can understand the contents of the /proc filesystem, all the users processes are searched for
/// matches.
static void find_process(const wchar_t *proc, expand_flags_t flags,
std::vector<completion_t> *out) {
if (!(flags & EXPAND_SKIP_JOBS)) {
bool found = false;
iothread_perform_on_main([&]() { found = find_job(proc, flags, out); });
if (found) {
return;
}
}
// Iterate over all processes.
wcstring process_name;
pid_t process_pid;
process_iterator_t iterator;
while (iterator.next_process(&process_name, &process_pid)) {
size_t offset = 0;
if (match_pid(process_name, proc, &offset)) {
if (flags & EXPAND_FOR_COMPLETIONS) {
append_completion(out, process_name.c_str() + offset + wcslen(proc),
COMPLETE_PROCESS_DESC, 0);
} else {
append_completion(out, to_string<long>(process_pid));
}
}
}
}
/// Parse an array slicing specification Returns 0 on success. If a parse error occurs, returns the
/// index of the bad token. Note that 0 can never be a bad index because the string always starts
/// with [.