From 76adfed0e7f84d5fb6aa5cc29691c525993400d0 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 15 Jan 2023 19:52:08 -0800 Subject: [PATCH] Implement builtin_wait in Rust This implements builtin_wait in Rust. --- CMakeLists.txt | 2 +- fish-rust/build.rs | 1 + fish-rust/src/builtins/mod.rs | 2 + fish-rust/src/builtins/shared.rs | 147 ++++++++++++++++++ fish-rust/src/builtins/wait.rs | 246 +++++++++++++++++++++++++++++++ fish-rust/src/ffi.rs | 59 ++++++++ fish-rust/src/lib.rs | 2 + fish-rust/src/wutil/gettext.rs | 2 +- src/builtin.cpp | 36 ++++- src/builtin.h | 5 + src/builtins/wait.cpp | 202 ------------------------- src/builtins/wait.h | 11 -- src/wait_handle.cpp | 6 + src/wait_handle.h | 8 + 14 files changed, 512 insertions(+), 217 deletions(-) create mode 100644 fish-rust/src/builtins/mod.rs create mode 100644 fish-rust/src/builtins/shared.rs create mode 100644 fish-rust/src/builtins/wait.rs delete mode 100644 src/builtins/wait.cpp delete mode 100644 src/builtins/wait.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 971dad866..935438924 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,7 +111,7 @@ set(FISH_BUILTIN_SRCS src/builtins/realpath.cpp src/builtins/return.cpp src/builtins/set.cpp src/builtins/set_color.cpp src/builtins/source.cpp src/builtins/status.cpp src/builtins/string.cpp src/builtins/test.cpp src/builtins/type.cpp src/builtins/ulimit.cpp - src/builtins/wait.cpp) +) # List of other sources. set(FISH_SRCS diff --git a/fish-rust/build.rs b/fish-rust/build.rs index cba23f92a..ab8977600 100644 --- a/fish-rust/build.rs +++ b/fish-rust/build.rs @@ -24,6 +24,7 @@ fn main() -> miette::Result<()> { "src/ffi_tests.rs", "src/smoke.rs", "src/topic_monitor.rs", + "src/builtins/shared.rs", ]; cxx_build::bridges(source_files) .flag_if_supported("-std=c++11") diff --git a/fish-rust/src/builtins/mod.rs b/fish-rust/src/builtins/mod.rs new file mode 100644 index 000000000..9ae08c6e6 --- /dev/null +++ b/fish-rust/src/builtins/mod.rs @@ -0,0 +1,2 @@ +pub mod shared; +pub mod wait; diff --git a/fish-rust/src/builtins/shared.rs b/fish-rust/src/builtins/shared.rs new file mode 100644 index 000000000..f92454f65 --- /dev/null +++ b/fish-rust/src/builtins/shared.rs @@ -0,0 +1,147 @@ +use crate::builtins::wait; +use crate::ffi::{self, parser_t, wcharz_t, Repin, RustBuiltin}; +use crate::wchar::{self, wstr}; +use crate::wchar_ffi::{c_str, empty_wstring}; +use libc::c_int; +use std::pin::Pin; + +#[cxx::bridge] +mod builtins_ffi { + extern "C++" { + include!("wutil.h"); + include!("parser.h"); + include!("builtin.h"); + + type wcharz_t = crate::ffi::wcharz_t; + type parser_t = crate::ffi::parser_t; + type io_streams_t = crate::ffi::io_streams_t; + type RustBuiltin = crate::ffi::RustBuiltin; + } + extern "Rust" { + fn rust_run_builtin( + parser: Pin<&mut parser_t>, + streams: Pin<&mut io_streams_t>, + cpp_args: &Vec, + builtin: RustBuiltin, + ); + } + + impl Vec {} +} + +/// A handy return value for successful builtins. +pub const STATUS_CMD_OK: Option = Some(0); + +/// A handy return value for invalid args. +pub const STATUS_INVALID_ARGS: Option = Some(2); + +/// A wrapper around output_stream_t. +pub struct output_stream_t(*mut ffi::output_stream_t); + +impl output_stream_t { + /// \return the underlying output_stream_t. + fn ffi(&mut self) -> Pin<&mut ffi::output_stream_t> { + unsafe { (*self.0).pin() } + } + + /// Append a &wtr or WString. + pub fn append>(&mut self, s: Str) -> bool { + self.ffi().append1(c_str!(s)) + } +} + +// Convenience wrappers around C++ io_streams_t. +pub struct io_streams_t { + streams: *mut builtins_ffi::io_streams_t, + pub out: output_stream_t, + pub err: output_stream_t, +} + +impl io_streams_t { + fn new(mut streams: Pin<&mut builtins_ffi::io_streams_t>) -> io_streams_t { + let out = output_stream_t(streams.as_mut().get_out().unpin()); + let err = output_stream_t(streams.as_mut().get_err().unpin()); + let streams = streams.unpin(); + io_streams_t { streams, out, err } + } + + fn ffi_pin(&mut self) -> Pin<&mut builtins_ffi::io_streams_t> { + unsafe { Pin::new_unchecked(&mut *self.streams) } + } + + fn ffi_ref(&self) -> &builtins_ffi::io_streams_t { + unsafe { &*self.streams } + } +} + +fn rust_run_builtin( + parser: Pin<&mut parser_t>, + streams: Pin<&mut builtins_ffi::io_streams_t>, + cpp_args: &Vec, + builtin: RustBuiltin, +) { + let mut storage = Vec::::new(); + for arg in cpp_args { + storage.push(arg.into()); + } + let mut args = Vec::new(); + for arg in &storage { + args.push(arg.as_utfstr()); + } + let streams = &mut io_streams_t::new(streams); + run_builtin(parser.unpin(), streams, args.as_mut_slice(), builtin); +} + +pub fn run_builtin( + parser: &mut parser_t, + streams: &mut io_streams_t, + args: &mut [&wstr], + builtin: RustBuiltin, +) -> Option { + match builtin { + RustBuiltin::Wait => wait::wait(parser, streams, args), + } +} + +// Covers of these functions that take care of the pinning, etc. +// These all return STATUS_INVALID_ARGS. +pub fn builtin_missing_argument( + parser: &mut parser_t, + streams: &mut io_streams_t, + cmd: &wstr, + opt: &wstr, + print_hints: bool, +) { + ffi::builtin_missing_argument( + parser.pin(), + streams.ffi_pin(), + c_str!(cmd), + c_str!(opt), + print_hints, + ); +} + +pub fn builtin_unknown_option( + parser: &mut parser_t, + streams: &mut io_streams_t, + cmd: &wstr, + opt: &wstr, + print_hints: bool, +) { + ffi::builtin_missing_argument( + parser.pin(), + streams.ffi_pin(), + c_str!(cmd), + c_str!(opt), + print_hints, + ); +} + +pub fn builtin_print_help(parser: &mut parser_t, streams: &io_streams_t, cmd: &wstr) { + ffi::builtin_print_help( + parser.pin(), + streams.ffi_ref(), + c_str!(cmd), + empty_wstring(), + ); +} diff --git a/fish-rust/src/builtins/wait.rs b/fish-rust/src/builtins/wait.rs new file mode 100644 index 000000000..36d9a8246 --- /dev/null +++ b/fish-rust/src/builtins/wait.rs @@ -0,0 +1,246 @@ +use libc::{c_int, pid_t}; + +use crate::builtins::shared::{ + builtin_missing_argument, builtin_print_help, builtin_unknown_option, io_streams_t, + STATUS_CMD_OK, STATUS_INVALID_ARGS, +}; +use crate::ffi::{job_t, parser_t, proc_wait_any, wait_handle_ref_t, Repin}; +use crate::signal::sigchecker_t; +use crate::wchar::{widestrs, wstr}; +use crate::wgetopt::{wgetopter_t, wopt, woption, woption_argument_t}; +use crate::wutil::{self, fish_wcstoi, wgettext_fmt}; + +/// \return true if we can wait on a job. +fn can_wait_on_job(j: &cxx::SharedPtr) -> bool { + j.is_constructed() && !j.is_foreground() && !j.is_stopped() +} + +/// \return true if a wait handle matches a pid or a process name. +/// For convenience, this returns false if the wait handle is null. +fn wait_handle_matches(query: WaitHandleQuery, wh: &wait_handle_ref_t) -> bool { + if wh.is_null() { + return false; + } + match query { + WaitHandleQuery::Pid(pid) => wh.get_pid().0 == pid, + WaitHandleQuery::ProcName(proc_name) => proc_name == wh.get_base_name(), + } +} + +/// \return true if all chars are numeric. +fn iswnumeric(s: &wstr) -> bool { + s.chars().all(|c| c.is_ascii_digit()) +} + +// Hack to copy wait handles into a vector. +fn get_wait_handle_list(parser: &parser_t) -> Vec { + let mut handles = Vec::new(); + let whs = parser.get_wait_handles1(); + for idx in 0..whs.size() { + handles.push(whs.get(idx)); + } + handles +} + +#[derive(Copy, Clone)] +enum WaitHandleQuery<'a> { + Pid(pid_t), + ProcName(&'a wstr), +} + +/// Walk the list of jobs, looking for a process with the given pid or proc name. +/// Append all matching wait handles to \p handles. +/// \return true if we found a matching job (even if not waitable), false if not. +fn find_wait_handles( + query: WaitHandleQuery<'_>, + parser: &parser_t, + handles: &mut Vec, +) -> bool { + // Has a job already completed? + // TODO: we can avoid traversing this list if searching by pid. + let mut matched = false; + for wh in get_wait_handle_list(parser) { + if wait_handle_matches(query, &wh) { + handles.push(wh); + matched = true; + } + } + + // Is there a running job match? + for j in parser.get_jobs() { + // We want to set 'matched' to true if we could have matched, even if the job was stopped. + let provide_handle = can_wait_on_job(j); + for proc in j.get_procs() { + let wh = proc.pin_mut().make_wait_handle(j.get_internal_job_id()); + if wait_handle_matches(query, &wh) { + matched = true; + if provide_handle { + handles.push(wh); + } + } + } + } + matched +} + +fn get_all_wait_handles(parser: &parser_t) -> Vec { + let mut result = Vec::new(); + // Get wait handles for reaped jobs. + let wait_handles = parser.get_wait_handles1(); + for idx in 0..wait_handles.size() { + result.push(wait_handles.get(idx)); + } + + // Get wait handles for running jobs. + for j in parser.get_jobs() { + if !can_wait_on_job(j) { + continue; + } + for proc_ptr in j.get_procs().iter_mut() { + let proc = proc_ptr.pin_mut(); + let wh = proc.make_wait_handle(j.get_internal_job_id()); + if !wh.is_null() { + result.push(wh); + } + } + } + result +} + +fn is_completed(wh: &wait_handle_ref_t) -> bool { + wh.is_completed() +} + +/// Wait for the given wait handles to be marked as completed. +/// If \p any_flag is set, wait for the first one; otherwise wait for all. +/// \return a status code. +fn wait_for_completion( + parser: &mut parser_t, + whs: &[wait_handle_ref_t], + any_flag: bool, +) -> Option { + if whs.is_empty() { + return Some(0); + } + + let mut sigint = sigchecker_t::new_sighupint(); + loop { + let finished = if any_flag { + whs.iter().any(is_completed) + } else { + whs.iter().all(is_completed) + }; + + if finished { + // Remove completed wait handles (at most 1 if any_flag is set). + for wh in whs { + if is_completed(wh) { + parser.pin().get_wait_handles().remove(wh); + if any_flag { + break; + } + } + } + return Some(0); + } + if sigint.check() { + return Some(128 + libc::SIGINT); + } + proc_wait_any(parser.pin()); + } +} + +#[widestrs] +pub fn wait( + parser: &mut parser_t, + streams: &mut io_streams_t, + argv: &mut [&wstr], +) -> Option { + let cmd = argv[0]; + let argc = argv.len(); + let mut any_flag = false; // flag for -n option + let mut print_help = false; + let print_hints = false; + + const shortopts: &wstr = ":nh"L; + const longopts: &[woption] = &[ + wopt("any"L, woption_argument_t::no_argument, 'n'), + wopt("help"L, woption_argument_t::no_argument, 'h'), + ]; + + let mut w = wgetopter_t::new(shortopts, longopts, argv); + while let Some(c) = w.wgetopt_long() { + match c { + 'n' => { + any_flag = true; + } + 'h' => { + print_help = true; + } + ':' => { + builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], print_hints); + return STATUS_INVALID_ARGS; + } + '?' => { + builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], print_hints); + return STATUS_INVALID_ARGS; + } + _ => { + panic!("unexpected retval from wgeopter.next()"); + } + } + } + + if print_help { + builtin_print_help(parser, streams, cmd); + return STATUS_CMD_OK; + } + + if w.woptind == argc { + // No jobs specified. + // Note this may succeed with an empty wait list. + return wait_for_completion(parser, &get_all_wait_handles(parser), any_flag); + } + + // Get the list of wait handles for our waiting. + let mut wait_handles: Vec = Vec::new(); + for i in w.woptind..argc { + if iswnumeric(argv[i]) { + // argument is pid + let mpid: Result = fish_wcstoi(argv[i].chars()); + if mpid.is_err() || mpid.unwrap() <= 0 { + streams.err.append(wgettext_fmt!( + "%ls: '%ls' is not a valid process id\n", + cmd, + argv[i], + )); + continue; + } + let pid = mpid.unwrap() as pid_t; + if !find_wait_handles(WaitHandleQuery::Pid(pid), parser, &mut wait_handles) { + streams.err.append(wgettext_fmt!( + "%ls: Could not find a job with process id '%d'\n", + cmd, + pid, + )); + } + } else { + // argument is process name + if !find_wait_handles( + WaitHandleQuery::ProcName(argv[i]), + parser, + &mut wait_handles, + ) { + streams.err.append(wgettext_fmt!( + "%ls: Could not find child processes with the name '%ls'\n", + cmd, + argv[i], + )); + } + } + } + if wait_handles.is_empty() { + return STATUS_INVALID_ARGS; + } + return wait_for_completion(parser, &wait_handles, any_flag); +} diff --git a/fish-rust/src/ffi.rs b/fish-rust/src/ffi.rs index a9396cc16..1c95ff328 100644 --- a/fish-rust/src/ffi.rs +++ b/fish-rust/src/ffi.rs @@ -1,6 +1,8 @@ use crate::wchar::{self}; +use ::std::pin::Pin; use ::std::slice; use autocxx::prelude::*; +use cxx::SharedPtr; // autocxx has been hacked up to know about this. pub type wchar_t = u32; @@ -33,6 +35,39 @@ include_cpp! { generate!("wildcard_match") generate!("wgettext_ptr") + generate!("parser_t") + generate!("job_t") + generate!("process_t") + + generate!("proc_wait_any") + + generate!("output_stream_t") + generate!("io_streams_t") + + generate_pod!("RustFFIJobList") + generate_pod!("RustFFIProcList") + generate_pod!("RustBuiltin") + + generate!("builtin_missing_argument") + generate!("builtin_unknown_option") + generate!("builtin_print_help") + + generate!("wait_handle_t") + generate!("wait_handle_store_t") +} + +impl parser_t { + pub fn get_jobs(&self) -> &[SharedPtr] { + let ffi_jobs = self.ffi_jobs(); + unsafe { slice::from_raw_parts(ffi_jobs.jobs, ffi_jobs.count) } + } +} + +impl job_t { + pub fn get_procs(&self) -> &mut [UniquePtr] { + let ffi_procs = self.ffi_processes(); + unsafe { slice::from_raw_parts_mut(ffi_procs.procs, ffi_procs.count) } + } } /// Allow wcharz_t to be "into" wstr. @@ -53,6 +88,30 @@ impl From for wchar::WString { } } +/// A bogus trait for turning &mut Foo into Pin<&mut Foo>. +/// autocxx enforces that non-const methods must be called through Pin, +/// but this means we can't pass around mutable references to types like parser_t. +/// We also don't want to assert that parser_t is Unpin. +/// So we just allow constructing a pin from a mutable reference; none of the C++ code. +/// It's worth considering disabling this in cxx; for now we use this trait. +/// Eventually parser_t and io_streams_t will not require Pin so we just unsafe-it away. +pub trait Repin { + fn pin(&mut self) -> Pin<&mut Self> { + unsafe { Pin::new_unchecked(self) } + } + + fn unpin(self: Pin<&mut Self>) -> &mut Self { + unsafe { self.get_unchecked_mut() } + } +} + +// Implement Repin for our types. +impl Repin for parser_t {} +impl Repin for job_t {} +impl Repin for process_t {} +impl Repin for io_streams_t {} +impl Repin for output_stream_t {} + pub use autocxx::c_int; pub use ffi::*; pub use libc::c_char; diff --git a/fish-rust/src/lib.rs b/fish-rust/src/lib.rs index 54ee35a2d..cb8dafcc9 100644 --- a/fish-rust/src/lib.rs +++ b/fish-rust/src/lib.rs @@ -20,3 +20,5 @@ mod wchar_ext; mod wchar_ffi; mod wgetopt; mod wutil; + +mod builtins; diff --git a/fish-rust/src/wutil/gettext.rs b/fish-rust/src/wutil/gettext.rs index b4cfb9533..53febbf30 100644 --- a/fish-rust/src/wutil/gettext.rs +++ b/fish-rust/src/wutil/gettext.rs @@ -17,7 +17,6 @@ pub fn wgettext_impl_do_not_use_directly(text: &[wchar_t]) -> &'static wstr { /// Get a (possibly translated) string from a string literal. /// This returns a &'static wstr. -#[allow(unused_macros)] macro_rules! wgettext { ($string:literal) => { crate::wutil::gettext::wgettext_impl_do_not_use_directly( @@ -25,6 +24,7 @@ macro_rules! wgettext { ) }; } +pub(crate) use wgettext; /// Like wgettext, but applies a sprintf format string. /// The result is a WString. diff --git a/src/builtin.cpp b/src/builtin.cpp index 085b278da..cf9f7f338 100644 --- a/src/builtin.cpp +++ b/src/builtin.cpp @@ -59,14 +59,16 @@ #include "builtins/return.h" #include "builtins/set.h" #include "builtins/set_color.h" +#include "builtins/shared.rs.h" #include "builtins/source.h" #include "builtins/status.h" #include "builtins/string.h" #include "builtins/test.h" #include "builtins/type.h" #include "builtins/ulimit.h" -#include "builtins/wait.h" #include "complete.h" +#include "cxx.h" +#include "cxxgen.h" #include "fallback.h" // IWYU pragma: keep #include "flog.h" #include "io.h" @@ -79,6 +81,10 @@ #include "wgetopt.h" #include "wutil.h" // IWYU pragma: keep +static maybe_t try_get_rust_builtin(const wcstring &cmd); +static proc_status_t builtin_run_rust(parser_t &parser, io_streams_t &streams, + const wcstring_list_t &argv, RustBuiltin builtin); + /// Counts the number of arguments in the specified null-terminated array int builtin_count_args(const wchar_t *const *argv) { int argc; @@ -223,6 +229,10 @@ static maybe_t builtin_generic(parser_t &parser, io_streams_t &streams, con return STATUS_CMD_ERROR; } +static maybe_t implemented_in_rust(parser_t &, io_streams_t &, const wchar_t **) { + DIE("builtin is implemented in Rust, this should not be called"); +} + // How many bytes we read() at once. // Since this is just for counting, it can be massive. #define COUNT_CHUNK_SIZE (512 * 256) @@ -410,7 +420,7 @@ static constexpr builtin_data_t builtin_datas[] = { {L"true", &builtin_true, N_(L"Return a successful result")}, {L"type", &builtin_type, N_(L"Check if a thing is a thing")}, {L"ulimit", &builtin_ulimit, N_(L"Get/set resource usage limits")}, - {L"wait", &builtin_wait, N_(L"Wait for background processes completed")}, + {L"wait", &implemented_in_rust, N_(L"Wait for background processes completed")}, {L"while", &builtin_generic, N_(L"Perform a command multiple times")}, }; ASSERT_SORTED_BY_NAME(builtin_datas); @@ -442,6 +452,11 @@ proc_status_t builtin_run(parser_t &parser, const wcstring_list_t &argv, io_stre if (argv.empty()) return proc_status_t::from_exit_code(STATUS_INVALID_ARGS); const wcstring &cmdname = argv.front(); + auto rust_builtin = try_get_rust_builtin(cmdname); + if (rust_builtin.has_value()) { + return builtin_run_rust(parser, streams, argv, *rust_builtin); + } + // We can be handed a keyword by the parser as if it was a command. This happens when the user // follows the keyword by `-h` or `--help`. Since it isn't really a builtin command we need to // handle displaying help for it here. @@ -512,3 +527,20 @@ const wchar_t *builtin_get_desc(const wcstring &name) { } return result; } + +static maybe_t try_get_rust_builtin(const wcstring &cmd) { + if (cmd == L"wait") { + return RustBuiltin::Wait; + } + return none(); +} + +static proc_status_t builtin_run_rust(parser_t &parser, io_streams_t &streams, + const wcstring_list_t &argv, RustBuiltin builtin) { + ::rust::Vec rust_argv; + for (const wcstring &arg : argv) { + rust_argv.emplace_back(arg.c_str()); + } + rust_run_builtin(parser, streams, rust_argv, builtin); + return proc_status_t{}; +} diff --git a/src/builtin.h b/src/builtin.h index 3e0685683..a24ea3665 100644 --- a/src/builtin.h +++ b/src/builtin.h @@ -106,4 +106,9 @@ struct help_only_cmd_opts_t { }; int parse_help_only_cmd_opts(help_only_cmd_opts_t &opts, int *optind, int argc, const wchar_t **argv, parser_t &parser, io_streams_t &streams); + +/// An enum of the builtins implemented in Rust. +enum RustBuiltin : int32_t { + Wait, +}; #endif diff --git a/src/builtins/wait.cpp b/src/builtins/wait.cpp deleted file mode 100644 index b8bbcfed0..000000000 --- a/src/builtins/wait.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/// Functions for waiting for processes completed. -#include "config.h" // IWYU pragma: keep - -#include "wait.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../builtin.h" -#include "../common.h" -#include "../io.h" -#include "../maybe.h" -#include "../parser.h" -#include "../proc.h" -#include "../signals.h" -#include "../topic_monitor.h" -#include "../wait_handle.h" -#include "../wgetopt.h" -#include "../wutil.h" - -/// \return true if we can wait on a job. -static bool can_wait_on_job(const std::shared_ptr &j) { - return j->is_constructed() && !j->is_foreground() && !j->is_stopped(); -} - -/// \return true if a wait handle matches a pid or a process name. Exactly one should be passed. -/// For convenience, this returns false if the wait handle is null. -static bool wait_handle_matches(pid_t pid, const wchar_t *proc_name, const wait_handle_ref_t &wh) { - assert((pid > 0 || proc_name) && "Must specify either pid or proc_name"); - if (!wh) return false; - return (pid > 0 && pid == wh->pid) || (proc_name && proc_name == wh->base_name); -} - -/// Walk the list of jobs, looking for a process with \p pid (if nonzero) or \p proc_name (if not -/// null). Append all matching wait handles to \p handles. -/// \return true if we found a matching job (even if not waitable), false if not. -static bool find_wait_handles(pid_t pid, const wchar_t *proc_name, const parser_t &parser, - std::vector *handles) { - assert((pid > 0 || proc_name) && "Must specify either pid or proc_name"); - - // Has a job already completed? - // TODO: we can avoid traversing this list if searching by pid. - bool matched = false; - for (const auto &wh : parser.get_wait_handles().get_list()) { - if (wait_handle_matches(pid, proc_name, wh)) { - handles->push_back(wh); - matched = true; - } - } - - // Is there a running job match? - for (const auto &j : parser.jobs()) { - // We want to set 'matched' to true if we could have matched, even if the job was stopped. - bool provide_handle = can_wait_on_job(j); - for (const auto &proc : j->processes) { - auto wh = proc->make_wait_handle(j->internal_job_id); - if (wait_handle_matches(pid, proc_name, wh)) { - matched = true; - if (provide_handle) handles->push_back(std::move(wh)); - } - } - } - return matched; -} - -/// \return all wait handles for all jobs, current and already completed (!). -static std::vector get_all_wait_handles(const parser_t &parser) { - std::vector result; - // Get wait handles for reaped jobs. - const auto &whs = parser.get_wait_handles().get_list(); - result.insert(result.end(), whs.begin(), whs.end()); - - // Get wait handles for running jobs. - for (const auto &j : parser.jobs()) { - if (!can_wait_on_job(j)) continue; - for (const auto &proc : j->processes) { - if (auto wh = proc->make_wait_handle(j->internal_job_id)) { - result.push_back(std::move(wh)); - } - } - } - return result; -} - -static inline bool is_completed(const wait_handle_ref_t &wh) { return wh->completed; } - -/// Wait for the given wait handles to be marked as completed. -/// If \p any_flag is set, wait for the first one; otherwise wait for all. -/// \return a status code. -static int wait_for_completion(parser_t &parser, const std::vector &whs, - bool any_flag) { - if (whs.empty()) return 0; - - sigchecker_t sigint(topic_t::sighupint); - for (;;) { - if (any_flag ? std::any_of(whs.begin(), whs.end(), is_completed) - : std::all_of(whs.begin(), whs.end(), is_completed)) { - // Remove completed wait handles (at most 1 if any_flag is set). - for (const auto &wh : whs) { - if (is_completed(wh)) { - parser.get_wait_handles().remove(wh); - if (any_flag) break; - } - } - return 0; - } - if (sigint.check()) { - return 128 + SIGINT; - } - proc_wait_any(parser); - } - DIE("Unreachable"); -} - -/// 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; -} - -maybe_t builtin_wait(parser_t &parser, io_streams_t &streams, const wchar_t **argv) { - const wchar_t *cmd = argv[0]; - int argc = builtin_count_args(argv); - bool any_flag = false; // flag for -n option - bool print_help = false; - - static const wchar_t *const short_options = L":nh"; - static const struct woption long_options[] = { - {L"any", no_argument, 'n'}, {L"help", no_argument, 'h'}, {}}; - - 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 'h': - print_help = 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"); - } - } - } - - if (print_help) { - builtin_print_help(parser, streams, cmd); - return STATUS_CMD_OK; - } - - if (w.woptind == argc) { - // No jobs specified. - // Note this may succeed with an empty wait list. - return wait_for_completion(parser, get_all_wait_handles(parser), any_flag); - } - - // Get the list of wait handles for our waiting. - std::vector wait_handles; - 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 (!find_wait_handles(pid, nullptr, parser, &wait_handles)) { - 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_wait_handles(0, argv[i], parser, &wait_handles)) { - streams.err.append_format( - _(L"%ls: Could not find child processes with the name '%ls'\n"), cmd, argv[i]); - } - } - } - if (wait_handles.empty()) return STATUS_INVALID_ARGS; - return wait_for_completion(parser, wait_handles, any_flag); -} diff --git a/src/builtins/wait.h b/src/builtins/wait.h deleted file mode 100644 index 2bc0a0bcd..000000000 --- a/src/builtins/wait.h +++ /dev/null @@ -1,11 +0,0 @@ -// Prototypes for executing builtin_wait function. -#ifndef FISH_BUILTIN_WAIT_H -#define FISH_BUILTIN_WAIT_H - -#include "../maybe.h" - -class parser_t; -struct io_streams_t; - -maybe_t builtin_wait(parser_t &parser, io_streams_t &streams, const wchar_t **argv); -#endif diff --git a/src/wait_handle.cpp b/src/wait_handle.cpp index 765419151..9d2c17252 100644 --- a/src/wait_handle.cpp +++ b/src/wait_handle.cpp @@ -40,6 +40,12 @@ void wait_handle_store_t::remove_by_pid(pid_t pid) { } } +wait_handle_ref_t wait_handle_store_t::get(size_t idx) const { + // TODO: this is O(N)! + assert(idx < handles_.size() && "index out of range"); + return *std::next(std::begin(handles_), idx); +} + wait_handle_ref_t wait_handle_store_t::get_by_pid(pid_t pid) const { auto iter = handle_map_.find(pid); if (iter == handle_map_.end()) return nullptr; diff --git a/src/wait_handle.h b/src/wait_handle.h index a040330dd..421e0c028 100644 --- a/src/wait_handle.h +++ b/src/wait_handle.h @@ -37,6 +37,11 @@ struct wait_handle_t { /// Set to true when the process is completed. bool completed{false}; + + /// Autocxx junk. + bool is_completed() const { return completed; } + int get_pid() const { return pid; } + const wcstring &get_base_name() const { return base_name; } }; using wait_handle_ref_t = std::shared_ptr; @@ -70,6 +75,9 @@ class wait_handle_store_t : noncopyable_t { /// Get the list of all wait handles. const wait_handle_list_t &get_list() const { return handles_; } + /// autocxx does not support std::list so allow accessing by index. + wait_handle_ref_t get(size_t idx) const; + /// Convenience to return the size, for testing. size_t size() const { return handles_.size(); }