mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 21:33:09 +00:00
Port event.cpp to rust
Port src/event.cpp to fish-rust/event.rs and some needed functions. Co-authored-by: Mahmoud Al-Qudsi <mqudsi@neosmart.net>
This commit is contained in:
parent
c8d2f7a0da
commit
9ac6cbefb1
23 changed files with 1082 additions and 773 deletions
|
@ -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",
|
||||
|
|
|
@ -4,9 +4,9 @@ use widestring_suffix::widestrs;
|
|||
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<W0String> = argv[opts.optind + 1..]
|
||||
.iter()
|
||||
.map(|s| W0String::from_ustr(s).unwrap())
|
||||
.collect();
|
||||
let event_arg_ptrs: Vec<ffi::wcharz_t> = 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
|
||||
|
|
923
fish-rust/src/event.rs
Normal file
923
fish-rust/src/event.rs
Normal file
|
@ -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<CxxWString>,
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type EventHandler;
|
||||
type Event;
|
||||
|
||||
fn new_event_generic(desc: wcharz_t) -> Box<Event>;
|
||||
fn new_event_variable_erase(name: &CxxWString) -> Box<Event>;
|
||||
fn new_event_variable_set(name: &CxxWString) -> Box<Event>;
|
||||
fn new_event_process_exit(pid: i32, status: i32) -> Box<Event>;
|
||||
fn new_event_job_exit(pgid: i32, jid: u64) -> Box<Event>;
|
||||
fn new_event_caller_exit(internal_job_id: u64, job_id: i32) -> Box<Event>;
|
||||
#[cxx_name = "clone"]
|
||||
fn clone_ffi(self: &Event) -> Box<Event>;
|
||||
|
||||
#[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<event_description_t>;
|
||||
|
||||
fn desc(self: &EventHandler) -> event_description_t;
|
||||
fn function_name(self: &EventHandler) -> UniquePtr<CxxWString>;
|
||||
fn set_removed(self: &mut EventHandler);
|
||||
|
||||
fn event_fire_generic_ffi(
|
||||
parser: Pin<&mut parser_t>,
|
||||
name: &CxxWString,
|
||||
arguments: &CxxVector<wcharz_t>,
|
||||
);
|
||||
#[cxx_name = "event_get_desc"]
|
||||
fn event_get_desc_ffi(parser: &parser_t, evt: &Event) -> UniquePtr<CxxWString>;
|
||||
#[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<WString>) -> 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<Arc<EventHandler>>;
|
||||
|
||||
impl EventHandler {
|
||||
fn desc(&self) -> event_description_t {
|
||||
(&self.desc).into()
|
||||
}
|
||||
fn function_name(self: &EventHandler) -> UniquePtr<CxxWString> {
|
||||
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<WString>,
|
||||
}
|
||||
|
||||
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<Event> {
|
||||
Box::new(Event::generic(desc.into()))
|
||||
}
|
||||
|
||||
fn new_event_variable_erase(name: &CxxWString) -> Box<Event> {
|
||||
Box::new(Event::variable_erase(name.from_ffi()))
|
||||
}
|
||||
|
||||
fn new_event_variable_set(name: &CxxWString) -> Box<Event> {
|
||||
Box::new(Event::variable_set(name.from_ffi()))
|
||||
}
|
||||
|
||||
fn new_event_process_exit(pid: i32, status: i32) -> Box<Event> {
|
||||
Box::new(Event::process_exit(pid, status))
|
||||
}
|
||||
|
||||
fn new_event_job_exit(pgid: i32, jid: u64) -> Box<Event> {
|
||||
Box::new(Event::job_exit(pgid, jid))
|
||||
}
|
||||
|
||||
fn new_event_caller_exit(internal_job_id: u64, job_id: i32) -> Box<Event> {
|
||||
Box::new(Event::caller_exit(internal_job_id, job_id))
|
||||
}
|
||||
|
||||
impl Event {
|
||||
fn clone_ffi(&self) -> Box<Event> {
|
||||
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<u32>,
|
||||
}
|
||||
|
||||
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<T>`).**
|
||||
static EVENT_HANDLERS: Mutex<EventHandlerList> = 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<Vec<Event>> = 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<CxxWString> {
|
||||
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<event_description_t> {
|
||||
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<WString>) {
|
||||
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<wcharz_t>,
|
||||
) {
|
||||
fire_generic(
|
||||
parser.unpin(),
|
||||
name.from_ffi(),
|
||||
arguments.iter().map(WString::from).collect(),
|
||||
);
|
||||
}
|
|
@ -9,6 +9,7 @@ use ::std::slice;
|
|||
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_cpp! {
|
|||
#include "tokenizer.h"
|
||||
#include "wildcard.h"
|
||||
#include "wutil.h"
|
||||
#include "termsize.h"
|
||||
|
||||
safety!(unsafe_ffi)
|
||||
|
||||
|
@ -77,8 +79,6 @@ include_cpp! {
|
|||
generate!("wait_handle_t")
|
||||
generate!("wait_handle_store_t")
|
||||
|
||||
generate!("event_fire_generic")
|
||||
|
||||
generate!("escape_string")
|
||||
generate!("sig2wcs")
|
||||
generate!("wcs2sig")
|
||||
|
@ -93,9 +93,24 @@ include_cpp! {
|
|||
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<job_t>] {
|
||||
let ffi_jobs = self.ffi_jobs();
|
||||
unsafe { slice::from_raw_parts(ffi_jobs.jobs, ffi_jobs.count) }
|
||||
|
@ -110,6 +125,11 @@ impl parser_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<Box<re::regex_result_ffi>> {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#[macro_use]
|
||||
mod common;
|
||||
mod color;
|
||||
mod event;
|
||||
mod fd_monitor;
|
||||
mod fd_readable_set;
|
||||
mod fds;
|
||||
|
|
|
@ -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<wcstring>(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<wcstring>(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<event_handler_t>(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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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 { \
|
||||
|
|
10
src/env.cpp
10
src/env.cpp
|
@ -1315,7 +1315,7 @@ mod_result_t env_stack_impl_t::remove(const wcstring &key, int mode) {
|
|||
return result;
|
||||
}
|
||||
|
||||
std::vector<event_t> env_stack_t::universal_sync(bool always) {
|
||||
std::vector<rust::Box<Event>> 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<event_t> 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<event_t> result;
|
||||
std::vector<rust::Box<Event>> 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> &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);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <vector>
|
||||
|
||||
#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<env_stack_impl_t> impl_;
|
||||
std::unique_ptr<env_stack_impl_t> impl_;
|
||||
|
||||
/// All environment stacks are guarded by a global lock.
|
||||
acquired_lock<env_stack_impl_t> 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<event_t> universal_sync(bool always);
|
||||
std::vector<rust::Box<Event>> universal_sync(bool always);
|
||||
|
||||
// Compatibility hack; access the "environment stack" from back when there was just one.
|
||||
static const std::shared_ptr<env_stack_t> &principal_ref();
|
||||
|
|
548
src/event.cpp
548
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<uint32_t> counter_{0};
|
||||
|
||||
/// List of pending signals.
|
||||
std::array<relaxed_atomic_bool_t, SIGNAL_COUNT> received_{};
|
||||
|
||||
/// The last counter visible in acquire_pending().
|
||||
/// This is not accessed from a signal handler.
|
||||
owning_lock<uint32_t> 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<size_t>(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<SIGNAL_COUNT> 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<SIGNAL_COUNT> 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<event_handler_list_t> 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<relaxed_atomic_t<uint32_t>, 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<event_handler_t> 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 <typename T>
|
||||
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<event_handler_t> &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<decltype(ld.is_event)> inc_event{&ld.is_event, ld.is_event + 1};
|
||||
|
||||
// Suppress fish_trace during events.
|
||||
scoped_push<bool> suppress_trace{&ld.suppress_fish_trace, true};
|
||||
|
||||
// Capture the event handlers that match this event.
|
||||
std::vector<std::shared_ptr<event_handler_t>> 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<bool> 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<shared_ptr<const event_t>> 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_t>(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_t>(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<event_handler_t> &e1, const shared_ptr<event_handler_t> &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<event_type_t> last_type{};
|
||||
for (const shared_ptr<event_handler_t> &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<wcharz_t> ffi_args;
|
||||
for (const auto &arg : args) ffi_args.push_back(arg.c_str());
|
||||
event_fire_generic_ffi(parser, name, ffi_args);
|
||||
}
|
||||
|
|
157
src/event.h
157
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 <unistd.h>
|
||||
|
@ -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<std::shared_ptr<event_handler_t>>;
|
||||
|
||||
/// 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<event_handler_t> 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
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#endif
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> box_to_shared_ptr(rust::Box<T> &&value) {
|
||||
inline std::shared_ptr<T> box_to_shared_ptr(rust::Box<T> &&value) {
|
||||
T *ptr = value.into_raw();
|
||||
std::shared_ptr<T> shared(ptr, [](T *ptr) { rust::Box<T>::from_raw(ptr); });
|
||||
return shared;
|
||||
|
|
|
@ -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)});
|
||||
|
|
|
@ -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<std::shared_ptr<event_handler_t>> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
5
src/io.h
5
src/io.h
|
@ -336,13 +336,18 @@ class io_chain_t : public std::vector<io_data_ref_t> {
|
|||
// 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.
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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<const Event *>(evt_);
|
||||
block_t b{block_type_t::event};
|
||||
b.event.reset(new event_t(std::move(evt)));
|
||||
b.event =
|
||||
std::make_shared<rust::Box<Event>>(evt.clone()); // TODO Post-FFI: move instead of clone.
|
||||
return b;
|
||||
}
|
||||
|
||||
|
|
19
src/parser.h
19
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_t> event;
|
||||
std::shared_ptr<rust::Box<Event>> 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<std::shared_ptr<const event_t>> 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<parser_t> {
|
|||
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<parser_t> {
|
|||
/// 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;
|
||||
|
|
20
src/proc.cpp
20
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<event_t> *out_evts) {
|
||||
static void generate_process_exit_events(const job_ref_t &j,
|
||||
std::vector<rust::Box<Event>> *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<event_t> *out_evts) {
|
||||
static void generate_job_exit_events(const job_ref_t &j, std::vector<rust::Box<Event>> *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<job_t> &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<event_t> exit_events;
|
||||
std::vector<rust::Box<Event>> 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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<termsize_t> (*)();
|
||||
|
|
Loading…
Reference in a new issue