#![deny(unsafe_code)]
#![warn(missing_docs)]
pub mod crash;
pub mod snapshot;
pub mod probe_diag {
pub mod v1 {
#![allow(missing_docs)]
include!(concat!(
env!("OUT_DIR"),
"/running_process.probe_diag.v1.rs"
));
}
}
#[derive(Debug, Clone, Default)]
pub struct HookConfig {
_private: (),
}
impl HookConfig {
pub fn standard() -> Self {
Self { _private: () }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum HookSupport {
FeatureDisabled,
Available,
Unavailable {
reason: &'static str,
},
}
impl HookSupport {
pub fn as_str(self) -> &'static str {
match self {
HookSupport::FeatureDisabled => "feature-disabled",
HookSupport::Available => "available",
HookSupport::Unavailable { .. } => "unavailable",
}
}
}
pub fn negotiate_hook_support() -> HookSupport {
#[cfg(not(feature = "embed-helper"))]
{
HookSupport::FeatureDisabled
}
#[cfg(feature = "embed-helper")]
{
#[cfg(target_os = "windows")]
{
HookSupport::Available
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
{
HookSupport::Available
}
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
{
HookSupport::Unavailable {
reason: "#551: no injector wired for this OS",
}
}
}
}
#[cfg(feature = "embed-helper")]
pub mod embed {
use std::io;
use std::path::PathBuf;
const CACHE_SUBDIR: &str = "running-process-probe";
pub fn helper_cache_dir() -> io::Result<PathBuf> {
let base = dirs::cache_dir().ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"could not determine OS cache directory via dirs::cache_dir()",
)
})?;
let dir = base.join(CACHE_SUBDIR);
std::fs::create_dir_all(&dir)?;
Ok(dir)
}
pub fn helper_filename() -> String {
let version = env!("CARGO_PKG_VERSION");
let target = std::env::consts::ARCH;
let os = std::env::consts::OS;
let ext = if cfg!(windows) { ".exe" } else { "" };
format!("running-process-probe-agent-{version}-{target}-{os}{ext}")
}
pub fn helper_cache_path() -> io::Result<PathBuf> {
Ok(helper_cache_dir()?.join(helper_filename()))
}
pub fn extract_helper_blob(blob: &[u8]) -> io::Result<PathBuf> {
let path = helper_cache_path()?;
extract_helper_blob_to(&path, blob)
}
pub fn extract_helper_blob_to(path: &std::path::Path, blob: &[u8]) -> io::Result<PathBuf> {
let expected_hash = blake3::hash(blob);
if path.exists() {
if let Ok(existing) = std::fs::read(path) {
if blake3::hash(&existing) == expected_hash {
return Ok(path.to_path_buf());
}
}
}
let tmp = path.with_extension(format!("partial.{}", std::process::id()));
std::fs::write(&tmp, blob)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = std::fs::metadata(&tmp)?.permissions();
perms.set_mode(0o755);
std::fs::set_permissions(&tmp, perms)?;
}
std::fs::rename(&tmp, path)?;
Ok(path.to_path_buf())
}
}
#[cfg(all(feature = "embed-helper", target_os = "windows"))]
pub mod inject_windows;
#[cfg(all(feature = "embed-helper", target_os = "windows"))]
pub use inject_windows::inject_into_pid;
#[cfg(all(
feature = "embed-helper",
any(target_os = "linux", target_os = "macos")
))]
pub mod inject_unix;
#[cfg(all(
feature = "embed-helper",
any(target_os = "linux", target_os = "macos")
))]
pub use inject_unix::{inject_env_name, inject_via_env};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hook_support_string_forms_are_stable() {
assert_eq!(HookSupport::FeatureDisabled.as_str(), "feature-disabled");
assert_eq!(HookSupport::Available.as_str(), "available");
assert_eq!(
HookSupport::Unavailable {
reason: "test reason"
}
.as_str(),
"unavailable"
);
}
#[test]
fn negotiate_default_build_reports_feature_disabled() {
#[cfg(not(feature = "embed-helper"))]
{
assert_eq!(negotiate_hook_support(), HookSupport::FeatureDisabled);
}
#[cfg(feature = "embed-helper")]
{
let s = negotiate_hook_support();
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
assert_eq!(s, HookSupport::Available);
#[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
assert!(matches!(s, HookSupport::Unavailable { reason } if reason.contains("#551")));
}
}
#[test]
fn standard_hook_config_constructs() {
let _ = HookConfig::standard();
}
#[cfg(feature = "embed-helper")]
mod embed_tests {
use super::super::embed::*;
#[test]
fn helper_cache_dir_creates_and_returns_a_path() {
let p = helper_cache_dir().expect("cache dir");
assert!(
p.exists() && p.is_dir(),
"expected cache dir to exist, got {p:?}"
);
assert!(
p.ends_with("running-process-probe"),
"expected cache path to end in running-process-probe, got {p:?}"
);
}
#[test]
fn helper_filename_carries_version_and_arch() {
let name = helper_filename();
assert!(name.starts_with("running-process-probe-agent-"));
assert!(
name.contains(env!("CARGO_PKG_VERSION")),
"filename must carry the crate version: {name}"
);
#[cfg(windows)]
assert!(
name.ends_with(".exe"),
"Windows filename needs .exe: {name}"
);
#[cfg(not(windows))]
assert!(
!name.contains(".exe"),
"Unix filename must not have .exe: {name}"
);
}
#[test]
fn extract_helper_blob_writes_and_is_idempotent() {
let tmp = tempfile::tempdir().expect("tempdir");
let path = tmp.path().join("helper-bin");
let blob: &[u8] = b"#!/bin/sh\necho stub helper bytes\n";
let p1 = extract_helper_blob_to(&path, blob).expect("first extract");
assert!(p1.exists(), "extracted file should exist at {p1:?}");
let read1 = std::fs::read(&p1).expect("read back");
assert_eq!(read1, blob, "extracted bytes must match input");
let p2 = extract_helper_blob_to(&path, blob).expect("second extract");
assert_eq!(p1, p2, "idempotent re-extract should return same path");
let blob2: &[u8] = b"#!/bin/sh\necho different stub\n";
let p3 = extract_helper_blob_to(&path, blob2).expect("third extract");
assert_eq!(p1, p3);
let read3 = std::fs::read(&p3).expect("read back v2");
assert_eq!(read3, blob2, "rewrite must replace contents");
}
#[cfg(unix)]
#[test]
fn extract_helper_blob_sets_executable_bit_on_unix() {
use std::os::unix::fs::PermissionsExt;
let tmp = tempfile::tempdir().expect("tempdir");
let path = tmp.path().join("helper-bin");
let blob: &[u8] = b"#!/bin/sh\nexit 0\n";
let p = extract_helper_blob_to(&path, blob).expect("extract");
let mode = std::fs::metadata(&p).expect("stat").permissions().mode();
assert_ne!(mode & 0o100, 0, "owner exec bit missing: mode=0o{:o}", mode);
}
#[test]
fn extract_helper_blob_smoke_test_against_real_cache_dir() {
let blob: &[u8] = b"smoke-test-distinctive-blob-marker\n";
let p = extract_helper_blob(blob).expect("smoke extract");
assert!(p.exists());
let read_back = std::fs::read(&p).expect("read");
assert_eq!(read_back, blob);
let _ = std::fs::remove_file(&p);
}
}
}