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
//! The identity a worker-deployment record captures for the binary it names.
//!
//! A `builtin` deployment names THIS server's executable, and the record has to
//! say which executable that was when the record was written — the content, not
//! the path, because a path is a label recording where a file used to be. The
//! supervisor captures the identity again at every spawn, and the two
//! disagreeing is how a restart across an upgrade becomes a visible fact
//! instead of a silent swap.
//!
//! This lives in the worker domain rather than on a transport because both
//! transports and auto-provision mint records, and three copies of one capture
//! is three chances for them to disagree about what a deployment's binary is.
use aion_store::DeployedBinaryIdentity;
use crate::build_identity::BuildIdentity;
use sha2::{Digest, Sha256};
/// Typed deploy-time executable identity capture failures.
#[derive(Debug, thiserror::Error)]
pub enum BinaryIdentityCaptureError {
/// The operating system could not identify this executable.
#[error("could not resolve the running server executable: {source}")]
CurrentExecutable {
/// The operating system's own diagnosis.
#[source]
source: std::io::Error,
},
/// The executable bytes could not be read.
#[error("could not read running server executable `{path}`: {source}")]
ReadExecutable {
/// The executable whose bytes could not be read.
path: std::path::PathBuf,
/// The operating system's own diagnosis.
#[source]
source: std::io::Error,
},
}
/// Capture the running server executable's identity for a deployment record.
///
/// # Errors
///
/// Returns [`BinaryIdentityCaptureError`] when the operating system cannot name
/// this executable or its bytes cannot be read. Neither is defaulted: a record
/// carrying an invented hash would report every future upgrade as a match.
pub fn capture_binary_identity() -> Result<DeployedBinaryIdentity, BinaryIdentityCaptureError> {
let path = std::env::current_exe()
.map_err(|source| BinaryIdentityCaptureError::CurrentExecutable { source })?;
let bytes =
std::fs::read(&path).map_err(|source| BinaryIdentityCaptureError::ReadExecutable {
path: path.clone(),
source,
})?;
let identity = BuildIdentity::current();
let content_hash = lowercase_hex(&Sha256::digest(bytes));
Ok(DeployedBinaryIdentity {
version: identity.version.to_owned(),
commit: identity.commit.to_owned(),
dirty: identity.dirty.to_owned(),
content_hash,
})
}
/// Lowercase hexadecimal rendering of a digest.
///
/// Written out rather than pulled from a formatting helper so the wire form of
/// a content hash is decided in one place and cannot acquire a `0x`, an
/// uppercase run, or a separator from a dependency's default.
#[must_use]
pub fn lowercase_hex(bytes: &[u8]) -> String {
const DIGITS: &[u8; 16] = b"0123456789abcdef";
let mut encoded = String::with_capacity(bytes.len().saturating_mul(2));
for byte in bytes {
encoded.push(char::from(DIGITS[usize::from(byte >> 4)]));
encoded.push(char::from(DIGITS[usize::from(byte & 0x0f)]));
}
encoded
}