Skip to main content

agentsight_capture/
binary_extractor.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 eunomia-bpf org.
3
4use std::fs;
5use std::io::Write;
6#[cfg(unix)]
7use std::os::unix::fs::PermissionsExt;
8use std::path::{Path, PathBuf};
9use std::sync::{Mutex, OnceLock};
10use tempfile::TempDir;
11#[cfg(target_os = "linux")]
12use tokio::time::{Duration, sleep};
13
14#[cfg(target_os = "linux")]
15const PROCESS_BINARY: &[u8] = include_bytes!("../vendor/bpf/process");
16#[cfg(target_os = "linux")]
17const SSLSNIFF_BINARY: &[u8] = include_bytes!("../vendor/bpf/sslsniff");
18#[cfg(target_os = "linux")]
19const STDIOCAP_BINARY: &[u8] = include_bytes!("../vendor/bpf/stdiocap");
20#[cfg(not(target_os = "linux"))]
21const STDIOCAP_BINARY: &[u8] = &[];
22
23pub struct BinaryExtractor {
24    _temp_dir: TempDir, // Keep alive to prevent cleanup
25    pub process_path: PathBuf,
26    pub sslsniff_path: PathBuf,
27    stdiocap_init_lock: Mutex<()>,
28    stdiocap_path: OnceLock<PathBuf>,
29}
30
31impl BinaryExtractor {
32    #[cfg(target_os = "linux")]
33    pub async fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
34        let temp_dir = TempDir::new()?;
35        let temp_path = temp_dir.path();
36
37        log::debug!("Created temporary directory: {}", temp_path.display());
38
39        // Extract and setup the process binary
40        let process_path = temp_path.join("process");
41        Self::extract_binary(&process_path, PROCESS_BINARY, "process").await?;
42
43        // Extract and setup the sslsniff binary
44        let sslsniff_path = temp_path.join("sslsniff");
45        Self::extract_binary(&sslsniff_path, SSLSNIFF_BINARY, "sslsniff").await?;
46
47        // Small delay to ensure files are fully written
48        sleep(Duration::from_millis(100)).await;
49
50        Ok(Self {
51            _temp_dir: temp_dir,
52            process_path,
53            sslsniff_path,
54            stdiocap_init_lock: Mutex::new(()),
55            stdiocap_path: OnceLock::new(),
56        })
57    }
58
59    #[cfg(not(target_os = "linux"))]
60    pub async fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
61        Err("eBPF capture is available on Linux only; use top, bind, vis, or report on this platform"
62            .into())
63    }
64
65    #[cfg(target_os = "linux")]
66    async fn extract_binary(
67        path: &Path,
68        binary_data: &[u8],
69        name: &str,
70    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
71        {
72            let mut file = fs::File::create(path)?;
73            file.write_all(binary_data)?;
74            file.flush()?;
75        } // File is closed here
76
77        // Make the binary executable
78        set_executable(path)?;
79
80        log::debug!("Extracted {} binary to: {}", name, path.display());
81
82        Ok(())
83    }
84
85    pub fn get_process_path(&self) -> &Path {
86        &self.process_path
87    }
88
89    pub fn get_sslsniff_path(&self) -> &Path {
90        &self.sslsniff_path
91    }
92
93    pub fn get_stdiocap_path(&self) -> Result<&Path, Box<dyn std::error::Error + Send + Sync>> {
94        if let Some(path) = self.stdiocap_path.get() {
95            return Ok(path.as_path());
96        }
97
98        let _guard = self
99            .stdiocap_init_lock
100            .lock()
101            .map_err(|_| std::io::Error::other("stdiocap extraction lock poisoned"))?;
102
103        if let Some(path) = self.stdiocap_path.get() {
104            return Ok(path.as_path());
105        }
106
107        let stdiocap_path = self._temp_dir.path().join("stdiocap");
108        {
109            let mut file = fs::File::create(&stdiocap_path)?;
110            file.write_all(STDIOCAP_BINARY)?;
111            file.flush()?;
112        }
113
114        set_executable(&stdiocap_path)?;
115        log::debug!("Extracted stdiocap binary to: {}", stdiocap_path.display());
116
117        self.stdiocap_path
118            .set(stdiocap_path)
119            .map_err(|_| std::io::Error::other("stdiocap path initialized concurrently"))?;
120
121        Ok(self
122            .stdiocap_path
123            .get()
124            .expect("stdiocap path should be initialized")
125            .as_path())
126    }
127}
128
129#[cfg(unix)]
130fn set_executable(path: &Path) -> std::io::Result<()> {
131    let mut perms = fs::metadata(path)?.permissions();
132    perms.set_mode(0o755);
133    fs::set_permissions(path, perms)
134}
135
136#[cfg(not(unix))]
137fn set_executable(_path: &Path) -> std::io::Result<()> {
138    Ok(())
139}