1use crate::common::output;
31use crate::common::report::report_error;
32use yash_env::Env;
33use yash_env::builtin::Result;
34use yash_env::option::State;
35#[cfg(doc)]
36use yash_env::option::canonicalize;
37#[cfg(doc)]
38use yash_env::option::parse_long;
39#[cfg(doc)]
40use yash_env::option::parse_short;
41use yash_env::option::{Interactive, Monitor};
42use yash_env::parser::IsName;
43use yash_env::semantics::ExitStatus;
44use yash_env::semantics::Field;
45use yash_env::stack::Frame::Subshell;
46use yash_env::system::{
47 Close, Dup, Fcntl, GetPid, Isatty, Open, Sigaction, Sigmask, Signals, TcSetPgrp, Write,
48};
49use yash_env::variable::Scope::Global;
50
51#[derive(Clone, Debug, Eq, PartialEq)]
54pub enum Command {
55 PrintVariables,
57
58 PrintOptionsHumanReadable,
60
61 PrintOptionsMachineReadable,
63
64 Modify {
66 options: Vec<(yash_env::option::Option, State)>,
68 positional_params: std::option::Option<Vec<Field>>,
70 },
71}
72
73pub mod syntax;
74async fn update_internal_dispositions_for_stoppers<S>(env: &mut Env<S>)
79where
80 S: Signals + Sigmask + Sigaction,
81{
82 if env.options.get(Interactive) == State::On && env.options.get(Monitor) == State::On {
83 env.traps
84 .enable_internal_dispositions_for_stoppers(&env.system)
85 .await
86 } else {
87 env.traps
88 .disable_internal_dispositions_for_stoppers(&env.system)
89 .await
90 }
91 .ok();
92}
93
94async fn ensure_foreground<S>(env: &mut Env<S>)
97where
98 S: Open + Dup + Close + GetPid + Signals + Sigmask + Sigaction + TcSetPgrp,
99{
100 if env.options.get(Monitor) == State::On {
101 env.ensure_foreground().await.ok();
102 }
103}
104
105async fn modify<S>(
107 env: &mut Env<S>,
108 options: Vec<(yash_env::option::Option, State)>,
109 positional_params: Option<Vec<Field>>,
110) where
111 S: Open + Dup + Close + GetPid + Signals + Sigmask + Sigaction + TcSetPgrp,
112{
113 let mut monitor_changed = false;
115 for (option, state) in options {
116 env.options.set(option, state);
117 monitor_changed |= option == Monitor;
118 }
119
120 if monitor_changed && !env.stack.contains(&Subshell) {
122 update_internal_dispositions_for_stoppers(env).await;
125 ensure_foreground(env).await;
126 }
127
128 if let Some(fields) = positional_params {
130 let params = env.variables.positional_params_mut();
131 params.values = fields.into_iter().map(|f| f.value).collect();
132 params.last_modified_location = env.stack.current_builtin().map(|b| b.name.origin.clone());
133 }
134}
135
136pub async fn main<S>(env: &mut Env<S>, args: Vec<Field>) -> Result
142where
143 S: Open
144 + Dup
145 + Close
146 + Fcntl
147 + GetPid
148 + Isatty
149 + Signals
150 + Sigmask
151 + Sigaction
152 + TcSetPgrp
153 + Write
154 + 'static,
155{
156 use std::fmt::Write as _;
157
158 match syntax::parse(args) {
159 Ok(Command::PrintVariables) => {
160 let IsName(is_name) = env.any.get().expect("`IsName` should be in `env.any`");
161 let mut vars: Vec<_> = env
162 .variables
163 .iter(Global)
164 .filter(|(name, _)| is_name(env, name))
165 .collect();
166 vars.sort_unstable_by_key(|&(name, _)| name);
168
169 let mut print = String::new();
170 for (name, var) in vars {
171 if let Some(value) = &var.value {
172 writeln!(print, "{}={}", name, value.quote()).unwrap();
173 }
174 }
175 output(env, &print).await
176 }
177
178 Ok(Command::PrintOptionsHumanReadable) => {
179 let mut print = String::new();
180 for option in yash_env::option::Option::iter() {
181 let state = env.options.get(option);
182 writeln!(print, "{option:16} {state}").unwrap();
183 }
184 output(env, &print).await
185 }
186
187 Ok(Command::PrintOptionsMachineReadable) => {
188 let mut print = String::new();
189 for option in yash_env::option::Option::iter() {
190 let skip = if option.is_modifiable() { "" } else { "#" };
191 let flag = match env.options.get(option) {
192 State::On => '-',
193 State::Off => '+',
194 };
195 writeln!(print, "{skip}set {flag}o {option}").unwrap();
196 }
197 output(env, &print).await
198 }
199
200 Ok(Command::Modify {
201 options,
202 positional_params,
203 }) => {
204 modify(env, options, positional_params).await;
205 Result::new(ExitStatus::SUCCESS)
206 }
207
208 Err(error) => report_error(env, &error).await,
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215 use futures_util::FutureExt;
216 use std::ops::ControlFlow::Continue;
217 use std::rc::Rc;
218 use yash_env::VirtualSystem;
219 use yash_env::builtin::Builtin;
220 use yash_env::builtin::Type::Special;
221 use yash_env::option::Option::*;
222 use yash_env::option::OptionSet;
223 use yash_env::option::State::*;
224 use yash_env::system::Disposition;
225 use yash_env::system::r#virtual::SIGTSTP;
226 use yash_env::test_helper::assert_stderr;
227 use yash_env::test_helper::assert_stdout;
228 use yash_env::variable::Scope;
229 use yash_env::variable::Value;
230 use yash_semantics::command::Command as _;
231 use yash_syntax::syntax::List;
232
233 #[test]
234 fn printing_variables() {
235 let system = VirtualSystem::new();
236 let state = Rc::clone(&system.state);
237 let mut env = Env::with_system(system);
238 env.any
239 .insert(Box::new(IsName::<VirtualSystem>(|_env, name| {
240 yash_syntax::parser::lex::is_name(name)
241 })));
242 let mut var = env.variables.get_or_new("foo", Scope::Global);
243 var.assign("value", None).unwrap();
244 var.export(true);
245 let mut var = env.variables.get_or_new("bar", Scope::Global);
246 var.assign("Hello, world!", None).unwrap();
247 let mut var = env.variables.get_or_new("baz", Scope::Global);
248 var.assign(Value::array(["one", ""]), None).unwrap();
249 let mut var = env.variables.get_or_new("bad=name", Scope::Global);
250 var.assign("Oops!", None).unwrap();
251
252 let args = vec![];
253 let result = main(&mut env, args).now_or_never().unwrap();
254 assert_eq!(result, Result::new(ExitStatus::SUCCESS));
255 assert_stdout(&state, |stdout| {
256 assert_eq!(stdout, "bar='Hello, world!'\nbaz=(one '')\nfoo=value\n")
257 });
258 }
259
260 #[test]
261 fn printing_options_human_readable() {
262 let system = VirtualSystem::new();
263 let state = Rc::clone(&system.state);
264 let mut env = Env::with_system(system);
265 env.options.set(AllExport, On);
266 env.options.set(Unset, Off);
267
268 let args = Field::dummies(["-o"]);
269 let result = main(&mut env, args).now_or_never().unwrap();
270 assert_eq!(result, Result::new(ExitStatus::SUCCESS));
271 assert_stdout(&state, |stdout| {
272 assert_eq!(
273 stdout,
274 "allexport on
275clobber on
276cmdline off
277errexit off
278exec on
279glob on
280hashondefinition off
281ignoreeof off
282interactive off
283log on
284login off
285monitor off
286notify off
287pipefail off
288posixlycorrect off
289stdin off
290unset off
291verbose off
292vi off
293xtrace off
294"
295 )
296 });
297 }
298
299 #[test]
300 fn printing_options_machine_readable() {
301 let system = VirtualSystem::new();
302 let state = Rc::clone(&system.state);
303 let mut env = Env::with_system(system);
304 env.options.set(Clobber, Off);
305 env.options.set(Verbose, On);
306 let options = env.options;
307
308 let args = Field::dummies(["+o"]);
309 let result = main(&mut env, args).now_or_never().unwrap();
310 assert_eq!(result, Result::new(ExitStatus::SUCCESS));
311
312 let commands: List = assert_stdout(&state, |stdout| stdout.parse().unwrap());
314
315 env.builtins.insert(
316 "set",
317 Builtin::new(Special, |env, args| Box::pin(main(env, args))),
318 );
319 env.options = Default::default();
320
321 let result = commands.execute(&mut env).now_or_never().unwrap();
323 assert_eq!(result, Continue(()));
324 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
325 assert_eq!(env.options, options);
326
327 assert_stderr(&state, |stderr| assert_eq!(stderr, ""));
329 }
330
331 #[test]
332 fn setting_some_options() {
333 let mut env = Env::new_virtual();
334 let args = Field::dummies(["-a", "-n"]);
335 let result = main(&mut env, args).now_or_never().unwrap();
336 assert_eq!(result, Result::new(ExitStatus::SUCCESS));
337
338 let mut options = OptionSet::default();
339 options.set(AllExport, On);
340 options.set(Exec, Off);
341 assert_eq!(env.options, options);
342 }
343
344 #[test]
345 fn setting_some_positional_parameters() {
346 let name = Field::dummy("set");
347 let location = name.origin.clone();
348 let is_special = true;
349 let mut env = Env::new_virtual();
350 let mut env = env.push_frame(yash_env::stack::Builtin { name, is_special }.into());
351 let args = Field::dummies(["a", "b", "z"]);
352
353 let result = main(&mut env, args).now_or_never().unwrap();
354 assert_eq!(result, Result::new(ExitStatus::SUCCESS));
355
356 let params = env.variables.positional_params();
357 assert_eq!(
358 params.values,
359 ["a".to_string(), "b".to_string(), "z".to_string()],
360 );
361 assert_eq!(params.last_modified_location, Some(location));
362 }
363
364 #[test]
365 fn enabling_monitor_option() {
366 let system = VirtualSystem::new();
367 let state = Rc::clone(&system.state);
368 let mut env = Env::with_system(system);
369 env.options.set(Interactive, On);
370 let args = Field::dummies(["-m"]);
371
372 let result = main(&mut env, args).now_or_never().unwrap();
373 assert_eq!(result, Result::new(ExitStatus::SUCCESS));
374 let mut expected_options = OptionSet::default();
375 expected_options.extend([Interactive, Monitor]);
376 assert_eq!(env.options, expected_options);
377 let state = state.borrow();
378 let disposition = state.processes[&env.main_pid].disposition(SIGTSTP);
379 assert_eq!(disposition, Disposition::Ignore);
380 }
381
382 #[test]
383 fn disabling_monitor_option() {
384 let system = VirtualSystem::new();
385 let state = Rc::clone(&system.state);
386 let mut env = Env::with_system(system);
387 env.options.set(Interactive, On);
388 let args = Field::dummies(["-m"]);
389 _ = main(&mut env, args).now_or_never().unwrap();
390 let args = Field::dummies(["+m"]);
391
392 let result = main(&mut env, args).now_or_never().unwrap();
393 assert_eq!(result, Result::new(ExitStatus::SUCCESS));
394 let mut expected_options = OptionSet::default();
395 expected_options.set(Interactive, On);
396 assert_eq!(env.options, expected_options);
397 let state = state.borrow();
398 let disposition = state.processes[&env.main_pid].disposition(SIGTSTP);
399 assert_eq!(disposition, Disposition::Default);
400 }
401
402 #[test]
403 fn internal_dispositions_not_enabled_for_stoppers_in_non_interactive_shell() {
404 let system = VirtualSystem::new();
405 let state = Rc::clone(&system.state);
406 let mut env = Env::with_system(system);
407 let args = Field::dummies(["-m"]);
408
409 let result = main(&mut env, args).now_or_never().unwrap();
410 assert_eq!(result, Result::new(ExitStatus::SUCCESS));
411 let mut expected_options = OptionSet::default();
412 expected_options.set(Monitor, On);
413 assert_eq!(env.options, expected_options);
414 let state = state.borrow();
415 let disposition = state.processes[&env.main_pid].disposition(SIGTSTP);
416 assert_eq!(disposition, Disposition::Default);
417 }
418
419 #[test]
420 fn internal_dispositions_not_enabled_for_stoppers_in_subshell() {
421 let system = VirtualSystem::new();
422 let state = Rc::clone(&system.state);
423 let mut env = Env::with_system(system);
424 let mut env = env.push_frame(Subshell);
425 env.options.set(Interactive, On);
426 let args = Field::dummies(["-m"]);
427
428 let result = main(&mut env, args).now_or_never().unwrap();
429 assert_eq!(result, Result::new(ExitStatus::SUCCESS));
430 let mut expected_options = OptionSet::default();
431 expected_options.extend([Interactive, Monitor]);
432 assert_eq!(env.options, expected_options);
433 let state = state.borrow();
434 let disposition = state.processes[&env.main_pid].disposition(SIGTSTP);
435 assert_eq!(disposition, Disposition::Default);
436 }
437
438 }