Skip to content

Commit 1f2e11b

Browse files
committed
rust: intercept implementation drafted
1 parent 2dec5ce commit 1f2e11b

File tree

5 files changed

+176
-8
lines changed

5 files changed

+176
-8
lines changed

rust/Cargo.toml

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

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

rust/bear/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ nom.workspace = true
4141
regex.workspace = true
4242
crossbeam.workspace = true
4343
crossbeam-channel.workspace = true
44-
rand.workspace = true
44+
rand.workspace = true
45+
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: 171 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

3+
use std::path::{Path, PathBuf};
4+
use std::process::{Command, ExitCode};
5+
use std::sync::Arc;
6+
use std::{env, thread};
7+
use crossbeam_channel::{bounded, Receiver};
48
use bear::input::EventFileReader;
59
use bear::output::OutputWriter;
610
use bear::recognition::Recognition;
711
use bear::transformation::Transformation;
812
use bear::{args, config};
913
use log;
14+
use bear::intercept::collector::{EventCollector, EventCollectorOnTcp};
15+
use bear::intercept::{Envelope, KEY_DESTINATION, KEY_PRELOAD_PATH};
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,36 @@ 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.write_into(&mut writer)
134+
.expect("Failed to write the envelope");
135+
}
136+
});
137+
138+
let status = environment.execute_build_command(input);
139+
140+
writer_thread.join()
141+
.expect("Failed to join the writer thread");
142+
143+
status.unwrap_or(ExitCode::FAILURE)
144+
}
145+
config::Intercept::Preload { .. } => {
146+
todo!()
147+
}
148+
}
117149
}
118150
Application::Semantic {
119151
event_source,
@@ -145,3 +177,136 @@ impl Application {
145177
}
146178
}
147179
}
180+
181+
struct InterceptService {
182+
collector: Arc<EventCollectorOnTcp>,
183+
receiver: Receiver<Envelope>,
184+
collector_thread: Option<thread::JoinHandle<()>>,
185+
}
186+
187+
impl InterceptService {
188+
pub fn new() -> anyhow::Result<Self> {
189+
let collector = EventCollectorOnTcp::new()?;
190+
let collector_arc = Arc::new(collector);
191+
let (sender, receiver) = bounded(32);
192+
193+
let collector_in_thread = collector_arc.clone();
194+
let collector_thread = thread::spawn(move || {
195+
collector_in_thread.collect(sender).unwrap();
196+
});
197+
198+
Ok(InterceptService {
199+
collector: collector_arc,
200+
receiver,
201+
collector_thread: Some(collector_thread),
202+
})
203+
}
204+
205+
pub fn receiver(&self) -> Receiver<Envelope> {
206+
self.receiver.clone()
207+
}
208+
209+
pub fn address(&self) -> String {
210+
self.collector.address()
211+
}
212+
}
213+
214+
impl Drop for InterceptService {
215+
fn drop(&mut self) {
216+
self.collector.stop().expect("Failed to stop the collector");
217+
if let Some(thread) = self.collector_thread.take() {
218+
thread.join().expect("Failed to join the collector thread");
219+
}
220+
}
221+
}
222+
223+
enum InterceptEnvironment {
224+
Wrapper {
225+
bin_dir: tempfile::TempDir,
226+
address: String,
227+
},
228+
Preload {
229+
path: PathBuf,
230+
address: String,
231+
},
232+
}
233+
234+
impl InterceptEnvironment {
235+
pub fn new(
236+
config: &config::Intercept,
237+
address: String,
238+
) -> anyhow::Result<Self> {
239+
let result = match config {
240+
config::Intercept::Wrapper { path, directory, executables } => {
241+
// Create a temporary directory and populate it with the executables.
242+
let bin_dir = tempfile::TempDir::with_prefix_in(directory, "bear-")?;
243+
for executable in executables {
244+
std::fs::hard_link(&executable, &path)?;
245+
}
246+
InterceptEnvironment::Wrapper {
247+
bin_dir,
248+
address,
249+
}
250+
}
251+
config::Intercept::Preload { path } => {
252+
InterceptEnvironment::Preload {
253+
path: path.clone(),
254+
address,
255+
}
256+
}
257+
};
258+
Ok(result)
259+
}
260+
261+
pub fn execute_build_command(
262+
self,
263+
input: args::BuildCommand,
264+
) -> anyhow::Result<ExitCode> {
265+
let environment = self.environment();
266+
let mut child = Command::new(input.arguments[0].clone())
267+
.args(input.arguments)
268+
.envs(environment)
269+
.spawn()?;
270+
271+
let result = child.wait()?;
272+
273+
if result.success() {
274+
Ok(ExitCode::SUCCESS)
275+
} else {
276+
result.code().map_or(Ok(ExitCode::FAILURE), |code| Ok(ExitCode::from(code as u8)))
277+
}
278+
}
279+
280+
fn environment(&self) -> Vec<(String, String)> {
281+
match self {
282+
InterceptEnvironment::Wrapper { bin_dir, address, .. } => {
283+
let path_original = env::var("PATH").unwrap_or_else(|_| String::new());
284+
let path_updated = InterceptEnvironment::insert_to_path(&path_original, Self::to_string(bin_dir.path()));
285+
vec![
286+
("PATH".to_string(), path_updated),
287+
(KEY_DESTINATION.to_string(), address.clone()),
288+
]
289+
}
290+
InterceptEnvironment::Preload { path, address, .. } => {
291+
let path_original = env::var(KEY_PRELOAD_PATH).unwrap_or_else(|_| String::new());
292+
let path_updated = InterceptEnvironment::insert_to_path(&path_original, Self::to_string(path));
293+
vec![
294+
(KEY_PRELOAD_PATH.to_string(), path_updated),
295+
(KEY_DESTINATION.to_string(), address.clone()),
296+
]
297+
}
298+
}
299+
}
300+
301+
fn insert_to_path(original: &str, first: String) -> String {
302+
let mut paths: Vec<_> = original.split(':')
303+
.filter(|it| it != &first)
304+
.collect();
305+
paths.insert(0, first.as_str());
306+
paths.join(":")
307+
}
308+
309+
fn to_string(path: &Path) -> String {
310+
path.to_str().unwrap_or("").to_string()
311+
}
312+
}

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)