aion_server/worker/deployed_binary.rs
1//! The identity a worker-deployment record captures for the binary it names.
2//!
3//! A `builtin` deployment names THIS server's executable, and the record has to
4//! say which executable that was when the record was written — the content, not
5//! the path, because a path is a label recording where a file used to be. The
6//! supervisor captures the identity again at every spawn, and the two
7//! disagreeing is how a restart across an upgrade becomes a visible fact
8//! instead of a silent swap.
9//!
10//! This lives in the worker domain rather than on a transport because both
11//! transports and auto-provision mint records, and three copies of one capture
12//! is three chances for them to disagree about what a deployment's binary is.
13
14use aion_store::DeployedBinaryIdentity;
15
16use crate::build_identity::BuildIdentity;
17use sha2::{Digest, Sha256};
18
19/// Typed deploy-time executable identity capture failures.
20#[derive(Debug, thiserror::Error)]
21pub enum BinaryIdentityCaptureError {
22 /// The operating system could not identify this executable.
23 #[error("could not resolve the running server executable: {source}")]
24 CurrentExecutable {
25 /// The operating system's own diagnosis.
26 #[source]
27 source: std::io::Error,
28 },
29 /// The executable bytes could not be read.
30 #[error("could not read running server executable `{path}`: {source}")]
31 ReadExecutable {
32 /// The executable whose bytes could not be read.
33 path: std::path::PathBuf,
34 /// The operating system's own diagnosis.
35 #[source]
36 source: std::io::Error,
37 },
38}
39
40/// Capture the running server executable's identity for a deployment record.
41///
42/// # Errors
43///
44/// Returns [`BinaryIdentityCaptureError`] when the operating system cannot name
45/// this executable or its bytes cannot be read. Neither is defaulted: a record
46/// carrying an invented hash would report every future upgrade as a match.
47pub fn capture_binary_identity() -> Result<DeployedBinaryIdentity, BinaryIdentityCaptureError> {
48 let path = std::env::current_exe()
49 .map_err(|source| BinaryIdentityCaptureError::CurrentExecutable { source })?;
50 let bytes =
51 std::fs::read(&path).map_err(|source| BinaryIdentityCaptureError::ReadExecutable {
52 path: path.clone(),
53 source,
54 })?;
55 let identity = BuildIdentity::current();
56 let content_hash = lowercase_hex(&Sha256::digest(bytes));
57 Ok(DeployedBinaryIdentity {
58 version: identity.version.to_owned(),
59 commit: identity.commit.to_owned(),
60 dirty: identity.dirty.to_owned(),
61 content_hash,
62 })
63}
64
65/// Lowercase hexadecimal rendering of a digest.
66///
67/// Written out rather than pulled from a formatting helper so the wire form of
68/// a content hash is decided in one place and cannot acquire a `0x`, an
69/// uppercase run, or a separator from a dependency's default.
70#[must_use]
71pub fn lowercase_hex(bytes: &[u8]) -> String {
72 const DIGITS: &[u8; 16] = b"0123456789abcdef";
73 let mut encoded = String::with_capacity(bytes.len().saturating_mul(2));
74 for byte in bytes {
75 encoded.push(char::from(DIGITS[usize::from(byte >> 4)]));
76 encoded.push(char::from(DIGITS[usize::from(byte & 0x0f)]));
77 }
78 encoded
79}