1use crate::Env;
44use crate::builtin::Builtin;
45use crate::builtin::Type::{Extension, Special, Substitutive};
46use crate::function::Function;
47use crate::option::{On, PosixlyCorrect};
48use crate::path::PathBuf;
49use crate::system::IsExecutableFile;
50use crate::variable::Expansion;
51use crate::variable::PATH;
52use std::ffi::CStr;
53use std::ffi::CString;
54use std::rc::Rc;
55
56pub enum Target<S> {
69 Builtin {
71 builtin: Builtin<S>,
73
74 path: CString,
84 },
85
86 Function(Rc<Function<S>>),
88
89 External {
91 path: CString,
102 },
103}
104
105impl<S> Clone for Target<S> {
107 fn clone(&self) -> Self {
108 match self {
109 Self::Builtin { builtin, path } => Self::Builtin {
110 builtin: *builtin,
111 path: path.clone(),
112 },
113 Self::Function(f) => Self::Function(f.clone()),
114 Self::External { path } => Self::External { path: path.clone() },
115 }
116 }
117}
118
119impl<S> PartialEq for Target<S> {
120 fn eq(&self, other: &Self) -> bool {
121 match (self, other) {
122 (
123 Self::Builtin {
124 builtin: l_builtin,
125 path: l_path,
126 },
127 Self::Builtin {
128 builtin: r_builtin,
129 path: r_path,
130 },
131 ) => l_builtin == r_builtin && l_path == r_path,
132 (Self::Function(l), Self::Function(r)) => l == r,
133 (Self::External { path: l_path }, Self::External { path: r_path }) => l_path == r_path,
134 _ => false,
135 }
136 }
137}
138
139impl<S> Eq for Target<S> {}
140
141impl<S> std::fmt::Debug for Target<S> {
142 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143 match self {
144 Self::Builtin { builtin, path } => f
145 .debug_struct("Builtin")
146 .field("builtin", builtin)
147 .field("path", path)
148 .finish(),
149 Self::Function(func) => f.debug_tuple("Function").field(func).finish(),
150 Self::External { path } => f.debug_struct("External").field("path", path).finish(),
151 }
152 }
153}
154
155impl<S> From<Rc<Function<S>>> for Target<S> {
156 #[inline]
157 fn from(function: Rc<Function<S>>) -> Target<S> {
158 Target::Function(function)
159 }
160}
161
162impl<S> From<Function<S>> for Target<S> {
163 #[inline]
164 fn from(function: Function<S>) -> Target<S> {
165 Target::Function(function.into())
166 }
167}
168
169pub trait ClassifyEnv<S> {
175 #[must_use]
177 fn builtin(&self, name: &str) -> Option<Builtin<S>>;
178
179 #[must_use]
181 fn function(&self, name: &str) -> Option<&Rc<Function<S>>>;
182}
183
184pub trait PathEnv {
186 #[must_use]
192 fn path(&self) -> Expansion<'_>;
193
194 #[must_use]
196 fn is_executable_file(&self, path: &CStr) -> bool;
197 }
199
200impl<S: IsExecutableFile> PathEnv for Env<S> {
201 fn path(&self) -> Expansion<'_> {
206 self.variables
207 .get(PATH)
208 .and_then(|var| {
209 assert_eq!(var.quirk, None, "PATH does not support quirks");
210 var.value.as_ref()
211 })
212 .into()
213 }
214
215 fn is_executable_file(&self, path: &CStr) -> bool {
216 self.system.is_executable_file(path)
217 }
218}
219
220impl<S> ClassifyEnv<S> for Env<S> {
221 fn builtin(&self, name: &str) -> Option<Builtin<S>> {
222 let builtin = self.builtins.get(name).copied()?;
223 let available = builtin.r#type != Extension || self.options.get(PosixlyCorrect) != On;
224 available.then_some(builtin)
225 }
226
227 #[inline]
228 fn function(&self, name: &str) -> Option<&Rc<Function<S>>> {
229 self.functions.get(name)
230 }
231}
232
233#[must_use]
244pub fn search<S, E: ClassifyEnv<S> + PathEnv>(env: &mut E, name: &str) -> Option<Target<S>> {
245 let mut target = classify(env, name);
246
247 'fill_path: {
248 let path = match &mut target {
249 Target::Builtin { builtin, path } if builtin.r#type == Substitutive => {
250 path
252 }
253
254 Target::External { path } => {
255 if name.contains('/') {
256 *path = CString::new(name).ok()?;
258 break 'fill_path;
259 } else {
260 path
262 }
263 }
264
265 Target::Builtin { .. } | Target::Function(_) => {
266 break 'fill_path;
268 }
269 };
270
271 if let Some(real_path) = search_path(env, name) {
272 *path = real_path;
273 } else {
274 return None;
275 }
276 }
277
278 Some(target)
279}
280
281#[must_use]
292pub fn classify<S, E: ClassifyEnv<S>>(env: &E, name: &str) -> Target<S> {
293 if name.contains('/') {
294 return Target::External {
295 path: CString::default(),
296 };
297 }
298
299 let builtin = env.builtin(name);
300 if let Some(builtin) = builtin
301 && builtin.r#type == Special
302 {
303 let path = CString::default();
304 return Target::Builtin { builtin, path };
305 }
306
307 if let Some(function) = env.function(name) {
308 return Rc::clone(function).into();
309 }
310
311 if let Some(builtin) = builtin {
312 let path = CString::default();
313 return Target::Builtin { builtin, path };
314 }
315
316 Target::External {
317 path: CString::default(),
318 }
319}
320
321#[must_use]
331pub fn search_path<E: PathEnv>(env: &mut E, name: &str) -> Option<CString> {
332 env.path()
333 .split()
334 .filter_map(|dir| {
335 let candidate = PathBuf::from_iter([dir, name])
336 .into_unix_string()
337 .into_vec();
338 CString::new(candidate).ok()
339 })
340 .find(|path| env.is_executable_file(path))
341}
342
343#[allow(clippy::field_reassign_with_default, reason = "for readability")]
344#[cfg(test)]
345mod tests {
346 use super::*;
347 use crate::builtin::Type::{Elective, Extension, Mandatory};
348 use crate::function::{FunctionBody, FunctionBodyObject, FunctionSet};
349 use crate::option::Off;
350 use crate::source::Location;
351 use crate::variable::Value;
352 use assert_matches::assert_matches;
353 use std::collections::HashMap;
354 use std::collections::HashSet;
355
356 #[test]
357 fn env_builtin_returns_special_builtin() {
358 let mut env = Env::new_virtual();
359 let builtin = Builtin::new(Special, |_, _| unreachable!());
360 env.builtins.insert("foo", builtin);
361 assert_eq!(env.builtin("foo"), Some(builtin));
362 }
363
364 #[test]
365 fn env_builtin_returns_mandatory_builtin() {
366 let mut env = Env::new_virtual();
367 let builtin = Builtin::new(Mandatory, |_, _| unreachable!());
368 env.builtins.insert("foo", builtin);
369 assert_eq!(env.builtin("foo"), Some(builtin));
370 }
371
372 #[test]
373 fn env_builtin_returns_elective_builtin() {
374 let mut env = Env::new_virtual();
375 let builtin = Builtin::new(Elective, |_, _| unreachable!());
376 env.builtins.insert("foo", builtin);
377 assert_eq!(env.builtin("foo"), Some(builtin));
378 }
379
380 #[test]
381 fn env_builtin_returns_extension_builtin_if_not_posixly_correct() {
382 let mut env = Env::new_virtual();
383 let builtin = Builtin::new(Extension, |_, _| unreachable!());
384 env.builtins.insert("foo", builtin);
385 assert_eq!(env.options.get(PosixlyCorrect), Off);
386 assert_eq!(env.builtin("foo"), Some(builtin));
387 }
388
389 #[test]
390 fn env_builtin_does_not_return_extension_builtin_if_posixly_correct() {
391 let mut env = Env::new_virtual();
392 env.options.set(PosixlyCorrect, On);
393 let builtin = Builtin::new(Extension, |_, _| unreachable!());
394 env.builtins.insert("foo", builtin);
395 assert_eq!(env.builtin("foo"), None);
396 }
397
398 #[test]
399 fn env_builtin_returns_substitutive_builtin() {
400 let mut env = Env::new_virtual();
403 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
404 env.builtins.insert("foo", builtin);
405 assert_eq!(env.builtin("foo"), Some(builtin));
406 }
407
408 #[derive(Default)]
409 struct DummyEnv {
410 builtins: HashMap<&'static str, Builtin<()>>,
411 functions: FunctionSet<()>,
412 path: Expansion<'static>,
413 executables: HashSet<String>,
414 }
415
416 impl PathEnv for DummyEnv {
417 fn path(&self) -> Expansion<'_> {
418 self.path.as_ref()
419 }
420 fn is_executable_file(&self, path: &CStr) -> bool {
421 if let Ok(path) = path.to_str() {
422 self.executables.contains(path)
423 } else {
424 false
425 }
426 }
427 }
428
429 impl ClassifyEnv<()> for DummyEnv {
430 fn builtin(&self, name: &str) -> Option<Builtin<()>> {
431 self.builtins.get(name).copied()
432 }
433 fn function(&self, name: &str) -> Option<&Rc<Function<()>>> {
434 self.functions.get(name)
435 }
436 }
437
438 #[derive(Clone, Debug)]
439 struct FunctionBodyStub;
440
441 impl std::fmt::Display for FunctionBodyStub {
442 fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
443 unreachable!()
444 }
445 }
446 impl<S> FunctionBody<S> for FunctionBodyStub {
447 async fn execute(&self, _: &mut Env<S>) -> crate::semantics::Result {
448 unreachable!()
449 }
450 }
451
452 fn function_body_stub<S>() -> Rc<dyn FunctionBodyObject<S>> {
453 Rc::new(FunctionBodyStub)
454 }
455
456 #[test]
457 fn nothing_is_found_in_empty_env() {
458 let mut env = DummyEnv::default();
459 let target = search(&mut env, "foo");
460 assert!(target.is_none(), "target = {target:?}");
461 }
462
463 #[test]
464 fn nothing_is_found_with_name_unmatched() {
465 let mut env = DummyEnv::default();
466 env.builtins
467 .insert("foo", Builtin::new(Special, |_, _| unreachable!()));
468 let function = Function::new("foo", function_body_stub(), Location::dummy(""));
469 env.functions.define(function).unwrap();
470
471 let target = search(&mut env, "bar");
472 assert!(target.is_none(), "target = {target:?}");
473 }
474
475 #[test]
476 fn classify_defaults_to_external() {
477 let env = DummyEnv::default();
480 let target = classify(&env, "foo");
481 assert_eq!(
482 target,
483 Target::External {
484 path: CString::default()
485 }
486 );
487 }
488
489 #[test]
490 fn special_builtin_is_found() {
491 let mut env = DummyEnv::default();
492 let builtin = Builtin::new(Special, |_, _| unreachable!());
493 env.builtins.insert("foo", builtin);
494
495 assert_matches!(
496 search(&mut env, "foo"),
497 Some(Target::Builtin { builtin: result, path }) => {
498 assert_eq!(result.r#type, builtin.r#type);
499 assert_eq!(*path, *c"");
500 }
501 );
502 assert_matches!(
503 classify(&env, "foo"),
504 Target::Builtin { builtin: result, path } => {
505 assert_eq!(result.r#type, builtin.r#type);
506 assert_eq!(*path, *c"");
507 }
508 );
509 }
510
511 #[test]
512 fn function_is_found_if_not_hidden_by_special_builtin() {
513 let mut env = DummyEnv::default();
514 let function = Rc::new(Function::new(
515 "foo",
516 function_body_stub(),
517 Location::dummy("location"),
518 ));
519 env.functions.define(function.clone()).unwrap();
520
521 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
522 assert_eq!(result, function);
523 });
524 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
525 assert_eq!(result, function);
526 });
527 }
528
529 #[test]
530 fn special_builtin_takes_priority_over_function() {
531 let mut env = DummyEnv::default();
532 let builtin = Builtin::new(Special, |_, _| unreachable!());
533 env.builtins.insert("foo", builtin);
534 let function = Function::new("foo", function_body_stub(), Location::dummy("location"));
535 env.functions.define(function).unwrap();
536
537 assert_matches!(
538 search(&mut env, "foo"),
539 Some(Target::Builtin { builtin: result, path }) => {
540 assert_eq!(result.r#type, builtin.r#type);
541 assert_eq!(*path, *c"");
542 }
543 );
544 assert_matches!(
545 classify(&env, "foo"),
546 Target::Builtin { builtin: result, path } => {
547 assert_eq!(result.r#type, builtin.r#type);
548 assert_eq!(*path, *c"");
549 }
550 );
551 }
552
553 #[test]
554 fn mandatory_builtin_is_found_if_not_hidden_by_function() {
555 let mut env = DummyEnv::default();
556 let builtin = Builtin::new(Mandatory, |_, _| unreachable!());
557 env.builtins.insert("foo", builtin);
558
559 assert_matches!(
560 search(&mut env, "foo"),
561 Some(Target::Builtin { builtin: result, path }) => {
562 assert_eq!(result.r#type, builtin.r#type);
563 assert_eq!(*path, *c"");
564 }
565 );
566 assert_matches!(
567 classify(&env, "foo"),
568 Target::Builtin { builtin: result, path } => {
569 assert_eq!(result.r#type, builtin.r#type);
570 assert_eq!(*path, *c"");
571 }
572 );
573 }
574
575 #[test]
576 fn elective_builtin_is_found_if_not_hidden_by_function() {
577 let mut env = DummyEnv::default();
578 let builtin = Builtin::new(Elective, |_, _| unreachable!());
579 env.builtins.insert("foo", builtin);
580
581 assert_matches!(
582 search(&mut env, "foo"),
583 Some(Target::Builtin { builtin: result, path }) => {
584 assert_eq!(result.r#type, builtin.r#type);
585 assert_eq!(*path, *c"");
586 }
587 );
588 assert_matches!(
589 classify(&env, "foo"),
590 Target::Builtin { builtin: result, path } => {
591 assert_eq!(result.r#type, builtin.r#type);
592 assert_eq!(*path, *c"");
593 }
594 );
595 }
596
597 #[test]
598 fn extension_builtin_is_found_if_not_hidden_by_function_or_option() {
599 let mut env = DummyEnv::default();
600 let builtin = Builtin::new(Extension, |_, _| unreachable!());
601 env.builtins.insert("foo", builtin);
602
603 assert_matches!(
604 search(&mut env, "foo"),
605 Some(Target::Builtin { builtin: result, path }) => {
606 assert_eq!(result.r#type, builtin.r#type);
607 assert_eq!(*path, *c"");
608 }
609 );
610 assert_matches!(
611 classify(&env, "foo"),
612 Target::Builtin { builtin: result, path } => {
613 assert_eq!(result.r#type, builtin.r#type);
614 assert_eq!(*path, *c"");
615 }
616 );
617 }
618
619 #[test]
620 fn function_takes_priority_over_mandatory_builtin() {
621 let mut env = DummyEnv::default();
622 env.builtins
623 .insert("foo", Builtin::new(Mandatory, |_, _| unreachable!()));
624
625 let function = Rc::new(Function::new(
626 "foo",
627 function_body_stub(),
628 Location::dummy("location"),
629 ));
630 env.functions.define(function.clone()).unwrap();
631
632 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
633 assert_eq!(result, function);
634 });
635 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
636 assert_eq!(result, function);
637 });
638 }
639
640 #[test]
641 fn function_takes_priority_over_elective_builtin() {
642 let mut env = DummyEnv::default();
643 env.builtins
644 .insert("foo", Builtin::new(Elective, |_, _| unreachable!()));
645
646 let function = Rc::new(Function::new(
647 "foo",
648 function_body_stub(),
649 Location::dummy("location"),
650 ));
651 env.functions.define(function.clone()).unwrap();
652
653 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
654 assert_eq!(result, function);
655 });
656 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
657 assert_eq!(result, function);
658 });
659 }
660
661 #[test]
662 fn function_takes_priority_over_extension_builtin() {
663 let mut env = DummyEnv::default();
664 env.builtins
665 .insert("foo", Builtin::new(Extension, |_, _| unreachable!()));
666
667 let function = Rc::new(Function::new(
668 "foo",
669 function_body_stub(),
670 Location::dummy("location"),
671 ));
672 env.functions.define(function.clone()).unwrap();
673
674 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
675 assert_eq!(result, function);
676 });
677 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
678 assert_eq!(result, function);
679 });
680 }
681
682 #[test]
683 fn substitutive_builtin_is_found_if_external_executable_exists() {
684 let mut env = DummyEnv::default();
685 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
686 env.builtins.insert("foo", builtin);
687 env.path = Expansion::from("/bin");
688 env.executables.insert("/bin/foo".to_string());
689
690 assert_matches!(
691 search(&mut env, "foo"),
692 Some(Target::Builtin { builtin: result, path }) => {
693 assert_eq!(result.r#type, builtin.r#type);
694 assert_eq!(*path, *c"/bin/foo");
695 }
696 );
697 assert_matches!(
698 classify(&env, "foo"),
699 Target::Builtin { builtin: result, path } => {
700 assert_eq!(result.r#type, builtin.r#type);
701 assert_eq!(*path, *c"");
702 }
703 );
704 }
705
706 #[test]
707 fn substitutive_builtin_is_not_found_without_external_executable() {
708 let mut env = DummyEnv::default();
709 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
710 env.builtins.insert("foo", builtin);
711
712 let target = search(&mut env, "foo");
713 assert!(target.is_none(), "target = {target:?}");
714 }
715
716 #[test]
717 fn substitutive_builtin_is_classified_even_without_external_executable() {
718 let mut env = DummyEnv::default();
719 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
720 env.builtins.insert("foo", builtin);
721
722 assert_matches!(
723 classify(&env, "foo"),
724 Target::Builtin { builtin: result, path } => {
725 assert_eq!(result.r#type, builtin.r#type);
726 assert_eq!(*path, *c"");
727 }
728 );
729 }
730
731 #[test]
732 fn function_takes_priority_over_substitutive_builtin() {
733 let mut env = DummyEnv::default();
734 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
735 env.builtins.insert("foo", builtin);
736 env.path = Expansion::from("/bin");
737 env.executables.insert("/bin/foo".to_string());
738
739 let function = Rc::new(Function::new(
740 "foo",
741 function_body_stub(),
742 Location::dummy("location"),
743 ));
744 env.functions.define(function.clone()).unwrap();
745
746 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
747 assert_eq!(result, function);
748 });
749 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
750 assert_eq!(result, function);
751 });
752 }
753
754 #[test]
755 fn external_utility_is_found_if_external_executable_exists() {
756 let mut env = DummyEnv::default();
757 env.path = Expansion::from("/bin");
758 env.executables.insert("/bin/foo".to_string());
759
760 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
761 assert_eq!(*path, *c"/bin/foo");
762 });
763 assert_matches!(classify(&env, "foo"), Target::External { path } => {
764 assert_eq!(*path, *c"");
765 });
766 }
767
768 #[test]
769 fn returns_external_utility_if_name_contains_slash() {
770 let mut env = DummyEnv::default();
772 let builtin = Builtin::new(Special, |_, _| unreachable!());
775 env.builtins.insert("bar/baz", builtin);
776
777 assert_matches!(search(&mut env, "bar/baz"), Some(Target::External { path }) => {
778 assert_eq!(*path, *c"bar/baz");
779 });
780 assert_matches!(classify(&env, "bar/baz"), Target::External { path } => {
781 assert_eq!(*path, *c"");
782 });
783 }
784
785 #[test]
786 fn external_target_is_first_executable_found_in_path_scalar() {
787 let mut env = DummyEnv::default();
788 env.path = Expansion::from("/usr/local/bin:/usr/bin:/bin");
789 env.executables.insert("/usr/bin/foo".to_string());
790 env.executables.insert("/bin/foo".to_string());
791
792 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
793 assert_eq!(*path, *c"/usr/bin/foo");
794 });
795
796 env.executables.insert("/usr/local/bin/foo".to_string());
797
798 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
799 assert_eq!(*path, *c"/usr/local/bin/foo");
800 });
801 }
802
803 #[test]
804 fn external_target_is_first_executable_found_in_path_array() {
805 let mut env = DummyEnv::default();
806 env.path = Expansion::from(Value::array(["/usr/local/bin", "/usr/bin", "/bin"]));
807 env.executables.insert("/usr/bin/foo".to_string());
808 env.executables.insert("/bin/foo".to_string());
809
810 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
811 assert_eq!(*path, *c"/usr/bin/foo");
812 });
813
814 env.executables.insert("/usr/local/bin/foo".to_string());
815
816 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
817 assert_eq!(*path, *c"/usr/local/bin/foo");
818 });
819 }
820
821 #[test]
822 fn empty_string_in_path_names_current_directory() {
823 let mut env = DummyEnv::default();
824 env.path = Expansion::from("/x::/y");
825 env.executables.insert("foo".to_string());
826
827 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
828 assert_eq!(*path, *c"foo");
829 });
830 }
831}