use std::env;
use super::*;
use crate::embed::{OutcomeCaptureHandle, ShellDiagnostic, TraceEvent};
const GETOPTS_CURSOR_VAR: &str = "__MXSH_GETOPTS_CURSOR";
const GETOPTS_INDEX_VAR: &str = "__MXSH_GETOPTS_INDEX";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub(super) enum BranchControl {
None,
Break(u32),
Continue(u32),
Return,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Variable {
pub value: String,
pub attrib: u32,
}
#[derive(Debug, Clone)]
pub(super) enum JobState {
Running,
Stopped(i32),
Done(i32),
}
#[derive(Debug, Clone)]
pub(super) struct JobInfo {
pub(super) job_id: u32,
pub(super) display_pid: Option<u32>,
pub(super) state: JobState,
}
#[derive(Debug, Clone)]
pub(super) struct ShellJob {
pub(super) job_id: u32,
pub(super) handle: sys::ProcessHandle,
pub(super) display_pid: Option<u32>,
pub(super) state: JobState,
pub(super) command_id: String,
pub(super) finish_emitted: bool,
}
#[derive(Debug, Clone)]
pub struct ShellState {
pub exit_code: i32,
pub options: u32,
pub frame: Vec<String>,
pub interactive: bool,
pub last_status: i32,
pub(super) vars: HashMap<String, Variable>,
pub cwd: PathBuf,
pub(super) functions: HashMap<String, AstCommand>,
pub(super) aliases: Arc<HashMap<String, String>>,
pub(super) branch: BranchControl,
pub(super) loop_depth: u32,
pub(super) function_depth: u32,
pub(super) last_bg_pid: Option<u32>,
pub(super) traps: HashMap<i32, String>,
pub(super) notified_jobs: HashSet<u32>,
pub(super) next_job_id: u32,
pub(super) jobs: Vec<ShellJob>,
pub(super) warned_vi_unsupported: bool,
pub(crate) manage_signals: bool,
pub(super) monitor_signals: MonitorSignalState,
pub(super) trap_signals: TrapSignalState,
pub(super) monitor_mode_active: bool,
pub(super) expansion_error: Option<i32>,
pub(super) current_source: Option<String>,
pub(super) active_command_id: Option<String>,
pub stdin_fd: sys::FileDescriptor,
pub stdout_fd: sys::FileDescriptor,
pub stderr_fd: sys::FileDescriptor,
pub(super) raw_fds: HashMap<i32, sys::FileDescriptor>,
pub(super) named_fds: HashMap<String, i32>,
pub(crate) outcome_captures: Vec<OutcomeCaptureHandle>,
pub(crate) definition: Arc<crate::embed::ShellDefinition>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(super) struct BackgroundStateSnapshot {
pub(super) exit_code: i32,
pub(super) options: u32,
pub(super) frame: Vec<String>,
pub(super) interactive: bool,
pub(super) last_status: i32,
pub(super) vars: HashMap<String, Variable>,
pub(super) cwd: PathBuf,
pub(super) functions: HashMap<String, AstCommand>,
pub(super) aliases: HashMap<String, String>,
pub(super) branch: BranchControl,
pub(super) loop_depth: u32,
pub(super) function_depth: u32,
pub(super) last_bg_pid: Option<u32>,
pub(super) traps: HashMap<i32, String>,
pub(super) warned_vi_unsupported: bool,
pub(super) expansion_error: Option<i32>,
pub(super) current_source: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(super) struct BackgroundMachinePayload {
pub(super) state: BackgroundStateSnapshot,
pub(super) and_or_list: AndOrList,
pub(super) command_id: String,
}
impl Default for ShellState {
fn default() -> Self {
Self::new()
}
}
impl ShellState {
pub fn new() -> Self {
Self {
exit_code: -1,
options: 0,
frame: Vec::new(),
interactive: false,
last_status: 0,
vars: HashMap::new(),
cwd: env::current_dir().unwrap_or_else(|_| PathBuf::from("/")),
functions: HashMap::new(),
aliases: Arc::new(HashMap::new()),
branch: BranchControl::None,
loop_depth: 0,
function_depth: 0,
last_bg_pid: None,
traps: HashMap::new(),
notified_jobs: HashSet::new(),
next_job_id: 1,
jobs: Vec::new(),
warned_vi_unsupported: false,
manage_signals: false,
monitor_signals: MonitorSignalState::default(),
trap_signals: TrapSignalState::default(),
monitor_mode_active: false,
expansion_error: None,
current_source: None,
active_command_id: None,
stdin_fd: sys::FileDescriptor::STDIN,
stdout_fd: sys::FileDescriptor::STDOUT,
stderr_fd: sys::FileDescriptor::STDERR,
raw_fds: HashMap::new(),
named_fds: HashMap::new(),
outcome_captures: Vec::new(),
definition: Arc::new(crate::embed::ShellDefinition::default()),
}
}
pub(crate) fn fork_session(&self) -> Self {
self.clone()
}
pub(crate) fn detached_session(&self) -> Self {
let mut detached = self.clone();
detached.active_command_id = None;
detached.outcome_captures.clear();
detached
}
pub fn last_background_pid(&self) -> Option<u32> {
self.last_bg_pid
}
pub(crate) fn shell_name(&self) -> &str {
self.definition.identity.name()
}
pub(super) fn active_command_id(&self) -> Option<&str> {
self.active_command_id.as_deref()
}
pub(super) fn set_active_command_id(&mut self, command_id: Option<String>) {
self.active_command_id = command_id;
}
pub(crate) fn has_outcome_capture(&self) -> bool {
!self.outcome_captures.is_empty()
}
pub(crate) fn push_outcome_capture(&mut self, capture: OutcomeCaptureHandle) {
self.outcome_captures.push(capture);
}
pub(crate) fn pop_outcome_capture(&mut self) -> Option<OutcomeCaptureHandle> {
self.outcome_captures.pop()
}
pub(crate) fn record_diagnostic(&self, diagnostic: ShellDiagnostic) {
for capture in &self.outcome_captures {
capture
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.diagnostics
.push(diagnostic.clone());
}
}
pub(crate) fn record_trace_event(&self, event: TraceEvent) {
for capture in &self.outcome_captures {
capture
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.trace
.push(event.clone());
}
}
pub(crate) fn prefixed_message(&self, message: impl AsRef<str>) -> String {
format!("{}: {}", self.shell_name(), message.as_ref())
}
pub(super) fn background_snapshot(&self) -> BackgroundStateSnapshot {
BackgroundStateSnapshot {
exit_code: self.exit_code,
options: self.options,
frame: self.frame.clone(),
interactive: self.interactive,
last_status: self.last_status,
vars: self.vars.clone(),
cwd: self.cwd.clone(),
functions: self.functions.clone(),
aliases: self.aliases.as_ref().clone(),
branch: self.branch,
loop_depth: self.loop_depth,
function_depth: self.function_depth,
last_bg_pid: self.last_bg_pid,
traps: self.traps.clone(),
warned_vi_unsupported: self.warned_vi_unsupported,
expansion_error: self.expansion_error,
current_source: self.current_source.clone(),
}
}
#[cfg(any(
feature = "frontend",
all(test, feature = "test-support", feature = "unix-runtime")
))]
pub(super) fn apply_background_snapshot(&mut self, snapshot: BackgroundStateSnapshot) {
self.exit_code = snapshot.exit_code;
self.options = snapshot.options;
self.frame = snapshot.frame;
self.interactive = snapshot.interactive;
self.last_status = snapshot.last_status;
self.vars = snapshot.vars;
self.cwd = snapshot.cwd;
self.functions = snapshot.functions;
self.aliases = Arc::new(snapshot.aliases);
self.branch = snapshot.branch;
self.loop_depth = snapshot.loop_depth;
self.function_depth = snapshot.function_depth;
self.last_bg_pid = snapshot.last_bg_pid;
self.traps = snapshot.traps;
self.warned_vi_unsupported = snapshot.warned_vi_unsupported;
self.expansion_error = snapshot.expansion_error;
self.current_source = snapshot.current_source;
self.active_command_id = None;
self.outcome_captures.clear();
self.notified_jobs.clear();
self.next_job_id = 1;
self.jobs.clear();
self.manage_signals = true;
self.monitor_signals = MonitorSignalState::default();
self.trap_signals = TrapSignalState::default();
self.monitor_mode_active = false;
}
pub fn env_set(&mut self, key: &str, value: String, attrib: u32) -> bool {
if self
.vars
.get(key)
.is_some_and(|var| (var.attrib & VAR_READONLY) != 0)
{
return false;
}
self.vars
.insert(key.to_string(), Variable { value, attrib });
self.reset_getopts_state_for(key);
true
}
pub(crate) fn env_set_internal(&mut self, key: &str, value: String, attrib: u32) {
self.vars
.insert(key.to_string(), Variable { value, attrib });
self.reset_getopts_state_for(key);
}
pub fn env_unset(&mut self, key: &str) {
if self
.vars
.get(key)
.is_some_and(|var| (var.attrib & VAR_READONLY) != 0)
{
return;
}
self.vars.remove(key);
self.reset_getopts_state_for(key);
}
pub fn env_get(&self, key: &str) -> Option<&str> {
self.vars.get(key).map(|v| v.value.as_str())
}
fn reset_getopts_state_for(&mut self, key: &str) {
if key == "OPTIND" {
self.vars.remove(GETOPTS_CURSOR_VAR);
self.vars.remove(GETOPTS_INDEX_VAR);
}
}
pub fn exported_env(&self) -> Vec<(String, String)> {
self.vars
.iter()
.filter(|(_, v)| (v.attrib & VAR_EXPORT) != 0)
.map(|(k, v)| (k.clone(), v.value.clone()))
.collect()
}
pub(crate) fn inherited_fds(&self) -> Vec<sys::PassedFileDescriptor> {
let mut inherited: Vec<_> = self
.raw_fds
.iter()
.filter_map(|(&child_fd, &parent_fd)| {
parent_fd.is_valid().then_some(sys::PassedFileDescriptor {
parent_fd,
child_fd: sys::FileDescriptor::new(child_fd),
})
})
.collect();
inherited.sort_by_key(|passed| passed.child_fd.as_i32());
inherited
}
pub(crate) fn inherited_fd_entries(&self) -> Vec<(i32, sys::FileDescriptor)> {
let mut inherited: Vec<_> = self
.raw_fds
.iter()
.map(|(&child, &parent)| (child, parent))
.collect();
inherited.sort_by_key(|(child, _)| *child);
inherited
}
pub(crate) fn named_inherited_fd_entries(&self) -> Vec<crate::embed::NamedFileDescriptor> {
let mut inherited: Vec<_> = self
.named_fds
.iter()
.filter_map(|(name, &child)| {
self.raw_fds.get(&child).copied().map(|parent| {
crate::embed::NamedFileDescriptor::new(name.clone(), child, parent)
})
})
.collect();
inherited.sort_by(|left, right| left.name.cmp(&right.name));
inherited
}
pub(crate) fn named_inherited_fd(
&self,
name: &str,
) -> Option<crate::embed::NamedFileDescriptor> {
let child_fd = *self.named_fds.get(name)?;
let parent_fd = *self.raw_fds.get(&child_fd)?;
Some(crate::embed::NamedFileDescriptor::new(
name, child_fd, parent_fd,
))
}
pub(crate) fn set_inherited_fd(
&mut self,
child_fd: i32,
parent_fd: sys::FileDescriptor,
) -> Option<sys::FileDescriptor> {
self.raw_fds.insert(child_fd, parent_fd)
}
pub(crate) fn set_named_inherited_fd(
&mut self,
named_fd: crate::embed::NamedFileDescriptor,
) -> Option<crate::embed::NamedFileDescriptor> {
let crate::embed::NamedFileDescriptor {
name,
child_fd,
parent_fd,
} = named_fd;
let previous = self.named_inherited_fd(&name);
self.named_fds.insert(name, child_fd);
let _ = self.set_inherited_fd(child_fd, parent_fd);
if let Some(ref previous) = previous
&& previous.child_fd != child_fd
&& !self
.named_fds
.values()
.any(|&named_child| named_child == previous.child_fd)
{
let _ = self.raw_fds.remove(&previous.child_fd);
}
previous
}
pub(crate) fn aliases(&self) -> Vec<(String, String)> {
let mut aliases: Vec<_> = self
.aliases
.iter()
.map(|(name, value)| (name.clone(), value.clone()))
.collect();
aliases.sort_by(|left, right| left.0.cmp(&right.0));
aliases
}
pub(crate) fn alias(&self, name: &str) -> Option<&str> {
self.aliases.get(name).map(String::as_str)
}
pub(crate) fn set_alias(&mut self, name: impl Into<String>, value: impl Into<String>) {
Arc::make_mut(&mut self.aliases).insert(name.into(), value.into());
}
pub(crate) fn functions(&self) -> Vec<(String, AstCommand)> {
let mut functions: Vec<_> = self
.functions
.iter()
.map(|(name, body)| (name.clone(), body.clone()))
.collect();
functions.sort_by(|left, right| left.0.cmp(&right.0));
functions
}
pub(crate) fn function(&self, name: &str) -> Option<&AstCommand> {
self.functions.get(name)
}
pub(crate) fn set_function(&mut self, name: impl Into<String>, body: AstCommand) {
self.functions.insert(name.into(), body);
}
pub(crate) fn argv0(&self) -> Option<&str> {
self.frame.first().map(String::as_str)
}
pub(crate) fn positional_parameters(&self) -> &[String] {
self.frame.get(1..).unwrap_or(&[])
}
pub(crate) fn initialize_defaults(&mut self) {
self.env_set("IFS", " \t\n".to_string(), 0);
self.env_set("PPID", sys::parent_pid().to_string(), 0);
let pwd = self
.env_get("PWD")
.map(ToString::to_string)
.unwrap_or_else(|| self.cwd.display().to_string());
self.env_set_internal("PWD", pwd, VAR_EXPORT);
self.env_set("OPTIND", "1".to_string(), 0);
}
pub fn populate_env(&mut self) {
for (k, v) in env::vars() {
self.env_set(&k, v, VAR_EXPORT);
}
self.initialize_defaults();
}
pub(super) fn has_option(&self, opt: u32) -> bool {
(self.options & opt) != 0
}
pub(super) fn aliases_snapshot(&self) -> Arc<HashMap<String, String>> {
Arc::clone(&self.aliases)
}
pub(crate) fn set_last_status(&mut self, status: i32) {
self.last_status = status;
}
pub(crate) fn set_exit_code(&mut self, code: i32) {
self.exit_code = code;
}
pub(super) fn set_exit_code_if_unset(&mut self, code: i32) {
if self.exit_code < 0 {
self.exit_code = code;
}
}
pub(super) fn set_last_background_pid(&mut self, pid: Option<u32>) {
self.last_bg_pid = pid;
}
pub(super) fn set_current_source(&mut self, source: Option<String>) {
self.current_source = source;
}
pub(super) fn record_expansion_error(&mut self, status: i32) {
if self.expansion_error.is_none() {
self.expansion_error = Some(status);
}
}
pub(super) fn take_expansion_error(&mut self) -> Option<i32> {
self.expansion_error.take()
}
}