Skip to content

Commit 1b15d5e

Browse files
committed
rust: intercept implementation drafted
1 parent f856ccf commit 1b15d5e

File tree

5 files changed

+177
-8
lines changed

5 files changed

+177
-8
lines changed

rust/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ directories = "5.0"
2424
nom = { version = "7.1", default-features = false, features = ["std"] }
2525
regex = "1.9"
2626
shell-words = "1.1"
27+
tempfile = "3.13"
2728

2829
[workspace.package]
2930
version = "4.0.0"

rust/bear/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ nom.workspace = true
4040
regex.workspace = true
4141
crossbeam.workspace = true
4242
crossbeam-channel.workspace = true
43-
rand.workspace = true
43+
rand.workspace = true
44+
tempfile.workspace = true

rust/bear/src/args.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub enum Mode {
4545
/// Represents the execution of a command.
4646
#[derive(Debug, PartialEq)]
4747
pub struct BuildCommand {
48-
arguments: Vec<String>,
48+
pub arguments: Vec<String>,
4949
}
5050

5151
#[derive(Debug, PartialEq)]

rust/bear/src/bin/bear.rs

Lines changed: 172 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
// SPDX-License-Identifier: GPL-3.0-or-later
2-
use std::process::ExitCode;
32

43
use bear::input::EventFileReader;
4+
use bear::intercept::collector::{EventCollector, EventCollectorOnTcp};
5+
use bear::intercept::{Envelope, KEY_DESTINATION, KEY_PRELOAD_PATH};
56
use bear::output::OutputWriter;
67
use bear::recognition::Recognition;
78
use bear::transformation::Transformation;
89
use bear::{args, config};
10+
use crossbeam_channel::{bounded, Receiver};
911
use log;
12+
use std::path::{Path, PathBuf};
13+
use std::process::{Command, ExitCode};
14+
use std::sync::Arc;
15+
use std::{env, thread};
1016

1117
/// Driver function of the application.
1218
fn main() -> anyhow::Result<ExitCode> {
@@ -41,7 +47,7 @@ enum Application {
4147
Intercept {
4248
input: args::BuildCommand,
4349
output: args::BuildEvents,
44-
intercept_config: config::Intercept,
50+
config: config::Intercept,
4551
},
4652
/// The semantic mode we are deduct the semantic meaning of the
4753
/// executed commands from the build process.
@@ -73,7 +79,7 @@ impl Application {
7379
let result = Application::Intercept {
7480
input,
7581
output,
76-
intercept_config,
82+
config: intercept_config,
7783
};
7884
Ok(result)
7985
}
@@ -110,10 +116,38 @@ impl Application {
110116
Application::Intercept {
111117
input,
112118
output,
113-
intercept_config,
119+
config,
114120
} => {
115-
// TODO: Implement the intercept mode.
116-
ExitCode::FAILURE
121+
match &config {
122+
config::Intercept::Wrapper { .. } => {
123+
let service = InterceptService::new()
124+
.expect("Failed to create the intercept service");
125+
let environment = InterceptEnvironment::new(&config, service.address())
126+
.expect("Failed to create the intercept environment");
127+
128+
// start writer thread
129+
let writer_thread = thread::spawn(move || {
130+
let mut writer = std::fs::File::create(output.file_name)
131+
.expect("Failed to create the output file");
132+
for envelope in service.receiver().iter() {
133+
envelope
134+
.write_into(&mut writer)
135+
.expect("Failed to write the envelope");
136+
}
137+
});
138+
139+
let status = environment.execute_build_command(input);
140+
141+
writer_thread
142+
.join()
143+
.expect("Failed to join the writer thread");
144+
145+
status.unwrap_or(ExitCode::FAILURE)
146+
}
147+
config::Intercept::Preload { .. } => {
148+
todo!()
149+
}
150+
}
117151
}
118152
Application::Semantic {
119153
event_source,
@@ -145,3 +179,135 @@ impl Application {
145179
}
146180
}
147181
}
182+
183+
struct InterceptService {
184+
collector: Arc<EventCollectorOnTcp>,
185+
receiver: Receiver<Envelope>,
186+
collector_thread: Option<thread::JoinHandle<()>>,
187+
}
188+
189+
impl InterceptService {
190+
pub fn new() -> anyhow::Result<Self> {
191+
let collector = EventCollectorOnTcp::new()?;
192+
let collector_arc = Arc::new(collector);
193+
let (sender, receiver) = bounded(32);
194+
195+
let collector_in_thread = collector_arc.clone();
196+
let collector_thread = thread::spawn(move || {
197+
collector_in_thread.collect(sender).unwrap();
198+
});
199+
200+
Ok(InterceptService {
201+
collector: collector_arc,
202+
receiver,
203+
collector_thread: Some(collector_thread),
204+
})
205+
}
206+
207+
pub fn receiver(&self) -> Receiver<Envelope> {
208+
self.receiver.clone()
209+
}
210+
211+
pub fn address(&self) -> String {
212+
self.collector.address()
213+
}
214+
}
215+
216+
impl Drop for InterceptService {
217+
fn drop(&mut self) {
218+
self.collector.stop().expect("Failed to stop the collector");
219+
if let Some(thread) = self.collector_thread.take() {
220+
thread.join().expect("Failed to join the collector thread");
221+
}
222+
}
223+
}
224+
225+
enum InterceptEnvironment {
226+
Wrapper {
227+
bin_dir: tempfile::TempDir,
228+
address: String,
229+
},
230+
Preload {
231+
path: PathBuf,
232+
address: String,
233+
},
234+
}
235+
236+
impl InterceptEnvironment {
237+
pub fn new(config: &config::Intercept, address: String) -> anyhow::Result<Self> {
238+
let result = match config {
239+
config::Intercept::Wrapper {
240+
path,
241+
directory,
242+
executables,
243+
} => {
244+
// Create a temporary directory and populate it with the executables.
245+
let bin_dir = tempfile::TempDir::with_prefix_in(directory, "bear-")?;
246+
for executable in executables {
247+
std::fs::hard_link(&executable, &path)?;
248+
}
249+
InterceptEnvironment::Wrapper { bin_dir, address }
250+
}
251+
config::Intercept::Preload { path } => InterceptEnvironment::Preload {
252+
path: path.clone(),
253+
address,
254+
},
255+
};
256+
Ok(result)
257+
}
258+
259+
pub fn execute_build_command(self, input: args::BuildCommand) -> anyhow::Result<ExitCode> {
260+
let environment = self.environment();
261+
let mut child = Command::new(input.arguments[0].clone())
262+
.args(input.arguments)
263+
.envs(environment)
264+
.spawn()?;
265+
266+
let result = child.wait()?;
267+
268+
if result.success() {
269+
Ok(ExitCode::SUCCESS)
270+
} else {
271+
result
272+
.code()
273+
.map_or(Ok(ExitCode::FAILURE), |code| Ok(ExitCode::from(code as u8)))
274+
}
275+
}
276+
277+
fn environment(&self) -> Vec<(String, String)> {
278+
match self {
279+
InterceptEnvironment::Wrapper {
280+
bin_dir, address, ..
281+
} => {
282+
let path_original = env::var("PATH").unwrap_or_else(|_| String::new());
283+
let path_updated = InterceptEnvironment::insert_to_path(
284+
&path_original,
285+
Self::to_string(bin_dir.path()),
286+
);
287+
vec![
288+
("PATH".to_string(), path_updated),
289+
(KEY_DESTINATION.to_string(), address.clone()),
290+
]
291+
}
292+
InterceptEnvironment::Preload { path, address, .. } => {
293+
let path_original = env::var(KEY_PRELOAD_PATH).unwrap_or_else(|_| String::new());
294+
let path_updated =
295+
InterceptEnvironment::insert_to_path(&path_original, Self::to_string(path));
296+
vec![
297+
(KEY_PRELOAD_PATH.to_string(), path_updated),
298+
(KEY_DESTINATION.to_string(), address.clone()),
299+
]
300+
}
301+
}
302+
}
303+
304+
fn insert_to_path(original: &str, first: String) -> String {
305+
let mut paths: Vec<_> = original.split(':').filter(|it| it != &first).collect();
306+
paths.insert(0, first.as_str());
307+
paths.join(":")
308+
}
309+
310+
fn to_string(path: &Path) -> String {
311+
path.to_str().unwrap_or("").to_string()
312+
}
313+
}

rust/bear/src/intercept/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,4 @@ impl Envelope {
108108

109109
/// Declare the environment variable name for the reporter address.
110110
pub const KEY_DESTINATION: &str = "INTERCEPT_REPORTER_ADDRESS";
111+
pub const KEY_PRELOAD_PATH: &str = "LD_PRELOAD";

0 commit comments

Comments
 (0)