|
1 | 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | | -use std::process::ExitCode; |
3 | 2 |
|
| 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}; |
4 | 8 | use bear::input::EventFileReader; |
5 | 9 | use bear::output::OutputWriter; |
6 | 10 | use bear::recognition::Recognition; |
7 | 11 | use bear::transformation::Transformation; |
8 | 12 | use bear::{args, config}; |
9 | 13 | use log; |
| 14 | +use bear::intercept::collector::{EventCollector, EventCollectorOnTcp}; |
| 15 | +use bear::intercept::{Envelope, KEY_DESTINATION, KEY_PRELOAD_PATH}; |
10 | 16 |
|
11 | 17 | /// Driver function of the application. |
12 | 18 | fn main() -> anyhow::Result<ExitCode> { |
@@ -41,7 +47,7 @@ enum Application { |
41 | 47 | Intercept { |
42 | 48 | input: args::BuildCommand, |
43 | 49 | output: args::BuildEvents, |
44 | | - intercept_config: config::Intercept, |
| 50 | + config: config::Intercept, |
45 | 51 | }, |
46 | 52 | /// The semantic mode we are deduct the semantic meaning of the |
47 | 53 | /// executed commands from the build process. |
@@ -73,7 +79,7 @@ impl Application { |
73 | 79 | let result = Application::Intercept { |
74 | 80 | input, |
75 | 81 | output, |
76 | | - intercept_config, |
| 82 | + config: intercept_config, |
77 | 83 | }; |
78 | 84 | Ok(result) |
79 | 85 | } |
@@ -110,10 +116,36 @@ impl Application { |
110 | 116 | Application::Intercept { |
111 | 117 | input, |
112 | 118 | output, |
113 | | - intercept_config, |
| 119 | + config, |
114 | 120 | } => { |
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 | + } |
117 | 149 | } |
118 | 150 | Application::Semantic { |
119 | 151 | event_source, |
@@ -145,3 +177,136 @@ impl Application { |
145 | 177 | } |
146 | 178 | } |
147 | 179 | } |
| 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 | +} |
0 commit comments