Implement builtin_wait in Rust

This implements builtin_wait in Rust.
This commit is contained in:
ridiculousfish 2023-01-15 19:52:08 -08:00
parent f38543ccb7
commit 76adfed0e7
14 changed files with 512 additions and 217 deletions

View file

@ -111,7 +111,7 @@ set(FISH_BUILTIN_SRCS
src/builtins/realpath.cpp src/builtins/return.cpp src/builtins/set.cpp 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/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/string.cpp src/builtins/test.cpp src/builtins/type.cpp src/builtins/ulimit.cpp
src/builtins/wait.cpp) )
# List of other sources. # List of other sources.
set(FISH_SRCS set(FISH_SRCS

View file

@ -24,6 +24,7 @@ fn main() -> miette::Result<()> {
"src/ffi_tests.rs", "src/ffi_tests.rs",
"src/smoke.rs", "src/smoke.rs",
"src/topic_monitor.rs", "src/topic_monitor.rs",
"src/builtins/shared.rs",
]; ];
cxx_build::bridges(source_files) cxx_build::bridges(source_files)
.flag_if_supported("-std=c++11") .flag_if_supported("-std=c++11")

View file

@ -0,0 +1,2 @@
pub mod shared;
pub mod wait;

View file

@ -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<wcharz_t>,
builtin: RustBuiltin,
);
}
impl Vec<wcharz_t> {}
}
/// A handy return value for successful builtins.
pub const STATUS_CMD_OK: Option<c_int> = Some(0);
/// A handy return value for invalid args.
pub const STATUS_INVALID_ARGS: Option<c_int> = 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<Str: AsRef<wstr>>(&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<wcharz_t>,
builtin: RustBuiltin,
) {
let mut storage = Vec::<wchar::WString>::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<c_int> {
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(),
);
}

View file

@ -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<job_t>) -> 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<wait_handle_ref_t> {
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<wait_handle_ref_t>,
) -> 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<wait_handle_ref_t> {
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<c_int> {
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<c_int> {
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<wait_handle_ref_t> = Vec::new();
for i in w.woptind..argc {
if iswnumeric(argv[i]) {
// argument is pid
let mpid: Result<pid_t, wutil::Error> = 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);
}

View file

@ -1,6 +1,8 @@
use crate::wchar::{self}; use crate::wchar::{self};
use ::std::pin::Pin;
use ::std::slice; use ::std::slice;
use autocxx::prelude::*; use autocxx::prelude::*;
use cxx::SharedPtr;
// autocxx has been hacked up to know about this. // autocxx has been hacked up to know about this.
pub type wchar_t = u32; pub type wchar_t = u32;
@ -33,6 +35,39 @@ include_cpp! {
generate!("wildcard_match") generate!("wildcard_match")
generate!("wgettext_ptr") 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<job_t>] {
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<process_t>] {
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. /// Allow wcharz_t to be "into" wstr.
@ -53,6 +88,30 @@ impl From<wcharz_t> 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 autocxx::c_int;
pub use ffi::*; pub use ffi::*;
pub use libc::c_char; pub use libc::c_char;

View file

@ -20,3 +20,5 @@ mod wchar_ext;
mod wchar_ffi; mod wchar_ffi;
mod wgetopt; mod wgetopt;
mod wutil; mod wutil;
mod builtins;

View file

@ -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. /// Get a (possibly translated) string from a string literal.
/// This returns a &'static wstr. /// This returns a &'static wstr.
#[allow(unused_macros)]
macro_rules! wgettext { macro_rules! wgettext {
($string:literal) => { ($string:literal) => {
crate::wutil::gettext::wgettext_impl_do_not_use_directly( 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. /// Like wgettext, but applies a sprintf format string.
/// The result is a WString. /// The result is a WString.

View file

@ -59,14 +59,16 @@
#include "builtins/return.h" #include "builtins/return.h"
#include "builtins/set.h" #include "builtins/set.h"
#include "builtins/set_color.h" #include "builtins/set_color.h"
#include "builtins/shared.rs.h"
#include "builtins/source.h" #include "builtins/source.h"
#include "builtins/status.h" #include "builtins/status.h"
#include "builtins/string.h" #include "builtins/string.h"
#include "builtins/test.h" #include "builtins/test.h"
#include "builtins/type.h" #include "builtins/type.h"
#include "builtins/ulimit.h" #include "builtins/ulimit.h"
#include "builtins/wait.h"
#include "complete.h" #include "complete.h"
#include "cxx.h"
#include "cxxgen.h"
#include "fallback.h" // IWYU pragma: keep #include "fallback.h" // IWYU pragma: keep
#include "flog.h" #include "flog.h"
#include "io.h" #include "io.h"
@ -79,6 +81,10 @@
#include "wgetopt.h" #include "wgetopt.h"
#include "wutil.h" // IWYU pragma: keep #include "wutil.h" // IWYU pragma: keep
static maybe_t<RustBuiltin> 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 /// Counts the number of arguments in the specified null-terminated array
int builtin_count_args(const wchar_t *const *argv) { int builtin_count_args(const wchar_t *const *argv) {
int argc; int argc;
@ -223,6 +229,10 @@ static maybe_t<int> builtin_generic(parser_t &parser, io_streams_t &streams, con
return STATUS_CMD_ERROR; return STATUS_CMD_ERROR;
} }
static maybe_t<int> 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. // How many bytes we read() at once.
// Since this is just for counting, it can be massive. // Since this is just for counting, it can be massive.
#define COUNT_CHUNK_SIZE (512 * 256) #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"true", &builtin_true, N_(L"Return a successful result")},
{L"type", &builtin_type, N_(L"Check if a thing is a thing")}, {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"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")}, {L"while", &builtin_generic, N_(L"Perform a command multiple times")},
}; };
ASSERT_SORTED_BY_NAME(builtin_datas); 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); if (argv.empty()) return proc_status_t::from_exit_code(STATUS_INVALID_ARGS);
const wcstring &cmdname = argv.front(); 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 // 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 // follows the keyword by `-h` or `--help`. Since it isn't really a builtin command we need to
// handle displaying help for it here. // handle displaying help for it here.
@ -512,3 +527,20 @@ const wchar_t *builtin_get_desc(const wcstring &name) {
} }
return result; return result;
} }
static maybe_t<RustBuiltin> 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<wcharz_t> 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{};
}

View file

@ -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, 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); 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 #endif

View file

@ -1,202 +0,0 @@
/// Functions for waiting for processes completed.
#include "config.h" // IWYU pragma: keep
#include "wait.h"
#include <algorithm>
#include <cerrno>
#include <csignal>
#include <deque>
#include <list>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#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<job_t> &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<wait_handle_ref_t> *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<wait_handle_ref_t> get_all_wait_handles(const parser_t &parser) {
std::vector<wait_handle_ref_t> 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<wait_handle_ref_t> &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<int> 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_handle_ref_t> 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);
}

View file

@ -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<int> builtin_wait(parser_t &parser, io_streams_t &streams, const wchar_t **argv);
#endif

View file

@ -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 { wait_handle_ref_t wait_handle_store_t::get_by_pid(pid_t pid) const {
auto iter = handle_map_.find(pid); auto iter = handle_map_.find(pid);
if (iter == handle_map_.end()) return nullptr; if (iter == handle_map_.end()) return nullptr;

View file

@ -37,6 +37,11 @@ struct wait_handle_t {
/// Set to true when the process is completed. /// Set to true when the process is completed.
bool completed{false}; 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<wait_handle_t>; using wait_handle_ref_t = std::shared_ptr<wait_handle_t>;
@ -70,6 +75,9 @@ class wait_handle_store_t : noncopyable_t {
/// Get the list of all wait handles. /// Get the list of all wait handles.
const wait_handle_list_t &get_list() const { return 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. /// Convenience to return the size, for testing.
size_t size() const { return handles_.size(); } size_t size() const { return handles_.size(); }