// Functions for waiting for processes completed. #include "builtin_wait.h" #include #include #include #include "builtin.h" #include "common.h" #include "parser.h" #include "proc.h" #include "signal.h" #include "wgetopt.h" #include "wutil.h" /// Return the job id to which the process with pid belongs. /// If a specified process has already finished but the job hasn't, parser_t::job_get_from_pid() /// doesn't work properly, so use this function in wait command. static job_id_t get_job_id_from_pid(pid_t pid, const parser_t &parser) { for (const auto &j : parser.jobs()) { if (j->pgid == pid) { return j->job_id; } // Check if the specified pid is a child process of the job. for (const process_ptr_t &p : j->processes) { if (p->pid == pid) { return j->job_id; } } } return 0; } static bool all_jobs_finished(const parser_t &parser) { for (const auto &j : parser.jobs()) { // If any job is not completed, return false. // If there are stopped jobs, they are ignored. if (j->is_constructed() && !j->is_completed() && !j->is_stopped()) { return false; } } return true; } static bool any_jobs_finished(size_t jobs_len, const parser_t &parser) { bool no_jobs_running = true; // If any job is removed from list, return true. if (jobs_len != parser.jobs().size()) { return true; } for (const auto &j : parser.jobs()) { // If any job is completed, return true. if (j->is_constructed() && (j->is_completed() || j->is_stopped())) { return true; } // Check for jobs running exist or not. if (j->is_constructed() && !j->is_stopped()) { no_jobs_running = false; } } return no_jobs_running; } static int wait_for_backgrounds(parser_t &parser, bool any_flag) { sigint_checker_t sigint; size_t jobs_len = parser.jobs().size(); while ((!any_flag && !all_jobs_finished(parser)) || (any_flag && !any_jobs_finished(jobs_len, parser))) { if (sigint.check()) { return 128 + SIGINT; } proc_wait_any(parser); } return 0; } static bool all_specified_jobs_finished(const std::vector &ids) { for (auto id : ids) { 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->is_constructed() && !j->is_completed() && !j->is_stopped()) { return false; } } } return true; } static bool any_specified_jobs_finished(const std::vector &ids) { for (auto id : ids) { if (job_t *j = job_t::from_job_id(id)) { // If any specified job is completed, return true. if (j->is_constructed() && (j->is_completed() || j->is_stopped())) { return true; } } else { // If any specified job is removed from list, return true. return true; } } return false; } static int wait_for_backgrounds_specified(parser_t &parser, const std::vector &ids, bool any_flag) { sigint_checker_t sigint; while ((!any_flag && !all_specified_jobs_finished(ids)) || (any_flag && !any_specified_jobs_finished(ids))) { if (sigint.check()) { return 128 + SIGINT; } proc_wait_any(parser); } return 0; } /// Tests if all characters in the wide string are numeric. static bool iswnumeric(const wchar_t *n) { for (; *n; n++) { if (*n < L'0' || *n > L'9') { return false; } } return true; } /// See if the process described by \c proc matches the commandline \c cmd. static bool match_pid(const wcstring &cmd, const wchar_t *proc) { // Don't wait for itself if (std::wcscmp(proc, L"wait") == 0) return false; // Get the command to match against. We're only interested in the last path component. const wcstring base_cmd = wbasename(cmd); return std::wcscmp(proc, base_cmd.c_str()) == 0; } /// It should search the job list for something matching the given proc. static bool find_job_by_name(const wchar_t *proc, std::vector &ids, const parser_t &parser) { bool found = false; for (const auto &j : parser.jobs()) { if (j->command().empty()) continue; if (match_pid(j->command(), proc)) { if (!contains(ids, j->job_id)) { // If pids doesn't already have the pgid, add it. ids.push_back(j->job_id); } found = true; } // Check if the specified pid is a child process of the job. for (const process_ptr_t &p : j->processes) { if (p->actual_cmd.empty()) continue; if (match_pid(p->actual_cmd, proc)) { if (!contains(ids, j->job_id)) { // If pids doesn't already have the pgid, add it. ids.push_back(j->job_id); } found = true; } } } return found; } /// The following function is invoked on the main thread, because the job operation is not thread /// safe. It waits for child jobs, not for child processes individually. int builtin_wait(parser_t &parser, io_streams_t &streams, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); int retval = STATUS_CMD_OK; const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); bool any_flag = false; // flag for -n option static const wchar_t *const short_options = L":n"; static const struct woption long_options[] = {{L"any", no_argument, nullptr, 'n'}, {nullptr, 0, nullptr, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) { switch (opt) { case 'n': any_flag = true; break; case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (w.woptind == argc) { // no jobs specified retval = wait_for_backgrounds(parser, any_flag); } else { // jobs specified std::vector waited_job_ids; for (int i = w.woptind; i < argc; i++) { if (iswnumeric(argv[i])) { // argument is pid pid_t pid = fish_wcstoi(argv[i]); if (errno || pid <= 0) { streams.err.append_format(_(L"%ls: '%ls' is not a valid process id\n"), cmd, argv[i]); continue; } if (job_id_t id = get_job_id_from_pid(pid, parser)) { waited_job_ids.push_back(id); } else { streams.err.append_format( _(L"%ls: Could not find a job with process id '%d'\n"), cmd, pid); } } else { // argument is process name if (!find_job_by_name(argv[i], waited_job_ids, parser)) { streams.err.append_format( _(L"%ls: Could not find child processes with the name '%ls'\n"), cmd, argv[i]); } } } if (waited_job_ids.empty()) return STATUS_INVALID_ARGS; retval = wait_for_backgrounds_specified(parser, waited_job_ids, any_flag); } return retval; }