Skip to main content

rto_exec/
runtime_pins.rs

1// The pinned sandbox-runtime archives, and the host-platform selection.
2//
3// # Why this file is `include!`d rather than imported
4//
5// These pins are needed in two places that cannot share a crate graph: this
6// library, which provisions and verifies the archive, and `build.rs`, which
7// refuses to build `exec-boxlite` against anything else. A build script cannot
8// depend on the crate it builds, so the single source of truth is this file and
9// `build.rs` pulls it in with `include!`.
10//
11// That constrains what may appear here: **no `use`, no `crate::` paths, no
12// references to anything outside this file.** It must compile standalone.
13//
14// # What is pinned, and why it has to be
15//
16// `boxlite` does not build a hypervisor when it is compiled from crates.io. Its
17// three `-sys` crates each detect a published package (`.cargo_vcs_info.json`)
18// and disable themselves, and `libkrun-sys` excludes the sources they would
19// otherwise build. What actually runs is a prebuilt tarball that `boxlite`'s own
20// `build.rs` fetches with a bare `curl -fsSL`, `include_bytes!`s into the rlib,
21// and extracts and executes at run time.
22//
23// That fetch has **no expected digest of any kind** — searched for one four
24// ways (`expected|_SHA256|checksum|digest`; `sha256|integrity|signature|cosign`;
25// and two 64-hex-literal patterns over `build.rs` and `src/`), all NOT FOUND —
26// and its URL is overridable through `BOXLITE_RUNTIME_URL`. Two builds of the
27// same crate version can therefore embed different bytes, undetectably.
28//
29// Roteiro will not ship that. The digests below were computed from the real
30// v0.9.7 release assets, and are what makes the embedded runtime reproducible:
31// `roteiro security prefetch --allow-download` fetches and verifies the archive
32// against them, and `build.rs` then refuses to build unless `BOXLITE_RUNTIME_URL`
33// points at a local file whose bytes match. `boxlite`'s `curl` never reaches the
34// network, because the `file://` URL it is given is already on disk.
35//
36// Bump these together with the `boxlite` pin in `Cargo.toml`; a version skew is
37// caught by `build.rs` rather than discovered at run time.
38
39/// One platform's prebuilt sandbox-runtime archive.
40///
41/// The `target` names are `boxlite`'s own, from its `runtime_target()` — they
42/// are what appears in the release asset's filename, so they are the identifiers
43/// that can actually be checked against upstream.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct PinnedArchive {
46    /// The platform, as the upstream release names it.
47    pub target: &'static str,
48    /// Where the archive is published.
49    pub url: &'static str,
50    /// Lowercase hex SHA-256 of the archive. Verified before it is installed and
51    /// again before it is built against.
52    pub sha256: &'static str,
53    /// Its exact size. Redundant with the digest, and kept because a truncated
54    /// body is the common failure and "expected 26520984 bytes, got 1043" says
55    /// so far more clearly than two digests that differ.
56    pub bytes: u64,
57}
58
59/// The `boxlite` release these archives belong to.
60///
61/// Checked against `boxlite`'s own `CARGO_PKG_VERSION` at build time, so a
62/// dependency bump that forgets these pins fails the build instead of silently
63/// pairing a new library with an old runtime.
64pub const RUNTIME_VERSION: &str = "0.9.7";
65
66/// The asset id the archive is provisioned under.
67pub const RUNTIME_ASSET: &str = "boxlite-runtime";
68
69/// The file name the archive is installed as.
70pub const RUNTIME_FILE: &str = "boxlite-runtime.tar.gz";
71
72/// Every platform Roteiro pins a sandbox runtime for.
73///
74/// These are the three `boxlite` publishes. A host outside this list cannot
75/// build `exec-boxlite`, and is told so by name rather than by a link error.
76pub const RUNTIME_ARCHIVES: &[PinnedArchive] = &[
77    PinnedArchive {
78        target: "darwin-arm64",
79        url: "https://github.com/boxlite-ai/boxlite/releases/download/v0.9.7/boxlite-runtime-v0.9.7-darwin-arm64.tar.gz",
80        sha256: "7f64529978cd2af420411ddfd4cc3b5799ca20234d90346c887cb596d52f8d4e",
81        bytes: 26_520_984,
82    },
83    PinnedArchive {
84        target: "linux-x64-gnu",
85        url: "https://github.com/boxlite-ai/boxlite/releases/download/v0.9.7/boxlite-runtime-v0.9.7-linux-x64-gnu.tar.gz",
86        sha256: "9ae495f55d363e6af04640ab55025ac80b4bf4762e38fa0b8ac80c7604e3148c",
87        bytes: 24_957_005,
88    },
89    PinnedArchive {
90        target: "linux-arm64-gnu",
91        url: "https://github.com/boxlite-ai/boxlite/releases/download/v0.9.7/boxlite-runtime-v0.9.7-linux-arm64-gnu.tar.gz",
92        sha256: "78e978d6398d5a78dc76d675941cb05287e8c70b1b647e98a479058a9652be28",
93        bytes: 28_737_386,
94    },
95];
96
97/// The upstream target name for an `(os, arch)` pair, or `None` for a platform
98/// with no published runtime.
99///
100/// This mirrors `boxlite`'s own `runtime_target()`. It is spelled out rather
101/// than derived so that a platform upstream adds later is a deliberate pin here,
102/// not an automatic one.
103#[must_use]
104pub fn runtime_target(os: &str, arch: &str) -> Option<&'static str> {
105    match (os, arch) {
106        ("macos", "aarch64") => Some("darwin-arm64"),
107        ("linux", "x86_64") => Some("linux-x64-gnu"),
108        ("linux", "aarch64") => Some("linux-arm64-gnu"),
109        _ => None,
110    }
111}
112
113/// The pinned archive for an `(os, arch)` pair.
114#[must_use]
115pub fn archive_for(os: &str, arch: &str) -> Option<&'static PinnedArchive> {
116    let target = runtime_target(os, arch)?;
117    let mut index = 0;
118    // A plain loop rather than an iterator: this file is `include!`d into a
119    // build script, where keeping the surface to the language core is the point.
120    while index < RUNTIME_ARCHIVES.len() {
121        if str_eq(RUNTIME_ARCHIVES[index].target, target) {
122            return Some(&RUNTIME_ARCHIVES[index]);
123        }
124        index += 1;
125    }
126    None
127}
128
129/// Byte equality for two `&str`, usable in the `const`-flavoured context above.
130fn str_eq(a: &str, b: &str) -> bool {
131    a.as_bytes() == b.as_bytes()
132}