1use self::any::DataSet;
35use self::builtin::Builtin;
36use self::function::FunctionSet;
37use self::io::Fd;
38use self::job::JobList;
39use self::job::Pid;
40use self::job::ProcessResult;
41use self::job::ProcessState;
42use self::option::On;
43use self::option::OptionSet;
44use self::option::{AllExport, ErrExit, Interactive, Monitor};
45use self::semantics::Divert;
46use self::semantics::ExitStatus;
47use self::stack::Frame;
48use self::stack::Stack;
49use self::system::Errno;
50use self::system::Mode;
51use self::system::OfdAccess;
52use self::system::OpenFlag;
53pub use self::system::SharedSystem;
54pub use self::system::System;
55use self::system::SystemEx;
56#[cfg(unix)]
57pub use self::system::real::RealSystem;
58pub use self::system::r#virtual::VirtualSystem;
59use self::trap::TrapSet;
60use self::variable::PPID;
61use self::variable::Scope;
62use self::variable::VariableRefMut;
63use self::variable::VariableSet;
64use std::collections::HashMap;
65use std::fmt::Debug;
66use std::ops::ControlFlow::{self, Break, Continue};
67use std::rc::Rc;
68use std::task::Context;
69use std::task::Poll;
70use std::task::Waker;
71pub use unix_path as path;
72pub use unix_str as str;
73use yash_syntax::alias::AliasSet;
74
75#[derive(Clone, Debug)]
89#[non_exhaustive]
90pub struct Env {
91 pub aliases: AliasSet,
93
94 pub arg0: String,
98
99 pub builtins: HashMap<&'static str, Builtin>,
101
102 pub exit_status: ExitStatus,
104
105 pub functions: FunctionSet,
107
108 pub jobs: JobList,
110
111 pub main_pgid: Pid,
113
114 pub main_pid: Pid,
118
119 pub options: OptionSet,
121
122 pub stack: Stack,
124
125 pub traps: TrapSet,
127
128 pub tty: Option<Fd>,
133
134 pub variables: VariableSet,
136
137 pub any: DataSet,
139
140 pub system: SharedSystem,
142}
143
144impl Env {
145 #[must_use]
151 pub fn with_system(system: Box<dyn System>) -> Env {
152 Env {
153 aliases: Default::default(),
154 arg0: Default::default(),
155 builtins: Default::default(),
156 exit_status: Default::default(),
157 functions: Default::default(),
158 jobs: Default::default(),
159 main_pgid: system.getpgrp(),
160 main_pid: system.getpid(),
161 options: Default::default(),
162 stack: Default::default(),
163 traps: Default::default(),
164 tty: Default::default(),
165 variables: Default::default(),
166 any: Default::default(),
167 system: SharedSystem::new(system),
168 }
169 }
170
171 #[must_use]
173 pub fn new_virtual() -> Env {
174 Env::with_system(Box::<VirtualSystem>::default())
175 }
176
177 #[must_use]
183 pub fn clone_with_system(&self, system: Box<dyn System>) -> Env {
184 Env {
185 aliases: self.aliases.clone(),
186 arg0: self.arg0.clone(),
187 builtins: self.builtins.clone(),
188 exit_status: self.exit_status,
189 functions: self.functions.clone(),
190 jobs: self.jobs.clone(),
191 main_pgid: self.main_pgid,
192 main_pid: self.main_pid,
193 options: self.options,
194 stack: self.stack.clone(),
195 traps: self.traps.clone(),
196 tty: self.tty,
197 variables: self.variables.clone(),
198 any: self.any.clone(),
199 system: SharedSystem::new(system),
200 }
201 }
202
203 pub fn init_variables(&mut self) {
219 self.variables.init();
220
221 self.variables
222 .get_or_new(PPID, Scope::Global)
223 .assign(self.system.getppid().to_string(), None)
224 .ok();
225
226 self.prepare_pwd().ok();
227 }
228
229 pub async fn wait_for_signals(&mut self) -> Rc<[signal::Number]> {
238 let result = self.system.wait_for_signals().await;
239 for signal in result.iter().copied() {
240 self.traps.catch_signal(signal);
241 }
242 result
243 }
244
245 pub async fn wait_for_signal(&mut self, signal: signal::Number) {
250 while !self.wait_for_signals().await.contains(&signal) {}
251 }
252
253 pub fn poll_signals(&mut self) -> Option<Rc<[signal::Number]>> {
260 let system = self.system.clone();
261
262 let mut future = std::pin::pin!(self.wait_for_signals());
263
264 let mut context = Context::from_waker(Waker::noop());
265 if let Poll::Ready(signals) = future.as_mut().poll(&mut context) {
266 return Some(signals);
267 }
268
269 system.select(true).ok();
270 if let Poll::Ready(signals) = future.poll(&mut context) {
271 return Some(signals);
272 }
273 None
274 }
275
276 #[must_use]
286 fn should_print_error_in_color(&self) -> bool {
287 self.system.isatty(Fd::STDERR)
290 }
291
292 pub fn get_tty(&mut self) -> Result<Fd, Errno> {
297 if let Some(fd) = self.tty {
298 return Ok(fd);
299 }
300
301 let first_fd = {
302 let mut result = self.system.open(
307 c"/dev/tty",
308 OfdAccess::ReadWrite,
309 OpenFlag::CloseOnExec | OpenFlag::NoCtty,
310 Mode::empty(),
311 );
312 if result == Err(Errno::EINVAL) {
313 result = self.system.open(
316 c"/dev/tty",
317 OfdAccess::ReadWrite,
318 OpenFlag::CloseOnExec.into(),
319 Mode::empty(),
320 );
321 }
322 result?
323 };
324
325 let final_fd = self.system.move_fd_internal(first_fd);
326 self.tty = final_fd.ok();
327 final_fd
328 }
329
330 pub fn ensure_foreground(&mut self) -> Result<(), Errno> {
351 let fd = self.get_tty()?;
352
353 if self.system.getsid(Pid(0)) == Ok(self.main_pgid) {
354 self.system.tcsetpgrp_with_block(fd, self.main_pgid)
355 } else {
356 self.system.tcsetpgrp_without_block(fd, self.main_pgid)
357 }
358 }
359
360 #[must_use]
367 pub fn is_interactive(&self) -> bool {
368 self.options.get(Interactive) == On && !self.stack.contains(&Frame::Subshell)
369 }
370
371 #[must_use]
378 pub fn controls_jobs(&self) -> bool {
379 self.options.get(Monitor) == On && !self.stack.contains(&Frame::Subshell)
380 }
381
382 pub async fn wait_for_subshell(&mut self, target: Pid) -> Result<(Pid, ProcessState), Errno> {
403 self.traps
406 .enable_internal_disposition_for_sigchld(&mut self.system)?;
407
408 let sigchld = self
409 .system
410 .signal_number_from_name(signal::Name::Chld)
411 .unwrap();
412 loop {
413 if let Some((pid, state)) = self.system.wait(target)? {
414 self.jobs.update_status(pid, state);
415 return Ok((pid, state));
416 }
417 self.wait_for_signal(sigchld).await;
418 }
419 }
420
421 pub async fn wait_for_subshell_to_halt(
428 &mut self,
429 target: Pid,
430 ) -> Result<(Pid, ProcessResult), Errno> {
431 loop {
432 let (pid, state) = self.wait_for_subshell(target).await?;
433 if let ProcessState::Halted(result) = state {
434 return Ok((pid, result));
435 }
436 }
437 }
438
439 pub async fn wait_for_subshell_to_finish(
447 &mut self,
448 target: Pid,
449 ) -> Result<(Pid, ExitStatus), Errno> {
450 loop {
451 let (pid, result) = self.wait_for_subshell_to_halt(target).await?;
452 if !result.is_stopped() {
453 return Ok((pid, result.into()));
454 }
455 }
456 }
457
458 pub fn update_all_subshell_statuses(&mut self) {
467 while let Ok(Some((pid, state))) = self.system.wait(Pid::ALL) {
468 self.jobs.update_status(pid, state);
469 }
470 }
471
472 pub fn get_or_create_variable<S>(&mut self, name: S, scope: Scope) -> VariableRefMut<'_>
482 where
483 S: Into<String>,
484 {
485 let mut variable = self.variables.get_or_new(name, scope);
486 if self.options.get(AllExport) == On {
487 variable.export(true);
488 }
489 variable
490 }
491
492 pub fn errexit_is_applicable(&self) -> bool {
500 self.options.get(ErrExit) == On && !self.stack.contains(&Frame::Condition)
501 }
502
503 pub fn apply_errexit(&self) -> ControlFlow<Divert> {
510 if !self.exit_status.is_successful() && self.errexit_is_applicable() {
511 Break(Divert::Exit(None))
512 } else {
513 Continue(())
514 }
515 }
516
517 pub fn apply_result(&mut self, result: crate::semantics::Result) {
522 match result {
523 Continue(_) => {}
524 Break(divert) => {
525 if let Some(exit_status) = divert.exit_status() {
526 self.exit_status = exit_status;
527 }
528 }
529 }
530 }
531}
532
533mod alias;
534pub mod any;
535pub mod builtin;
536mod decl_util;
537pub mod function;
538pub mod input;
539pub mod io;
540pub mod job;
541pub mod option;
542pub mod pwd;
543pub mod semantics;
544pub mod signal;
545pub mod stack;
546pub mod subshell;
547pub mod system;
548pub mod trap;
549pub mod variable;
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use crate::io::MIN_INTERNAL_FD;
555 use crate::job::Job;
556 use crate::subshell::Subshell;
557 use crate::system::r#virtual::FileBody;
558 use crate::system::r#virtual::Inode;
559 use crate::system::r#virtual::SIGCHLD;
560 use crate::system::r#virtual::SystemState;
561 use crate::trap::Action;
562 use assert_matches::assert_matches;
563 use futures_executor::LocalPool;
564 use futures_util::FutureExt as _;
565 use futures_util::task::LocalSpawnExt as _;
566 use std::cell::RefCell;
567 use std::str::from_utf8;
568 use yash_syntax::source::Location;
569
570 pub fn in_virtual_system<F, Fut, T>(f: F) -> T
572 where
573 F: FnOnce(Env, Rc<RefCell<SystemState>>) -> Fut,
574 Fut: Future<Output = T> + 'static,
575 T: 'static,
576 {
577 let system = VirtualSystem::new();
578 let state = Rc::clone(&system.state);
579 let mut executor = futures_executor::LocalPool::new();
580 state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
581
582 let env = Env::with_system(Box::new(system));
583 let shared_system = env.system.clone();
584 let task = f(env, Rc::clone(&state));
585 let mut task = executor.spawner().spawn_local_with_handle(task).unwrap();
586 loop {
587 if let Some(result) = (&mut task).now_or_never() {
588 return result;
589 }
590 executor.run_until_stalled();
591 shared_system.select(false).unwrap();
592 SystemState::select_all(&state);
593 }
594 }
595
596 pub fn assert_stderr<F, T>(state: &RefCell<SystemState>, f: F) -> T
598 where
599 F: FnOnce(&str) -> T,
600 {
601 let stderr = state.borrow().file_system.get("/dev/stderr").unwrap();
602 let stderr = stderr.borrow();
603 assert_matches!(&stderr.body, FileBody::Regular { content, .. } => {
604 f(from_utf8(content).unwrap())
605 })
606 }
607
608 #[test]
609 fn wait_for_signal_remembers_signal_in_trap_set() {
610 in_virtual_system(|mut env, state| async move {
611 env.traps
612 .set_action(
613 &mut env.system,
614 SIGCHLD,
615 Action::Command("".into()),
616 Location::dummy(""),
617 false,
618 )
619 .unwrap();
620 {
621 let mut state = state.borrow_mut();
622 let process = state.processes.get_mut(&env.main_pid).unwrap();
623 assert!(process.blocked_signals().contains(&SIGCHLD));
624 let _ = process.raise_signal(SIGCHLD);
625 }
626 env.wait_for_signal(SIGCHLD).await;
627
628 let trap_state = env.traps.get_state(SIGCHLD).0.unwrap();
629 assert!(trap_state.pending);
630 })
631 }
632
633 fn poll_signals_env() -> (Env, VirtualSystem) {
634 let system = VirtualSystem::new();
635 let shared_system = SharedSystem::new(Box::new(system.clone()));
636 let mut env = Env::with_system(Box::new(shared_system));
637 env.traps
638 .set_action(
639 &mut env.system,
640 SIGCHLD,
641 Action::Command("".into()),
642 Location::dummy(""),
643 false,
644 )
645 .unwrap();
646 (env, system)
647 }
648
649 #[test]
650 fn poll_signals_none() {
651 let mut env = poll_signals_env().0;
652 let result = env.poll_signals();
653 assert_eq!(result, None);
654 }
655
656 #[test]
657 fn poll_signals_some() {
658 let (mut env, system) = poll_signals_env();
659 {
660 let mut state = system.state.borrow_mut();
661 let process = state.processes.get_mut(&system.process_id).unwrap();
662 assert!(process.blocked_signals().contains(&SIGCHLD));
663 let _ = process.raise_signal(SIGCHLD);
664 }
665
666 let result = env.poll_signals().unwrap();
667 assert_eq!(*result, [SIGCHLD]);
668 }
669
670 #[test]
671 fn get_tty_opens_tty() {
672 let system = VirtualSystem::new();
673 let tty = Rc::new(RefCell::new(Inode::new([])));
674 system
675 .state
676 .borrow_mut()
677 .file_system
678 .save("/dev/tty", Rc::clone(&tty))
679 .unwrap();
680 let mut env = Env::with_system(Box::new(system.clone()));
681
682 let fd = env.get_tty().unwrap();
683 assert!(
684 fd >= MIN_INTERNAL_FD,
685 "get_tty returned {fd}, which should be >= {MIN_INTERNAL_FD}"
686 );
687 system
688 .with_open_file_description(fd, |ofd| {
689 assert!(Rc::ptr_eq(ofd.inode(), &tty));
690 Ok(())
691 })
692 .unwrap();
693
694 system.state.borrow_mut().file_system = Default::default();
695
696 let fd = env.get_tty().unwrap();
698 system
699 .with_open_file_description(fd, |ofd| {
700 assert!(Rc::ptr_eq(ofd.inode(), &tty));
701 Ok(())
702 })
703 .unwrap();
704 }
705
706 #[test]
707 fn start_and_wait_for_subshell() {
708 in_virtual_system(|mut env, _state| async move {
709 let subshell = Subshell::new(|env, _job_control| {
710 Box::pin(async { env.exit_status = ExitStatus(42) })
711 });
712 let (pid, _) = subshell.start(&mut env).await.unwrap();
713 let result = env.wait_for_subshell(pid).await;
714 assert_eq!(result, Ok((pid, ProcessState::exited(42))));
715 });
716 }
717
718 #[test]
719 fn start_and_wait_for_subshell_with_job_list() {
720 in_virtual_system(|mut env, _state| async move {
721 let subshell = Subshell::new(|env, _job_control| {
722 Box::pin(async { env.exit_status = ExitStatus(42) })
723 });
724 let (pid, _) = subshell.start(&mut env).await.unwrap();
725 let mut job = Job::new(pid);
726 job.name = "my job".to_string();
727 let job_index = env.jobs.add(job.clone());
728 let result = env.wait_for_subshell(pid).await;
729 assert_eq!(result, Ok((pid, ProcessState::exited(42))));
730 job.state = ProcessState::exited(42);
731 assert_eq!(env.jobs[job_index], job);
732 });
733 }
734
735 #[test]
736 fn wait_for_subshell_no_subshell() {
737 let system = VirtualSystem::new();
738 let mut executor = LocalPool::new();
739 system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
740 let mut env = Env::with_system(Box::new(system));
741 executor.run_until(async move {
742 let result = env.wait_for_subshell(Pid::ALL).await;
743 assert_eq!(result, Err(Errno::ECHILD));
744 });
745 }
746
747 #[test]
748 fn update_all_subshell_statuses_without_subshells() {
749 let mut env = Env::new_virtual();
750 env.update_all_subshell_statuses();
751 }
752
753 #[test]
754 fn update_all_subshell_statuses_with_subshells() {
755 let system = VirtualSystem::new();
756 let mut executor = futures_executor::LocalPool::new();
757 system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
758
759 let mut env = Env::with_system(Box::new(system));
760
761 let [job_1, job_2, job_3] = executor.run_until(async {
762 let subshell_1 = Subshell::new(|env, _job_control| {
764 Box::pin(async { env.exit_status = ExitStatus(12) })
765 });
766 let (pid_1, _) = subshell_1.start(&mut env).await.unwrap();
767
768 let subshell_2 = Subshell::new(|env, _job_control| {
770 Box::pin(async { env.exit_status = ExitStatus(35) })
771 });
772 let (pid_2, _) = subshell_2.start(&mut env).await.unwrap();
773
774 let subshell_3 =
776 Subshell::new(|_env, _job_control| Box::pin(futures_util::future::pending()));
777 let (pid_3, _) = subshell_3.start(&mut env).await.unwrap();
778
779 let subshell_4 = Subshell::new(|env, _job_control| {
781 Box::pin(async { env.exit_status = ExitStatus(100) })
782 });
783 let (_pid_4, _) = subshell_4.start(&mut env).await.unwrap();
784
785 let job_1 = env.jobs.add(Job::new(pid_1));
787 let job_2 = env.jobs.add(Job::new(pid_2));
788 let job_3 = env.jobs.add(Job::new(pid_3));
789 [job_1, job_2, job_3]
790 });
791
792 executor.run_until_stalled();
794
795 assert_eq!(env.jobs[job_1].state, ProcessState::Running);
797 assert_eq!(env.jobs[job_2].state, ProcessState::Running);
798 assert_eq!(env.jobs[job_3].state, ProcessState::Running);
799
800 env.update_all_subshell_statuses();
801
802 assert_eq!(env.jobs[job_3].state, ProcessState::Running);
806 }
807
808 #[test]
809 fn get_or_create_variable_with_all_export_off() {
810 let mut env = Env::new_virtual();
811 let mut a = env.get_or_create_variable("a", Scope::Global);
812 assert!(!a.is_exported);
813 a.export(true);
814 let a = env.get_or_create_variable("a", Scope::Global);
815 assert!(a.is_exported);
816 }
817
818 #[test]
819 fn get_or_create_variable_with_all_export_on() {
820 let mut env = Env::new_virtual();
821 env.options.set(AllExport, On);
822 let mut a = env.get_or_create_variable("a", Scope::Global);
823 assert!(a.is_exported);
824 a.export(false);
825 let a = env.get_or_create_variable("a", Scope::Global);
826 assert!(a.is_exported);
827 }
828
829 #[test]
830 fn errexit_on() {
831 let mut env = Env::new_virtual();
832 env.exit_status = ExitStatus::FAILURE;
833 env.options.set(ErrExit, On);
834 assert_eq!(env.apply_errexit(), Break(Divert::Exit(None)));
835 }
836
837 #[test]
838 fn errexit_with_zero_exit_status() {
839 let mut env = Env::new_virtual();
840 env.options.set(ErrExit, On);
841 assert_eq!(env.apply_errexit(), Continue(()));
842 }
843
844 #[test]
845 fn errexit_in_condition() {
846 let mut env = Env::new_virtual();
847 env.exit_status = ExitStatus::FAILURE;
848 env.options.set(ErrExit, On);
849 let env = env.push_frame(Frame::Condition);
850 assert_eq!(env.apply_errexit(), Continue(()));
851 }
852
853 #[test]
854 fn errexit_off() {
855 let mut env = Env::new_virtual();
856 env.exit_status = ExitStatus::FAILURE;
857 assert_eq!(env.apply_errexit(), Continue(()));
858 }
859
860 #[test]
861 fn apply_result_with_continue() {
862 let mut env = Env::new_virtual();
863 env.apply_result(Continue(()));
864 assert_eq!(env.exit_status, ExitStatus::default());
865 }
866
867 #[test]
868 fn apply_result_with_divert_without_exit_status() {
869 let mut env = Env::new_virtual();
870 env.apply_result(Break(Divert::Exit(None)));
871 assert_eq!(env.exit_status, ExitStatus::default());
872 }
873
874 #[test]
875 fn apply_result_with_divert_with_exit_status() {
876 let mut env = Env::new_virtual();
877 env.apply_result(Break(Divert::Exit(Some(ExitStatus(67)))));
878 assert_eq!(env.exit_status, ExitStatus(67));
879 }
880}