1use super::Invoke;
20use super::identify::NotFound;
21use super::search::SearchEnv;
22use crate::common::report::report_failure;
23use crate::exec::ExecFailure;
24use std::ops::ControlFlow::{Break, Continue};
25use yash_env::Env;
26use yash_env::semantics::ExitStatus;
27use yash_env::semantics::Field;
28use yash_env::semantics::command::RunFunction;
29use yash_env::semantics::command::run_external_utility_in_subshell;
30use yash_env::semantics::command::search::{Target, search};
31use yash_env::system::resource::SetRlimit;
32use yash_env::system::{
33 Close, Dup, Exec, Exit, Fcntl, Fork, GetPid, IsExecutableFile, Isatty, Open, SendSignal,
34 SetPgid, ShellPath, Sigaction, Sigmask, Signals, Sysconf, TcSetPgrp, Wait, Write,
35};
36
37impl Invoke {
38 pub async fn execute<S>(self, env: &mut Env<S>) -> crate::Result
40 where
41 S: Close
42 + Dup
43 + Exec
44 + Exit
45 + Fcntl
46 + Fork
47 + GetPid
48 + IsExecutableFile
49 + Isatty
50 + Open
51 + SendSignal
52 + SetPgid
53 + SetRlimit
54 + ShellPath
55 + Sigaction
56 + Sigmask
57 + Signals
58 + Sysconf
59 + TcSetPgrp
60 + Wait
61 + Write
62 + 'static,
63 {
64 let Some(name) = self.fields.first() else {
65 return crate::Result::default();
66 };
67
68 let params = &self.search;
69 let search_env = &mut SearchEnv { env, params };
70 let Some(target) = search(search_env, &name.value) else {
71 let mut result = report_failure(env, &NotFound { name }).await;
72 result.set_exit_status(ExitStatus::NOT_FOUND);
73 return result;
74 };
75
76 invoke_target(env, target, self.fields).await
77 }
78}
79
80async fn invoke_target<S>(
90 env: &mut Env<S>,
91 target: Target<S>,
92 mut fields: Vec<Field>,
93) -> crate::Result
94where
95 S: Close
96 + Dup
97 + Exec
98 + Exit
99 + Fcntl
100 + Fork
101 + GetPid
102 + Isatty
103 + Open
104 + SendSignal
105 + SetPgid
106 + SetRlimit
107 + ShellPath
108 + Sigaction
109 + Sigmask
110 + Signals
111 + TcSetPgrp
112 + Wait
113 + Write
114 + 'static,
115{
116 match target {
117 Target::Builtin { builtin, .. } => {
118 let frame = yash_env::stack::Builtin {
119 name: fields.remove(0),
120 is_special: false,
122 };
123 let mut env = env.push_frame(frame.into());
124 (builtin.execute)(&mut env, fields).await
125 }
126
127 Target::Function(function) => {
128 let RunFunction(run_function) =
129 env.any.get().expect("`RunFunction` should be in `env.any`");
130 let divert = run_function(env, function, fields, None).await;
131 crate::Result::with_exit_status_and_divert(env.exit_status, divert)
132 }
133
134 Target::External { path } => {
135 let result = run_external_utility_in_subshell(
136 env,
137 path,
138 fields,
139 |env, error| Box::pin(async move { _ = report_failure(env, &error).await }),
140 |env, inner, location| {
141 Box::pin(async move {
142 _ = report_failure(env, &ExecFailure { inner, location }).await
143 })
144 },
145 )
146 .await;
147
148 match result {
149 Continue(exit_status) => exit_status.into(),
150 Break(divert) => {
151 crate::Result::with_exit_status_and_divert(env.exit_status, Break(divert))
152 }
153 }
154 }
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::super::Search;
161 use super::*;
162 use assert_matches::assert_matches;
163 use enumset::EnumSet;
164 use futures_util::FutureExt as _;
165 use std::ffi::CString;
166 use std::rc::Rc;
167 use yash_env::VirtualSystem;
168 use yash_env::builtin::Builtin;
169 use yash_env::builtin::Type::Special;
170 use yash_env::function::{Function, FunctionBody, FunctionBodyObject};
171 use yash_env::semantics::Field;
172 use yash_env::source::Location;
173 use yash_env::test_helper::assert_stderr;
174 use yash_env::test_helper::assert_stdout;
175 use yash_semantics::Divert::Return;
176 use yash_semantics::Runtime;
177 use yash_syntax::syntax::FullCompoundCommand;
178
179 #[derive(Clone, Debug)]
181 struct FunctionBodyImpl(FullCompoundCommand);
182
183 impl std::fmt::Display for FunctionBodyImpl {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 self.0.fmt(f)
186 }
187 }
188 impl<S: Runtime + 'static> FunctionBody<S> for FunctionBodyImpl {
189 async fn execute(&self, env: &mut Env<S>) -> yash_env::semantics::Result {
190 use yash_semantics::command::Command as _;
191 self.0.execute(env).await
192 }
193 }
194
195 fn function_body_impl<S: Runtime + 'static>(src: &str) -> Rc<dyn FunctionBodyObject<S>> {
196 Rc::new(FunctionBodyImpl(src.parse().unwrap()))
197 }
198
199 #[test]
200 fn empty_command_invocation() {
201 let mut env = Env::new_virtual();
202 let invoke = Invoke::default();
203 let result = invoke.execute(&mut env).now_or_never().unwrap();
204 assert_eq!(result, crate::Result::default());
205 }
206
207 #[test]
208 fn command_not_found() {
209 let system = VirtualSystem::new();
210 let state = Rc::clone(&system.state);
211 let mut env = Env::with_system(system);
212 env.builtins
213 .insert("foo", Builtin::new(Special, |_, _| unreachable!()));
214 let invoke = Invoke {
215 fields: Field::dummies(["foo"]),
216 search: Search {
217 standard_path: false,
218 categories: EnumSet::empty(),
219 },
220 };
221
222 let result = invoke.execute(&mut env).now_or_never().unwrap();
223 assert_eq!(result.exit_status(), ExitStatus::NOT_FOUND);
224 assert_stdout(&state, |stdout| assert_eq!(stdout, ""));
225 assert_stderr(&state, |stderr| {
226 assert!(stderr.contains("not found"), "stderr: {stderr:?}");
227 });
228 }
229
230 #[test]
231 fn invoking_builtin() {
232 fn make_result() -> yash_env::builtin::Result {
233 let mut result = crate::Result::default();
234 result.set_exit_status(ExitStatus(79));
235 result.set_divert(Break(Return(None)));
236 result.retain_redirs();
237 result
238 }
239
240 let mut env = Env::new_virtual();
241 let target = Target::Builtin {
242 builtin: Builtin::new(Special, |_, args| {
243 Box::pin(async move {
244 assert_eq!(args, Field::dummies(["bar", "baz"]));
245 make_result()
246 })
247 }),
248 path: CString::default(),
249 };
250
251 let result = invoke_target(&mut env, target, Field::dummies(["foo", "bar", "baz"]))
252 .now_or_never()
253 .unwrap();
254 assert_eq!(result, make_result());
255 }
256
257 #[test]
258 fn invoking_function() {
259 let mut env = Env::new_virtual();
260 env.any.insert(Box::new(RunFunction::<VirtualSystem>(
261 |env, function, fields, env_prep_hook| {
262 Box::pin(async move {
263 yash_semantics::command::simple_command::execute_function_body(
264 env,
265 function,
266 fields,
267 env_prep_hook,
268 )
269 .await
270 })
271 },
272 )));
273 env.builtins.insert(
274 ":",
275 Builtin::new(Special, |_, args| {
276 Box::pin(async move {
277 assert_matches!(args.as_slice(), [bar, baz] => {
278 assert_eq!(bar.value, "bar");
279 assert_eq!(baz.value, "baz");
280 });
281 crate::Result::with_exit_status_and_divert(ExitStatus(42), Break(Return(None)))
282 })
283 }),
284 );
285 let origin = Location::dummy("some location");
286 let target = Target::Function(Rc::new(Function::new(
287 "foo",
288 function_body_impl(r#"{ : "$@"; }"#),
289 origin,
290 )));
291
292 let result = invoke_target(&mut env, target, Field::dummies(["foo", "bar", "baz"]))
293 .now_or_never()
294 .unwrap();
295 assert_eq!(result, crate::Result::from(ExitStatus(42)));
296 }
297}