diff --git a/src/builtin_bg.cpp b/src/builtin_bg.cpp index 076e57f20..c97b1dddb 100644 --- a/src/builtin_bg.cpp +++ b/src/builtin_bg.cpp @@ -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); diff --git a/src/builtin_disown.cpp b/src/builtin_disown.cpp index 7a26e6f2d..7fda7ed11 100644 --- a/src/builtin_disown.cpp +++ b/src/builtin_disown.cpp @@ -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; } } diff --git a/src/builtin_fg.cpp b/src/builtin_fg.cpp index 2c838b949..f8483687d 100644 --- a/src/builtin_fg.cpp +++ b/src/builtin_fg.cpp @@ -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; } diff --git a/src/builtin_functions.cpp b/src/builtin_functions.cpp index 4c3cb4623..8b5b16d97 100644 --- a/src/builtin_functions.cpp +++ b/src/builtin_functions.cpp @@ -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; } diff --git a/src/builtin_jobs.cpp b/src/builtin_jobs.cpp index 7b585e376..ffddc85c5 100644 --- a/src/builtin_jobs.cpp +++ b/src/builtin_jobs.cpp @@ -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; } diff --git a/src/builtin_wait.cpp b/src/builtin_wait.cpp index f241a51aa..20c6bf369 100644 --- a/src/builtin_wait.cpp +++ b/src/builtin_wait.cpp @@ -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 &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 &ids) { static bool any_specified_jobs_finished(const std::vector &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 { diff --git a/src/enum_set.h b/src/enum_set.h new file mode 100644 index 000000000..6ef6e2c8c --- /dev/null +++ b/src/enum_set.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +template +class enum_set_t { + private: + using base_type_t = typename std::underlying_type::type; + std::bitset<8 * sizeof(base_type_t)> bitmask{0}; + static int index_of(T t) { return static_cast(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)); } +}; diff --git a/src/event.cpp b/src/event.cpp index a23f2f6b1..62db87a9e 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -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()); diff --git a/src/exec.cpp b/src/exec.cpp index 2cd1057c1..9b95dc4d5 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -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); } } diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index e442e7708..049980459 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -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 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()); } @@ -1184,15 +1184,15 @@ parse_execution_result_t parse_execution_context_t::run_1_job(tnode_t jo shared_ptr job = std::make_shared(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). diff --git a/src/postfork.cpp b/src/postfork.cpp index b9787b8a4..a05d63446 100644 --- a/src/postfork.cpp +++ b/src/postfork.cpp @@ -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 diff --git a/src/proc.cpp b/src/proc.cpp index 3178afc51..a536ab631 100644 --- a/src/proc.cpp +++ b/src/proc.cpp @@ -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 &p = j->processes.back(); + const std::unique_ptr &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()); diff --git a/src/proc.h b/src/proc.h index 718280ab9..64045cea8 100644 --- a/src/proc.h +++ b/src/proc.h @@ -16,6 +16,7 @@ #include #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_ptr_t; typedef std::vector 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 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); diff --git a/src/reader.cpp b/src/reader.cpp index e68b3b927..8cbafacd0 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -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; }