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 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}