Skip to main content

sys_rs/
input.rs

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
26/// Parses command line arguments and returns them as a vector of `CString`.
27///
28/// # Arguments
29///
30/// This function does not take parameters; it reads `env::args()` for the
31/// current process command line.
32///
33/// # Errors
34///
35/// This function will return an `Err` if no command is provided, if the command is not found,
36/// if it is not executable, or if there is an error converting arguments to `CString`.
37///
38/// # Returns
39///
40/// Returns a `Result` containing a vector of `CString` representing the command line arguments, or an `Err` if there was an error.
41pub 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
69/// Retrieves environment variables and returns them as a vector of `CString`.
70///
71/// # Arguments
72///
73/// This function does not take parameters; it reads the current process environment.
74///
75/// # Errors
76///
77/// This function will return an `Err` if there is an error converting environment variables to `CString`.
78///
79/// # Returns
80///
81/// Returns a vector of `CString` representing the environment variables.
82pub 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}