Clean up job flags, status helpers, and instance helper methods

* Convert JOB_* enums to scoped enums
* Convert standalone job_is_* functions to member functions
* Convert standalone job_{promote, signal, continue} to member functions
* Convert standolen job_get{,_from_pid} to `job_t` static functions
* Reduce usage of JOB_* enums outside of proc.cpp by using new
  `job_t::is_foo()` const helper methods instead.

This patch is only a refactor and should not change any functionality or
behavior (both observed and unobserved).
This commit is contained in:
Mahmoud Al-Qudsi 2018-10-02 12:30:23 -05:00
parent e753581df7
commit f9118d964e
14 changed files with 196 additions and 191 deletions

View file

@ -18,7 +18,7 @@
/// Helper function for builtin_bg().
static int send_to_bg(parser_t &parser, io_streams_t &streams, job_t *j) {
assert(j != NULL);
if (!j->get_flag(JOB_CONTROL)) {
if (!j->get_flag(job_flag_t::JOB_CONTROL)) {
streams.err.append_format(
_(L"%ls: Can't put job %d, '%ls' to background because it is not under job control\n"),
L"bg", j->job_id, j->command_wcstr());
@ -28,9 +28,9 @@ static int send_to_bg(parser_t &parser, io_streams_t &streams, job_t *j) {
streams.err.append_format(_(L"Send job %d '%ls' to background\n"), j->job_id,
j->command_wcstr());
job_promote(j);
j->set_flag(JOB_FOREGROUND, false);
job_continue(j, job_is_stopped(j));
j->promote();
j->set_flag(job_flag_t::FOREGROUND, false);
j->continue_job(j->is_stopped());
return STATUS_CMD_OK;
}
@ -54,7 +54,7 @@ int builtin_bg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
job_t *j;
job_iterator_t jobs;
while ((j = jobs.next())) {
if (job_is_stopped(j) && j->get_flag(JOB_CONTROL) && (!job_is_completed(j))) {
if (j->is_stopped() && j->get_flag(job_flag_t::JOB_CONTROL) && (!j->is_completed())) {
break;
}
}
@ -89,7 +89,7 @@ int builtin_bg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
// Background all existing jobs that match the pids.
// Non-existent jobs aren't an error, but information about them is useful.
for (auto p : pids) {
if (job_t *j = job_get_from_pid(p)) {
if (job_t *j = job_t::from_pid(p)) {
retval |= send_to_bg(parser, streams, j);
} else {
streams.err.append_format(_(L"%ls: Could not find job '%d'\n"), cmd, p);

View file

@ -24,7 +24,7 @@ static int disown_job(const wchar_t *cmd, parser_t &parser, io_streams_t &stream
}
// Stopped disowned jobs must be manually signaled; explain how to do so.
if (job_is_stopped(j)) {
if (j->is_stopped()) {
killpg(j->pgid, SIGCONT);
const wchar_t *fmt =
_(L"%ls: job %d ('%ls') was stopped and has been signalled to continue.\n");
@ -58,7 +58,7 @@ int builtin_disown(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
// Even jobs that aren't under job control can be disowned!
job_iterator_t jobs;
while ((j = jobs.next())) {
if (j->get_flag(JOB_CONSTRUCTED) && (!job_is_completed(j))) {
if (j->is_constructed() && (!j->is_completed())) {
break;
}
}

View file

@ -39,9 +39,9 @@ int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
// the foreground.
job_iterator_t jobs;
while ((j = jobs.next())) {
if (j->get_flag(JOB_CONSTRUCTED) && (!job_is_completed(j)) &&
((job_is_stopped(j) || (!j->get_flag(JOB_FOREGROUND))) &&
j->get_flag(JOB_CONTROL))) {
if (j->is_constructed() && (!j->is_completed()) &&
((j->is_stopped() || (!j->is_foreground())) &&
j->get_flag(job_flag_t::JOB_CONTROL))) {
break;
}
}
@ -57,7 +57,7 @@ int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
pid = fish_wcstoi(argv[optind]);
if (!(errno || pid < 0)) {
j = job_get_from_pid(pid);
j = job_t::from_pid(pid);
if (j) found_job = 1;
}
@ -76,11 +76,11 @@ int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
streams.err.append_format(BUILTIN_ERR_NOT_NUMBER, cmd, argv[optind]);
builtin_print_help(parser, streams, cmd, streams.err);
} else {
j = job_get_from_pid(pid);
if (!j || !j->get_flag(JOB_CONSTRUCTED) || job_is_completed(j)) {
j = job_t::from_pid(pid);
if (!j || !j->is_constructed() || j->is_completed()) {
streams.err.append_format(_(L"%ls: No suitable job: %d\n"), cmd, pid);
j = 0;
} else if (!j->get_flag(JOB_CONTROL)) {
} else if (!j->get_flag(job_flag_t::JOB_CONTROL)) {
streams.err.append_format(_(L"%ls: Can't put job %d, '%ls' to foreground because "
L"it is not under job control\n"),
cmd, pid, j->command_wcstr());
@ -106,9 +106,9 @@ int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (!ft.empty()) env_set_one(L"_", ENV_EXPORT, ft);
reader_write_title(j->command());
job_promote(j);
j->set_flag(JOB_FOREGROUND, true);
j->promote();
j->set_flag(job_flag_t::FOREGROUND, true);
job_continue(j, job_is_stopped(j));
j->continue_job(j->is_stopped());
return STATUS_CMD_OK;
}

View file

@ -170,7 +170,7 @@ static wcstring functions_def(const wcstring &name) {
break;
}
case EVENT_JOB_ID: {
const job_t *j = job_get(next->param1.job_id);
const job_t *j = job_t::from_job_id(next->param1.job_id);
if (j) append_format(out, L" --on-job-exit %d", j->pgid);
break;
}

View file

@ -68,7 +68,7 @@ static void builtin_jobs_print(const job_t *j, int mode, int header, io_streams_
#ifdef HAVE__PROC_SELF_STAT
streams.out.append_format(L"%d%%\t", cpu_use(j));
#endif
streams.out.append(job_is_stopped(j) ? _(L"stopped") : _(L"running"));
streams.out.append(j->is_stopped() ? _(L"stopped") : _(L"running"));
streams.out.append(L"\t");
streams.out.append(j->command_wcstr());
streams.out.append(L"\n");
@ -177,7 +177,7 @@ int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
job_iterator_t jobs;
const job_t *j;
while ((j = jobs.next())) {
if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j)) {
if (j->is_constructed() && !j->is_completed()) {
builtin_jobs_print(j, mode, !streams.out_is_redirected, streams);
return STATUS_CMD_ERROR;
}
@ -197,7 +197,7 @@ int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
streams.err.append_format(_(L"%ls: '%ls' is not a valid job id"), cmd, argv[i]);
return STATUS_INVALID_ARGS;
}
j = job_get(jobId);
j = job_t::from_job_id(jobId);
}
else {
int pid = fish_wcstoi(argv[i]);
@ -205,10 +205,10 @@ int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
streams.err.append_format(_(L"%ls: '%ls' is not a valid process id\n"), cmd, argv[i]);
return STATUS_INVALID_ARGS;
}
j = job_get_from_pid(pid);
j = job_t::from_pid(pid);
}
if (j && !job_is_completed(j) && (j->flags & JOB_CONSTRUCTED)) {
if (j && !j->is_completed() && j->is_constructed()) {
builtin_jobs_print(j, mode, false, streams);
found = 1;
} else {
@ -221,7 +221,7 @@ int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
const job_t *j;
while ((j = jobs.next())) {
// Ignore unconstructed jobs, i.e. ourself.
if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j)) {
if (j->is_constructed() && !j->is_completed()) {
builtin_jobs_print(j, mode, !found && !streams.out_is_redirected, streams);
found = 1;
}

View file

@ -37,7 +37,7 @@ static bool all_jobs_finished() {
while (job_t *j = jobs.next()) {
// If any job is not completed, return false.
// If there are stopped jobs, they are ignored.
if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j) && !job_is_stopped(j)) {
if (j->is_constructed() && !j->is_completed() && !j->is_stopped()) {
return false;
}
}
@ -54,11 +54,11 @@ static bool any_jobs_finished(size_t jobs_len) {
}
while (job_t *j = jobs.next()) {
// If any job is completed, return true.
if ((j->flags & JOB_CONSTRUCTED) && (job_is_completed(j) || job_is_stopped(j))) {
if (j->is_constructed() && (j->is_completed() || j->is_stopped())) {
return true;
}
// Check for jobs running exist or not.
if ((j->flags & JOB_CONSTRUCTED) && !job_is_stopped(j)) {
if (j->is_constructed() && !j->is_stopped()) {
no_jobs_running = false;
}
}
@ -83,10 +83,10 @@ static int wait_for_backgrounds(bool any_flag) {
static bool all_specified_jobs_finished(const std::vector<job_id_t> &ids) {
for (auto id : ids) {
if (job_t *j = job_get(id)) {
if (job_t *j = job_t::from_job_id(id)) {
// If any specified job is not completed, return false.
// If there are stopped jobs, they are ignored.
if ((j->flags & JOB_CONSTRUCTED) && !job_is_completed(j) && !job_is_stopped(j)) {
if (j->is_constructed() && !j->is_completed() && !j->is_stopped()) {
return false;
}
}
@ -96,9 +96,9 @@ static bool all_specified_jobs_finished(const std::vector<job_id_t> &ids) {
static bool any_specified_jobs_finished(const std::vector<job_id_t> &ids) {
for (auto id : ids) {
if (job_t *j = job_get(id)) {
if (job_t *j = job_t::from_job_id(id)) {
// If any specified job is completed, return true.
if ((j->flags & JOB_CONSTRUCTED) && (job_is_completed(j) || job_is_stopped(j))) {
if (j->is_constructed() && (j->is_completed() || j->is_stopped())) {
return true;
}
} else {

26
src/enum_set.h Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <bitset>
template <typename T>
class enum_set_t {
private:
using base_type_t = typename std::underlying_type<T>::type;
std::bitset<8 * sizeof(base_type_t)> bitmask{0};
static int index_of(T t) { return static_cast<base_type_t>(t); }
public:
bool get(T t) const { return bitmask.test(index_of(t)); }
void set(T t, bool v) {
if (v) {
bitmask.set(index_of(t));
} else {
bitmask.reset(index_of(t));
}
}
void set(T t) { bitmask.set(index_of(t)); }
void clear(T t) { bitmask.reset(index_of(t)); }
};

View file

@ -129,7 +129,7 @@ wcstring event_get_desc(const event_t &e) {
result = format_string(_(L"exit handler for process %d"), e.param1.pid);
} else {
// In events, PGIDs are stored as negative PIDs
job_t *j = job_get_from_pid(-e.param1.pid);
job_t *j = job_t::from_pid(-e.param1.pid);
if (j)
result = format_string(_(L"exit handler for job %d, '%ls'"), j->job_id,
j->command_wcstr());
@ -140,7 +140,7 @@ wcstring event_get_desc(const event_t &e) {
break;
}
case EVENT_JOB_ID: {
job_t *j = job_get(e.param1.job_id);
job_t *j = job_t::from_job_id(e.param1.job_id);
if (j) {
result = format_string(_(L"exit handler for job %d, '%ls'"), j->job_id,
j->command_wcstr());
@ -211,7 +211,7 @@ static wcstring event_desc_compact(const event_t &event) {
res = format_string(L"EVENT_EXIT(pid %d)", event.param1.pid);
} else {
// In events, PGIDs are stored as negative PIDs
job_t *j = job_get_from_pid(-event.param1.pid);
job_t *j = job_t::from_pid(-event.param1.pid);
if (j)
res = format_string(L"EVENT_EXIT(jobid %d: \"%ls\")", j->job_id,
j->command_wcstr());
@ -221,7 +221,7 @@ static wcstring event_desc_compact(const event_t &event) {
break;
}
case EVENT_JOB_ID: {
job_t *j = job_get(event.param1.job_id);
job_t *j = job_t::from_job_id(event.param1.job_id);
if (j)
res =
format_string(L"EVENT_JOB_ID(job %d: \"%ls\")", j->job_id, j->command_wcstr());

View file

@ -336,10 +336,10 @@ void internal_exec_helper(parser_t &parser, parsed_source_ref_t parsed_source, t
// foreground process group, we don't use posix_spawn if we're going to foreground the process. (If
// we use fork(), we can call tcsetpgrp after the fork, before the exec, and avoid the race).
static bool can_use_posix_spawn_for_job(const job_t *job, const process_t *process) {
if (job->get_flag(JOB_CONTROL)) { //!OCLINT(collapsible if statements)
if (job->get_flag(job_flag_t::JOB_CONTROL)) { //!OCLINT(collapsible if statements)
// We are going to use job control; therefore when we launch this job it will get its own
// process group ID. But will it be foregrounded?
if (job->get_flag(JOB_TERMINAL) && job->get_flag(JOB_FOREGROUND)) {
if (job->get_flag(job_flag_t::TERMINAL) && job->is_foreground()) {
// It will be foregrounded, so we will call tcsetpgrp(), therefore do not use
// posix_spawn.
return false;
@ -380,7 +380,7 @@ void internal_exec(job_t *j, const io_chain_t &&all_ios) {
// launch_process _never_ returns.
launch_process_nofork(j->processes.front().get());
} else {
j->set_flag(JOB_CONSTRUCTED, true);
j->set_flag(job_flag_t::CONSTRUCTED, true);
j->processes.front()->completed = 1;
return;
}
@ -392,7 +392,7 @@ static void on_process_created(job_t *j, pid_t child_pid) {
return;
}
if (j->get_flag(JOB_CONTROL)) {
if (j->get_flag(job_flag_t::JOB_CONTROL)) {
j->pgid = child_pid;
} else {
j->pgid = getpgrp();
@ -524,15 +524,15 @@ static bool exec_internal_builtin_proc(parser_t &parser, job_t *j, process_t *p,
// way, the builtin does not need to know what job it is part of. It could
// probably figure that out by walking the job list, but it seems more robust to
// make exec handle things.
const int fg = j->get_flag(JOB_FOREGROUND);
j->set_flag(JOB_FOREGROUND, false);
const int fg = j->is_foreground();
j->set_flag(job_flag_t::FOREGROUND, false);
// Note this call may block for a long time, while the builtin performs I/O.
p->status = builtin_run(parser, j->pgid, p->get_argv(), streams);
// Restore the fg flag, which is temporarily set to false during builtin
// execution so as not to confuse some job-handling builtins.
j->set_flag(JOB_FOREGROUND, fg);
j->set_flag(job_flag_t::FOREGROUND, fg);
// If stdin has been redirected, close the redirection stream.
if (close_stdin) {
@ -613,7 +613,7 @@ static bool handle_builtin_output(job_t *j, process_t *p, io_chain_t *io_chain,
debug(4, L"Set status of job %d (%ls) to %d using short circuit", j->job_id, j->preview().c_str(), p->status);
int status = p->status;
proc_set_last_status(j->get_flag(JOB_NEGATE) ? (!status) : status);
proc_set_last_status(j->get_flag(job_flag_t::NEGATE) ? (!status) : status);
}
} else {
// Ok, unfortunately, we have to do a real fork. Bummer. We work hard to make
@ -719,7 +719,7 @@ static bool exec_external_command(job_t *j, process_t *p, const io_chain_t &proc
// child group has been set. See discussion here:
// https://github.com/Microsoft/WSL/issues/2997 And confirmation that this persists
// past glibc 2.24+ here: https://github.com/fish-shell/fish-shell/issues/4715
if (j->get_flag(JOB_CONTROL) && getpgid(p->pid) != j->pgid) {
if (j->get_flag(job_flag_t::JOB_CONTROL) && getpgid(p->pid) != j->pgid) {
set_child_group(j, p->pid);
}
#else
@ -803,7 +803,7 @@ static bool exec_block_or_func_process(parser_t &parser, job_t *j, process_t *p,
// No buffer, so we exit directly. This means we have to manually set the exit
// status.
if (p->is_last_in_job) {
proc_set_last_status(j->get_flag(JOB_NEGATE) ? (!status) : status);
proc_set_last_status(j->get_flag(job_flag_t::NEGATE) ? (!status) : status);
}
p->completed = 1;
return true;
@ -828,7 +828,7 @@ static bool exec_block_or_func_process(parser_t &parser, job_t *j, process_t *p,
}
} else {
if (p->is_last_in_job) {
proc_set_last_status(j->get_flag(JOB_NEGATE) ? (!status) : status);
proc_set_last_status(j->get_flag(job_flag_t::NEGATE) ? (!status) : status);
}
p->completed = 1;
}
@ -1038,7 +1038,7 @@ void exec_job(parser_t &parser, job_t *j) {
// and https://github.com/Microsoft/WSL/issues/2786.
process_t keepalive;
bool needs_keepalive = false;
if (is_windows_subsystem_for_linux() && j->get_flag(JOB_CONTROL) && !exec_error) {
if (is_windows_subsystem_for_linux() && j->get_flag(job_flag_t::JOB_CONTROL) && !exec_error) {
for (const process_ptr_t &p : j->processes) {
// but not if it's the only process
if (j->processes.front()->type == EXTERNAL && !p->is_first_in_job) {
@ -1098,18 +1098,18 @@ void exec_job(parser_t &parser, job_t *j) {
kill(keepalive.pid, SIGKILL);
}
j->set_flag(JOB_CONSTRUCTED, true);
if (!j->get_flag(JOB_FOREGROUND)) {
j->set_flag(job_flag_t::CONSTRUCTED, true);
if (!j->is_foreground()) {
proc_last_bg_pid = j->pgid;
env_set(L"last_pid", ENV_GLOBAL, { to_string(proc_last_bg_pid) });
}
if (!exec_error) {
job_continue(j, false);
j->continue_job(false);
} else {
// Mark the errored job as not in the foreground. I can't fully justify whether this is the
// right place, but it prevents sanity_lose from complaining.
j->set_flag(JOB_FOREGROUND, false);
j->set_flag(job_flag_t::FOREGROUND, false);
}
}

View file

@ -772,7 +772,7 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process(
bool have_bg = false;
const job_t *bg = nullptr;
while ((bg = jobs.next())) {
if (!job_is_completed(bg)) {
if (!bg->is_completed()) {
have_bg = true;
break;
}
@ -975,7 +975,7 @@ bool parse_execution_context_t::determine_io_chain(tnode_t<g::arguments_or_redir
parse_execution_result_t parse_execution_context_t::populate_not_process(
job_t *job, process_t *proc, tnode_t<g::not_statement> not_statement) {
job->set_flag(JOB_NEGATE, !job->get_flag(JOB_NEGATE));
job->set_flag(job_flag_t::NEGATE, !job->get_flag(job_flag_t::NEGATE));
return this->populate_job_process(job, proc,
not_statement.require_get_child<g::statement, 1>());
}
@ -1184,15 +1184,15 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t<g::job> jo
shared_ptr<job_t> job = std::make_shared<job_t>(acquire_job_id(), block_io);
job->tmodes = tmodes;
job->set_flag(JOB_CONTROL,
job->set_flag(job_flag_t::JOB_CONTROL,
(job_control_mode == JOB_CONTROL_ALL) ||
((job_control_mode == JOB_CONTROL_INTERACTIVE) && shell_is_interactive()));
job->set_flag(JOB_FOREGROUND, !job_node_is_background(job_node));
job->set_flag(job_flag_t::FOREGROUND, !job_node_is_background(job_node));
job->set_flag(JOB_TERMINAL, job->get_flag(JOB_CONTROL) && !is_event);
job->set_flag(job_flag_t::TERMINAL, job->get_flag(job_flag_t::JOB_CONTROL) && !is_event);
job->set_flag(JOB_SKIP_NOTIFICATION,
job->set_flag(job_flag_t::SKIP_NOTIFICATION,
is_subshell || is_block || is_event || !shell_is_interactive());
// Tell the current block what its job is. This has to happen before we populate it (#1394).

View file

@ -64,7 +64,7 @@ static void debug_safe_int(int level, const char *format, int val) {
/// Returns true on sucess, false on failiure.
bool child_set_group(job_t *j, process_t *p) {
bool retval = true;
if (j->get_flag(JOB_CONTROL)) {
if (j->get_flag(job_flag_t::JOB_CONTROL)) {
if (j->pgid == INVALID_PID) {
j->pgid = p->pid;
}
@ -104,7 +104,7 @@ bool child_set_group(job_t *j, process_t *p) {
/// group in the case of JOB_CONTROL, and we can give the new process group control of the terminal
/// if it's to run in the foreground.
bool set_child_group(job_t *j, pid_t child_pid) {
if (j->get_flag(JOB_CONTROL)) {
if (j->get_flag(job_flag_t::JOB_CONTROL)) {
assert (j->pgid != INVALID_PID
&& "set_child_group called with JOB_CONTROL before job pgid determined!");
@ -135,7 +135,7 @@ bool set_child_group(job_t *j, pid_t child_pid) {
bool maybe_assign_terminal(const job_t *j) {
assert(j->pgid > 1 && "maybe_assign_terminal() called on job with invalid pgid!");
if (j->get_flag(JOB_TERMINAL) && j->get_flag(JOB_FOREGROUND)) { //!OCLINT(early exit)
if (j->get_flag(job_flag_t::TERMINAL) && j->is_foreground()) { //!OCLINT(early exit)
if (tcgetpgrp(STDIN_FILENO) == j->pgid) {
// We've already assigned the process group control of the terminal when the first
// process in the job was started. There's no need to do so again, and on some platforms
@ -338,7 +338,7 @@ bool fork_actions_make_spawn_properties(posix_spawnattr_t *attr,
bool should_set_process_group_id = false;
int desired_process_group_id = 0;
if (j->get_flag(JOB_CONTROL)) {
if (j->get_flag(job_flag_t::JOB_CONTROL)) {
should_set_process_group_id = true;
// set_child_group puts each job into its own process group

View file

@ -77,22 +77,6 @@ job_iterator_t::job_iterator_t() : job_list(&parser_t::principal_parser().job_li
size_t job_iterator_t::count() const { return this->job_list->size(); }
#if 0
// This isn't used so the lint tools were complaining about its presence. I'm keeping it in the
// source because it could be useful for debugging. However, it would probably be better to add a
// verbose or debug option to the builtin `jobs` command.
void print_jobs(void)
{
job_iterator_t jobs;
job_t *j;
while (j = jobs.next()) {
fwprintf(stdout, L"%p -> %ls -> (foreground %d, complete %d, stopped %d, constructed %d)\n",
j, j->command_wcstr(), j->get_flag(JOB_FOREGROUND), job_is_completed(j),
job_is_stopped(j), j->get_flag(JOB_CONSTRUCTED));
}
}
#endif
bool is_interactive_session = false;
bool is_subshell = false;
bool is_block = false;
@ -140,9 +124,9 @@ static int job_remove(job_t *j) {
return parser_t::principal_parser().job_remove(j);
}
void job_promote(job_t *job) {
void job_t::promote() {
ASSERT_IS_MAIN_THREAD();
parser_t::principal_parser().job_promote(job);
parser_t::principal_parser().job_promote(this);
}
void proc_destroy() {
@ -199,61 +183,49 @@ void release_job_id(job_id_t jid) {
consumed_job_ids->resize(count + 1);
}
job_t *job_get(job_id_t id) {
job_t *job_t::from_job_id(job_id_t id) {
ASSERT_IS_MAIN_THREAD();
return parser_t::principal_parser().job_get(id);
}
job_t *job_get_from_pid(int pid) {
job_t *job_t::from_pid(pid_t pid) {
ASSERT_IS_MAIN_THREAD();
return parser_t::principal_parser().job_get_from_pid(pid);
}
/// Return true if all processes in the job have stopped or completed.
///
/// \param j the job to test
int job_is_stopped(const job_t *j) {
for (const process_ptr_t &p : j->processes) {
bool job_t::is_stopped() const {
for (const process_ptr_t &p : processes) {
if (!p->completed && !p->stopped) {
return 0;
return false;
}
}
return 1;
return true;
}
/// Return true if the last processes in the job has completed.
///
/// \param j the job to test
bool job_is_completed(const job_t *j) {
assert(!j->processes.empty());
bool result = true;
for (const process_ptr_t &p : j->processes) {
bool job_t::is_completed() const {
assert(!processes.empty());
for (const process_ptr_t &p : processes) {
if (!p->completed) {
result = false;
break;
return false;
}
}
return result;
return true;
}
void job_t::set_flag(job_flag_t flag, bool set) {
if (set) {
this->flags |= flag;
} else {
this->flags &= ~flag;
}
}
void job_t::set_flag(job_flag_t flag, bool set) { this->flags.set(flag, set); }
bool job_t::get_flag(job_flag_t flag) const { return (this->flags & flag) == flag; }
bool job_t::get_flag(job_flag_t flag) const { return this->flags.get(flag); }
int job_signal(job_t *j, int signal) {
int job_t::signal(int signal) {
pid_t my_pgid = getpgrp();
int res = 0;
if (j->pgid != my_pgid) {
res = killpg(j->pgid, signal);
if (pgid != my_pgid) {
res = killpg(pgid, signal);
} else {
for (const process_ptr_t &p : j->processes) {
for (const process_ptr_t &p : processes) {
if (!p->completed && p->pid && kill(p->pid, signal)) {
res = -1;
break;
@ -344,7 +316,7 @@ static void handle_child_status(pid_t pid, int status) {
process_t::process_t() {}
job_t::job_t(job_id_t jobid, io_chain_t bio)
: block_io(std::move(bio)), pgid(INVALID_PID), tmodes(), job_id(jobid), flags(0) {}
: block_io(std::move(bio)), pgid(INVALID_PID), tmodes(), job_id(jobid), flags{} {}
job_t::~job_t() { release_job_id(job_id); }
@ -382,7 +354,7 @@ static bool process_mark_finished_children(bool block_on_fg) {
job_iterator_t jobs;
while (auto j = jobs.next()) {
// A job can have pgrp INVALID_PID if it consists solely of builtins that perform no IO
if (j->pgid == INVALID_PID || !j->get_flag(JOB_CONSTRUCTED)) {
if (j->pgid == INVALID_PID || !j->is_constructed()) {
// Job has not been fully constructed yet
debug(5, "Skipping wait on incomplete job %d (%ls)", j->job_id, j->preview().c_str());
continue;
@ -392,10 +364,11 @@ static bool process_mark_finished_children(bool block_on_fg) {
// nature of the process. Default is WNOHANG, but if foreground, constructed, not stopped, *and*
// block_on_fg is true, then no WNOHANG (i.e. "HANG").
int options = WUNTRACED | WNOHANG;
if (j->get_flag(JOB_FOREGROUND) && !job_is_stopped(j) && !job_is_completed(j)) {
if (j->is_foreground() && !j->is_stopped() && !j->is_completed()) {
assert(job_fg == nullptr && "More than one active, fully-constructed foreground job!");
job_fg = j;
}
// We should never block twice in the same go, as `waitpid()' returning could mean one
// process completed or many, and there is a race condition when calling `waitpid()` after
// the process group exits having reaped all children and terminated the process group and
@ -408,8 +381,8 @@ static bool process_mark_finished_children(bool block_on_fg) {
// never wait/block on fg processes after an error has been encountered to give ourselves
// (elsewhere) a chance to handle the fallout from process termination, etc.
if (!has_error && block_on_fg && j->pgid != shell_pgid
&& j == job_fg && j->get_flag(JOB_CONTROL)) {
debug(4, "Waiting on processes from foreground job %d.", j->pgid, shell_pgid);
&& j == job_fg && j->get_flag(job_flag_t::JOB_CONTROL)) {
debug(4, "Waiting on processes from foreground job %d", job_fg->pgid);
options &= ~WNOHANG;
}
@ -524,8 +497,8 @@ static int process_clean_after_marking(bool allow_interactive) {
// If we are reaping only jobs who do not need status messages sent to the console, do not
// consider reaping jobs that need status messages.
if ((!j->get_flag(JOB_SKIP_NOTIFICATION)) && (!interactive) &&
(!j->get_flag(JOB_FOREGROUND))) {
if ((!j->get_flag(job_flag_t::SKIP_NOTIFICATION)) && (!interactive) &&
(!j->is_foreground())) {
continue;
}
@ -550,9 +523,10 @@ static int process_clean_after_marking(bool allow_interactive) {
// Handle signals other than SIGPIPE.
int proc_is_job = (p->is_first_in_job && p->is_last_in_job);
if (proc_is_job) j->set_flag(JOB_NOTIFIED, true);
if (proc_is_job) j->set_flag(job_flag_t::NOTIFIED, true);
// Always report crashes.
if (j->get_flag(JOB_SKIP_NOTIFICATION) && !contains(crashsignals,WTERMSIG(p->status))) {
if (j->get_flag(job_flag_t::SKIP_NOTIFICATION) &&
!contains(crashsignals, WTERMSIG(p->status))) {
continue;
}
@ -565,7 +539,7 @@ static int process_clean_after_marking(bool allow_interactive) {
// signals. If echoctl is on, then the terminal will have written ^C to the console.
// If off, it won't have. We don't echo ^C either way, so as to respect the user's
// preference.
if (WTERMSIG(p->status) != SIGINT || !j->get_flag(JOB_FOREGROUND)) {
if (WTERMSIG(p->status) != SIGINT || !j->is_foreground()) {
if (proc_is_job) {
// We want to report the job number, unless it's the only job, in which case
// we don't need to.
@ -598,9 +572,9 @@ static int process_clean_after_marking(bool allow_interactive) {
// If all processes have completed, tell the user the job has completed and delete it from
// the active job list.
if (job_is_completed(j)) {
if (!j->get_flag(JOB_FOREGROUND) && !j->get_flag(JOB_NOTIFIED) &&
!j->get_flag(JOB_SKIP_NOTIFICATION)) {
if (j->is_completed()) {
if (!j->is_foreground() && !j->get_flag(job_flag_t::NOTIFIED) &&
!j->get_flag(job_flag_t::SKIP_NOTIFICATION)) {
format_job_info(j, JOB_ENDED);
found = 1;
}
@ -616,13 +590,13 @@ static int process_clean_after_marking(bool allow_interactive) {
proc_fire_event(L"JOB_EXIT", EVENT_JOB_ID, j->job_id, 0);
job_remove(j);
} else if (job_is_stopped(j) && !j->get_flag(JOB_NOTIFIED)) {
} else if (j->is_stopped() && !j->get_flag(job_flag_t::NOTIFIED)) {
// Notify the user about newly stopped jobs.
if (!j->get_flag(JOB_SKIP_NOTIFICATION)) {
if (!j->get_flag(job_flag_t::SKIP_NOTIFICATION)) {
format_job_info(j, JOB_STOPPED);
found = 1;
}
j->set_flag(JOB_NOTIFIED, true);
j->set_flag(job_flag_t::NOTIFIED, true);
}
}
@ -946,35 +920,35 @@ static bool terminal_return_from_job(job_t *j) {
return true;
}
void job_continue(job_t *j, bool cont) {
void job_t::continue_job(bool cont) {
// Put job first in the job list.
job_promote(j);
j->set_flag(JOB_NOTIFIED, false);
promote();
set_flag(job_flag_t::NOTIFIED, false);
CHECK_BLOCK();
debug(4, L"%ls job %d, gid %d (%ls), %ls, %ls", cont ? L"Continue" : L"Start", j->job_id,
j->pgid, j->command_wcstr(), job_is_completed(j) ? L"COMPLETED" : L"UNCOMPLETED",
debug(4, L"%ls job %d, gid %d (%ls), %ls, %ls", cont ? L"Continue" : L"Start", job_id, pgid,
command_wcstr(), is_completed() ? L"COMPLETED" : L"UNCOMPLETED",
is_interactive ? L"INTERACTIVE" : L"NON-INTERACTIVE");
if (!job_is_completed(j)) {
if (j->get_flag(JOB_TERMINAL) && j->get_flag(JOB_FOREGROUND)) {
if (!is_completed()) {
if (get_flag(job_flag_t::TERMINAL) && is_foreground()) {
// Put the job into the foreground. Hack: ensure that stdin is marked as blocking first
// (issue #176).
make_fd_blocking(STDIN_FILENO);
if (!terminal_give_to_job(j, cont)) return;
if (!terminal_give_to_job(this, cont)) return;
}
// Send the job a continue signal, if necessary.
if (cont) {
for (process_ptr_t &p : j->processes) p->stopped = false;
for (process_ptr_t &p : processes) p->stopped = false;
if (j->get_flag(JOB_CONTROL)) {
if (killpg(j->pgid, SIGCONT)) {
if (get_flag(job_flag_t::JOB_CONTROL)) {
if (killpg(pgid, SIGCONT)) {
wperror(L"killpg (SIGCONT)");
return;
}
} else {
for (const process_ptr_t &p : j->processes) {
for (const process_ptr_t &p : processes) {
if (kill(p->pid, SIGCONT) < 0) {
wperror(L"kill (SIGCONT)");
return;
@ -983,16 +957,16 @@ void job_continue(job_t *j, bool cont) {
}
}
if (j->get_flag(JOB_FOREGROUND)) {
if (is_foreground()) {
// Look for finished processes first, to avoid select() if it's already done.
process_mark_finished_children(false);
// Wait for job to report.
while (!reader_exit_forced() && !job_is_stopped(j) && !job_is_completed(j)) {
switch (select_try(j)) {
while (!reader_exit_forced() && !is_stopped() && !is_completed()) {
switch (select_try(this)) {
case 1: {
// debug(1, L"select_try() 1" );
read_try(j);
read_try(this);
process_mark_finished_children(false);
break;
}
@ -1022,8 +996,8 @@ void job_continue(job_t *j, bool cont) {
}
}
if (j->get_flag(JOB_FOREGROUND)) {
if (job_is_completed(j)) {
if (is_foreground()) {
if (is_completed()) {
// It's possible that the job will produce output and exit before we've even read from
// it.
//
@ -1031,23 +1005,21 @@ void job_continue(job_t *j, bool cont) {
// This is why my prompt colors kept getting screwed up - the builtin echo calls
// were sometimes having their output combined with the set_color calls in the wrong
// order!
read_try(j);
read_try(this);
const std::unique_ptr<process_t> &p = j->processes.back();
const std::unique_ptr<process_t> &p = processes.back();
// Mark process status only if we are in the foreground and the last process in a pipe,
// and it is not a short circuited builtin.
if ((WIFEXITED(p->status) || WIFSIGNALED(p->status)) && p->pid) {
int status = proc_format_status(p->status);
// fwprintf(stdout, L"setting status %d for %ls\n", job_get_flag( j, JOB_NEGATE
// )?!status:status, j->command);
proc_set_last_status(j->get_flag(JOB_NEGATE) ? !status : status);
proc_set_last_status(get_flag(job_flag_t::NEGATE) ? !status : status);
}
}
// Put the shell back in the foreground.
if (j->get_flag(JOB_TERMINAL) && j->get_flag(JOB_FOREGROUND)) {
terminal_return_from_job(j);
if (get_flag(job_flag_t::TERMINAL) && is_foreground()) {
terminal_return_from_job(this);
}
}
}
@ -1066,10 +1038,10 @@ void proc_sanity_check() {
job_iterator_t jobs;
while (const job_t *j = jobs.next()) {
if (!j->get_flag(JOB_CONSTRUCTED)) continue;
if (!j->is_constructed()) continue;
// More than one foreground job?
if (j->get_flag(JOB_FOREGROUND) && !(job_is_stopped(j) || job_is_completed(j))) {
if (j->is_foreground() && !(j->is_stopped() || j->is_completed())) {
if (fg_job) {
debug(0, _(L"More than one job in foreground: job 1: '%ls' job 2: '%ls'"),
fg_job->command_wcstr(), j->command_wcstr());

View file

@ -16,6 +16,7 @@
#include <vector>
#include "common.h"
#include "enum_set.h"
#include "io.h"
#include "parse_tree.h"
#include "tnode.h"
@ -139,23 +140,23 @@ typedef std::unique_ptr<process_t> process_ptr_t;
typedef std::vector<process_ptr_t> process_list_t;
/// Constants for the flag variable in the job struct.
enum job_flag_t {
enum class job_flag_t {
/// Whether the user has been told about stopped job.
JOB_NOTIFIED = 1 << 0,
NOTIFIED,
/// Whether this job is in the foreground.
JOB_FOREGROUND = 1 << 1,
FOREGROUND,
/// Whether the specified job is completely constructed, i.e. completely parsed, and every
/// process in the job has been forked, etc.
JOB_CONSTRUCTED = 1 << 2,
CONSTRUCTED,
/// Whether the specified job is a part of a subshell, event handler or some other form of
/// special job that should not be reported.
JOB_SKIP_NOTIFICATION = 1 << 3,
SKIP_NOTIFICATION,
/// Whether the exit status should be negated. This flag can only be set by the not builtin.
JOB_NEGATE = 1 << 4,
NEGATE,
/// Whether the job is under job control.
JOB_CONTROL = 1 << 5,
JOB_CONTROL,
/// Whether the job wants to own the terminal when in the foreground.
JOB_TERMINAL = 1 << 6
TERMINAL,
};
typedef int job_id_t;
@ -215,7 +216,7 @@ class job_t {
/// this shell, and is used e.g. in process expansion.
const job_id_t job_id;
/// Bitset containing information about the job. A combination of the JOB_* constants.
unsigned int flags;
enum_set_t<job_flag_t> flags;
// Get and set flags
bool get_flag(job_flag_t flag) const;
@ -227,6 +228,37 @@ class job_t {
/// Fetch all the IO redirections associated with the job.
io_chain_t all_io_redirections() const;
// Helper functions to check presence of flags on instances of jobs
/// The job has been fully constructed, i.e. all its member processes have been launched
bool is_constructed() const { return get_flag(job_flag_t::CONSTRUCTED); };
/// The job was launched in the foreground and has control of the terminal
bool is_foreground() const { return get_flag(job_flag_t::FOREGROUND); };
/// The job is complete, i.e. all its member processes have been reaped
bool is_completed() const;
/// The job is in a stopped state
bool is_stopped() const;
/// Resume a (possibly) stopped job. Puts job in the foreground. If cont is true, restore the
/// saved terminal modes and send the process group a SIGCONT signal to wake it up before we
/// block.
///
/// \param cont Whether the function should wait for the job to complete before returning
// (This would just be called `continue` but that's obviously a reserved keyword)
void continue_job(bool cont);
/// Promotes the job to the front of the job list.
void promote();
/// Send the specified signal to all processes in this job.
int signal(int signal);
/// Return the job instance matching this unique job id.
/// If id is 0 or less, return the last job used.
static job_t *from_job_id(job_id_t id);
/// Return the job containing the process identified by the unique pid provided.
static job_t *from_pid(pid_t pid);
};
/// Whether we are reading from the keyboard right now.
@ -306,28 +338,6 @@ void proc_set_last_status(int s);
/// Returns the status of the last process to exit.
int proc_get_last_status();
/// Promotes a job to the front of the job list.
void job_promote(job_t *job);
/// Return the job with the specified job id. If id is 0 or less, return the last job used.
job_t *job_get(job_id_t id);
/// Return the job with the specified pid.
job_t *job_get_from_pid(int pid);
/// Tests if the job is stopped.
int job_is_stopped(const job_t *j);
/// Tests if the job has completed, i.e. if the last process of the pipeline has ended.
bool job_is_completed(const job_t *j);
/// Reassume a (possibly) stopped job. Put job j in the foreground. If cont is true, restore the
/// saved terminal modes and send the process group a SIGCONT signal to wake it up before we block.
///
/// \param j The job
/// \param cont Whether the function should wait for the job to complete before returning
void job_continue(job_t *j, bool cont);
/// Notify the user about stopped or terminated jobs. Delete terminated jobs from the job list.
///
/// \param interactive whether interactive jobs should be reaped as well
@ -336,9 +346,6 @@ int job_reap(bool interactive);
/// Signal handler for SIGCHLD. Mark any processes with relevant information.
void job_handle_signal(int signal, siginfo_t *info, void *con);
/// Send the specified signal to all processes in the specified job.
int job_signal(job_t *j, int signal);
/// Mark a process as failed to execute (and therefore completed).
void job_mark_process_as_failed(job_t *job, const process_t *p);

View file

@ -2242,7 +2242,7 @@ void reader_bg_job_warning() {
job_iterator_t jobs;
while (job_t *j = jobs.next()) {
if (!job_is_completed(j)) {
if (!j->is_completed()) {
fwprintf(stdout, L"%6d %ls\n", j->processes[0]->pid, j->command_wcstr());
}
}
@ -2254,9 +2254,9 @@ void reader_bg_job_warning() {
void kill_background_jobs() {
job_iterator_t jobs;
while (job_t *j = jobs.next()) {
if (!job_is_completed(j)) {
if (job_is_stopped(j)) job_signal(j, SIGCONT);
job_signal(j, SIGHUP);
if (!j->is_completed()) {
if (j->is_stopped()) j->signal(SIGCONT);
j->signal(SIGHUP);
}
}
}
@ -2277,7 +2277,7 @@ static void handle_end_loop() {
bool bg_jobs = false;
job_iterator_t jobs;
while (const job_t *j = jobs.next()) {
if (!job_is_completed(j)) {
if (!j->is_completed()) {
bg_jobs = true;
break;
}