1use nix::{
2 errno::Errno,
3 unistd::{access, AccessFlags},
4};
5use std::{env, ffi::CString, ffi::NulError, path::Path, result};
6
7use crate::diag::{Error, Result};
8
9fn is_executable(path: &Path) -> bool {
10 access(path.to_str().unwrap_or(""), AccessFlags::X_OK).is_ok()
11}
12
13fn find_executable_in_path(file_name: &str) -> Option<String> {
14 env::var_os("PATH").and_then(|paths| {
15 env::split_paths(&paths).find_map(|dir| {
16 let full_path = dir.join(file_name);
17 if is_executable(&full_path) {
18 full_path.to_str().map(String::from)
19 } else {
20 None
21 }
22 })
23 })
24}
25
26pub fn args() -> Result<Vec<CString>> {
42 let mut args_iter = env::args().skip(1);
43 let this = env::args()
44 .next()
45 .ok_or_else(|| Error::from(Errno::EINVAL))?;
46 let cmd = args_iter.next().map_or_else(
47 || {
48 eprintln!("Usage: {this} command [args]");
49 Err(Error::from(Errno::EINVAL))
50 },
51 Ok,
52 )?;
53
54 let mut args: Vec<CString> = args_iter
55 .map(CString::new)
56 .collect::<result::Result<_, NulError>>()
57 .map_err(Error::from)?;
58
59 let executable_path = if is_executable(Path::new(&cmd)) {
60 cmd
61 } else {
62 find_executable_in_path(&cmd).ok_or_else(|| Error::from(Errno::ENOENT))?
63 };
64
65 args.insert(0, CString::new(executable_path).map_err(Error::from)?);
66 Ok(args)
67}
68
69pub fn env() -> Result<Vec<CString>> {
83 env::vars_os()
84 .map(|(key, val)| {
85 let e = "Error: OsString conversion failed";
86 let key_str =
87 key.into_string().map_err(|_| Error::from(e.to_string()))?;
88 let val_str =
89 val.into_string().map_err(|_| Error::from(e.to_string()))?;
90 let env_str = format!("{key_str}={val_str}");
91 CString::new(env_str).map_err(Error::from)
92 })
93 .collect()
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_is_executable() {
102 assert!(is_executable(Path::new("/bin/ls")));
103 assert!(!is_executable(Path::new("/bin/nonexistent")));
104 }
105
106 #[test]
107 fn test_find_executable_in_path() {
108 assert!(find_executable_in_path("ls").is_some());
109 assert!(find_executable_in_path("nonexistent").is_none());
110 }
111
112 #[test]
113 fn test_env() {
114 env::set_var("TEST_ENV_VAR", "test_value");
115
116 let result = env();
117 assert!(result.is_ok());
118 let cstrings = result.expect("Failed to get environment variables");
119 let env_var = CString::new("TEST_ENV_VAR=test_value")
120 .expect("Failed to create CString");
121 assert!(cstrings.contains(&env_var));
122
123 env::remove_var("TEST_ENV_VAR");
124 }
125}