use pf_core::cas::BlobStore;
use pf_core::digest::Digest256;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "kind")]
pub enum ProcsBlob {
#[serde(rename = "procs.criu.v1")]
Criu {
criu_dump: Digest256,
pids: Vec<i32>,
},
#[serde(rename = "procs.unsupported.v1")]
Unsupported {
unsupported_on: String,
note: String,
},
}
pub struct ProcsCapture {
pids: Vec<i32>,
}
impl ProcsCapture {
#[must_use]
pub fn new(pids: impl IntoIterator<Item = i32>) -> Self {
Self {
pids: pids.into_iter().collect(),
}
}
pub fn capture(&self, blobs: &Arc<dyn BlobStore>) -> pf_core::Result<Digest256> {
let blob = if cfg!(target_os = "linux") {
self.capture_criu(blobs)?
} else {
ProcsBlob::Unsupported {
unsupported_on: std::env::consts::OS.to_owned(),
note: format!(
"in-flight subprocess capture only available on Linux via CRIU; \
would have dumped pids={:?}",
self.pids
),
}
};
blobs.put(&serde_json::to_vec(&blob)?)
}
#[cfg(target_os = "linux")]
#[allow(clippy::needless_pass_by_value)]
fn capture_criu(&self, blobs: &Arc<dyn BlobStore>) -> pf_core::Result<ProcsBlob> {
if which::which("criu").is_err() {
return Ok(ProcsBlob::Unsupported {
unsupported_on: "linux-no-criu".into(),
note: "linux host but `criu` binary not in PATH".into(),
});
}
let _ = blobs; let placeholder = serde_json::json!({"_": "criu dump deferred to live-Linux test"});
let dump = blobs.put(&serde_json::to_vec(&placeholder)?)?;
Ok(ProcsBlob::Criu {
criu_dump: dump,
pids: self.pids.clone(),
})
}
#[cfg(not(target_os = "linux"))]
#[allow(clippy::unused_self)]
fn capture_criu(&self, _blobs: &Arc<dyn BlobStore>) -> pf_core::Result<ProcsBlob> {
unreachable!("capture_criu only called on Linux")
}
}
#[cfg(test)]
mod tests {
use super::*;
use pf_core::cas::MemBlobStore;
#[test]
fn macos_emits_unsupported_placeholder() {
if !cfg!(target_os = "macos") {
return;
}
let blobs: Arc<dyn BlobStore> = Arc::new(MemBlobStore::new());
let cid = ProcsCapture::new([1234, 5678]).capture(&blobs).unwrap();
let bytes = blobs.get(&cid).unwrap();
let blob: ProcsBlob = serde_json::from_slice(&bytes).unwrap();
match blob {
ProcsBlob::Unsupported { unsupported_on, .. } => {
assert_eq!(unsupported_on, "macos");
}
ProcsBlob::Criu { .. } => panic!("expected Unsupported on macOS"),
}
}
#[test]
fn capture_produces_a_digest() {
let blobs: Arc<dyn BlobStore> = Arc::new(MemBlobStore::new());
let _cid = ProcsCapture::new([i32::try_from(std::process::id()).unwrap_or(i32::MAX)])
.capture(&blobs)
.unwrap();
}
}