mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Implement builtin_wait in Rust
This implements builtin_wait in Rust.
This commit is contained in:
parent
f38543ccb7
commit
76adfed0e7
14 changed files with 512 additions and 217 deletions
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
2
fish-rust/src/builtins/mod.rs
Normal file
2
fish-rust/src/builtins/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod shared;
|
||||
pub mod wait;
|
147
fish-rust/src/builtins/shared.rs
Normal file
147
fish-rust/src/builtins/shared.rs
Normal 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(),
|
||||
);
|
||||
}
|
246
fish-rust/src/builtins/wait.rs
Normal file
246
fish-rust/src/builtins/wait.rs
Normal 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);
|
||||
}
|
|
@ -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<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.
|
||||
|
@ -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 ffi::*;
|
||||
pub use libc::c_char;
|
||||
|
|
|
@ -20,3 +20,5 @@ mod wchar_ext;
|
|||
mod wchar_ffi;
|
||||
mod wgetopt;
|
||||
mod wutil;
|
||||
|
||||
mod builtins;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<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
|
||||
int builtin_count_args(const wchar_t *const *argv) {
|
||||
int argc;
|
||||
|
@ -223,6 +229,10 @@ static maybe_t<int> builtin_generic(parser_t &parser, io_streams_t &streams, con
|
|||
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.
|
||||
// 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<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{};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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<wait_handle_t>;
|
||||
|
||||
|
@ -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(); }
|
||||
|
||||
|
|
Loading…
Reference in a new issue