1use self::builtin::getopts::GetoptsState;
35use self::builtin::Builtin;
36use self::function::FunctionSet;
37use self::io::Fd;
38use self::job::JobList;
39use self::job::Pid;
40use self::job::ProcessState;
41use self::option::On;
42use self::option::OptionSet;
43use self::option::{AllExport, ErrExit, Interactive, Monitor};
44use self::semantics::Divert;
45use self::semantics::ExitStatus;
46use self::stack::Frame;
47use self::stack::Stack;
48pub use self::system::r#virtual::VirtualSystem;
49#[cfg(unix)]
50pub use self::system::real::RealSystem;
51use self::system::Errno;
52pub use self::system::SharedSystem;
53pub use self::system::System;
54use self::system::SystemEx;
55use self::trap::TrapSet;
56use self::variable::Scope;
57use self::variable::VariableRefMut;
58use self::variable::VariableSet;
59use self::variable::PPID;
60use futures_util::task::noop_waker_ref;
61use std::collections::HashMap;
62use std::fmt::Debug;
63use std::future::Future;
64use std::ops::ControlFlow::{self, Break, Continue};
65use std::rc::Rc;
66use std::task::Context;
67use std::task::Poll;
68pub use unix_path as path;
69pub use unix_str as str;
70use yash_syntax::alias::AliasSet;
71
72#[derive(Clone, Debug)]
86#[non_exhaustive]
87pub struct Env {
88 pub aliases: AliasSet,
90
91 pub arg0: String,
95
96 pub builtins: HashMap<&'static str, Builtin>,
98
99 pub exit_status: ExitStatus,
101
102 pub functions: FunctionSet,
104
105 pub getopts_state: Option<GetoptsState>,
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 system: SharedSystem,
139}
140
141impl Env {
142 #[must_use]
148 pub fn with_system(system: Box<dyn System>) -> Env {
149 Env {
150 aliases: Default::default(),
151 arg0: Default::default(),
152 builtins: Default::default(),
153 exit_status: Default::default(),
154 functions: Default::default(),
155 getopts_state: Default::default(),
156 jobs: Default::default(),
157 main_pgid: system.getpgrp(),
158 main_pid: system.getpid(),
159 options: Default::default(),
160 stack: Default::default(),
161 traps: Default::default(),
162 tty: Default::default(),
163 variables: Default::default(),
164 system: SharedSystem::new(system),
165 }
166 }
167
168 #[must_use]
170 pub fn new_virtual() -> Env {
171 Env::with_system(Box::<VirtualSystem>::default())
172 }
173
174 #[must_use]
180 pub fn clone_with_system(&self, system: Box<dyn System>) -> Env {
181 Env {
182 aliases: self.aliases.clone(),
183 arg0: self.arg0.clone(),
184 builtins: self.builtins.clone(),
185 exit_status: self.exit_status,
186 functions: self.functions.clone(),
187 getopts_state: self.getopts_state.clone(),
188 jobs: self.jobs.clone(),
189 main_pgid: self.main_pgid,
190 main_pid: self.main_pid,
191 options: self.options,
192 stack: self.stack.clone(),
193 traps: self.traps.clone(),
194 tty: self.tty,
195 variables: self.variables.clone(),
196 system: SharedSystem::new(system),
197 }
198 }
199
200 pub fn init_variables(&mut self) {
216 self.variables.init();
217
218 self.variables
219 .get_or_new(PPID, Scope::Global)
220 .assign(self.system.getppid().to_string(), None)
221 .ok();
222
223 self.prepare_pwd().ok();
224 }
225
226 pub async fn wait_for_signals(&mut self) -> Rc<[signal::Number]> {
235 let result = self.system.wait_for_signals().await;
236 for signal in result.iter().copied() {
237 self.traps.catch_signal(signal);
238 }
239 result
240 }
241
242 pub async fn wait_for_signal(&mut self, signal: signal::Number) {
247 while !self.wait_for_signals().await.contains(&signal) {}
248 }
249
250 pub fn poll_signals(&mut self) -> Option<Rc<[signal::Number]>> {
257 let system = self.system.clone();
258
259 let mut future = std::pin::pin!(self.wait_for_signals());
260
261 let mut context = Context::from_waker(noop_waker_ref());
262 if let Poll::Ready(signals) = future.as_mut().poll(&mut context) {
263 return Some(signals);
264 }
265
266 system.select(true).ok();
267 if let Poll::Ready(signals) = future.poll(&mut context) {
268 return Some(signals);
269 }
270 None
271 }
272
273 #[must_use]
283 fn should_print_error_in_color(&self) -> bool {
284 self.system.isatty(Fd::STDERR)
287 }
288
289 pub fn get_tty(&mut self) -> Result<Fd, Errno> {
294 if let Some(fd) = self.tty {
295 return Ok(fd);
296 }
297
298 let first_fd = self.system.open(
299 c"/dev/tty",
300 crate::system::OfdAccess::ReadWrite,
301 crate::system::OpenFlag::CloseOnExec.into(),
302 crate::system::Mode::empty(),
303 )?;
304 let final_fd = self.system.move_fd_internal(first_fd);
305 self.tty = final_fd.ok();
306 final_fd
307 }
308
309 #[must_use]
316 pub fn is_interactive(&self) -> bool {
317 self.options.get(Interactive) == On && !self.stack.contains(&Frame::Subshell)
318 }
319
320 #[must_use]
327 pub fn controls_jobs(&self) -> bool {
328 self.options.get(Monitor) == On && !self.stack.contains(&Frame::Subshell)
329 }
330
331 pub async fn wait_for_subshell(&mut self, target: Pid) -> Result<(Pid, ProcessState), Errno> {
352 self.traps
355 .enable_internal_disposition_for_sigchld(&mut self.system)?;
356
357 let sigchld = self
358 .system
359 .signal_number_from_name(signal::Name::Chld)
360 .unwrap();
361 loop {
362 if let Some((pid, state)) = self.system.wait(target)? {
363 self.jobs.update_status(pid, state);
364 return Ok((pid, state));
365 }
366 self.wait_for_signal(sigchld).await;
367 }
368 }
369
370 pub async fn wait_for_subshell_to_finish(
378 &mut self,
379 target: Pid,
380 ) -> Result<(Pid, ExitStatus), Errno> {
381 loop {
382 let (pid, state) = self.wait_for_subshell(target).await?;
383 if let Ok(exit_status) = state.try_into() {
384 if !state.is_alive() {
385 return Ok((pid, exit_status));
386 }
387 }
388 }
389 }
390
391 pub fn update_all_subshell_statuses(&mut self) {
400 while let Ok(Some((pid, state))) = self.system.wait(Pid::ALL) {
401 self.jobs.update_status(pid, state);
402 }
403 }
404
405 pub fn get_or_create_variable<S>(&mut self, name: S, scope: Scope) -> VariableRefMut
415 where
416 S: Into<String>,
417 {
418 let mut variable = self.variables.get_or_new(name, scope);
419 if self.options.get(AllExport) == On {
420 variable.export(true);
421 }
422 variable
423 }
424
425 pub fn errexit_is_applicable(&self) -> bool {
433 self.options.get(ErrExit) == On && !self.stack.contains(&Frame::Condition)
434 }
435
436 pub fn apply_errexit(&self) -> ControlFlow<Divert> {
443 if !self.exit_status.is_successful() && self.errexit_is_applicable() {
444 Break(Divert::Exit(None))
445 } else {
446 Continue(())
447 }
448 }
449
450 pub fn apply_result(&mut self, result: crate::semantics::Result) {
455 match result {
456 Continue(_) => {}
457 Break(divert) => {
458 if let Some(exit_status) = divert.exit_status() {
459 self.exit_status = exit_status;
460 }
461 }
462 }
463 }
464}
465
466mod alias;
467pub mod builtin;
468pub mod function;
469pub mod input;
470pub mod io;
471pub mod job;
472pub mod option;
473pub mod pwd;
474pub mod semantics;
475pub mod signal;
476pub mod stack;
477pub mod subshell;
478pub mod system;
479pub mod trap;
480pub mod variable;
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485 use crate::io::MIN_INTERNAL_FD;
486 use crate::job::Job;
487 use crate::subshell::Subshell;
488 use crate::system::r#virtual::FileBody;
489 use crate::system::r#virtual::Inode;
490 use crate::system::r#virtual::SystemState;
491 use crate::system::r#virtual::SIGCHLD;
492 use crate::trap::Action;
493 use assert_matches::assert_matches;
494 use futures_executor::LocalPool;
495 use futures_util::task::LocalSpawnExt as _;
496 use futures_util::FutureExt as _;
497 use std::cell::RefCell;
498 use std::str::from_utf8;
499 use yash_syntax::source::Location;
500
501 pub fn in_virtual_system<F, Fut, T>(f: F) -> T
503 where
504 F: FnOnce(Env, Rc<RefCell<SystemState>>) -> Fut,
505 Fut: Future<Output = T> + 'static,
506 T: 'static,
507 {
508 let system = VirtualSystem::new();
509 let state = Rc::clone(&system.state);
510 let mut executor = futures_executor::LocalPool::new();
511 state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
512
513 let env = Env::with_system(Box::new(system));
514 let shared_system = env.system.clone();
515 let task = f(env, Rc::clone(&state));
516 let mut task = executor.spawner().spawn_local_with_handle(task).unwrap();
517 loop {
518 if let Some(result) = (&mut task).now_or_never() {
519 return result;
520 }
521 executor.run_until_stalled();
522 shared_system.select(false).unwrap();
523 SystemState::select_all(&state);
524 }
525 }
526
527 pub fn assert_stderr<F, T>(state: &RefCell<SystemState>, f: F) -> T
529 where
530 F: FnOnce(&str) -> T,
531 {
532 let stderr = state.borrow().file_system.get("/dev/stderr").unwrap();
533 let stderr = stderr.borrow();
534 assert_matches!(&stderr.body, FileBody::Regular { content, .. } => {
535 f(from_utf8(content).unwrap())
536 })
537 }
538
539 #[test]
540 fn wait_for_signal_remembers_signal_in_trap_set() {
541 in_virtual_system(|mut env, state| async move {
542 env.traps
543 .set_action(
544 &mut env.system,
545 SIGCHLD,
546 Action::Command("".into()),
547 Location::dummy(""),
548 false,
549 )
550 .unwrap();
551 {
552 let mut state = state.borrow_mut();
553 let process = state.processes.get_mut(&env.main_pid).unwrap();
554 assert!(process.blocked_signals().contains(&SIGCHLD));
555 let _ = process.raise_signal(SIGCHLD);
556 }
557 env.wait_for_signal(SIGCHLD).await;
558
559 let trap_state = env.traps.get_state(SIGCHLD).0.unwrap();
560 assert!(trap_state.pending);
561 })
562 }
563
564 fn poll_signals_env() -> (Env, VirtualSystem) {
565 let system = VirtualSystem::new();
566 let shared_system = SharedSystem::new(Box::new(system.clone()));
567 let mut env = Env::with_system(Box::new(shared_system));
568 env.traps
569 .set_action(
570 &mut env.system,
571 SIGCHLD,
572 Action::Command("".into()),
573 Location::dummy(""),
574 false,
575 )
576 .unwrap();
577 (env, system)
578 }
579
580 #[test]
581 fn poll_signals_none() {
582 let mut env = poll_signals_env().0;
583 let result = env.poll_signals();
584 assert_eq!(result, None);
585 }
586
587 #[test]
588 fn poll_signals_some() {
589 let (mut env, system) = poll_signals_env();
590 {
591 let mut state = system.state.borrow_mut();
592 let process = state.processes.get_mut(&system.process_id).unwrap();
593 assert!(process.blocked_signals().contains(&SIGCHLD));
594 let _ = process.raise_signal(SIGCHLD);
595 }
596
597 let result = env.poll_signals().unwrap();
598 assert_eq!(*result, [SIGCHLD]);
599 }
600
601 #[test]
602 fn get_tty_opens_tty() {
603 let system = VirtualSystem::new();
604 let tty = Rc::new(RefCell::new(Inode::new([])));
605 system
606 .state
607 .borrow_mut()
608 .file_system
609 .save("/dev/tty", Rc::clone(&tty))
610 .unwrap();
611 let mut env = Env::with_system(Box::new(system.clone()));
612
613 let fd = env.get_tty().unwrap();
614 assert!(
615 fd >= MIN_INTERNAL_FD,
616 "get_tty returned {fd}, which should be >= {MIN_INTERNAL_FD}"
617 );
618 system
619 .with_open_file_description(fd, |ofd| {
620 assert!(Rc::ptr_eq(ofd.inode(), &tty));
621 Ok(())
622 })
623 .unwrap();
624
625 system.state.borrow_mut().file_system = Default::default();
626
627 let fd = env.get_tty().unwrap();
629 system
630 .with_open_file_description(fd, |ofd| {
631 assert!(Rc::ptr_eq(ofd.inode(), &tty));
632 Ok(())
633 })
634 .unwrap();
635 }
636
637 #[test]
638 fn start_and_wait_for_subshell() {
639 in_virtual_system(|mut env, _state| async move {
640 let subshell = Subshell::new(|env, _job_control| {
641 Box::pin(async { env.exit_status = ExitStatus(42) })
642 });
643 let (pid, _) = subshell.start(&mut env).await.unwrap();
644 let result = env.wait_for_subshell(pid).await;
645 assert_eq!(result, Ok((pid, ProcessState::exited(42))));
646 });
647 }
648
649 #[test]
650 fn start_and_wait_for_subshell_with_job_list() {
651 in_virtual_system(|mut env, _state| async move {
652 let subshell = Subshell::new(|env, _job_control| {
653 Box::pin(async { env.exit_status = ExitStatus(42) })
654 });
655 let (pid, _) = subshell.start(&mut env).await.unwrap();
656 let mut job = Job::new(pid);
657 job.name = "my job".to_string();
658 let job_index = env.jobs.add(job.clone());
659 let result = env.wait_for_subshell(pid).await;
660 assert_eq!(result, Ok((pid, ProcessState::exited(42))));
661 job.state = ProcessState::exited(42);
662 assert_eq!(env.jobs[job_index], job);
663 });
664 }
665
666 #[test]
667 fn wait_for_subshell_no_subshell() {
668 let system = VirtualSystem::new();
669 let mut executor = LocalPool::new();
670 system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
671 let mut env = Env::with_system(Box::new(system));
672 executor.run_until(async move {
673 let result = env.wait_for_subshell(Pid::ALL).await;
674 assert_eq!(result, Err(Errno::ECHILD));
675 });
676 }
677
678 #[test]
679 fn update_all_subshell_statuses_without_subshells() {
680 let mut env = Env::new_virtual();
681 env.update_all_subshell_statuses();
682 }
683
684 #[test]
685 fn update_all_subshell_statuses_with_subshells() {
686 let system = VirtualSystem::new();
687 let mut executor = futures_executor::LocalPool::new();
688 system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
689
690 let mut env = Env::with_system(Box::new(system));
691
692 let [job_1, job_2, job_3] = executor.run_until(async {
693 let subshell_1 = Subshell::new(|env, _job_control| {
695 Box::pin(async { env.exit_status = ExitStatus(12) })
696 });
697 let (pid_1, _) = subshell_1.start(&mut env).await.unwrap();
698
699 let subshell_2 = Subshell::new(|env, _job_control| {
701 Box::pin(async { env.exit_status = ExitStatus(35) })
702 });
703 let (pid_2, _) = subshell_2.start(&mut env).await.unwrap();
704
705 let subshell_3 =
707 Subshell::new(|_env, _job_control| Box::pin(futures_util::future::pending()));
708 let (pid_3, _) = subshell_3.start(&mut env).await.unwrap();
709
710 let subshell_4 = Subshell::new(|env, _job_control| {
712 Box::pin(async { env.exit_status = ExitStatus(100) })
713 });
714 let (_pid_4, _) = subshell_4.start(&mut env).await.unwrap();
715
716 let job_1 = env.jobs.add(Job::new(pid_1));
718 let job_2 = env.jobs.add(Job::new(pid_2));
719 let job_3 = env.jobs.add(Job::new(pid_3));
720 [job_1, job_2, job_3]
721 });
722
723 executor.run_until_stalled();
725
726 assert_eq!(env.jobs[job_1].state, ProcessState::Running);
728 assert_eq!(env.jobs[job_2].state, ProcessState::Running);
729 assert_eq!(env.jobs[job_3].state, ProcessState::Running);
730
731 env.update_all_subshell_statuses();
732
733 assert_eq!(env.jobs[job_3].state, ProcessState::Running);
737 }
738
739 #[test]
740 fn get_or_create_variable_with_all_export_off() {
741 let mut env = Env::new_virtual();
742 let mut a = env.get_or_create_variable("a", Scope::Global);
743 assert!(!a.is_exported);
744 a.export(true);
745 let a = env.get_or_create_variable("a", Scope::Global);
746 assert!(a.is_exported);
747 }
748
749 #[test]
750 fn get_or_create_variable_with_all_export_on() {
751 let mut env = Env::new_virtual();
752 env.options.set(AllExport, On);
753 let mut a = env.get_or_create_variable("a", Scope::Global);
754 assert!(a.is_exported);
755 a.export(false);
756 let a = env.get_or_create_variable("a", Scope::Global);
757 assert!(a.is_exported);
758 }
759
760 #[test]
761 fn errexit_on() {
762 let mut env = Env::new_virtual();
763 env.exit_status = ExitStatus::FAILURE;
764 env.options.set(ErrExit, On);
765 assert_eq!(env.apply_errexit(), Break(Divert::Exit(None)));
766 }
767
768 #[test]
769 fn errexit_with_zero_exit_status() {
770 let mut env = Env::new_virtual();
771 env.options.set(ErrExit, On);
772 assert_eq!(env.apply_errexit(), Continue(()));
773 }
774
775 #[test]
776 fn errexit_in_condition() {
777 let mut env = Env::new_virtual();
778 env.exit_status = ExitStatus::FAILURE;
779 env.options.set(ErrExit, On);
780 let env = env.push_frame(Frame::Condition);
781 assert_eq!(env.apply_errexit(), Continue(()));
782 }
783
784 #[test]
785 fn errexit_off() {
786 let mut env = Env::new_virtual();
787 env.exit_status = ExitStatus::FAILURE;
788 assert_eq!(env.apply_errexit(), Continue(()));
789 }
790
791 #[test]
792 fn apply_result_with_continue() {
793 let mut env = Env::new_virtual();
794 env.apply_result(Continue(()));
795 assert_eq!(env.exit_status, ExitStatus::default());
796 }
797
798 #[test]
799 fn apply_result_with_divert_without_exit_status() {
800 let mut env = Env::new_virtual();
801 env.apply_result(Break(Divert::Exit(None)));
802 assert_eq!(env.exit_status, ExitStatus::default());
803 }
804
805 #[test]
806 fn apply_result_with_divert_with_exit_status() {
807 let mut env = Env::new_virtual();
808 env.apply_result(Break(Divert::Exit(Some(ExitStatus(67)))));
809 assert_eq!(env.exit_status, ExitStatus(67));
810 }
811}