From 9ac6cbefb15d8a2a1bc0c6f990f4614aafc9bb75 Mon Sep 17 00:00:00 2001 From: Xiretza Date: Sat, 11 Feb 2023 21:31:08 +0100 Subject: [PATCH] Port event.cpp to rust Port src/event.cpp to fish-rust/event.rs and some needed functions. Co-authored-by: Mahmoud Al-Qudsi --- fish-rust/build.rs | 1 + fish-rust/src/builtins/emit.rs | 27 +- fish-rust/src/event.rs | 923 +++++++++++++++++++++++++++++++++ fish-rust/src/ffi.rs | 24 +- fish-rust/src/lib.rs | 1 + src/builtins/function.cpp | 51 +- src/builtins/set.cpp | 2 +- src/common.cpp | 1 + src/common.h | 4 + src/env.cpp | 10 +- src/env.h | 7 +- src/event.cpp | 548 +------------------ src/event.h | 157 +----- src/ffi.h | 2 +- src/fish.cpp | 2 +- src/function.cpp | 19 +- src/io.h | 5 + src/parse_execution.cpp | 9 +- src/parser.cpp | 16 +- src/parser.h | 19 +- src/proc.cpp | 20 +- src/termsize.cpp | 4 + src/termsize.h | 3 + 23 files changed, 1082 insertions(+), 773 deletions(-) create mode 100644 fish-rust/src/event.rs diff --git a/fish-rust/build.rs b/fish-rust/build.rs index e76b193c7..1064615b8 100644 --- a/fish-rust/build.rs +++ b/fish-rust/build.rs @@ -20,6 +20,7 @@ fn main() -> miette::Result<()> { // This must come before autocxx so that cxx can emit its cxx.h header. let source_files = vec![ "src/abbrs.rs", + "src/event.rs", "src/fd_monitor.rs", "src/fd_readable_set.rs", "src/fds.rs", diff --git a/fish-rust/src/builtins/emit.rs b/fish-rust/src/builtins/emit.rs index 49869848d..16009de1e 100644 --- a/fish-rust/src/builtins/emit.rs +++ b/fish-rust/src/builtins/emit.rs @@ -4,9 +4,9 @@ use super::shared::{ builtin_print_help, io_streams_t, HelpOnlyCmdOpts, STATUS_CMD_OK, STATUS_INVALID_ARGS, }; -use crate::ffi::{self, parser_t, Repin}; -use crate::wchar::wstr; -use crate::wchar_ffi::{W0String, WCharToFFI}; +use crate::event; +use crate::ffi::parser_t; +use crate::wchar::{wstr, WString}; use crate::wutil::format::printf::sprintf; #[widestrs] @@ -33,20 +33,13 @@ pub fn emit( return STATUS_INVALID_ARGS; }; - let event_args: Vec = argv[opts.optind + 1..] - .iter() - .map(|s| W0String::from_ustr(s).unwrap()) - .collect(); - let event_arg_ptrs: Vec = event_args - .iter() - .map(|s| ffi::wcharz_t { str_: s.as_ptr() }) - .collect(); - - ffi::event_fire_generic( - parser.pin(), - event_name.to_ffi(), - event_arg_ptrs.as_ptr(), - c_int::try_from(event_arg_ptrs.len()).unwrap().into(), + event::fire_generic( + parser, + (*event_name).to_owned(), + argv[opts.optind + 1..] + .iter() + .map(|&s| WString::from(s)) + .collect(), ); STATUS_CMD_OK diff --git a/fish-rust/src/event.rs b/fish-rust/src/event.rs new file mode 100644 index 000000000..e0e1c448f --- /dev/null +++ b/fish-rust/src/event.rs @@ -0,0 +1,923 @@ +//! Functions for handling event triggers +//! +//! Because most of these functions can be called by signal handler, it is important to make it well +//! defined when these functions produce output or perform memory allocations, since such functions +//! may not be safely called by signal handlers. + +use autocxx::WithinUniquePtr; +use cxx::{CxxVector, CxxWString, UniquePtr}; +use libc::pid_t; +use std::pin::Pin; +use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use std::sync::{Arc, Mutex}; +use widestring_suffix::widestrs; + +use crate::builtins::shared::io_streams_t; +use crate::common::{escape_string, EscapeFlags, EscapeStringStyle}; +use crate::ffi::{ + self, block_t, parser_t, signal_check_cancel, signal_handle, termsize_container_t, Repin, +}; +use crate::flog::FLOG; +use crate::signal::{sig2wcs, signal_get_desc}; +use crate::wchar::{wstr, WString, L}; +use crate::wchar_ffi::{wcharz_t, AsWstr, WCharFromFFI, WCharToFFI}; +use crate::wutil::sprintf; + +#[cxx::bridge] +mod event_ffi { + extern "C++" { + include!("wutil.h"); + include!("parser.h"); + include!("io.h"); + type wcharz_t = crate::ffi::wcharz_t; + type parser_t = crate::ffi::parser_t; + type io_streams_t = crate::ffi::io_streams_t; + } + + enum event_type_t { + any, + signal, + variable, + process_exit, + job_exit, + caller_exit, + generic, + } + + struct event_description_t { + typ: event_type_t, + signal: i32, + pid: i32, + internal_job_id: u64, + caller_id: u64, + str_param1: UniquePtr, + } + + extern "Rust" { + type EventHandler; + type Event; + + fn new_event_generic(desc: wcharz_t) -> Box; + fn new_event_variable_erase(name: &CxxWString) -> Box; + fn new_event_variable_set(name: &CxxWString) -> Box; + fn new_event_process_exit(pid: i32, status: i32) -> Box; + fn new_event_job_exit(pgid: i32, jid: u64) -> Box; + fn new_event_caller_exit(internal_job_id: u64, job_id: i32) -> Box; + #[cxx_name = "clone"] + fn clone_ffi(self: &Event) -> Box; + + #[cxx_name = "event_add_handler"] + fn event_add_handler_ffi(desc: &event_description_t, name: &CxxWString); + #[cxx_name = "event_remove_function_handlers"] + fn event_remove_function_handlers_ffi(name: &CxxWString) -> usize; + #[cxx_name = "event_get_function_handler_descs"] + fn event_get_function_handler_descs_ffi(name: &CxxWString) -> Vec; + + fn desc(self: &EventHandler) -> event_description_t; + fn function_name(self: &EventHandler) -> UniquePtr; + fn set_removed(self: &mut EventHandler); + + fn event_fire_generic_ffi( + parser: Pin<&mut parser_t>, + name: &CxxWString, + arguments: &CxxVector, + ); + #[cxx_name = "event_get_desc"] + fn event_get_desc_ffi(parser: &parser_t, evt: &Event) -> UniquePtr; + #[cxx_name = "event_fire_delayed"] + fn event_fire_delayed_ffi(parser: Pin<&mut parser_t>); + #[cxx_name = "event_fire"] + fn event_fire_ffi(parser: Pin<&mut parser_t>, event: &Event); + #[cxx_name = "event_print"] + fn event_print_ffi(streams: Pin<&mut io_streams_t>, type_filter: &CxxWString); + + #[cxx_name = "event_enqueue_signal"] + fn enqueue_signal(signal: usize); + #[cxx_name = "event_is_signal_observed"] + fn is_signal_observed(sig: usize) -> bool; + } +} + +pub use event_ffi::{event_description_t, event_type_t}; + +const ANY_PID: pid_t = 0; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum EventType { + /// Matches any event type (not always any event, as the function name may limit the choice as + /// well). + Any, + /// An event triggered by a signal. + Signal { signal: usize }, + /// An event triggered by a variable update. + Variable { name: WString }, + /// An event triggered by a process exit. + ProcessExit { + /// Process ID. Use [`ANY_PID`] to match any pid. + pid: pid_t, + }, + /// An event triggered by a job exit. + JobExit { + /// pid requested by the event, or [`ANY_PID`] for all. + pid: pid_t, + /// `internal_job_id` of the job to match. + /// If this is 0, we match either all jobs (`pid == ANY_PID`) or no jobs (otherwise). + internal_job_id: u64, + }, + /// An event triggered by a job exit, triggering the 'caller'-style events only. + CallerExit { + /// Internal job ID. + caller_id: u64, + }, + /// A generic event. + Generic { + /// The parameter describing this generic event. + param: WString, + }, +} + +impl EventType { + fn str_param1(&self) -> Option<&wstr> { + match self { + EventType::Any + | EventType::Signal { .. } + | EventType::ProcessExit { .. } + | EventType::JobExit { .. } + | EventType::CallerExit { .. } => None, + EventType::Variable { name } => Some(name), + EventType::Generic { param } => Some(param), + } + } + + #[widestrs] + fn name(&self) -> &'static wstr { + match self { + EventType::Any => "any"L, + EventType::Signal { .. } => "signal"L, + EventType::Variable { .. } => "variable"L, + EventType::ProcessExit { .. } => "process-exit"L, + EventType::JobExit { .. } => "job-exit"L, + EventType::CallerExit { .. } => "caller-exit"L, + EventType::Generic { .. } => "generic"L, + } + } + + fn matches_filter(&self, filter: &wstr) -> bool { + if filter.is_empty() { + return true; + } + + match self { + EventType::Any => return false, + EventType::ProcessExit { .. } + | EventType::JobExit { .. } + | EventType::CallerExit { .. } => { + if filter == L!("exit") { + return true; + } + } + _ => {} + } + + filter == self.name() + } +} + +impl From<&EventType> for event_type_t { + fn from(typ: &EventType) -> Self { + match typ { + EventType::Any => event_type_t::any, + EventType::Signal { .. } => event_type_t::signal, + EventType::Variable { .. } => event_type_t::variable, + EventType::ProcessExit { .. } => event_type_t::process_exit, + EventType::JobExit { .. } => event_type_t::job_exit, + EventType::CallerExit { .. } => event_type_t::caller_exit, + EventType::Generic { .. } => event_type_t::generic, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EventDescription { + // TODO: remove the wrapper struct and just put `EventType` where `EventDescription` is now + typ: EventType, +} + +impl From<&event_description_t> for EventDescription { + fn from(desc: &event_description_t) -> Self { + EventDescription { + typ: match desc.typ { + event_type_t::any => EventType::Any, + event_type_t::signal => EventType::Signal { + signal: desc.signal.try_into().unwrap(), + }, + event_type_t::variable => EventType::Variable { + name: desc.str_param1.from_ffi(), + }, + event_type_t::process_exit => EventType::ProcessExit { pid: desc.pid }, + event_type_t::job_exit => EventType::JobExit { + pid: desc.pid, + internal_job_id: desc.internal_job_id, + }, + event_type_t::caller_exit => EventType::CallerExit { + caller_id: desc.caller_id, + }, + event_type_t::generic => EventType::Generic { + param: desc.str_param1.from_ffi(), + }, + _ => panic!("invalid event description"), + }, + } + } +} + +impl From<&EventDescription> for event_description_t { + fn from(desc: &EventDescription) -> Self { + let mut result = event_description_t { + typ: (&desc.typ).into(), + signal: Default::default(), + pid: Default::default(), + internal_job_id: Default::default(), + caller_id: Default::default(), + str_param1: match desc.typ.str_param1() { + Some(param) => param.to_ffi(), + None => UniquePtr::null(), + }, + }; + match desc.typ { + EventType::Any => (), + EventType::Signal { signal } => result.signal = signal.try_into().unwrap(), + EventType::Variable { .. } => (), + EventType::ProcessExit { pid } => result.pid = pid, + EventType::JobExit { + pid, + internal_job_id, + } => { + result.pid = pid; + result.internal_job_id = internal_job_id; + } + EventType::CallerExit { caller_id } => result.caller_id = caller_id, + EventType::Generic { .. } => (), + } + result + } +} + +#[derive(Debug)] +pub struct EventHandler { + /// Properties of the event to match. + desc: EventDescription, + /// Name of the function to invoke. + function_name: WString, + /// A flag set when an event handler is removed from the global list. + /// Once set, this is never cleared. + removed: AtomicBool, + /// A flag set when an event handler is first fired. + fired: AtomicBool, +} + +impl EventHandler { + pub fn new(desc: EventDescription, name: Option) -> Self { + Self { + desc, + function_name: name.unwrap_or_else(WString::new), + removed: AtomicBool::new(false), + fired: AtomicBool::new(false), + } + } + + /// \return true if a handler is "one shot": it fires at most once. + fn is_one_shot(&self) -> bool { + match self.desc.typ { + EventType::ProcessExit { pid } => pid != ANY_PID, + EventType::JobExit { pid, .. } => pid != ANY_PID, + EventType::CallerExit { .. } => true, + EventType::Signal { .. } + | EventType::Variable { .. } + | EventType::Generic { .. } + | EventType::Any => false, + } + } + + /// Tests if this event handler matches an event that has occurred. + fn matches(&self, event: &Event) -> bool { + match (&self.desc.typ, &event.desc.typ) { + (EventType::Any, _) => true, + (EventType::Signal { signal }, EventType::Signal { signal: ev_signal }) => { + signal == ev_signal + } + (EventType::Variable { name }, EventType::Variable { name: ev_name }) => { + name == ev_name + } + (EventType::ProcessExit { pid }, EventType::ProcessExit { pid: ev_pid }) => { + *pid == ANY_PID || pid == ev_pid + } + ( + EventType::JobExit { + pid, + internal_job_id, + }, + EventType::JobExit { + internal_job_id: ev_internal_job_id, + .. + }, + ) => *pid == ANY_PID || internal_job_id == ev_internal_job_id, + ( + EventType::CallerExit { caller_id }, + EventType::CallerExit { + caller_id: ev_caller_id, + }, + ) => caller_id == ev_caller_id, + (EventType::Generic { param }, EventType::Generic { param: ev_param }) => { + param == ev_param + } + (_, _) => false, + } + } +} +type EventHandlerList = Vec>; + +impl EventHandler { + fn desc(&self) -> event_description_t { + (&self.desc).into() + } + fn function_name(self: &EventHandler) -> UniquePtr { + self.function_name.to_ffi() + } + fn set_removed(self: &mut EventHandler) { + self.removed.store(true, Ordering::Relaxed); + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Event { + desc: EventDescription, + arguments: Vec, +} + +impl Event { + pub fn generic(desc: WString) -> Self { + Self { + desc: EventDescription { + typ: EventType::Generic { param: desc }, + }, + arguments: vec![], + } + } + + pub fn variable_erase(name: WString) -> Self { + Self { + desc: EventDescription { + typ: EventType::Variable { name: name.clone() }, + }, + arguments: vec!["VARIABLE".into(), "ERASE".into(), name], + } + } + + pub fn variable_set(name: WString) -> Self { + Self { + desc: EventDescription { + typ: EventType::Variable { name: name.clone() }, + }, + arguments: vec!["VARIABLE".into(), "SET".into(), name], + } + } + + pub fn process_exit(pid: pid_t, status: i32) -> Self { + Self { + desc: EventDescription { + typ: EventType::ProcessExit { pid }, + }, + arguments: vec![ + "PROCESS_EXIT".into(), + pid.to_string().into(), + status.to_string().into(), + ], + } + } + + pub fn job_exit(pgid: pid_t, jid: u64) -> Self { + Self { + desc: EventDescription { + typ: EventType::JobExit { + pid: pgid, + internal_job_id: jid, + }, + }, + arguments: vec![ + "JOB_EXIT".into(), + pgid.to_string().into(), + "0".into(), // historical + ], + } + } + + pub fn caller_exit(internal_job_id: u64, job_id: i32) -> Self { + Self { + desc: EventDescription { + typ: EventType::CallerExit { + caller_id: internal_job_id, + }, + }, + arguments: vec![ + "JOB_EXIT".into(), + job_id.to_string().into(), + "0".into(), // historical + ], + } + } + + /// Test if specified event is blocked. + fn is_blocked(&self, parser: &mut parser_t) -> bool { + let mut i = 0; + while let Some(block) = parser.get_block_at_index(i) { + i += 1; + if block.ffi_event_blocks() != 0 { + return true; + } + } + + parser.ffi_global_event_blocks() != 0 + } +} + +fn new_event_generic(desc: wcharz_t) -> Box { + Box::new(Event::generic(desc.into())) +} + +fn new_event_variable_erase(name: &CxxWString) -> Box { + Box::new(Event::variable_erase(name.from_ffi())) +} + +fn new_event_variable_set(name: &CxxWString) -> Box { + Box::new(Event::variable_set(name.from_ffi())) +} + +fn new_event_process_exit(pid: i32, status: i32) -> Box { + Box::new(Event::process_exit(pid, status)) +} + +fn new_event_job_exit(pgid: i32, jid: u64) -> Box { + Box::new(Event::job_exit(pgid, jid)) +} + +fn new_event_caller_exit(internal_job_id: u64, job_id: i32) -> Box { + Box::new(Event::caller_exit(internal_job_id, job_id)) +} + +impl Event { + fn clone_ffi(&self) -> Box { + Box::new(self.clone()) + } +} + +fn event_add_handler_ffi(desc: &event_description_t, name: &CxxWString) { + add_handler(EventHandler::new(desc.into(), Some(name.from_ffi()))); +} + +const SIGNAL_COUNT: usize = 65; // FIXME: NSIG + +struct PendingSignals { + /// A counter that is incremented each time a pending signal is received. + counter: AtomicU32, + /// List of pending signals. + received: [AtomicBool; SIGNAL_COUNT], + /// The last counter visible in `acquire_pending()`. + /// This is not accessed from a signal handler. + last_counter: Mutex, +} + +impl PendingSignals { + /// Mark a signal as pending. This may be called from a signal handler. We expect only one + /// signal handler to execute at once. Also note that these may be coalesced. + pub fn mark(&self, which: usize) { + if let Some(received) = self.received.get(which) { + received.store(true, Ordering::Relaxed); + let count = self.counter.load(Ordering::Relaxed); + self.counter.store(count + 1, Ordering::Release); + } + } + + /// \return the list of signals that were set, clearing them. + // TODO: return bitvec? + pub fn acquire_pending(&self) -> [bool; SIGNAL_COUNT] { + let mut current = self + .last_counter + .lock() + .expect("mutex should not be poisoned"); + + // Check the counter first. If it hasn't changed, no signals have been received. + let count = self.counter.load(Ordering::Acquire); + let mut result = [false; SIGNAL_COUNT]; + if count == *current { + return result; + } + + // The signal count has changed. Store the new counter and fetch all set signals. + *current = count; + for (i, received) in self.received.iter().enumerate() { + if received.load(Ordering::Relaxed) { + result[i] = true; + received.store(false, Ordering::Relaxed); + } + } + + result + } +} + +// Required until inline const is stabilized. +#[allow(clippy::declare_interior_mutable_const)] +const ATOMIC_BOOL_FALSE: AtomicBool = AtomicBool::new(false); +#[allow(clippy::declare_interior_mutable_const)] +const ATOMIC_U32_0: AtomicU32 = AtomicU32::new(0); + +static PENDING_SIGNALS: PendingSignals = PendingSignals { + counter: AtomicU32::new(0), + received: [ATOMIC_BOOL_FALSE; SIGNAL_COUNT], + last_counter: Mutex::new(0), +}; + +/// List of event handlers. **While this is locked to allow safely accessing/modifying the vector, +/// note that it does NOT provide exclusive access to the [`EventHandler`] objects which are shared +/// references (in an `Arc`).** +static EVENT_HANDLERS: Mutex = Mutex::new(Vec::new()); + +/// Tracks the number of registered event handlers for each signal. +/// This is inspected by a signal handler. We assume no values in here overflow. +static OBSERVED_SIGNALS: [AtomicU32; SIGNAL_COUNT] = [ATOMIC_U32_0; SIGNAL_COUNT]; + +/// List of events that have been sent but have not yet been delivered because they are blocked. +/// +/// This was part of profile_item_t accessed as parser.libdata().blocked_events and has been +/// temporarily moved here. There was no mutex around this in the cpp code. TODO: Move it back. +static BLOCKED_EVENTS: Mutex> = Mutex::new(Vec::new()); + +fn inc_signal_observed(sig: usize) { + if let Some(sig) = OBSERVED_SIGNALS.get(sig) { + sig.fetch_add(1, Ordering::Relaxed); + } +} + +fn dec_signal_observed(sig: usize) { + if let Some(sig) = OBSERVED_SIGNALS.get(sig) { + sig.fetch_sub(1, Ordering::Relaxed); + } +} + +/// Returns whether an event listener is registered for the given signal. This is safe to call from +/// a signal handler. +pub fn is_signal_observed(sig: usize) -> bool { + // We are in a signal handler! + OBSERVED_SIGNALS + .get(sig) + .map_or(false, |s| s.load(Ordering::Relaxed) > 0) +} + +pub fn get_desc(parser: &parser_t, evt: &Event) -> WString { + let s = match &evt.desc.typ { + EventType::Signal { signal } => format!( + "signal handler for {} ({})", + sig2wcs(*signal), + signal_get_desc(*signal) + ), + EventType::Variable { name } => format!("handler for variable '{name}'"), + EventType::ProcessExit { pid } => format!("exit handler for process {pid}"), + EventType::JobExit { pid, .. } => { + if let Some(job) = parser.job_get_from_pid(*pid) { + format!( + "exit handler for job {}, '{}'", + job.job_id().0, + job.command() + ) + } else { + format!("exit handler for job with pid {pid}") + } + } + EventType::CallerExit { .. } => "exit handler for command substitution caller".to_string(), + EventType::Generic { param } => format!("handler for generic event '{param}'"), + EventType::Any => unreachable!(), + }; + + WString::from_str(&s) +} + +fn event_get_desc_ffi(parser: &parser_t, evt: &Event) -> UniquePtr { + get_desc(parser, evt).to_ffi() +} + +/// Add an event handler. +pub fn add_handler(eh: EventHandler) { + if let EventType::Signal { signal } = eh.desc.typ { + signal_handle( + i32::try_from(signal) + .expect("signal should be < 2^31") + .into(), + ); + inc_signal_observed(signal); + } + + EVENT_HANDLERS + .lock() + .expect("event handler list should not be poisoned") + .push(Arc::new(eh)); +} + +/// Remove handlers where `pred` returns true. Simultaneously update our `signal_observed` array. +fn remove_handlers_if(pred: impl Fn(&EventHandler) -> bool) -> usize { + let mut handlers = EVENT_HANDLERS + .lock() + .expect("event handler list should not be poisoned"); + + let mut removed = 0; + for i in (0..handlers.len()).rev() { + let handler = &handlers[i]; + if pred(handler) { + handler.removed.store(true, Ordering::Relaxed); + if let EventType::Signal { signal } = handler.desc.typ { + dec_signal_observed(signal); + } + handlers.remove(i); + removed += 1; + } + } + + removed +} + +/// Remove all events for the given function name. +pub fn remove_function_handlers(name: &wstr) -> usize { + remove_handlers_if(|h| h.function_name == name) +} + +fn event_remove_function_handlers_ffi(name: &CxxWString) -> usize { + remove_function_handlers(name.as_wstr()) +} + +/// Return all event handlers for the given function. +pub fn get_function_handlers(name: &wstr) -> EventHandlerList { + EVENT_HANDLERS + .lock() + .expect("event handler list should not be poisoned") + .iter() + .filter(|h| h.function_name == name) + .cloned() + .collect() +} + +fn event_get_function_handler_descs_ffi(name: &CxxWString) -> Vec { + get_function_handlers(name.as_wstr()) + .iter() + .map(|h| event_description_t::from(&h.desc)) + .collect() +} + +/// Perform the specified event. Since almost all event firings will not be matched by even a single +/// event handler, we make sure to optimize the 'no matches' path. This means that nothing is +/// allocated/initialized unless needed. +fn fire_internal(parser: &mut parser_t, event: &Event) { + assert!( + parser.libdata_pod().is_event >= 0, + "is_event should not be negative" + ); + + let saved_is_event = parser.libdata_pod().is_event; + parser.libdata_pod().is_event += 1; + // Suppress fish_trace during events. + let saved_suppress_fish_trace = parser.libdata_pod().suppress_fish_trace; + parser.libdata_pod().suppress_fish_trace = true; + + // Capture the event handlers that match this event. + let fire: Vec<_> = EVENT_HANDLERS + .lock() + .expect("event handler list should not be poisoned") + .iter() + .filter(|h| h.matches(event)) + .cloned() + .collect(); + + // Iterate over our list of matching events. Fire the ones that are still present. + let mut fired_one_shot = false; + for handler in fire { + // A previous handler may have erased this one. + if handler.removed.load(Ordering::Relaxed) { + continue; + }; + + // Construct a buffer to evaluate, starting with the function name and then all the + // arguments. + let mut buffer = handler.function_name.clone(); + for arg in &event.arguments { + buffer.push(' '); + buffer.push_utfstr(&escape_string( + arg, + EscapeStringStyle::Script(EscapeFlags::default()), + )); + } + + // Event handlers are not part of the main flow of code, so they are marked as + // non-interactive. + let saved_is_interactive = parser.libdata_pod().is_interactive; + parser.libdata_pod().is_interactive = false; + let prev_statuses = parser.get_last_statuses().within_unique_ptr(); + + FLOG!( + event, + "Firing event '", + event.desc.typ.str_param1().unwrap_or(L!("")), + "' to handler '", + handler.function_name, + "'" + ); + + let b = parser + .pin() + .push_block(block_t::event_block((event as *const Event).cast()).within_unique_ptr()); + parser + .pin() + .eval_string_ffi1(&buffer.to_ffi()) + .within_unique_ptr(); + parser.pin().pop_block(b); + parser.pin().set_last_statuses(prev_statuses); + + handler.fired.store(true, Ordering::Relaxed); + fired_one_shot |= handler.is_one_shot(); + parser.libdata_pod().is_interactive = saved_is_interactive; + } + + if fired_one_shot { + remove_handlers_if(|h| h.fired.load(Ordering::Relaxed) && h.is_one_shot()); + } + + parser.libdata_pod().suppress_fish_trace = saved_suppress_fish_trace; + parser.libdata_pod().is_event = saved_is_event; +} + +/// Fire all delayed events attached to the given parser. +pub fn fire_delayed(parser: &mut parser_t) { + let ld = parser.libdata_pod(); + + // Do not invoke new event handlers from within event handlers. + if ld.is_event != 0 { + return; + }; + // Do not invoke new event handlers if we are unwinding (#6649). + if signal_check_cancel().0 != 0 { + return; + }; + + // We unfortunately can't keep this locked until we're done with it because the SIGWINCH handler + // code might call back into here and we would delay processing of the events, leading to a test + // failure under CI. (Yes, the `&mut parser_t` is a lie.) + let mut to_send = std::mem::take(&mut *BLOCKED_EVENTS.lock().expect("Mutex poisoned!")); + + // Append all signal events to to_send. + let signals = PENDING_SIGNALS.acquire_pending(); + for (sig, _) in signals.iter().enumerate().filter(|(_, pending)| **pending) { + // HACK: The only variables we change in response to a *signal* are $COLUMNS and $LINES. + // Do that now. + if sig == libc::SIGWINCH as usize { + termsize_container_t::ffi_updating(parser.pin()).within_unique_ptr(); + } + let event = Event { + desc: EventDescription { + typ: EventType::Signal { signal: sig }, + }, + arguments: vec![sig2wcs(sig).into()], + }; + to_send.push(event); + } + + // Fire or re-block all events. Don't obtain BLOCKED_EVENTS until we know that we have at least + // one event that is blocked. + let mut blocked_events = None; + for event in to_send { + if event.is_blocked(parser) { + if blocked_events.is_none() { + blocked_events = Some(BLOCKED_EVENTS.lock().expect("Mutex posioned")); + } + blocked_events.as_mut().unwrap().push(event); + } else { + // fire_internal() does not access BLOCKED_EVENTS so this call can't deadlock. + fire_internal(parser, &event); + } + } +} + +fn event_fire_delayed_ffi(parser: Pin<&mut parser_t>) { + fire_delayed(parser.unpin()) +} + +/// Enqueue a signal event. Invoked from a signal handler. +pub fn enqueue_signal(signal: usize) { + // Beware, we are in a signal handler + PENDING_SIGNALS.mark(signal); +} + +/// Fire the specified event event, executing it on `parser`. +pub fn fire(parser: &mut parser_t, event: Event) { + // Fire events triggered by signals. + fire_delayed(parser); + + if event.is_blocked(parser) { + BLOCKED_EVENTS.lock().expect("Mutex poisoned!").push(event); + } else { + fire_internal(parser, &event); + } +} + +fn event_fire_ffi(parser: Pin<&mut parser_t>, event: &Event) { + fire(parser.unpin(), event.clone()) +} + +#[widestrs] +const EVENT_FILTER_NAMES: [&wstr; 7] = [ + "signal"L, + "variable"L, + "exit"L, + "process-exit"L, + "job-exit"L, + "caller-exit"L, + "generic"L, +]; + +/// Print all events. If type_filter is not empty, only output events with that type. +pub fn print(streams: &mut io_streams_t, type_filter: &wstr) { + let mut tmp = EVENT_HANDLERS + .lock() + .expect("event handler list should not be poisoned") + .clone(); + + tmp.sort_by(|e1, e2| e1.desc.typ.cmp(&e2.desc.typ)); + + let mut last_type = None; + for evt in tmp { + // If we have a filter, skip events that don't match. + if !evt.desc.typ.matches_filter(type_filter) { + continue; + } + + if last_type.as_ref() != Some(&evt.desc.typ) { + if last_type.is_some() { + streams.out.append(L!("\n")); + } + + last_type = Some(evt.desc.typ.clone()); + streams + .out + .append(&sprintf!(L!("Event %ls\n"), evt.desc.typ.name())); + } + + match &evt.desc.typ { + EventType::Signal { signal } => { + streams.out.append(&sprintf!( + L!("%ls %ls\n"), + sig2wcs(*signal), + evt.function_name + )); + } + EventType::ProcessExit { .. } | EventType::JobExit { .. } => {} + EventType::CallerExit { .. } => { + streams + .out + .append(&sprintf!(L!("caller-exit %ls\n"), evt.function_name)); + } + EventType::Variable { name: param } | EventType::Generic { param } => { + streams + .out + .append(&sprintf!(L!("%ls %ls\n"), param, evt.function_name)); + } + EventType::Any => unreachable!(), + } + } +} + +fn event_print_ffi(streams: Pin<&mut ffi::io_streams_t>, type_filter: &CxxWString) { + let mut streams = io_streams_t::new(streams); + print(&mut streams, &type_filter.from_ffi()); +} + +/// Fire a generic event with the specified name. +pub fn fire_generic(parser: &mut parser_t, name: WString, arguments: Vec) { + fire( + parser, + Event { + desc: EventDescription { + typ: EventType::Generic { param: name }, + }, + arguments, + }, + ) +} + +fn event_fire_generic_ffi( + parser: Pin<&mut parser_t>, + name: &CxxWString, + arguments: &CxxVector, +) { + fire_generic( + parser.unpin(), + name.from_ffi(), + arguments.iter().map(WString::from).collect(), + ); +} diff --git a/fish-rust/src/ffi.rs b/fish-rust/src/ffi.rs index a9675742a..9c340954b 100644 --- a/fish-rust/src/ffi.rs +++ b/fish-rust/src/ffi.rs @@ -9,6 +9,7 @@ use crate::wchar::wstr; use autocxx::prelude::*; use cxx::SharedPtr; +use libc::pid_t; // autocxx has been hacked up to know about this. pub type wchar_t = u32; @@ -30,6 +31,7 @@ #include "tokenizer.h" #include "wildcard.h" #include "wutil.h" + #include "termsize.h" safety!(unsafe_ffi) @@ -77,8 +79,6 @@ generate!("wait_handle_t") generate!("wait_handle_store_t") - generate!("event_fire_generic") - generate!("escape_string") generate!("sig2wcs") generate!("wcs2sig") @@ -93,9 +93,24 @@ generate!("re::try_compile_ffi") generate!("wcs2string") generate!("str2wcstring") + + generate!("signal_handle") + generate!("signal_check_cancel") + + generate!("block_t") + generate!("block_type_t") + generate!("statuses_t") + generate!("io_chain_t") + + generate!("termsize_container_t") } impl parser_t { + pub fn get_block_at_index(&self, i: usize) -> Option<&block_t> { + let b = self.block_at_index(i); + unsafe { b.as_ref() } + } + pub fn get_jobs(&self) -> &[SharedPtr] { let ffi_jobs = self.ffi_jobs(); unsafe { slice::from_raw_parts(ffi_jobs.jobs, ffi_jobs.count) } @@ -110,6 +125,11 @@ pub fn libdata_pod(&mut self) -> &mut library_data_pod_t { pub fn remove_var(&mut self, var: &wstr, flags: c_int) -> c_int { self.pin().remove_var_ffi(&var.to_ffi(), flags) } + + pub fn job_get_from_pid(&self, pid: pid_t) -> Option<&job_t> { + let job = self.ffi_job_get_from_pid(pid.into()); + unsafe { job.as_ref() } + } } pub fn try_compile(anchored: &wstr, flags: &re::flags_t) -> Pin> { diff --git a/fish-rust/src/lib.rs b/fish-rust/src/lib.rs index c4c97c97e..f1ea2b5e5 100644 --- a/fish-rust/src/lib.rs +++ b/fish-rust/src/lib.rs @@ -10,6 +10,7 @@ #[macro_use] mod common; mod color; +mod event; mod fd_monitor; mod fd_readable_set; mod fds; diff --git a/src/builtins/function.cpp b/src/builtins/function.cpp index 1eca5b2ef..44a5caa12 100644 --- a/src/builtins/function.cpp +++ b/src/builtins/function.cpp @@ -101,7 +101,10 @@ static int parse_cmd_opts(function_cmd_opts_t &opts, int *optind, //!OCLINT(hig streams.err.append_format(_(L"%ls: Unknown signal '%ls'"), cmd, w.woptarg); return STATUS_INVALID_ARGS; } - opts.events.push_back(event_description_t::signal(sig)); + event_description_t event_desc; + event_desc.typ = event_type_t::signal; + event_desc.signal = sig; + opts.events.push_back(std::move(event_desc)); break; } case 'v': { @@ -110,16 +113,23 @@ static int parse_cmd_opts(function_cmd_opts_t &opts, int *optind, //!OCLINT(hig return STATUS_INVALID_ARGS; } - opts.events.push_back(event_description_t::variable(w.woptarg)); + event_description_t event_desc; + event_desc.typ = event_type_t::variable; + event_desc.str_param1 = std::make_unique(w.woptarg); + opts.events.push_back(std::move(event_desc)); break; } case 'e': { - opts.events.push_back(event_description_t::generic(w.woptarg)); + event_description_t event_desc; + event_desc.typ = event_type_t::generic; + event_desc.str_param1 = std::make_unique(w.woptarg); + opts.events.push_back(std::move(event_desc)); break; } case 'j': case 'p': { - event_description_t e(event_type_t::any); + event_description_t e; + e.typ = event_type_t::any; if ((opt == 'j') && (wcscasecmp(w.woptarg, L"caller") == 0)) { internal_job_id_t caller_id = @@ -129,11 +139,11 @@ static int parse_cmd_opts(function_cmd_opts_t &opts, int *optind, //!OCLINT(hig _(L"%ls: calling job for event handler not found"), cmd); return STATUS_INVALID_ARGS; } - e.type = event_type_t::caller_exit; - e.param1.caller_id = caller_id; + e.typ = event_type_t::caller_exit; + e.caller_id = caller_id; } else if ((opt == 'p') && (wcscasecmp(w.woptarg, L"%self") == 0)) { - e.type = event_type_t::process_exit; - e.param1.pid = getpid(); + e.typ = event_type_t::process_exit; + e.pid = getpid(); } else { pid_t pid = fish_wcstoi(w.woptarg); if (errno || pid < 0) { @@ -142,14 +152,15 @@ static int parse_cmd_opts(function_cmd_opts_t &opts, int *optind, //!OCLINT(hig return STATUS_INVALID_ARGS; } if (opt == 'p') { - e.type = event_type_t::process_exit; - e.param1.pid = pid; + e.typ = event_type_t::process_exit; + e.pid = pid; } else { - e.type = event_type_t::job_exit; - e.param1.jobspec = {pid, job_id_for_pid(pid, parser)}; + e.typ = event_type_t::job_exit; + e.pid = pid; + e.internal_job_id = job_id_for_pid(pid, parser); } } - opts.events.push_back(e); + opts.events.push_back(std::move(e)); break; } case 'a': { @@ -294,25 +305,25 @@ int builtin_function(parser_t &parser, io_streams_t &streams, const wcstring_lis // Add any event handlers. for (const event_description_t &ed : opts.events) { - event_add_handler(std::make_shared(ed, function_name)); + event_add_handler(ed, function_name); } // If there is an --on-process-exit or --on-job-exit event handler for some pid, and that // process has already exited, run it immediately (#7210). for (const event_description_t &ed : opts.events) { - if (ed.type == event_type_t::process_exit) { - pid_t pid = ed.param1.pid; + if (ed.typ == event_type_t::process_exit) { + pid_t pid = ed.pid; if (pid == EVENT_ANY_PID) continue; wait_handle_ref_t wh = parser.get_wait_handles().get_by_pid(pid); if (wh && wh->completed) { - event_fire(parser, event_t::process_exit(pid, wh->status)); + event_fire(parser, *new_event_process_exit(pid, wh->status)); } - } else if (ed.type == event_type_t::job_exit) { - pid_t pid = ed.param1.jobspec.pid; + } else if (ed.typ == event_type_t::job_exit) { + pid_t pid = ed.pid; if (pid == EVENT_ANY_PID) continue; wait_handle_ref_t wh = parser.get_wait_handles().get_by_pid(pid); if (wh && wh->completed) { - event_fire(parser, event_t::job_exit(pid, wh->internal_job_id)); + event_fire(parser, *new_event_job_exit(pid, wh->internal_job_id)); } } } diff --git a/src/builtins/set.cpp b/src/builtins/set.cpp index 9417b884d..d6d3b6e94 100644 --- a/src/builtins/set.cpp +++ b/src/builtins/set.cpp @@ -652,7 +652,7 @@ static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, handle_env_return(retval, cmd, split->varname, streams); } if (retval == ENV_OK) { - event_fire(parser, event_t::variable_erase(split->varname)); + event_fire(parser, *new_event_variable_erase(split->varname)); } } else { // remove just the specified indexes of the var if (!split->var) return STATUS_CMD_ERROR; diff --git a/src/common.cpp b/src/common.cpp index 25c9e4940..854fbd3a8 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -57,6 +57,7 @@ struct termios shell_modes; const wcstring g_empty_string{}; +const wcstring_list_t g_empty_string_list{}; /// This allows us to notice when we've forked. static relaxed_atomic_bool_t is_forked_proc{false}; diff --git a/src/common.h b/src/common.h index 343e3150f..94ae45aa6 100644 --- a/src/common.h +++ b/src/common.h @@ -200,6 +200,10 @@ extern const bool has_working_tty_timestamps; /// empty string. extern const wcstring g_empty_string; +/// A global, empty wcstring_list_t. This is useful for functions which wish to return a reference +/// to an empty string. +extern const wcstring_list_t g_empty_string_list; + // Pause for input, then exit the program. If supported, print a backtrace first. #define FATAL_EXIT() \ do { \ diff --git a/src/env.cpp b/src/env.cpp index 4eb17ac68..0072df28c 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -1315,7 +1315,7 @@ mod_result_t env_stack_impl_t::remove(const wcstring &key, int mode) { return result; } -std::vector env_stack_t::universal_sync(bool always) { +std::vector> env_stack_t::universal_sync(bool always) { if (s_uvar_scope_is_global) return {}; if (!always && !s_uvars_locally_modified) return {}; s_uvars_locally_modified = false; @@ -1326,11 +1326,11 @@ std::vector env_stack_t::universal_sync(bool always) { universal_notifier_t::default_notifier().post_notification(); } // React internally to changes to special variables like LANG, and populate on-variable events. - std::vector result; + std::vector> result; for (const callback_data_t &cb : callbacks) { env_dispatch_var_change(cb.key, *this); - event_t evt = - cb.is_erase() ? event_t::variable_erase(cb.key) : event_t::variable_set(cb.key); + auto evt = + cb.is_erase() ? new_event_variable_erase(cb.key) : new_event_variable_set(cb.key); result.push_back(std::move(evt)); } return result; @@ -1479,6 +1479,8 @@ const std::shared_ptr &env_stack_t::principal_ref() { env_stack_t::~env_stack_t() = default; +env_stack_t::env_stack_t(env_stack_t &&) = default; + #if defined(__APPLE__) || defined(__CYGWIN__) static int check_runtime_path(const char *path) { UNUSED(path); diff --git a/src/env.h b/src/env.h index 972846efd..7d9b1efd1 100644 --- a/src/env.h +++ b/src/env.h @@ -13,6 +13,7 @@ #include #include "common.h" +#include "cxx.h" #include "maybe.h" class owning_null_terminated_array_t; @@ -20,7 +21,7 @@ class owning_null_terminated_array_t; extern size_t read_byte_limit; extern bool curses_initialized; -struct event_t; +struct Event; // Flags that may be passed as the 'mode' in env_stack_t::set() / environment_t::get(). enum : uint16_t { @@ -213,7 +214,7 @@ class env_stack_t final : public environment_t { friend class parser_t; /// The implementation. Do not access this directly. - const std::unique_ptr impl_; + std::unique_ptr impl_; /// All environment stacks are guarded by a global lock. acquired_lock acquire_impl(); @@ -287,7 +288,7 @@ class env_stack_t final : public environment_t { /// If \p always is set, perform synchronization even if there's no pending changes from this /// instance (that is, look for changes from other fish instances). /// \return a list of events for changed variables. - std::vector universal_sync(bool always); + std::vector> universal_sync(bool always); // Compatibility hack; access the "environment stack" from back when there was just one. static const std::shared_ptr &principal_ref(); diff --git a/src/event.cpp b/src/event.cpp index a0483e669..61ee29bd3 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -1,4 +1,6 @@ -// Functions for handling event triggers. +// event.h and event.cpp only contain cpp-side ffi compat code to make event.rs.h a drop-in +// replacement. There is no logic still in here that needs to be ported to rust. + #include "config.h" // IWYU pragma: keep #include "event.h" @@ -26,547 +28,13 @@ #include "wcstringutil.h" #include "wutil.h" // IWYU pragma: keep -namespace { -class pending_signals_t { - static constexpr size_t SIGNAL_COUNT = NSIG; - - /// A counter that is incremented each time a pending signal is received. - std::atomic counter_{0}; - - /// List of pending signals. - std::array received_{}; - - /// The last counter visible in acquire_pending(). - /// This is not accessed from a signal handler. - owning_lock last_counter_{0}; - - public: - pending_signals_t() = default; - - /// No copying. - pending_signals_t(const pending_signals_t &) = delete; - pending_signals_t &operator=(const pending_signals_t &) = delete; - - /// Mark a signal as pending. This may be called from a signal handler. - /// We expect only one signal handler to execute at once. - /// Also note that these may be coalesced. - void mark(int which) { - if (which >= 0 && static_cast(which) < received_.size()) { - // Must mark our received first, then pending. - received_[which] = true; - uint32_t count = counter_.load(std::memory_order_relaxed); - counter_.store(1 + count, std::memory_order_release); - } - } - - /// \return the list of signals that were set, clearing them. - std::bitset acquire_pending() { - auto current = last_counter_.acquire(); - - // Check the counter first. If it hasn't changed, no signals have been received. - uint32_t count = counter_.load(std::memory_order_acquire); - if (count == *current) { - return {}; - } - - // The signal count has changed. Store the new counter and fetch all set signals. - *current = count; - std::bitset result{}; - for (size_t i = 0; i < NSIG; i++) { - if (received_[i]) { - result.set(i); - received_[i] = false; - } - } - return result; - } -}; -} // namespace - -static pending_signals_t s_pending_signals; - -/// List of event handlers. -static owning_lock s_event_handlers; - -/// Tracks the number of registered event handlers for each signal. -/// This is inspected by a signal handler. We assume no values in here overflow. -static std::array, NSIG> s_observed_signals; - -static inline void inc_signal_observed(int sig) { - if (0 <= sig && sig < NSIG) { - s_observed_signals[sig]++; - } -} - -static inline void dec_signal_observed(int sig) { - if (0 <= sig && sig < NSIG) { - s_observed_signals[sig]--; - } -} - -bool event_is_signal_observed(int sig) { - // We are in a signal handler! - uint32_t count = 0; - if (0 <= sig && sig < NSIG) { - count = s_observed_signals[sig]; - } - return count > 0; -} - -/// \return true if a handler is "one shot": it fires at most once. -static bool handler_is_one_shot(const event_handler_t &handler) { - switch (handler.desc.type) { - case event_type_t::process_exit: - return handler.desc.param1.pid != EVENT_ANY_PID; - case event_type_t::job_exit: - return handler.desc.param1.jobspec.pid != EVENT_ANY_PID; - case event_type_t::caller_exit: - return true; - case event_type_t::signal: - case event_type_t::variable: - case event_type_t::generic: - case event_type_t::any: - return false; - } - DIE("Unreachable"); -} - -/// Tests if one event instance matches the definition of an event class. -/// In case of a match, \p only_once indicates that the event cannot match again by nature. -static bool handler_matches(const event_handler_t &handler, const event_t &instance) { - if (handler.desc.type == event_type_t::any) return true; - if (handler.desc.type != instance.desc.type) return false; - - switch (handler.desc.type) { - case event_type_t::signal: { - return handler.desc.param1.signal == instance.desc.param1.signal; - } - case event_type_t::variable: { - return instance.desc.str_param1 == handler.desc.str_param1; - } - case event_type_t::process_exit: { - if (handler.desc.param1.pid == EVENT_ANY_PID) return true; - return handler.desc.param1.pid == instance.desc.param1.pid; - } - case event_type_t::job_exit: { - const auto &jobspec = handler.desc.param1.jobspec; - if (jobspec.pid == EVENT_ANY_PID) return true; - return jobspec.internal_job_id == instance.desc.param1.jobspec.internal_job_id; - } - case event_type_t::caller_exit: { - return handler.desc.param1.caller_id == instance.desc.param1.caller_id; - } - case event_type_t::generic: { - return handler.desc.str_param1 == instance.desc.str_param1; - } - case event_type_t::any: - default: { - DIE("unexpected classv.type"); - return false; - } - } -} - -/// Test if specified event is blocked. -static bool event_is_blocked(parser_t &parser, const event_t &e) { - (void)e; - const block_t *block; - size_t idx = 0; - while ((block = parser.block_at_index(idx++))) { - if (block->event_blocks) return true; - } - return parser.global_event_blocks; -} - -wcstring event_get_desc(const parser_t &parser, const event_t &evt) { - const event_description_t &ed = evt.desc; - switch (ed.type) { - case event_type_t::signal: { - return format_string(_(L"signal handler for %ls (%ls)"), sig2wcs(ed.param1.signal), - signal_get_desc(ed.param1.signal)); - } - - case event_type_t::variable: { - return format_string(_(L"handler for variable '%ls'"), ed.str_param1.c_str()); - } - - case event_type_t::process_exit: { - return format_string(_(L"exit handler for process %d"), ed.param1.pid); - } - - case event_type_t::job_exit: { - const auto &jobspec = ed.param1.jobspec; - if (const job_t *j = parser.job_get_from_pid(jobspec.pid)) { - return format_string(_(L"exit handler for job %d, '%ls'"), j->job_id(), - j->command_wcstr()); - } else { - return format_string(_(L"exit handler for job with pid %d"), jobspec.pid); - } - } - - case event_type_t::caller_exit: { - return _(L"exit handler for command substitution caller"); - } - - case event_type_t::generic: { - return format_string(_(L"handler for generic event '%ls'"), ed.str_param1.c_str()); - } - case event_type_t::any: { - DIE("Unreachable"); - } - default: - DIE("Unknown event type"); - } -} - -void event_add_handler(std::shared_ptr eh) { - if (eh->desc.type == event_type_t::signal) { - signal_handle(eh->desc.param1.signal); - inc_signal_observed(eh->desc.param1.signal); - } - - s_event_handlers.acquire()->push_back(std::move(eh)); -} - -// \remove handlers for which \p func returns true. -// Simultaneously update our signal_observed array. -template -static void remove_handlers_if(const T &func) { - auto handlers = s_event_handlers.acquire(); - auto iter = handlers->begin(); - while (iter != handlers->end()) { - event_handler_t *handler = iter->get(); - if (func(*handler)) { - handler->removed = true; - if (handler->desc.type == event_type_t::signal) { - dec_signal_observed(handler->desc.param1.signal); - } - iter = handlers->erase(iter); - } else { - ++iter; - } - } -} - -void event_remove_function_handlers(const wcstring &name) { - remove_handlers_if( - [&](const event_handler_t &handler) { return handler.function_name == name; }); -} - -event_handler_list_t event_get_function_handlers(const wcstring &name) { - auto handlers = s_event_handlers.acquire(); - event_handler_list_t result; - for (const shared_ptr &eh : *handlers) { - if (eh->function_name == name) { - result.push_back(eh); - } - } - return result; -} - -/// Perform the specified event. Since almost all event firings will not be matched by even a single -/// event handler, we make sure to optimize the 'no matches' path. This means that nothing is -/// allocated/initialized unless needed. -static void event_fire_internal(parser_t &parser, const event_t &event) { - auto &ld = parser.libdata(); - assert(ld.is_event >= 0 && "is_event should not be negative"); - scoped_push inc_event{&ld.is_event, ld.is_event + 1}; - - // Suppress fish_trace during events. - scoped_push suppress_trace{&ld.suppress_fish_trace, true}; - - // Capture the event handlers that match this event. - std::vector> fire; - { - auto event_handlers = s_event_handlers.acquire(); - for (const auto &handler : *event_handlers) { - if (handler_matches(*handler, event)) { - fire.push_back(handler); - } - } - } - - // Iterate over our list of matching events. Fire the ones that are still present. - bool fired_one_shot = false; - for (const auto &handler : fire) { - // A previous handlers may have erased this one. - if (handler->removed) continue; - - // Construct a buffer to evaluate, starting with the function name and then all the - // arguments. - wcstring buffer = handler->function_name; - for (const wcstring &arg : event.arguments) { - buffer.push_back(L' '); - buffer.append(escape_string(arg)); - } - - // Event handlers are not part of the main flow of code, so they are marked as - // non-interactive. - scoped_push interactive{&ld.is_interactive, false}; - auto prev_statuses = parser.get_last_statuses(); - - FLOGF(event, L"Firing event '%ls' to handler '%ls'", event.desc.str_param1.c_str(), - handler->function_name.c_str()); - block_t *b = parser.push_block(block_t::event_block(event)); - parser.eval(buffer, io_chain_t()); - parser.pop_block(b); - parser.set_last_statuses(std::move(prev_statuses)); - - handler->fired = true; - fired_one_shot |= handler_is_one_shot(*handler); - } - - // Remove any fired one-shot handlers. - if (fired_one_shot) { - remove_handlers_if([](const event_handler_t &handler) { - return handler.fired && handler_is_one_shot(handler); - }); - } -} - -/// Handle all pending signal events. -void event_fire_delayed(parser_t &parser) { - auto &ld = parser.libdata(); - // Do not invoke new event handlers from within event handlers. - if (ld.is_event) return; - // Do not invoke new event handlers if we are unwinding (#6649). - if (signal_check_cancel()) return; - - std::vector> to_send; - to_send.swap(ld.blocked_events); - assert(ld.blocked_events.empty()); - - // Append all signal events to to_send. - auto signals = s_pending_signals.acquire_pending(); - if (signals.any()) { - for (uint32_t sig = 0; sig < signals.size(); sig++) { - if (signals.test(sig)) { - // HACK: The only variables we change in response to a *signal* - // are $COLUMNS and $LINES. - // Do that now. - if (sig == SIGWINCH) { - (void)termsize_container_t::shared().updating(parser); - } - auto e = std::make_shared(event_type_t::signal); - e->desc.param1.signal = sig; - e->arguments.push_back(sig2wcs(sig)); - to_send.push_back(std::move(e)); - } - } - } - - // Fire or re-block all events. - for (const auto &evt : to_send) { - if (event_is_blocked(parser, *evt)) { - ld.blocked_events.push_back(evt); - } else { - event_fire_internal(parser, *evt); - } - } -} - -void event_enqueue_signal(int signal) { - // Beware, we are in a signal handler - s_pending_signals.mark(signal); -} - -void event_fire(parser_t &parser, const event_t &event) { - // Fire events triggered by signals. - event_fire_delayed(parser); - - if (event_is_blocked(parser, event)) { - parser.libdata().blocked_events.push_back(std::make_shared(event)); - } else { - event_fire_internal(parser, event); - } -} - -static const wchar_t *event_name_for_type(event_type_t type) { - switch (type) { - case event_type_t::any: - return L"any"; - case event_type_t::signal: - return L"signal"; - case event_type_t::variable: - return L"variable"; - case event_type_t::process_exit: - return L"process-exit"; - case event_type_t::job_exit: - return L"job-exit"; - case event_type_t::caller_exit: - return L"caller-exit"; - case event_type_t::generic: - return L"generic"; - } - return L""; -} - +// TODO: Remove after porting functions.cpp to rust const wchar_t *const event_filter_names[] = {L"signal", L"variable", L"exit", L"process-exit", L"job-exit", L"caller-exit", L"generic", nullptr}; -static bool filter_matches_event(const wcstring &filter, event_type_t type) { - if (filter.empty()) return true; - switch (type) { - case event_type_t::any: - return false; - case event_type_t::signal: - return filter == L"signal"; - case event_type_t::variable: - return filter == L"variable"; - case event_type_t::process_exit: - return filter == L"process-exit" || filter == L"exit"; - case event_type_t::job_exit: - return filter == L"job-exit" || filter == L"exit"; - case event_type_t::caller_exit: - return filter == L"caller-exit" || filter == L"exit"; - case event_type_t::generic: - return filter == L"generic"; - } - DIE("Unreachable"); -} - -void event_print(io_streams_t &streams, const wcstring &type_filter) { - event_handler_list_t tmp = *s_event_handlers.acquire(); - std::sort(tmp.begin(), tmp.end(), - [](const shared_ptr &e1, const shared_ptr &e2) { - const event_description_t &d1 = e1->desc; - const event_description_t &d2 = e2->desc; - if (d1.type != d2.type) { - return d1.type < d2.type; - } - switch (d1.type) { - case event_type_t::signal: - return d1.param1.signal < d2.param1.signal; - case event_type_t::process_exit: - return d1.param1.pid < d2.param1.pid; - case event_type_t::job_exit: - return d1.param1.jobspec.pid < d2.param1.jobspec.pid; - case event_type_t::caller_exit: - return d1.param1.caller_id < d2.param1.caller_id; - case event_type_t::variable: - case event_type_t::any: - case event_type_t::generic: - return d1.str_param1 < d2.str_param1; - } - DIE("Unreachable"); - }); - - maybe_t last_type{}; - for (const shared_ptr &evt : tmp) { - // If we have a filter, skip events that don't match. - if (!filter_matches_event(type_filter, evt->desc.type)) { - continue; - } - - if (!last_type || *last_type != evt->desc.type) { - if (last_type) streams.out.append(L"\n"); - last_type = evt->desc.type; - streams.out.append_format(L"Event %ls\n", event_name_for_type(*last_type)); - } - switch (evt->desc.type) { - case event_type_t::signal: - streams.out.append_format(L"%ls %ls\n", sig2wcs(evt->desc.param1.signal), - evt->function_name.c_str()); - break; - case event_type_t::process_exit: - case event_type_t::job_exit: - break; - case event_type_t::caller_exit: - streams.out.append_format(L"caller-exit %ls\n", evt->function_name.c_str()); - break; - case event_type_t::variable: - case event_type_t::generic: - streams.out.append_format(L"%ls %ls\n", evt->desc.str_param1.c_str(), - evt->function_name.c_str()); - break; - case event_type_t::any: - DIE("Unreachable"); - default: - streams.out.append_format(L"%ls\n", evt->function_name.c_str()); - break; - } - } -} - -void event_fire_generic(parser_t &parser, wcstring name, const wcharz_t *argv, int argc) { - wcstring_list_t args_vec{}; - for (int i = 0; i < argc; i++) { - args_vec.push_back(argv[i]); - } - event_fire_generic(parser, std::move(name), std::move(args_vec)); -} - -void event_fire_generic(parser_t &parser, wcstring name, wcstring_list_t args) { - event_t ev(event_type_t::generic); - ev.desc.str_param1 = std::move(name); - ev.arguments = std::move(args); - event_fire(parser, ev); -} - -event_description_t event_description_t::signal(int sig) { - event_description_t event(event_type_t::signal); - event.param1.signal = sig; - return event; -} - -event_description_t event_description_t::variable(wcstring str) { - event_description_t event(event_type_t::variable); - event.str_param1 = std::move(str); - return event; -} - -event_description_t event_description_t::generic(wcstring str) { - event_description_t event(event_type_t::generic); - event.str_param1 = std::move(str); - return event; -} - -// static -event_t event_t::variable_erase(wcstring name) { - event_t evt{event_type_t::variable}; - evt.arguments = {L"VARIABLE", L"ERASE", name}; - evt.desc.str_param1 = std::move(name); - return evt; -} - -// static -event_t event_t::variable_set(wcstring name) { - event_t evt{event_type_t::variable}; - evt.arguments = {L"VARIABLE", L"SET", name}; - evt.desc.str_param1 = std::move(name); - return evt; -} - -// static -event_t event_t::process_exit(pid_t pid, int status) { - event_t evt{event_type_t::process_exit}; - evt.desc.param1.pid = pid; - evt.arguments.reserve(3); - evt.arguments.push_back(L"PROCESS_EXIT"); - evt.arguments.push_back(to_string(pid)); - evt.arguments.push_back(to_string(status)); - return evt; -} - -// static -event_t event_t::job_exit(pid_t pgid, internal_job_id_t jid) { - event_t evt{event_type_t::job_exit}; - evt.desc.param1.jobspec = {pgid, jid}; - evt.arguments.reserve(3); - evt.arguments.push_back(L"JOB_EXIT"); - evt.arguments.push_back(to_string(pgid)); - evt.arguments.push_back(L"0"); // historical - return evt; -} - -// static -event_t event_t::caller_exit(uint64_t internal_job_id, int job_id) { - event_t evt{event_type_t::caller_exit}; - evt.desc.param1.caller_id = internal_job_id; - evt.arguments.reserve(3); - evt.arguments.push_back(L"JOB_EXIT"); - evt.arguments.push_back(to_string(job_id)); - evt.arguments.push_back(L"0"); // historical - return evt; +void event_fire_generic(parser_t &parser, const wcstring &name, const wcstring_list_t &args) { + std::vector ffi_args; + for (const auto &arg : args) ffi_args.push_back(arg.c_str()); + event_fire_generic_ffi(parser, name, ffi_args); } diff --git a/src/event.h b/src/event.h index c7b2380c2..74a3da743 100644 --- a/src/event.h +++ b/src/event.h @@ -1,9 +1,8 @@ -// Functions for handling event triggers -// -// Because most of these functions can be called by signal handler, it is important to make it well -// defined when these functions produce output or perform memory allocations, since such functions -// may not be safely called by signal handlers. +// event.h and event.cpp only contain cpp-side ffi compat code to make event.rs.h a drop-in +// replacement. There is no logic still in here that needs to be ported to rust. + #ifndef FISH_EVENT_H +#ifdef INCLUDE_RUST_HEADERS #define FISH_EVENT_H #include @@ -17,156 +16,22 @@ #include "global_safety.h" #include "wutil.h" -struct io_streams_t; +class parser_t; +#include "event.rs.h" /// The process id that is used to match any process id. +// TODO: Remove after porting functions.cpp #define EVENT_ANY_PID 0 -/// Enumeration of event types. -enum class event_type_t { - /// Matches any event type (Not always any event, as the function name may limit the choice as - /// well. - any, - /// An event triggered by a signal. - signal, - /// An event triggered by a variable update. - variable, - /// An event triggered by a process exit. - process_exit, - /// An event triggered by a job exit. - job_exit, - /// An event triggered by a job exit, triggering the 'caller'-style events only. - caller_exit, - /// A generic event. - generic, -}; - /// Null-terminated list of valid event filter names. /// These are what are valid to pass to 'functions --handlers-type' +// TODO: Remove after porting functions.cpp extern const wchar_t *const event_filter_names[]; -/// Properties of an event. -struct event_description_t { - /// Helper type for on-job-exit events. - struct job_spec_t { - // pid requested by the event, or ANY_PID for all. - pid_t pid; - - // internal_job_id of the job to match. - // If this is 0, we match either all jobs (pid == ANY_PID) or no jobs (otherwise). - uint64_t internal_job_id; - }; - - /// The event type. - event_type_t type; - - /// The type-specific parameter. The int types are one of the following: - /// - /// signal: Signal number for signal-type events.Use EVENT_ANY_SIGNAL to match any signal - /// pid: Process id for process-type events. Use EVENT_ANY_PID to match any pid. - /// jobspec: Info for on-job-exit events. - /// caller_id: Internal job id for caller_exit type events - union { - int signal; - pid_t pid; - job_spec_t jobspec; - uint64_t caller_id; - } param1{}; - - /// The string types are one of the following: - /// - /// variable: Variable name for variable-type events. - /// param: The parameter describing this generic event. - wcstring str_param1{}; - - explicit event_description_t(event_type_t t) : type(t) {} - static event_description_t signal(int sig); - static event_description_t variable(wcstring str); - static event_description_t generic(wcstring str); -}; - -/// Represents a handler for an event. -struct event_handler_t { - /// Properties of the event to match. - const event_description_t desc; - - /// Name of the function to invoke. - const wcstring function_name{}; - - /// A flag set when an event handler is removed from the global list. - /// Once set, this is never cleared. - relaxed_atomic_bool_t removed{false}; - - /// A flag set when an event handler is first fired. - relaxed_atomic_bool_t fired{false}; - - explicit event_handler_t(event_type_t t) : desc(std::move(t)) {} - - event_handler_t(event_description_t d, wcstring name) - : desc(std::move(d)), function_name(std::move(name)) {} -}; -using event_handler_list_t = std::vector>; - -/// Represents a event that is fired, or capable of being fired. -struct event_t { - /// Properties of the event. - event_description_t desc; - - /// Arguments to any handler. - wcstring_list_t arguments{}; - - explicit event_t(event_type_t t) : desc(t) {} - - /// Create an event_type_t::variable event with the args for erasing a variable. - static event_t variable_erase(wcstring name); - /// Create an event_type_t::variable event with the args for setting a variable. - static event_t variable_set(wcstring name); - - /// Create a PROCESS_EXIT event. - static event_t process_exit(pid_t pid, int status); - - /// Create a JOB_EXIT event. The pgid should be positive. - /// The reported status is always 0 for historical reasons. - static event_t job_exit(pid_t pgid, internal_job_id_t jid); - - /// Create a caller_exit event. - static event_t caller_exit(uint64_t internal_job_id, int job_id); -}; - class parser_t; -/// Add an event handler. -void event_add_handler(std::shared_ptr eh); - -/// Remove all events for the given function name. -void event_remove_function_handlers(const wcstring &name); - -/// Return all event handlers for the given function. -event_handler_list_t event_get_function_handlers(const wcstring &name); - -/// Returns whether an event listener is registered for the given signal. This is safe to call from -/// a signal handler. -bool event_is_signal_observed(int signal); - -/// Fire the specified event \p event, executing it on \p parser. -void event_fire(parser_t &parser, const event_t &event); - -/// Fire all delayed events attached to the given parser. -void event_fire_delayed(parser_t &parser); - -/// Enqueue a signal event. Invoked from a signal handler. -void event_enqueue_signal(int signal); - -/// Print all events. If type_filter is not empty, only output events with that type. -void event_print(io_streams_t &streams, const wcstring &type_filter); - -/// Returns a string describing the specified event. -wcstring event_get_desc(const parser_t &parser, const event_t &e); - -// FFI helper for event_fire_generic -void event_fire_generic(parser_t &parser, wcstring name, const wcharz_t *argv, int argc); - -/// Fire a generic event with the specified name. -void event_fire_generic(parser_t &parser, wcstring name, wcstring_list_t args = {}); +void event_fire_generic(parser_t &parser, const wcstring &name, + const wcstring_list_t &args = g_empty_string_list); #endif +#endif diff --git a/src/ffi.h b/src/ffi.h index ced462d77..711ece232 100644 --- a/src/ffi.h +++ b/src/ffi.h @@ -8,7 +8,7 @@ #endif template -std::shared_ptr box_to_shared_ptr(rust::Box &&value) { +inline std::shared_ptr box_to_shared_ptr(rust::Box &&value) { T *ptr = value.into_raw(); std::shared_ptr shared(ptr, [](T *ptr) { rust::Box::from_raw(ptr); }); return shared; diff --git a/src/fish.cpp b/src/fish.cpp index b602bd8e1..eed026673 100644 --- a/src/fish.cpp +++ b/src/fish.cpp @@ -597,7 +597,7 @@ int main(int argc, char **argv) { } int exit_status = res ? STATUS_CMD_UNKNOWN : parser.get_last_status(); - event_fire(parser, event_t::process_exit(getpid(), exit_status)); + event_fire(parser, *new_event_process_exit(getpid(), exit_status)); // Trigger any exit handlers. event_fire_generic(parser, L"fish_exit", {to_string(exit_status)}); diff --git a/src/function.cpp b/src/function.cpp index eee67870d..d930b25fa 100644 --- a/src/function.cpp +++ b/src/function.cpp @@ -291,7 +291,7 @@ wcstring function_properties_t::annotated_definition(const wcstring &name) const wcstring out; wcstring desc = this->localized_description(); wcstring def = get_function_body_source(*this); - std::vector> ev = event_get_function_handlers(name); + auto handlers = event_get_function_handler_descs(name); out.append(L"function "); @@ -317,23 +317,22 @@ wcstring function_properties_t::annotated_definition(const wcstring &name) const out.append(L" --no-scope-shadowing"); } - for (const auto &next : ev) { - const event_description_t &d = next->desc; - switch (d.type) { + for (const auto &d : handlers) { + switch (d.typ) { case event_type_t::signal: { - append_format(out, L" --on-signal %ls", sig2wcs(d.param1.signal)); + append_format(out, L" --on-signal %ls", sig2wcs(d.signal)); break; } case event_type_t::variable: { - append_format(out, L" --on-variable %ls", d.str_param1.c_str()); + append_format(out, L" --on-variable %ls", d.str_param1->c_str()); break; } case event_type_t::process_exit: { - append_format(out, L" --on-process-exit %d", d.param1.pid); + append_format(out, L" --on-process-exit %d", d.pid); break; } case event_type_t::job_exit: { - append_format(out, L" --on-job-exit %d", d.param1.jobspec.pid); + append_format(out, L" --on-job-exit %d", d.pid); break; } case event_type_t::caller_exit: { @@ -341,12 +340,12 @@ wcstring function_properties_t::annotated_definition(const wcstring &name) const break; } case event_type_t::generic: { - append_format(out, L" --on-event %ls", d.str_param1.c_str()); + append_format(out, L" --on-event %ls", d.str_param1->c_str()); break; } case event_type_t::any: default: { - DIE("unexpected next->type"); + DIE("unexpected next->typ"); } } } diff --git a/src/io.h b/src/io.h index d9539dc48..15d48cc2b 100644 --- a/src/io.h +++ b/src/io.h @@ -336,13 +336,18 @@ class io_chain_t : public std::vector { // user-declared ctor to allow const init. Do not default this, it will break the build. io_chain_t() {} + /// autocxx falls over with this so hide it. +#if INCLUDE_RUST_HEADERS void remove(const io_data_ref_t &element); void push_back(io_data_ref_t element); +#endif bool append(const io_chain_t &chain); /// \return the last io redirection in the chain for the specified file descriptor, or nullptr /// if none. +#if INCLUDE_RUST_HEADERS io_data_ref_t io_for_fd(int fd) const; +#endif /// Attempt to resolve a list of redirection specs to IOs, appending to 'this'. /// \return true on success, false on error, in which case an error will have been printed. diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index 049bdf531..e67d2031e 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -464,7 +464,7 @@ end_execution_reason_t parse_execution_context_t::run_for_statement( block_t *fb = parser->push_block(block_t::for_block()); // We fire the same event over and over again, just construct it once. - event_t evt = event_t::variable_set(for_var_name); + auto evt = new_event_variable_set(for_var_name); // Now drive the for loop. for (const wcstring &val : arguments) { @@ -476,7 +476,7 @@ end_execution_reason_t parse_execution_context_t::run_for_statement( retval = vars.set(for_var_name, ENV_DEFAULT | ENV_USER, {val}); assert(retval == ENV_OK && "for loop variable should have been successfully set"); (void)retval; - event_fire(*parser, evt); + event_fire(*parser, *evt); auto &ld = parser->libdata(); ld.loop_status = loop_status_t::normals; @@ -787,9 +787,8 @@ end_execution_reason_t parse_execution_context_t::handle_command_not_found( } auto prev_statuses = parser->get_last_statuses(); - event_t event(event_type_t::generic); - event.desc.str_param1 = L"fish_command_not_found"; - block_t *b = parser->push_block(block_t::event_block(event)); + auto event = new_event_generic(L"fish_command_not_found"); + block_t *b = parser->push_block(block_t::event_block(&*event)); parser->eval(buffer, io); parser->pop_block(b); parser->set_last_statuses(std::move(prev_statuses)); diff --git a/src/parser.cpp b/src/parser.cpp index ba137284e..4e96967fc 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -65,7 +65,7 @@ void parser_t::assert_can_execute() const { ASSERT_IS_MAIN_THREAD(); } int parser_t::set_var_and_fire(const wcstring &key, env_mode_flags_t mode, wcstring_list_t vals) { int res = vars().set(key, mode, std::move(vals)); if (res == ENV_OK) { - event_fire(*this, event_t::variable_set(key)); + event_fire(*this, *new_event_variable_set(key)); } return res; } @@ -80,7 +80,7 @@ void parser_t::sync_uvars_and_fire(bool always) { if (this->syncs_uvars_) { auto evts = this->vars().universal_sync(always); for (const auto &evt : evts) { - event_fire(*this, evt); + event_fire(*this, *evt); } } } @@ -252,7 +252,7 @@ static void append_block_description_to_stack_trace(const parser_t &parser, cons } case block_type_t::event: { assert(b.event && "Should have an event"); - wcstring description = event_get_desc(parser, *b.event); + wcstring description = *event_get_desc(parser, **b.event); append_format(trace, _(L"in event handler: %ls\n"), description.c_str()); print_call_site = true; break; @@ -505,6 +505,8 @@ job_t *parser_t::job_get_from_pid(int64_t pid, size_t &job_pos) const { library_data_pod_t *parser_t::ffi_libdata_pod() { return &library_data; } +job_t *parser_t::ffi_job_get_from_pid(int pid) const { return job_get_from_pid(pid); } + profile_item_t *parser_t::create_profile_item() { if (g_profiling_active) { profile_items.emplace_back(); @@ -534,6 +536,8 @@ eval_res_t parser_t::eval(const wcstring &cmd, const io_chain_t &io, } } +eval_res_t parser_t::eval_string_ffi1(const wcstring &cmd) { return eval(cmd, io_chain_t()); } + eval_res_t parser_t::eval(const parsed_source_ref_t &ps, const io_chain_t &io, const job_group_ref_t &job_group, enum block_type_t block_type) { assert(block_type == block_type_t::top || block_type == block_type_t::subst); @@ -776,9 +780,11 @@ bool block_t::is_function_call() const { block_t block_t::if_block() { return block_t(block_type_t::if_block); } -block_t block_t::event_block(event_t evt) { +block_t block_t::event_block(const void *evt_) { + const auto &evt = *static_cast(evt_); block_t b{block_type_t::event}; - b.event.reset(new event_t(std::move(evt))); + b.event = + std::make_shared>(evt.clone()); // TODO Post-FFI: move instead of clone. return b; } diff --git a/src/parser.h b/src/parser.h index 8a8279ae0..c96819765 100644 --- a/src/parser.h +++ b/src/parser.h @@ -15,6 +15,7 @@ #include "common.h" #include "cxx.h" #include "env.h" +#include "event.h" #include "expand.h" #include "maybe.h" #include "operation_context.h" @@ -26,8 +27,9 @@ class autoclose_fd_t; class io_chain_t; -struct event_t; +struct Event; struct job_group_t; +class parser_t; /// Types of blocks. enum class block_type_t : uint8_t { @@ -55,11 +57,10 @@ enum class loop_status_t { /// block_t represents a block of commands. class block_t { - private: + public: /// Construct from a block type. explicit block_t(block_type_t t); - public: // If this is a function block, the function name. Otherwise empty. wcstring function_name{}; @@ -73,7 +74,7 @@ class block_t { filename_ref_t src_filename{}; // If this is an event block, the event. Otherwise ignored. - std::shared_ptr event; + std::shared_ptr> event; // If this is a source block, the source'd file, interned. // Otherwise nothing. @@ -101,7 +102,7 @@ class block_t { /// Entry points for creating blocks. static block_t if_block(); - static block_t event_block(event_t evt); + static block_t event_block(const void *evt_); static block_t function_block(wcstring name, wcstring_list_t args, bool shadows); static block_t source_block(filename_ref_t src); static block_t for_block(); @@ -113,6 +114,7 @@ class block_t { /// autocxx junk. void ffi_incr_event_blocks(); + uint64_t ffi_event_blocks() const { return event_blocks; } }; struct profile_item_t { @@ -205,9 +207,6 @@ struct library_data_t : public library_data_pod_t { /// The current filename we are evaluating, either from builtin source or on the command line. filename_ref_t current_filename{}; - /// List of events that have been sent but have not yet been delivered because they are blocked. - std::vector> blocked_events{}; - /// A stack of fake values to be returned by builtin_commandline. This is used by the completion /// machinery when wrapping: e.g. if `tig` wraps `git` then git completions need to see git on /// the command line. @@ -334,6 +333,9 @@ class parser_t : public std::enable_shared_from_this { const job_group_ref_t &job_group = {}, block_type_t block_type = block_type_t::top); + /// An ffi overload of `eval(const wcstring &cmd, ...)` but without the extra parameters. + eval_res_t eval_string_ffi1(const wcstring &cmd); + /// Evaluate the parsed source ps. /// Because the source has been parsed, a syntax error is impossible. eval_res_t eval(const parsed_source_ref_t &ps, const io_chain_t &io, @@ -485,6 +487,7 @@ class parser_t : public std::enable_shared_from_this { /// autocxx junk. RustFFIJobList ffi_jobs() const; library_data_pod_t *ffi_libdata_pod(); + job_t *ffi_job_get_from_pid(int pid) const; /// autocxx junk. bool ffi_has_funtion_block() const; diff --git a/src/proc.cpp b/src/proc.cpp index 1a2bbae11..18976e200 100644 --- a/src/proc.cpp +++ b/src/proc.cpp @@ -467,33 +467,34 @@ static void process_mark_finished_children(parser_t &parser, bool block_ok) { } /// Generate process_exit events for any completed processes in \p j. -static void generate_process_exit_events(const job_ref_t &j, std::vector *out_evts) { +static void generate_process_exit_events(const job_ref_t &j, + std::vector> *out_evts) { // Historically we have avoided generating events for foreground jobs from event handlers, as an // event handler may itself produce a new event. if (!j->from_event_handler() || !j->is_foreground()) { for (const auto &p : j->processes) { if (p->pid > 0 && p->completed && !p->posted_proc_exit) { p->posted_proc_exit = true; - out_evts->push_back(event_t::process_exit(p->pid, p->status.status_value())); + out_evts->push_back(new_event_process_exit(p->pid, p->status.status_value())); } } } } /// Given a job that has completed, generate job_exit and caller_exit events. -static void generate_job_exit_events(const job_ref_t &j, std::vector *out_evts) { +static void generate_job_exit_events(const job_ref_t &j, std::vector> *out_evts) { // Generate proc and job exit events, except for foreground jobs originating in event handlers. if (!j->from_event_handler() || !j->is_foreground()) { // job_exit events. if (j->posts_job_exit_events()) { auto last_pid = j->get_last_pid(); if (last_pid.has_value()) { - out_evts->push_back(event_t::job_exit(*last_pid, j->internal_job_id)); + out_evts->push_back(new_event_job_exit(*last_pid, j->internal_job_id)); } } } // Generate caller_exit events. - out_evts->push_back(event_t::caller_exit(j->internal_job_id, j->job_id())); + out_evts->push_back(new_event_caller_exit(j->internal_job_id, j->job_id())); } /// \return whether to emit a fish_job_summary call for a process. @@ -540,9 +541,8 @@ bool job_or_proc_wants_summary(const shared_ptr &j) { /// Invoke the fish_job_summary function by executing the given command. static void call_job_summary(parser_t &parser, const wcstring &cmd) { - event_t event(event_type_t::generic); - event.desc.str_param1 = L"fish_job_summary"; - block_t *b = parser.push_block(block_t::event_block(event)); + auto event = new_event_generic(L"fish_job_summary"); + block_t *b = parser.push_block(block_t::event_block(&*event)); auto saved_status = parser.get_last_statuses(); parser.eval(cmd, io_chain_t()); parser.set_last_statuses(saved_status); @@ -671,7 +671,7 @@ static bool process_clean_after_marking(parser_t &parser, bool allow_interactive // Accumulate exit events into a new list, which we fire after the list manipulation is // complete. - std::vector exit_events; + std::vector> exit_events; // Defer processing under-construction jobs or jobs that want a message when we are not // interactive. @@ -723,7 +723,7 @@ static bool process_clean_after_marking(parser_t &parser, bool allow_interactive // Post pending exit events. for (const auto &evt : exit_events) { - event_fire(parser, evt); + event_fire(parser, *evt); } if (printed) { diff --git a/src/termsize.cpp b/src/termsize.cpp index 8bdcd0d2a..75ba9685b 100644 --- a/src/termsize.cpp +++ b/src/termsize.cpp @@ -50,6 +50,10 @@ termsize_container_t &termsize_container_t::shared() { return *res; } +termsize_t termsize_container_t::ffi_updating(parser_t &parser) { + return shared().updating(parser); +} + termsize_t termsize_container_t::data_t::current() const { // This encapsulates our ordering logic. If we have a termsize from a tty, use it; otherwise use // what we have seen from the environment. diff --git a/src/termsize.h b/src/termsize.h index 7050cb51d..1cbe11779 100644 --- a/src/termsize.h +++ b/src/termsize.h @@ -72,6 +72,9 @@ struct termsize_container_t { /// \return the singleton shared container. static termsize_container_t &shared(); + /// autocxx junk. + static termsize_t ffi_updating(parser_t &parser); + private: /// A function used for accessing the termsize from the tty. This is only exposed for testing. using tty_size_reader_func_t = maybe_t (*)();