runproclike 0.1.1

runproclike is a CLI utility that analyzes a running process by its PID and prints the command and its environment in a reproducible form.
use crate::cli::CliArgs;
use procfs::process::Process;

pub struct Executable {
    cli_args: CliArgs,
}

impl Executable {
    pub fn new(cli_args: CliArgs) -> Self {
        Self { cli_args }
    }

    pub fn extract_info(&self) {
        let proc_file = Process::new(self.cli_args.pid).unwrap();

        let cwd_lines = self.extract_cwd(&proc_file);
        let env_lines = self.extract_env_vars(&proc_file);
        let cmd_lines = self.extract_cmdline(&proc_file);

        let mut commands = vec![];
        if !self.cli_args.command_only {
            commands.push(cwd_lines.join("\n"));
            commands.push(env_lines.join("\n"));
        }
        commands.push(cmd_lines.join(""));

        self.extract_user(&proc_file, commands.join("\n"));
    }

    fn extract_user(&self, proc_file: &Process, commands: String) {
        let status = proc_file.status().unwrap();

        println!("sudo -i -u \\#{} <<EOF", status.euid);
        print!("{}", commands);
        println!("EOF");
    }

    fn extract_cwd(&self, proc_file: &Process) -> Vec<String> {
        let mut vec = vec![];
        let cwd = proc_file.cwd().unwrap();

        if !self.cli_args.omit_comments {
            vec.push("# change cwd user to match the target process".to_string());
        }

        vec.push(format!(
            "cd {}",
            cwd.into_os_string().into_string().unwrap()
        ));
        vec
    }

    fn extract_env_vars(&self, proc_file: &Process) -> Vec<String> {
        let mut vec = vec![];
        let environ = proc_file.environ().unwrap();

        if !self.cli_args.omit_comments {
            vec.push("# export env variables to match the target process".to_string());
        }

        for (key, value) in environ.into_iter() {
            vec.push(format!(
                "export {}='{}'",
                key.into_string().unwrap(),
                value.into_string().unwrap()
            ));
        }

        vec
    }

    fn extract_cmdline(&self, proc_file: &Process) -> Vec<String> {
        let mut vec = vec![];
        let cmdline = proc_file.cmdline().unwrap();

        if !self.cli_args.omit_comments {
            vec.push("# change cmdline to match the target process\n".to_string());
        }

        for (i, arg) in cmdline.iter().enumerate() {
            let mut line = String::new();

            if i == 0 {
                line.push_str(arg);
            } else {
                line.push_str(&format!("   {}", arg));
            }

            if i < cmdline.len() - 1 {
                line.push_str(" \\");
            }

            line.push_str("\n");

            vec.push(line);
        }

        vec
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn extract_cwd_helper(executable: Executable, proc_file: &Process) {
        let cwd = proc_file.cwd().unwrap();
        let mut i = 0;

        let result = executable.extract_cwd(&proc_file);

        if !executable.cli_args.omit_comments {
            assert_eq!(result[i], "# change cwd user to match the target process");
            i += 1;
        }

        assert_eq!(
            result[i],
            format!("cd {}", cwd.into_os_string().into_string().unwrap())
        );
    }
    #[test]
    fn test_extract_cwd() {
        let proc_file = Process::myself().unwrap();

        let executable_default = Executable::new(CliArgs {
            pid: proc_file.pid(),
            command_only: false,
            omit_comments: false,
        });

        extract_cwd_helper(executable_default, &proc_file);

        let executable_omit_comm = Executable::new(CliArgs {
            pid: proc_file.pid(),
            command_only: false,
            omit_comments: false,
        });

        extract_cwd_helper(executable_omit_comm, &proc_file);
    }

    fn extract_env_vars_helper(executable: Executable, proc_file: &Process) {
        let environ = proc_file.environ().unwrap();
        let result = executable.extract_env_vars(&proc_file);

        if !executable.cli_args.omit_comments {
            assert_eq!(
                result[0],
                "# export env variables to match the target process"
            );
        }

        for (key, value) in environ.into_iter() {
            let found = result.iter().find(|x| {
                x.contains(&format!(
                    "export {}='{}'",
                    key.clone().into_string().unwrap(),
                    value.clone().into_string().unwrap()
                ))
            });
            assert!(found.is_some());
        }
    }
    #[test]
    fn test_extract_env_vars() {
        let proc_file = Process::myself().unwrap();

        let executable_default = Executable::new(CliArgs {
            pid: proc_file.pid(),
            command_only: false,
            omit_comments: false,
        });

        extract_env_vars_helper(executable_default, &proc_file);

        let executable_omit_comm = Executable::new(CliArgs {
            pid: proc_file.pid(),
            command_only: false,
            omit_comments: true,
        });

        extract_env_vars_helper(executable_omit_comm, &proc_file);
    }

    fn extract_cmdline_helper(executable: Executable, proc_file: &Process) {
        let result = executable.extract_cmdline(&proc_file);
        let cmdline = proc_file.cmdline().unwrap();
        let mut i = 0;

        if !executable.cli_args.omit_comments {
            assert_eq!(result[i], "# change cmdline to match the target process\n");
            i += 1;
        }

        for arg in cmdline.iter() {
            assert_eq!(
                result[i]
                    .trim_start()
                    .trim_end_matches(" \\\n")
                    .trim_end_matches("\n"),
                arg
            );
            i += 1;
        }
    }

    #[test]
    fn test_extract_cmdline() {
        let proc_file = Process::myself().unwrap();
        let executable_default = Executable::new(CliArgs {
            pid: proc_file.pid(),
            command_only: false,
            omit_comments: false,
        });

        extract_cmdline_helper(executable_default, &proc_file);
    }
}