From c7a682ed050e204b4640da19434da7c53501d1f4 Mon Sep 17 00:00:00 2001 From: slama Date: Sun, 22 Oct 2017 16:10:23 +0900 Subject: [PATCH] add wait command --- Makefile.in | 2 +- src/builtin.cpp | 2 + src/builtin_wait.cpp | 177 ++++++++++++++++++++++++++++++++++++++++++ src/builtin_wait.h | 8 ++ src/proc.cpp | 35 +++++++-- src/proc.h | 3 + tests/wait.expect | 108 ++++++++++++++++++++++++++ tests/wait.expect.err | 0 tests/wait.expect.out | 0 9 files changed, 326 insertions(+), 9 deletions(-) create mode 100644 src/builtin_wait.cpp create mode 100644 src/builtin_wait.h create mode 100644 tests/wait.expect create mode 100644 tests/wait.expect.err create mode 100644 tests/wait.expect.out diff --git a/Makefile.in b/Makefile.in index d712f2989..fd43320ee 100644 --- a/Makefile.in +++ b/Makefile.in @@ -117,7 +117,7 @@ FISH_OBJS := obj/autoload.o obj/builtin.o obj/builtin_bg.o obj/builtin_bind.o ob obj/builtin_random.o obj/builtin_read.o obj/builtin_realpath.o \ obj/builtin_return.o obj/builtin_set.o obj/builtin_set_color.o \ obj/builtin_source.o obj/builtin_status.o obj/builtin_string.o \ - obj/builtin_test.o obj/builtin_ulimit.o obj/color.o obj/common.o \ + obj/builtin_test.o obj/builtin_ulimit.o obj/builtin_wait.o obj/color.o obj/common.o \ obj/complete.o obj/env.o obj/env_universal_common.o obj/event.o obj/exec.o \ obj/expand.o obj/fallback.o obj/fish_version.o obj/function.o obj/highlight.o \ obj/history.o obj/input.o obj/input_common.o obj/intern.o obj/io.o \ diff --git a/src/builtin.cpp b/src/builtin.cpp index 7871d90ef..58cf00188 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -60,6 +60,7 @@ #include "builtin_string.h" #include "builtin_test.h" #include "builtin_ulimit.h" +#include "builtin_wait.h" #include "common.h" #include "complete.h" #include "exec.h" @@ -459,6 +460,7 @@ static const builtin_data_t builtin_datas[] = { {L"test", &builtin_test, N_(L"Test a condition")}, {L"true", &builtin_true, N_(L"Return a successful result")}, {L"ulimit", &builtin_ulimit, N_(L"Set or get the shells resource usage limits")}, + {L"wait", &builtin_wait, N_(L"Wait for background processes completed")}, {L"while", &builtin_generic, N_(L"Perform a command multiple times")}}; #define BUILTIN_COUNT (sizeof builtin_datas / sizeof *builtin_datas) diff --git a/src/builtin_wait.cpp b/src/builtin_wait.cpp new file mode 100644 index 000000000..d36af0b28 --- /dev/null +++ b/src/builtin_wait.cpp @@ -0,0 +1,177 @@ +// Functions for waiting for processes completed. +#include + +#include "builtin.h" +#include "builtin_wait.h" +#include "common.h" +#include "proc.h" +#include "wgetopt.h" +#include "wutil.h" + +#include + +static int retval; + +static bool all_jobs_finished() { + job_t *j; + job_iterator_t jobs; + while ((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)) { + return false; + } + } + return true; +} + +static bool any_jobs_finished(size_t jobs_len) { + job_t *j; + job_iterator_t jobs; + bool no_jobs_running = true; + + // If any job is removed from list, return true. + if (jobs_len != jobs.count()) { + return true; + } + while ((j = jobs.next())) { + // If any job is completed, return true. + if ((j->flags & JOB_CONSTRUCTED) && (job_is_completed(j) || job_is_stopped(j))) { + return true; + } + // Check for jobs running exist or not. + if ((j->flags & JOB_CONSTRUCTED) && !job_is_stopped(j)) { + no_jobs_running = false; + } + } + if (no_jobs_running) { + return true; + } + return false; +} + +static void wait_for_backgrounds(bool any_flag) { + job_iterator_t jobs; + size_t jobs_len = jobs.count(); + + while ((!any_flag && !all_jobs_finished()) || (any_flag && !any_jobs_finished(jobs_len))) { + pid_t pid = proc_wait_any(); + if (pid == -1 && errno == EINTR) { + retval = 128 + SIGINT; + return; + } + } +} + +static bool all_specified_jobs_finished(std::vector wjobs_pid) { + job_t *j; + for (auto pid : wjobs_pid) { + if ((j = job_get_from_pid(pid))) { + // 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)) { + return false; + } + } + } + return true; +} + +static bool any_specified_jobs_finished(std::vector wjobs_pid) { + job_t *j; + for (auto pid : wjobs_pid) { + if ((j = job_get_from_pid(pid))) { + // If any specified job is completed, return true. + if ((j->flags & JOB_CONSTRUCTED) && (job_is_completed(j) || job_is_stopped(j))) { + return true; + } + } else { + // If any specified job is removed from list, return true. + return true; + } + } + return false; +} + +static void wait_for_backgrounds_specified(std::vector wjobs_pid, bool any_flag) { + while ((!any_flag && !all_specified_jobs_finished(wjobs_pid)) || + (any_flag && !any_specified_jobs_finished(wjobs_pid))) { + pid_t pid = proc_wait_any(); + if (pid == -1 && errno == EINTR) { + retval = 128 + SIGINT; + return; + } + } +} + +int builtin_wait(parser_t &parser, io_streams_t &streams, wchar_t **argv) { + job_t *j; + job_iterator_t jobs; + const wchar_t *cmd = argv[0]; + int argc = builtin_count_args(argv); + bool any_flag = false; // flag for -n option + + static const wchar_t *short_options = L":n"; + static const struct woption long_options[] = {{L"any", no_argument, NULL, 'n'}, + {NULL, 0, NULL, 0}}; + + int opt; + wgetopter_t w; + while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -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 + wait_for_backgrounds(any_flag); + } else { + // jobs specified + std::vector waited_jobs_pid; + + for (int i = w.woptind; i < argc; i++) { + int pid = fish_wcstoi(argv[i]); + if (errno || pid < 0) { + streams.err.append_format(_(L"%ls: '%ls' is not a valid job specifier\n"), cmd, + argv[i]); + continue; + } + if (job_get_from_pid(pid)) { + waited_jobs_pid.push_back(pid); + } else { + // If a specified process has already finished but the job hasn't, + // job_get_from_pid(pid) doesn't work properly, so check the pgid here. + while ((j = jobs.next())) { + if (j->pgid == pid) { + waited_jobs_pid.push_back(pid); + break; + } + } + if (!j) { + streams.err.append_format(_(L"%ls: Could not find job '%d'\n"), cmd, pid); + } + } + } + + if (waited_jobs_pid.empty()) return STATUS_INVALID_ARGS; + + wait_for_backgrounds_specified(waited_jobs_pid, any_flag); + } + + return retval; +} diff --git a/src/builtin_wait.h b/src/builtin_wait.h new file mode 100644 index 000000000..a57e7fb40 --- /dev/null +++ b/src/builtin_wait.h @@ -0,0 +1,8 @@ +// Prototypes for executing builtin_wait function. +#ifndef FISH_BUILTIN_WAIT_H +#define FISH_BUILTIN_WAIT_H + +class parser_t; + +int builtin_wait(parser_t &parser, io_streams_t &streams, wchar_t **argv); +#endif diff --git a/src/proc.cpp b/src/proc.cpp index 2eb866679..27b08f600 100644 --- a/src/proc.cpp +++ b/src/proc.cpp @@ -513,12 +513,12 @@ void proc_fire_event(const wchar_t *msg, int type, pid_t pid, int status) { event.arguments.resize(0); } -int job_reap(bool allow_interactive) { +static int process_clean_after_marking(bool allow_interactive) { ASSERT_IS_MAIN_THREAD(); job_t *jnext; int found = 0; - // job_reap may fire an event handler, we do not want to call ourselves recursively (to avoid + // this function may fire an event handler, we do not want to call ourselves recursively (to avoid // infinite recursion). static bool locked = false; if (locked) { @@ -530,10 +530,6 @@ int job_reap(bool allow_interactive) { // don't try to print in that case (#3222) const bool interactive = allow_interactive && cur_term != NULL; - process_mark_finished_children(false); - - // Preserve the exit status. - const int saved_status = proc_get_last_status(); job_iterator_t jobs; const size_t job_count = jobs.count(); @@ -637,11 +633,25 @@ int job_reap(bool allow_interactive) { if (found) fflush(stdout); + locked = false; + + return found; +} + +int job_reap(bool allow_interactive) { + ASSERT_IS_MAIN_THREAD(); + int found = 0; + + process_mark_finished_children(false); + + // Preserve the exit status. + const int saved_status = proc_get_last_status(); + + found = process_clean_after_marking(allow_interactive); + // Restore the exit status. proc_set_last_status(saved_status); - locked = false; - return found; } @@ -1095,3 +1105,12 @@ void proc_pop_interactive() { interactive_stack.pop_back(); if (is_interactive != old) signal_set_handlers(); } + +pid_t proc_wait_any() { + int pid_status; + pid_t pid = waitpid(-1, &pid_status, WUNTRACED); + if (pid == -1) return -1; + handle_child_status(pid, pid_status); + process_clean_after_marking(is_interactive); + return pid; +} diff --git a/src/proc.h b/src/proc.h index b57b89c25..52c956571 100644 --- a/src/proc.h +++ b/src/proc.h @@ -364,6 +364,9 @@ void proc_pop_interactive(); /// proc_set_last_status. int proc_format_status(int status); +/// Wait for any process finishing. +pid_t proc_wait_any(); + #endif bool terminal_give_to_job(job_t *j, int cont); diff --git a/tests/wait.expect b/tests/wait.expect new file mode 100644 index 000000000..94870f517 --- /dev/null +++ b/tests/wait.expect @@ -0,0 +1,108 @@ +# vim: set filetype=expect: +spawn $fish +expect_prompt + +# one background job +set error_msg "one background job: Fail" + +send_line "sleep 1 &" +expect_prompt +send_line "wait" +expect_prompt "Job 1, 'sleep 1 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "jobs" +expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg } + +# three background jobs +set error_msg "three background jobs: Fail" + +send_line "sleep 3 &; sleep 1 &; sleep 2 &" +expect_prompt +send_line "wait" +expect "Job 2, 'sleep 1 &' has ended" {} timeout { puts stderr $error_msg } +expect "Job 3, 'sleep 2 &' has ended" {} timeout { puts stderr $error_msg } +expect_prompt "Job 1, 'sleep 3 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "jobs" +expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg } + +# one job id specified +set error_msg "one job id specified" + +send_line "sleep 3 &; sleep 1 &; sleep 2 &" +expect_prompt +send_line "wait %3" +expect "Job 2, 'sleep 1 &' has ended" {} timeout { puts stderr $error_msg } +expect_prompt "Job 3, 'sleep 2 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "wait %1" +expect_prompt "Job 1, 'sleep 3 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "jobs" +expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg } + +# three job ids specified +set error_msg "three job ids specified: Fail" + +send_line "sleep 3 &; sleep 1 &; sleep 2 &; sleep 4 &;" +expect_prompt +send_line "wait %1 %3 %4" +expect "Job 2, 'sleep 1 &' has ended" {} timeout { puts stderr $error_msg } +expect "Job 3, 'sleep 2 &' has ended" {} timeout { puts stderr $error_msg } +expect "Job 1, 'sleep 3 &' has ended" {} timeout { puts stderr $error_msg } +expect_prompt "Job 4, 'sleep 4 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "jobs" +expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg } + +# wait with -n option +set error_msg "wait with -n option: Fail" + +send_line "sleep 3 &; sleep 1 &; sleep 2 &" +expect_prompt +send_line "wait -n" +expect_prompt "Job 2, 'sleep 1 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "wait -n" +expect_prompt "Job 3, 'sleep 2 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "wait -n" +expect_prompt "Job 1, 'sleep 3 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "jobs" +expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg } + +# specify job ids with -n option +set error_msg "specify job ids with -n option: Fail" + +send_line "sleep 3 &; sleep 1 &; sleep 2 &" +expect_prompt +send_line "wait -n %1 %3" +expect "Job 2, 'sleep 1 &' has ended" {} timeout { puts stderr $error_msg } +expect_prompt "Job 3, 'sleep 2 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "wait -n %1" +expect_prompt "Job 1, 'sleep 3 &' has ended" {} unmatched { puts stderr $error_msg } +send_line "jobs" +expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg } + +# don't wait stopped jobs +set error_msg "don't wait stopped jobs: Fail" + +send_line "sleep 3 &" +expect_prompt +send_line "kill -STOP %1" +expect_prompt +send_line "wait" +expect_prompt +send_line "wait %1" +expect_prompt +send_line "wait -n" +expect_prompt +send_line "bg %1" +expect_prompt +send_line "wait" +expect_prompt +send_line "jobs" +expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg } + +# return immediately when no jobs +set error_msg "don't wait stopped jobs: Fail" + +send_line "wait" +expect_prompt +send_line "wait -n" +expect_prompt +send_line "jobs" +expect_prompt "jobs: There are no jobs" {} unmatched { puts stderr $error_msg } diff --git a/tests/wait.expect.err b/tests/wait.expect.err new file mode 100644 index 000000000..e69de29bb diff --git a/tests/wait.expect.out b/tests/wait.expect.out new file mode 100644 index 000000000..e69de29bb