1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use log::debug;
use sha1_smol::{Digest, Sha1};
use tokio::fs::File;
use tokio::io::AsyncReadExt;
#[derive(Debug)]
pub enum IntegrityError {
SizeMismatch{ expect: usize, actual: usize },
Sha1Mismatch{ expect: Digest, actual: Digest }
}
impl Display for IntegrityError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
IntegrityError::SizeMismatch{ expect, actual } =>
write!(f, "size mismatch (expect {expect} bytes, got {actual} bytes)"),
IntegrityError::Sha1Mismatch {expect, actual} =>
write!(f, "sha1 mismatch (expect {expect}, got {actual})")
}
}
}
impl Error for IntegrityError {}
pub fn verify_sha1(expect: Digest, s: &str) -> Result<(), Digest> {
let dig = Sha1::from(s).digest();
if dig == expect {
return Ok(());
}
Err(dig)
}
#[derive(Debug)]
pub enum FileVerifyError {
Integrity(PathBuf, IntegrityError),
Open(PathBuf, tokio::io::Error),
Read(PathBuf, tokio::io::Error),
}
impl Display for FileVerifyError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
FileVerifyError::Integrity(path, e) => write!(f, "file integrity error {}: {}", path.display(), e),
FileVerifyError::Open(path, e) => write!(f, "error opening file {}: {}", path.display(), e),
FileVerifyError::Read(path, e) => write!(f, "error reading file {}: {}", path.display(), e)
}
}
}
impl Error for FileVerifyError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
FileVerifyError::Integrity(_, e) => Some(e),
FileVerifyError::Open(_, e) => Some(e),
FileVerifyError::Read(_, e) => Some(e)
}
}
}
pub async fn verify_file(path: impl AsRef<Path>, expect_size: Option<usize>, expect_sha1: Option<Digest>) -> Result<(), FileVerifyError> {
let path = path.as_ref();
if expect_size.is_none() && expect_sha1.is_none() {
debug!("No size or sha1 for {}, have to assume it's good.", path.display());
return Ok(());
}
let mut file = File::open(path).await.map_err(|e| FileVerifyError::Open(path.to_owned(), e))?;
let mut tally = 0usize;
let mut st = Sha1::new();
let mut buf = [0u8; 4096];
loop {
let n = match file.read(&mut buf).await {
Ok(n) => n,
Err(e) => match e.kind() {
ErrorKind::Interrupted => continue,
_ => return Err(FileVerifyError::Read(path.to_owned(), e))
}
};
if n == 0 {
break;
}
st.update(&buf[..n]);
tally += n;
}
let dig = st.digest();
if expect_size.is_some_and(|sz| sz != tally) {
return Err(FileVerifyError::Integrity(path.to_owned(), IntegrityError::SizeMismatch {
expect: expect_size.unwrap(),
actual: tally
}));
} else if expect_sha1.is_some_and(|exp_dig| exp_dig != dig) {
return Err(FileVerifyError::Integrity(path.to_owned(), IntegrityError::Sha1Mismatch {
expect: expect_sha1.unwrap(),
actual: dig
}));
}
Ok(())
}
|