tracexec_core/
cli.rs

1use std::{
2  io::{BufWriter, stderr, stdout},
3  path::PathBuf,
4};
5
6use args::{DebuggerArgs, PtraceArgs, TuiModeArgs};
7use clap::{CommandFactory, Parser, Subcommand};
8use config::Config;
9use options::ExportFormat;
10use tracing::debug;
11
12use crate::{cli::args::ExporterArgs, output::Output};
13
14use self::{
15  args::{LogModeArgs, ModifierArgs, TracerEventArgs},
16  options::Color,
17};
18
19pub mod args;
20pub mod config;
21pub mod options;
22pub mod theme;
23
24#[derive(Parser, Debug)]
25#[clap(author, version, about)]
26pub struct Cli {
27  #[arg(long, default_value_t = Color::Auto, help = "Control whether colored output is enabled. This flag has no effect on TUI mode.")]
28  pub color: Color,
29  #[arg(
30    short = 'C',
31    long,
32    help = "Change current directory to this path before doing anything"
33  )]
34  pub cwd: Option<PathBuf>,
35  #[arg(
36    short = 'P',
37    long,
38    help = "Load profile from this path",
39    conflicts_with = "no_profile"
40  )]
41  pub profile: Option<PathBuf>,
42  #[arg(long, help = "Do not load profiles")]
43  pub no_profile: bool,
44  #[arg(
45    short,
46    long,
47    help = "Run as user. This option is only available when running tracexec as root"
48  )]
49  pub user: Option<String>,
50  #[clap(subcommand)]
51  pub cmd: CliCommand,
52}
53
54#[derive(Subcommand, Debug)]
55pub enum CliCommand {
56  #[clap(about = "Run tracexec in logging mode")]
57  Log {
58    #[arg(last = true, required = true, help = "command to be executed")]
59    cmd: Vec<String>,
60    #[clap(flatten)]
61    tracing_args: LogModeArgs,
62    #[clap(flatten)]
63    modifier_args: ModifierArgs,
64    #[clap(flatten)]
65    ptrace_args: PtraceArgs,
66    #[clap(flatten)]
67    tracer_event_args: TracerEventArgs,
68    #[clap(
69      short,
70      long,
71      help = "Output, stderr by default. A single hyphen '-' represents stdout."
72    )]
73    output: Option<PathBuf>,
74  },
75  #[clap(about = "Run tracexec in TUI mode, stdin/out/err are redirected to /dev/null by default")]
76  Tui {
77    #[arg(last = true, required = true, help = "command to be executed")]
78    cmd: Vec<String>,
79    #[clap(flatten)]
80    modifier_args: ModifierArgs,
81    #[clap(flatten)]
82    ptrace_args: PtraceArgs,
83    #[clap(flatten)]
84    tracer_event_args: TracerEventArgs,
85    #[clap(flatten)]
86    tui_args: TuiModeArgs,
87    #[clap(flatten)]
88    debugger_args: DebuggerArgs,
89  },
90  #[clap(about = "Generate shell completions for tracexec")]
91  GenerateCompletions {
92    #[arg(required = true, help = "The shell to generate completions for")]
93    shell: clap_complete::Shell,
94  },
95  #[clap(about = "Collect exec events and export them")]
96  Collect {
97    #[arg(last = true, required = true, help = "command to be executed")]
98    cmd: Vec<String>,
99    #[clap(flatten)]
100    modifier_args: ModifierArgs,
101    #[clap(flatten)]
102    ptrace_args: PtraceArgs,
103    #[clap(flatten)]
104    exporter_args: ExporterArgs,
105    #[clap(short = 'F', long, help = "the format for exported exec events")]
106    format: ExportFormat,
107    #[clap(
108      short,
109      long,
110      help = "Output, stderr by default. A single hyphen '-' represents stdout."
111    )]
112    output: Option<PathBuf>,
113    #[clap(
114      long,
115      help = "Set the terminal foreground process group to tracee. This option is useful when tracexec is used interactively. [default]",
116      conflicts_with = "no_foreground"
117    )]
118    foreground: bool,
119    #[clap(
120      long,
121      help = "Do not set the terminal foreground process group to tracee",
122      conflicts_with = "foreground"
123    )]
124    no_foreground: bool,
125  },
126  #[cfg(feature = "ebpf")]
127  #[clap(about = "Experimental ebpf mode")]
128  Ebpf {
129    #[clap(subcommand)]
130    command: EbpfCommand,
131  },
132}
133
134#[derive(Subcommand, Debug)]
135#[cfg(feature = "ebpf")]
136pub enum EbpfCommand {
137  #[clap(about = "Run tracexec in logging mode")]
138  Log {
139    #[arg(
140      last = true,
141      help = "command to be executed. Leave it empty to trace all exec on system"
142    )]
143    cmd: Vec<String>,
144    #[clap(
145      short,
146      long,
147      help = "Output, stderr by default. A single hyphen '-' represents stdout."
148    )]
149    output: Option<PathBuf>,
150    #[clap(flatten)]
151    modifier_args: ModifierArgs,
152    #[clap(flatten)]
153    log_args: LogModeArgs,
154  },
155  #[clap(about = "Run tracexec in TUI mode, stdin/out/err are redirected to /dev/null by default")]
156  Tui {
157    #[arg(
158      last = true,
159      help = "command to be executed. Leave it empty to trace all exec on system"
160    )]
161    cmd: Vec<String>,
162    #[clap(flatten)]
163    modifier_args: ModifierArgs,
164    #[clap(flatten)]
165    tracer_event_args: TracerEventArgs,
166    #[clap(flatten)]
167    tui_args: TuiModeArgs,
168  },
169  #[clap(about = "Collect exec events and export them")]
170  Collect {
171    #[arg(
172      last = true,
173      help = "command to be executed. Leave it empty to trace all exec on system"
174    )]
175    cmd: Vec<String>,
176    #[clap(flatten)]
177    modifier_args: ModifierArgs,
178    #[clap(short = 'F', long, help = "the format for exported exec events")]
179    format: ExportFormat,
180    #[clap(flatten)]
181    exporter_args: ExporterArgs,
182    #[clap(
183      short,
184      long,
185      help = "Output, stderr by default. A single hyphen '-' represents stdout."
186    )]
187    output: Option<PathBuf>,
188    #[clap(
189      long,
190      help = "Set the terminal foreground process group to tracee. This option is useful when tracexec is used interactively. [default]",
191      conflicts_with = "no_foreground"
192    )]
193    foreground: bool,
194    #[clap(
195      long,
196      help = "Do not set the terminal foreground process group to tracee",
197      conflicts_with = "foreground"
198    )]
199    no_foreground: bool,
200  },
201}
202
203impl Cli {
204  pub fn get_output(path: Option<PathBuf>, color: Color) -> std::io::Result<Box<Output>> {
205    Ok(match path {
206      None => Box::new(stderr()),
207      Some(ref x) if x.as_os_str() == "-" => Box::new(stdout()),
208      Some(path) => {
209        let file = std::fs::OpenOptions::new()
210          .create(true)
211          .truncate(true)
212          .write(true)
213          .open(path)?;
214        if color != Color::Always {
215          // Disable color by default when output is file
216          owo_colors::control::set_should_colorize(false);
217        }
218        Box::new(BufWriter::new(file))
219      }
220    })
221  }
222
223  pub fn generate_completions(shell: clap_complete::Shell) {
224    let mut cmd = Self::command();
225    clap_complete::generate(shell, &mut cmd, env!("CARGO_CRATE_NAME"), &mut stdout())
226  }
227
228  pub fn merge_config(&mut self, config: Config) {
229    debug!("Merging config: {config:?}");
230    match &mut self.cmd {
231      CliCommand::Log {
232        tracing_args,
233        modifier_args,
234        ptrace_args,
235        ..
236      } => {
237        if let Some(c) = config.ptrace {
238          ptrace_args.merge_config(c);
239        }
240        if let Some(c) = config.modifier {
241          modifier_args.merge_config(c);
242        }
243        if let Some(c) = config.log {
244          tracing_args.merge_config(c);
245        }
246      }
247      CliCommand::Tui {
248        modifier_args,
249        ptrace_args,
250        tui_args,
251        debugger_args,
252        ..
253      } => {
254        if let Some(c) = config.ptrace {
255          ptrace_args.merge_config(c);
256        }
257        if let Some(c) = config.modifier {
258          modifier_args.merge_config(c);
259        }
260        if let Some(c) = config.tui {
261          tui_args.merge_config(c);
262        }
263        if let Some(c) = config.debugger {
264          debugger_args.merge_config(c);
265        }
266      }
267      CliCommand::Collect {
268        foreground,
269        no_foreground,
270        ptrace_args,
271        ..
272      } => {
273        if let Some(c) = config.ptrace {
274          ptrace_args.merge_config(c);
275        }
276        if let Some(c) = config.log
277          && (!*foreground)
278          && (!*no_foreground)
279          && let Some(x) = c.foreground
280        {
281          if x {
282            *foreground = true;
283          } else {
284            *no_foreground = true;
285          }
286        }
287      }
288      _ => (),
289    }
290  }
291}