Skip to main content

microsandbox_utils/
lib.rs

1//! Shared constants and utilities for the microsandbox project.
2
3pub mod copy;
4pub mod format;
5pub mod log_text;
6pub mod size;
7pub mod ttl_reverse_index;
8pub mod wake_pipe;
9
10//--------------------------------------------------------------------------------------------------
11// Constants: Directory Layout
12//--------------------------------------------------------------------------------------------------
13
14/// Name of the microsandbox home directory (relative to user's home).
15pub const BASE_DIR_NAME: &str = ".microsandbox";
16
17/// Subdirectory for shared libraries (libkrunfw).
18pub const LIB_SUBDIR: &str = "lib";
19
20/// Subdirectory for helper binaries.
21pub const BIN_SUBDIR: &str = "bin";
22
23/// Subdirectory for the database.
24pub const DB_SUBDIR: &str = "db";
25
26/// Subdirectory for OCI layer cache.
27pub const CACHE_SUBDIR: &str = "cache";
28
29/// Subdirectory for per-sandbox state.
30pub const SANDBOXES_SUBDIR: &str = "sandboxes";
31
32/// Subdirectory for named volumes.
33pub const VOLUMES_SUBDIR: &str = "volumes";
34
35/// Subdirectory for snapshot artifacts.
36pub const SNAPSHOTS_SUBDIR: &str = "snapshots";
37
38/// Subdirectory for logs.
39pub const LOGS_SUBDIR: &str = "logs";
40
41/// Subdirectory for secrets.
42pub const SECRETS_SUBDIR: &str = "secrets";
43
44/// Subdirectory for TLS certificates.
45pub const TLS_SUBDIR: &str = "tls";
46
47/// Subdirectory for SSH keys.
48pub const SSH_SUBDIR: &str = "ssh";
49
50/// Subdirectory for ephemeral runtime artifacts that should not be backed up.
51pub const RUN_SUBDIR: &str = "run";
52
53/// Subdirectory under `run` for metrics-related diagnostic artifacts.
54pub const METRICS_RUN_SUBDIR: &str = "metrics";
55
56/// Prefix used when constructing the POSIX shared-memory object name for the
57/// live metrics registry. Combined with a stable hash of `GlobalConfig::home()`
58/// so concurrent `MSB_HOME`-isolated environments do not collide.
59///
60/// Kept short because macOS limits `shm_open` names to ~31 bytes including the
61/// leading slash; the final form is `<prefix>-<hex16>-vN` (28 bytes for
62/// single-digit ABI versions).
63pub const METRICS_SHM_PREFIX: &str = "/msb-met";
64
65//--------------------------------------------------------------------------------------------------
66// Constants: Binary Names
67//--------------------------------------------------------------------------------------------------
68
69/// Guest agent binary name.
70pub const AGENTD_BINARY: &str = "agentd";
71
72/// CLI binary name.
73pub const MSB_BINARY: &str = "msb";
74
75//--------------------------------------------------------------------------------------------------
76// Constants: Versions
77//--------------------------------------------------------------------------------------------------
78
79/// Version for downloading prebuilt release artifacts.
80///
81/// This tracks the published crate/package version so the SDK and the
82/// downloaded runtime bundle stay aligned.
83pub const PREBUILT_VERSION: &str = env!("CARGO_PKG_VERSION");
84
85/// libkrunfw release version. Keep in sync with justfile.
86pub const LIBKRUNFW_VERSION: &str = "5.2.1";
87
88/// libkrunfw ABI version (soname major). Keep in sync with justfile.
89pub const LIBKRUNFW_ABI: &str = "5";
90
91//--------------------------------------------------------------------------------------------------
92// Constants: Filenames
93//--------------------------------------------------------------------------------------------------
94
95/// Database filename.
96pub const DB_FILENAME: &str = "msb.db";
97
98/// Global configuration filename.
99pub const CONFIG_FILENAME: &str = "config.json";
100
101/// Project-local sandbox configuration filename.
102pub const SANDBOXFILE_NAME: &str = "Sandboxfile";
103
104//--------------------------------------------------------------------------------------------------
105// Constants: GitHub
106//--------------------------------------------------------------------------------------------------
107
108/// GitHub organization.
109pub const GITHUB_ORG: &str = "superradcompany";
110
111/// Main repository name.
112pub const MICROSANDBOX_REPO: &str = "microsandbox";
113
114//--------------------------------------------------------------------------------------------------
115// Functions
116//--------------------------------------------------------------------------------------------------
117
118/// Derive a short, stable identifier from a path.
119///
120/// Used to build a POSIX shared-memory object name that depends only on the
121/// resolved home directory, so two processes pointed at the same `MSB_HOME`
122/// agree on a single registry without leaking the absolute path through a
123/// public name.
124pub fn stable_hash_path(path: &std::path::Path) -> String {
125    // Avoid pulling sha2 into the utils crate for one filename; a stable
126    // 64-bit FNV-1a over the OS-bytes is plenty for collision-resistance at
127    // this scale (one entry per concurrent MSB_HOME on a host).
128    let bytes = path.as_os_str().as_encoded_bytes();
129    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
130    for byte in bytes {
131        hash ^= u64::from(*byte);
132        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
133    }
134    format!("{hash:016x}")
135}
136
137/// Filename of the optional registry-name diagnostic file under `run/metrics`.
138pub fn metrics_registry_name_filename(registry_abi_version: u32) -> String {
139    format!("registry-v{registry_abi_version}.name")
140}
141
142/// Derive the POSIX shared-memory object name for a metrics registry.
143pub fn metrics_registry_shm_name(home: &std::path::Path, registry_abi_version: u32) -> String {
144    format!(
145        "{}-{}-v{}",
146        METRICS_SHM_PREFIX,
147        stable_hash_path(home),
148        registry_abi_version
149    )
150}
151
152/// Resolve the microsandbox home directory.
153///
154/// Order of resolution:
155/// 1. `MSB_HOME` env var (used as-is, no `.microsandbox` suffix appended)
156/// 2. `~/.microsandbox/` (i.e. `dirs::home_dir().join(BASE_DIR_NAME)`)
157/// 3. `./.microsandbox/` if no home is available
158///
159/// `MSB_HOME` lets CI and integration tests isolate microsandbox state
160/// (db, sandboxes, cache, logs) per process without disturbing other
161/// `$HOME`-rooted tooling.
162pub fn resolve_home() -> std::path::PathBuf {
163    if let Some(path) = std::env::var_os("MSB_HOME") {
164        return std::path::PathBuf::from(path);
165    }
166    dirs::home_dir()
167        .unwrap_or_else(|| std::path::PathBuf::from("."))
168        .join(BASE_DIR_NAME)
169}
170
171/// Returns the platform-specific libkrunfw filename.
172pub fn libkrunfw_filename(os: &str) -> String {
173    if os == "macos" {
174        format!("libkrunfw.{LIBKRUNFW_ABI}.dylib")
175    } else {
176        format!("libkrunfw.so.{LIBKRUNFW_VERSION}")
177    }
178}
179
180/// Returns the GitHub release download URL for libkrunfw.
181pub fn libkrunfw_download_url(version: &str, arch: &str, os: &str) -> String {
182    let (target_os, ext) = if os == "macos" {
183        ("darwin", "dylib")
184    } else {
185        ("linux", "so")
186    };
187
188    format!(
189        "https://github.com/{GITHUB_ORG}/{MICROSANDBOX_REPO}/releases/download/v{version}/libkrunfw-{target_os}-{arch}.{ext}"
190    )
191}
192
193/// Returns the GitHub release download URL for the agentd binary.
194pub fn agentd_download_url(version: &str, arch: &str) -> String {
195    format!(
196        "https://github.com/{GITHUB_ORG}/{MICROSANDBOX_REPO}/releases/download/v{version}/{AGENTD_BINARY}-{arch}"
197    )
198}
199
200/// Returns the GitHub release download URL for the microsandbox bundle tarball.
201pub fn bundle_download_url(version: &str, arch: &str, os: &str) -> String {
202    let target_os = if os == "macos" { "darwin" } else { "linux" };
203    format!(
204        "https://github.com/{GITHUB_ORG}/{MICROSANDBOX_REPO}/releases/download/v{version}/{MICROSANDBOX_REPO}-{target_os}-{arch}.tar.gz"
205    )
206}
207
208/// Returns an HTTP client configured for release asset downloads.
209#[cfg(feature = "http-client")]
210pub fn http_client() -> ureq::Agent {
211    ureq::Agent::config_builder()
212        .tls_config(
213            ureq::tls::TlsConfig::builder()
214                .root_certs(ureq::tls::RootCerts::PlatformVerifier)
215                .build(),
216        )
217        .build()
218        .new_agent()
219}
220
221/// Returns true when a user-provided text value should be interpreted as a
222/// local filesystem path rather than a named resource or OCI reference.
223pub fn looks_like_local_path_text(s: &str) -> bool {
224    s == "." || s == ".." || s.starts_with('/') || s.starts_with("./") || s.starts_with("../")
225}
226
227//--------------------------------------------------------------------------------------------------
228// Tests
229//--------------------------------------------------------------------------------------------------
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    /// `MSB_HOME` is honoured verbatim (no `.microsandbox` suffix appended)
236    /// so callers can isolate state per process without disturbing tooling
237    /// that reads `$HOME` (npm cache, ssh keys, etc.).
238    ///
239    /// Uses a unique env var per test process to avoid clashing with other
240    /// parallel tests that read `MSB_HOME`.
241    #[test]
242    fn test_resolve_home_respects_env_override() {
243        // SAFETY: This test sets a process-global env var. Vitest-style
244        // single-test isolation isn't available; rely on the test being
245        // the sole reader of `MSB_HOME` in this binary.
246        let custom = std::path::PathBuf::from("/tmp/msb-home-resolve-test-12345");
247        unsafe { std::env::set_var("MSB_HOME", &custom) };
248        let resolved = resolve_home();
249        unsafe { std::env::remove_var("MSB_HOME") };
250        assert_eq!(resolved, custom);
251    }
252
253    #[test]
254    fn test_metrics_registry_names_include_abi_version() {
255        let home = std::path::Path::new("/tmp/msb-home");
256
257        assert_eq!(metrics_registry_name_filename(2), "registry-v2.name");
258        assert_eq!(
259            metrics_registry_shm_name(home, 2),
260            format!("{}-{}-v2", METRICS_SHM_PREFIX, stable_hash_path(home))
261        );
262    }
263}