#![allow(
clippy::future_not_send, // We are not a library for now.
)]
#[cfg(feature = "ebpf")]
mod bpf;
mod log;
mod run_mode;
use std::{
process,
sync::Arc,
};
use clap::Parser;
use color_eyre::eyre::{
OptionExt,
bail,
};
use nix::unistd::Uid;
use signal_hook::consts::signal::*;
use tokio::sync::mpsc;
use tracexec_backend_ptrace::ptrace::BuildPtraceTracer;
use tracexec_core::{
account,
cli::{
Cli,
CliCommand,
args::TracerEventArgs,
config::{
Config,
ConfigLoadError,
},
options::Color,
},
elevate,
event::{
TracerEvent,
TracerEventDetails,
TracerMessage,
},
export::ExporterMetadata,
is_current_kernel_ge,
proc::BaselineInfo,
tracer::{
TracerBuilder,
TracerMode,
},
};
use tracexec_tui::{
app::{
App,
PTracer,
},
theme::current_theme,
};
use crate::log::initialize_panic_handler;
fn main() -> color_eyre::Result<()> {
let mut cli = Cli::parse();
if cli.elevate {
tracexec_core::elevate::elevate_and_reexec()?;
unreachable!();
}
if cli.restore_env_socket.is_some() && !Uid::effective().is_root() {
bail!("--restore-env-socket is only available after privilege elevation");
}
let restored_env = cli
.restore_env_socket
.take()
.map(|socket| elevate::request_env_from_parent(&socket))
.transpose()?;
let tracexec_env = restored_env
.as_ref()
.map(|env| elevate::filter_allowlisted_env(env));
let tracexec_env_ref = tracexec_env.as_deref();
let tracee_env = restored_env;
if let (Some(config_dir), Some(data_dir), Some(data_local_dir)) = (
cli.elevated_config_dir.take(),
cli.elevated_data_dir.take(),
cli.elevated_data_local_dir.take(),
) {
tracexec_core::cli::config::set_project_dir_overrides(config_dir, data_dir, data_local_dir);
}
if cli.color == Color::Auto && elevate::env_var_os(tracexec_env_ref, "NO_COLOR").is_some() {
cli.color = Color::Never;
}
if cli.color == Color::Always {
owo_colors::control::set_should_colorize(true);
color_eyre::install()?;
} else if cli.color == Color::Never {
owo_colors::control::set_should_colorize(false);
} else {
color_eyre::install()?;
}
initialize_panic_handler();
log::initialize_logging(tracexec_env_ref)?;
log::debug!("Commandline args: {:?}", cli);
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()?;
runtime.block_on(async_main(cli, tracee_env, tracexec_env))
}
async fn async_main(
mut cli: Cli,
tracee_env: Option<tracexec_core::elevate::EnvVars>,
tracexec_env: Option<tracexec_core::elevate::EnvVars>,
) -> color_eyre::Result<()> {
if let Some(cwd) = &cli.cwd {
std::env::set_current_dir(cwd)?;
}
let user = if let Some(user) = cli.user.as_deref() {
if !Uid::effective().is_root() {
bail!("--user option is only available when running tracexec as root!");
}
Some(account::user_from_name(user)?.ok_or_eyre("Failed to get user info")?)
} else {
None
};
let min_support_kver = (5, 3);
if !is_current_kernel_ge(min_support_kver)? {
log::warn!(
"Current kernel version is not supported! Minimum supported kernel version is {}.{}.",
min_support_kver.0,
min_support_kver.1
);
eprintln!(
"Current kernel version is not supported! Minimum supported kernel version is {}.{}.",
min_support_kver.0, min_support_kver.1
);
}
if !cli.no_profile {
match Config::load(cli.profile.clone()) {
Ok(config) => cli.merge_config(config),
Err(ConfigLoadError::NotFound) => (),
Err(e) => Err(e)?,
};
}
match cli.cmd {
CliCommand::Log {
cmd,
tracing_args,
modifier_args,
ptrace_args,
tracer_event_args,
output,
} => {
let modifier_args = modifier_args.processed();
let output = Cli::get_output(output, cli.color)?;
let baseline = BaselineInfo::new_with_env(tracee_env.as_deref())?;
let (tracer_tx, mut tracer_rx) = mpsc::unbounded_channel();
let (tracer, token) = TracerBuilder::new()
.mode(TracerMode::Log {
foreground: tracing_args.foreground(),
})
.modifier(modifier_args)
.user(user)
.tracee_env(tracee_env)
.tracer_tx(tracer_tx)
.baseline(Arc::new(baseline))
.filter(tracer_event_args.filter()?)
.ptrace_options(&ptrace_args)
.printer_from_cli(&tracing_args)
.build_ptrace()?;
let (_tracer, tracer_thread) = tracer.spawn(cmd, Some(output), token)?;
loop {
match tracer_rx.recv().await {
Some(TracerMessage::Event(TracerEvent {
details: TracerEventDetails::TraceeExit { exit_code, .. },
..
})) => {
tracing::debug!("Waiting for tracer thread to exit");
tracer_thread.await??;
process::exit(exit_code);
}
None | Some(TracerMessage::FatalError(_)) => {
tracing::debug!("Waiting for tracer thread to exit");
tracer_thread.await??;
process::exit(1);
}
_ => (),
}
}
}
CliCommand::Tui {
cmd,
modifier_args,
ptrace_args,
tracer_event_args,
tui_args,
debugger_args,
} => {
let executable_path = std::env::current_exe()?;
run_mode::initialize_tui(&tui_args, &executable_path)?;
let modifier_args = modifier_args.processed();
log::debug!(
"should colorize: {}",
owo_colors::control::should_colorize()
);
let (baseline, tracer_mode, pty_master) =
run_mode::setup_tui_io(tui_args.tty(), tracee_env.as_deref())?;
let tracing_args = run_mode::tui_log_args();
let baseline = Arc::new(baseline);
let (tracer_tx, tracer_rx) = mpsc::unbounded_channel();
let (tracer, token) = TracerBuilder::new()
.mode(tracer_mode)
.modifier(modifier_args.clone())
.user(user)
.tracee_env(tracee_env)
.tracer_tx(tracer_tx)
.baseline(baseline.clone())
.filter(tracer_event_args.filter()?)
.ptrace_options(&ptrace_args)
.printer_from_cli(&tracing_args)
.build_ptrace()?;
let frame_rate = tui_args.frame_rate.unwrap_or(60.);
let (tracer, tracer_thread) = tracer.spawn(cmd, None, token)?;
let mut app = App::new(
Some(PTracer {
tracer,
debugger_args,
}),
&tracing_args,
&modifier_args,
tui_args,
baseline,
pty_master,
current_theme(),
)?;
let mut tui = tracexec_tui::Tui::new()?.frame_rate(frame_rate);
tui.enter(tracer_rx)?;
app.run(&mut tui).await?;
app.exit()?;
tracexec_tui::restore_tui()?;
tracer_thread.await??;
}
CliCommand::Collect {
cmd,
format,
output,
modifier_args,
ptrace_args,
exporter_args,
foreground,
no_foreground,
} => {
let modifier_args = modifier_args.processed();
let output = Cli::get_output(output, cli.color)?;
let tracing_args = run_mode::collect_log_args(foreground, no_foreground);
let (tracer_tx, tracer_rx) = mpsc::unbounded_channel();
let baseline = Arc::new(BaselineInfo::new_with_env(tracee_env.as_deref())?);
let (tracer, token) = TracerBuilder::new()
.mode(TracerMode::Log {
foreground: tracing_args.foreground(),
})
.modifier(modifier_args)
.user(user)
.tracee_env(tracee_env)
.tracer_tx(tracer_tx)
.baseline(baseline.clone())
.filter(TracerEventArgs::all().filter()?)
.ptrace_options(&ptrace_args)
.printer_from_cli(&tracing_args)
.build_ptrace()?;
let (tracer, tracer_thread) = tracer.spawn(cmd, None, token)?;
run_mode::spawn_signal_handler([SIGTERM, SIGINT, SIGQUIT], move |signal| match signal {
SIGTERM | SIGINT => tracer
.request_termination()
.expect("Failed to terminate tracer"),
_ => unreachable!(),
})?;
let meta = ExporterMetadata {
baseline,
exporter_args,
};
let exit_code = run_mode::run_exporter(format, output, meta, tracer_rx).await?;
tracer_thread.await??;
process::exit(exit_code);
}
CliCommand::GenerateCompletions { shell } => {
Cli::generate_completions(shell);
}
#[cfg(feature = "ebpf")]
CliCommand::Ebpf { command } => {
bpf::main(command, user, cli.color, tracee_env, tracexec_env).await?;
}
}
Ok(())
}