agentsight_capture/
binary_extractor.rs1use 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, 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 let process_path = temp_path.join("process");
41 Self::extract_binary(&process_path, PROCESS_BINARY, "process").await?;
42
43 let sslsniff_path = temp_path.join("sslsniff");
45 Self::extract_binary(&sslsniff_path, SSLSNIFF_BINARY, "sslsniff").await?;
46
47 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 } 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}