hashdeep_compare/
command.rs1use std::process::{Command,Stdio};
2use std::fs::OpenOptions;
3use std::io::ErrorKind;
4
5use thiserror::Error;
6use anyhow::anyhow;
7use which::which;
8
9const CANNOT_FIND_BINARY_PATH_STR : &str = "external hashdeep binary cannot be found (is hashdeep installed?)";
10
11
12#[derive(Error, Debug)]
13pub enum RunHashdeepCommandError {
14
15 #[error("{}",CANNOT_FIND_BINARY_PATH_STR)]
16 CannotFindBinaryPath,
17
18 #[error("{0} exists (will not overwrite existing files)")]
19 OutputFileExists(String),
20
21 #[error("{0} and {1} exist (will not overwrite existing files)")]
22 OutputFilesExist(String, String),
23
24 #[error("\"{0}\" cannot be opened for writing (does the directory exist?)")]
25 OutputFileNotFound(String),
26
27 #[error("\"{0}\" cannot be opened for writing ({})", .1)]
28 OutputFileOtherError(String, #[source] std::io::Error),
29
30 #[error(transparent)]
31 Io(#[from] std::io::Error),
32
33 #[error(transparent)]
34 Other(#[from] anyhow::Error),
35}
36
37impl RunHashdeepCommandError {
38
39 fn new(e: std::io::Error, path: &str) -> Self {
40
41 match e.kind() {
42 ErrorKind::AlreadyExists => RunHashdeepCommandError::OutputFileExists(path.to_string()),
43 ErrorKind::NotFound => RunHashdeepCommandError::OutputFileNotFound(path.to_string()),
44 _ => RunHashdeepCommandError::OutputFileOtherError(path.to_string(), e),
45 }
46 }
47}
48
49pub fn run_hashdeep_command(
63 target_directory: &str,
64 output_path_base: &str,
65 hashdeep_command_name: &str,
66) -> Result<(), RunHashdeepCommandError> {
67
68 match which(hashdeep_command_name) {
70 Err(which::Error::CannotFindBinaryPath) => return Err(RunHashdeepCommandError::CannotFindBinaryPath),
71 Err(x) => return Err(anyhow!(x).into()),
72 _ => ()
73 };
74
75 let error_log_suffix = ".errors";
76
77 let output_error_path = format!("{output_path_base}{error_log_suffix}");
78
79
80 let maybe_output_file =
82 OpenOptions::new().write(true).create_new(true).open(output_path_base)
83 .map_err(|e| RunHashdeepCommandError::new(e, output_path_base));
84
85 let maybe_error_file =
86 OpenOptions::new().write(true).create_new(true).open(&output_error_path)
87 .map_err(|e| RunHashdeepCommandError::new(e, &output_error_path));
88
89
90 let (output_file, error_file) =
91
92 match (maybe_output_file, maybe_error_file) {
93
94 (Ok(output_file), Ok(error_file)) => (output_file, error_file),
95
96
97 (Err(output_file_error), Ok(_)) => {
100
101 std::fs::remove_file(&output_error_path)?;
103
104 return Err(output_file_error);
105 },
106
107 (Ok(_), Err(error_file_error)) => {
108
109 std::fs::remove_file(output_path_base)?;
111
112 return Err(error_file_error);
113 },
114
115 (Err(output_file_error), Err(error_file_error)) => {
116
117 return Err(
119 if let ( RunHashdeepCommandError::OutputFileExists(file1),
120 RunHashdeepCommandError::OutputFileExists(file2) )
121 = (&output_file_error, &error_file_error)
122 {
123 RunHashdeepCommandError::OutputFilesExist(file1.clone(), file2.clone())
124 }
125 else {
126 output_file_error
128 }
129 );
130 }
131 };
132
133 Command::new(hashdeep_command_name)
134
135 .arg("-l")
136 .arg("-r")
137 .arg("-o").arg("f")
138 .arg(target_directory)
139
140 .stdin(Stdio::null())
141 .stdout(output_file)
142 .stderr(error_file)
143
144 .status()?;
145 Ok(())
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn run_hashdeep_command_missing_hashdeep_test() {
154
155 assert_eq!(CANNOT_FIND_BINARY_PATH_STR,
159
160 run_hashdeep_command("fake_target_dir",
161 "fake_output_path_base",
162 "nonexistent_program_name_Cmn2TMmwGO9U2j7")
163 .unwrap_err().to_string()
164 );
165 }
166}