mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-23 10:00:43 +08:00
deleted no longer necessary codes due to removing process expansion.
This commit is contained in:
parent
c0f832a743
commit
d88866ccf7
390
src/expand.cpp
390
src/expand.cpp
|
@ -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 [.
|
||||
|
|
Loading…
Reference in New Issue
Block a user