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 *path = search_path(env, name)?;
272 }
273
274 Some(target)
275}
276
277#[must_use]
288pub fn classify<S, E: ClassifyEnv<S>>(env: &E, name: &str) -> Target<S> {
289 if name.contains('/') {
290 return Target::External {
291 path: CString::default(),
292 };
293 }
294
295 let builtin = env.builtin(name);
296 if let Some(builtin) = builtin
297 && builtin.r#type == Special
298 {
299 let path = CString::default();
300 return Target::Builtin { builtin, path };
301 }
302
303 if let Some(function) = env.function(name) {
304 return Rc::clone(function).into();
305 }
306
307 if let Some(builtin) = builtin {
308 let path = CString::default();
309 return Target::Builtin { builtin, path };
310 }
311
312 Target::External {
313 path: CString::default(),
314 }
315}
316
317#[must_use]
327pub fn search_path<E: PathEnv>(env: &mut E, name: &str) -> Option<CString> {
328 env.path()
329 .split()
330 .filter_map(|dir| {
331 let candidate = PathBuf::from_iter([dir, name])
332 .into_unix_string()
333 .into_vec();
334 CString::new(candidate).ok()
335 })
336 .find(|path| env.is_executable_file(path))
337}
338
339#[allow(clippy::field_reassign_with_default, reason = "for readability")]
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use crate::builtin::Type::{Elective, Extension, Mandatory};
344 use crate::function::{FunctionBody, FunctionBodyObject, FunctionSet};
345 use crate::option::Off;
346 use crate::source::Location;
347 use crate::variable::Value;
348 use assert_matches::assert_matches;
349 use std::collections::HashMap;
350 use std::collections::HashSet;
351
352 #[test]
353 fn env_builtin_returns_special_builtin() {
354 let mut env = Env::new_virtual();
355 let builtin = Builtin::new(Special, |_, _| unreachable!());
356 env.builtins.insert("foo", builtin);
357 assert_eq!(env.builtin("foo"), Some(builtin));
358 }
359
360 #[test]
361 fn env_builtin_returns_mandatory_builtin() {
362 let mut env = Env::new_virtual();
363 let builtin = Builtin::new(Mandatory, |_, _| unreachable!());
364 env.builtins.insert("foo", builtin);
365 assert_eq!(env.builtin("foo"), Some(builtin));
366 }
367
368 #[test]
369 fn env_builtin_returns_elective_builtin() {
370 let mut env = Env::new_virtual();
371 let builtin = Builtin::new(Elective, |_, _| unreachable!());
372 env.builtins.insert("foo", builtin);
373 assert_eq!(env.builtin("foo"), Some(builtin));
374 }
375
376 #[test]
377 fn env_builtin_returns_extension_builtin_if_not_posixly_correct() {
378 let mut env = Env::new_virtual();
379 let builtin = Builtin::new(Extension, |_, _| unreachable!());
380 env.builtins.insert("foo", builtin);
381 assert_eq!(env.options.get(PosixlyCorrect), Off);
382 assert_eq!(env.builtin("foo"), Some(builtin));
383 }
384
385 #[test]
386 fn env_builtin_does_not_return_extension_builtin_if_posixly_correct() {
387 let mut env = Env::new_virtual();
388 env.options.set(PosixlyCorrect, On);
389 let builtin = Builtin::new(Extension, |_, _| unreachable!());
390 env.builtins.insert("foo", builtin);
391 assert_eq!(env.builtin("foo"), None);
392 }
393
394 #[test]
395 fn env_builtin_returns_substitutive_builtin() {
396 let mut env = Env::new_virtual();
399 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
400 env.builtins.insert("foo", builtin);
401 assert_eq!(env.builtin("foo"), Some(builtin));
402 }
403
404 #[derive(Default)]
405 struct DummyEnv {
406 builtins: HashMap<&'static str, Builtin<()>>,
407 functions: FunctionSet<()>,
408 path: Expansion<'static>,
409 executables: HashSet<String>,
410 }
411
412 impl PathEnv for DummyEnv {
413 fn path(&self) -> Expansion<'_> {
414 self.path.as_ref()
415 }
416 fn is_executable_file(&self, path: &CStr) -> bool {
417 if let Ok(path) = path.to_str() {
418 self.executables.contains(path)
419 } else {
420 false
421 }
422 }
423 }
424
425 impl ClassifyEnv<()> for DummyEnv {
426 fn builtin(&self, name: &str) -> Option<Builtin<()>> {
427 self.builtins.get(name).copied()
428 }
429 fn function(&self, name: &str) -> Option<&Rc<Function<()>>> {
430 self.functions.get(name)
431 }
432 }
433
434 #[derive(Clone, Debug)]
435 struct FunctionBodyStub;
436
437 impl std::fmt::Display for FunctionBodyStub {
438 fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439 unreachable!()
440 }
441 }
442 impl<S> FunctionBody<S> for FunctionBodyStub {
443 async fn execute(&self, _: &mut Env<S>) -> crate::semantics::Result {
444 unreachable!()
445 }
446 }
447
448 fn function_body_stub<S>() -> Rc<dyn FunctionBodyObject<S>> {
449 Rc::new(FunctionBodyStub)
450 }
451
452 #[test]
453 fn nothing_is_found_in_empty_env() {
454 let mut env = DummyEnv::default();
455 let target = search(&mut env, "foo");
456 assert!(target.is_none(), "target = {target:?}");
457 }
458
459 #[test]
460 fn nothing_is_found_with_name_unmatched() {
461 let mut env = DummyEnv::default();
462 env.builtins
463 .insert("foo", Builtin::new(Special, |_, _| unreachable!()));
464 let function = Function::new("foo", function_body_stub(), Location::dummy(""));
465 env.functions.define(function).unwrap();
466
467 let target = search(&mut env, "bar");
468 assert!(target.is_none(), "target = {target:?}");
469 }
470
471 #[test]
472 fn classify_defaults_to_external() {
473 let env = DummyEnv::default();
476 let target = classify(&env, "foo");
477 assert_eq!(
478 target,
479 Target::External {
480 path: CString::default()
481 }
482 );
483 }
484
485 #[test]
486 fn special_builtin_is_found() {
487 let mut env = DummyEnv::default();
488 let builtin = Builtin::new(Special, |_, _| unreachable!());
489 env.builtins.insert("foo", builtin);
490
491 assert_matches!(
492 search(&mut env, "foo"),
493 Some(Target::Builtin { builtin: result, path }) => {
494 assert_eq!(result.r#type, builtin.r#type);
495 assert_eq!(*path, *c"");
496 }
497 );
498 assert_matches!(
499 classify(&env, "foo"),
500 Target::Builtin { builtin: result, path } => {
501 assert_eq!(result.r#type, builtin.r#type);
502 assert_eq!(*path, *c"");
503 }
504 );
505 }
506
507 #[test]
508 fn function_is_found_if_not_hidden_by_special_builtin() {
509 let mut env = DummyEnv::default();
510 let function = Rc::new(Function::new(
511 "foo",
512 function_body_stub(),
513 Location::dummy("location"),
514 ));
515 env.functions.define(function.clone()).unwrap();
516
517 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
518 assert_eq!(result, function);
519 });
520 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
521 assert_eq!(result, function);
522 });
523 }
524
525 #[test]
526 fn special_builtin_takes_priority_over_function() {
527 let mut env = DummyEnv::default();
528 let builtin = Builtin::new(Special, |_, _| unreachable!());
529 env.builtins.insert("foo", builtin);
530 let function = Function::new("foo", function_body_stub(), Location::dummy("location"));
531 env.functions.define(function).unwrap();
532
533 assert_matches!(
534 search(&mut env, "foo"),
535 Some(Target::Builtin { builtin: result, path }) => {
536 assert_eq!(result.r#type, builtin.r#type);
537 assert_eq!(*path, *c"");
538 }
539 );
540 assert_matches!(
541 classify(&env, "foo"),
542 Target::Builtin { builtin: result, path } => {
543 assert_eq!(result.r#type, builtin.r#type);
544 assert_eq!(*path, *c"");
545 }
546 );
547 }
548
549 #[test]
550 fn mandatory_builtin_is_found_if_not_hidden_by_function() {
551 let mut env = DummyEnv::default();
552 let builtin = Builtin::new(Mandatory, |_, _| unreachable!());
553 env.builtins.insert("foo", builtin);
554
555 assert_matches!(
556 search(&mut env, "foo"),
557 Some(Target::Builtin { builtin: result, path }) => {
558 assert_eq!(result.r#type, builtin.r#type);
559 assert_eq!(*path, *c"");
560 }
561 );
562 assert_matches!(
563 classify(&env, "foo"),
564 Target::Builtin { builtin: result, path } => {
565 assert_eq!(result.r#type, builtin.r#type);
566 assert_eq!(*path, *c"");
567 }
568 );
569 }
570
571 #[test]
572 fn elective_builtin_is_found_if_not_hidden_by_function() {
573 let mut env = DummyEnv::default();
574 let builtin = Builtin::new(Elective, |_, _| unreachable!());
575 env.builtins.insert("foo", builtin);
576
577 assert_matches!(
578 search(&mut env, "foo"),
579 Some(Target::Builtin { builtin: result, path }) => {
580 assert_eq!(result.r#type, builtin.r#type);
581 assert_eq!(*path, *c"");
582 }
583 );
584 assert_matches!(
585 classify(&env, "foo"),
586 Target::Builtin { builtin: result, path } => {
587 assert_eq!(result.r#type, builtin.r#type);
588 assert_eq!(*path, *c"");
589 }
590 );
591 }
592
593 #[test]
594 fn extension_builtin_is_found_if_not_hidden_by_function_or_option() {
595 let mut env = DummyEnv::default();
596 let builtin = Builtin::new(Extension, |_, _| unreachable!());
597 env.builtins.insert("foo", builtin);
598
599 assert_matches!(
600 search(&mut env, "foo"),
601 Some(Target::Builtin { builtin: result, path }) => {
602 assert_eq!(result.r#type, builtin.r#type);
603 assert_eq!(*path, *c"");
604 }
605 );
606 assert_matches!(
607 classify(&env, "foo"),
608 Target::Builtin { builtin: result, path } => {
609 assert_eq!(result.r#type, builtin.r#type);
610 assert_eq!(*path, *c"");
611 }
612 );
613 }
614
615 #[test]
616 fn function_takes_priority_over_mandatory_builtin() {
617 let mut env = DummyEnv::default();
618 env.builtins
619 .insert("foo", Builtin::new(Mandatory, |_, _| unreachable!()));
620
621 let function = Rc::new(Function::new(
622 "foo",
623 function_body_stub(),
624 Location::dummy("location"),
625 ));
626 env.functions.define(function.clone()).unwrap();
627
628 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
629 assert_eq!(result, function);
630 });
631 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
632 assert_eq!(result, function);
633 });
634 }
635
636 #[test]
637 fn function_takes_priority_over_elective_builtin() {
638 let mut env = DummyEnv::default();
639 env.builtins
640 .insert("foo", Builtin::new(Elective, |_, _| unreachable!()));
641
642 let function = Rc::new(Function::new(
643 "foo",
644 function_body_stub(),
645 Location::dummy("location"),
646 ));
647 env.functions.define(function.clone()).unwrap();
648
649 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
650 assert_eq!(result, function);
651 });
652 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
653 assert_eq!(result, function);
654 });
655 }
656
657 #[test]
658 fn function_takes_priority_over_extension_builtin() {
659 let mut env = DummyEnv::default();
660 env.builtins
661 .insert("foo", Builtin::new(Extension, |_, _| unreachable!()));
662
663 let function = Rc::new(Function::new(
664 "foo",
665 function_body_stub(),
666 Location::dummy("location"),
667 ));
668 env.functions.define(function.clone()).unwrap();
669
670 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
671 assert_eq!(result, function);
672 });
673 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
674 assert_eq!(result, function);
675 });
676 }
677
678 #[test]
679 fn substitutive_builtin_is_found_if_external_executable_exists() {
680 let mut env = DummyEnv::default();
681 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
682 env.builtins.insert("foo", builtin);
683 env.path = Expansion::from("/bin");
684 env.executables.insert("/bin/foo".to_string());
685
686 assert_matches!(
687 search(&mut env, "foo"),
688 Some(Target::Builtin { builtin: result, path }) => {
689 assert_eq!(result.r#type, builtin.r#type);
690 assert_eq!(*path, *c"/bin/foo");
691 }
692 );
693 assert_matches!(
694 classify(&env, "foo"),
695 Target::Builtin { builtin: result, path } => {
696 assert_eq!(result.r#type, builtin.r#type);
697 assert_eq!(*path, *c"");
698 }
699 );
700 }
701
702 #[test]
703 fn substitutive_builtin_is_not_found_without_external_executable() {
704 let mut env = DummyEnv::default();
705 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
706 env.builtins.insert("foo", builtin);
707
708 let target = search(&mut env, "foo");
709 assert!(target.is_none(), "target = {target:?}");
710 }
711
712 #[test]
713 fn substitutive_builtin_is_classified_even_without_external_executable() {
714 let mut env = DummyEnv::default();
715 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
716 env.builtins.insert("foo", builtin);
717
718 assert_matches!(
719 classify(&env, "foo"),
720 Target::Builtin { builtin: result, path } => {
721 assert_eq!(result.r#type, builtin.r#type);
722 assert_eq!(*path, *c"");
723 }
724 );
725 }
726
727 #[test]
728 fn function_takes_priority_over_substitutive_builtin() {
729 let mut env = DummyEnv::default();
730 let builtin = Builtin::new(Substitutive, |_, _| unreachable!());
731 env.builtins.insert("foo", builtin);
732 env.path = Expansion::from("/bin");
733 env.executables.insert("/bin/foo".to_string());
734
735 let function = Rc::new(Function::new(
736 "foo",
737 function_body_stub(),
738 Location::dummy("location"),
739 ));
740 env.functions.define(function.clone()).unwrap();
741
742 assert_matches!(search(&mut env, "foo"), Some(Target::Function(result)) => {
743 assert_eq!(result, function);
744 });
745 assert_matches!(classify(&env, "foo"), Target::Function(result) => {
746 assert_eq!(result, function);
747 });
748 }
749
750 #[test]
751 fn external_utility_is_found_if_external_executable_exists() {
752 let mut env = DummyEnv::default();
753 env.path = Expansion::from("/bin");
754 env.executables.insert("/bin/foo".to_string());
755
756 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
757 assert_eq!(*path, *c"/bin/foo");
758 });
759 assert_matches!(classify(&env, "foo"), Target::External { path } => {
760 assert_eq!(*path, *c"");
761 });
762 }
763
764 #[test]
765 fn returns_external_utility_if_name_contains_slash() {
766 let mut env = DummyEnv::default();
768 let builtin = Builtin::new(Special, |_, _| unreachable!());
771 env.builtins.insert("bar/baz", builtin);
772
773 assert_matches!(search(&mut env, "bar/baz"), Some(Target::External { path }) => {
774 assert_eq!(*path, *c"bar/baz");
775 });
776 assert_matches!(classify(&env, "bar/baz"), Target::External { path } => {
777 assert_eq!(*path, *c"");
778 });
779 }
780
781 #[test]
782 fn external_target_is_first_executable_found_in_path_scalar() {
783 let mut env = DummyEnv::default();
784 env.path = Expansion::from("/usr/local/bin:/usr/bin:/bin");
785 env.executables.insert("/usr/bin/foo".to_string());
786 env.executables.insert("/bin/foo".to_string());
787
788 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
789 assert_eq!(*path, *c"/usr/bin/foo");
790 });
791
792 env.executables.insert("/usr/local/bin/foo".to_string());
793
794 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
795 assert_eq!(*path, *c"/usr/local/bin/foo");
796 });
797 }
798
799 #[test]
800 fn external_target_is_first_executable_found_in_path_array() {
801 let mut env = DummyEnv::default();
802 env.path = Expansion::from(Value::array(["/usr/local/bin", "/usr/bin", "/bin"]));
803 env.executables.insert("/usr/bin/foo".to_string());
804 env.executables.insert("/bin/foo".to_string());
805
806 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
807 assert_eq!(*path, *c"/usr/bin/foo");
808 });
809
810 env.executables.insert("/usr/local/bin/foo".to_string());
811
812 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
813 assert_eq!(*path, *c"/usr/local/bin/foo");
814 });
815 }
816
817 #[test]
818 fn empty_string_in_path_names_current_directory() {
819 let mut env = DummyEnv::default();
820 env.path = Expansion::from("/x::/y");
821 env.executables.insert("foo".to_string());
822
823 assert_matches!(search(&mut env, "foo"), Some(Target::External { path }) => {
824 assert_eq!(*path, *c"foo");
825 });
826 }
827}