1use crate::arch::{
2 TRACE_CLOCK, TRACE_CREDS, TRACE_DESC, TRACE_FILE, TRACE_FSTAT, TRACE_FSTATFS, TRACE_IPC,
3 TRACE_LSTAT, TRACE_MEMORY, TRACE_NETWORK, TRACE_PROCESS, TRACE_PURE, TRACE_SIGNAL, TRACE_STAT,
4 TRACE_STATFS, TRACE_STATFS_LIKE, TRACE_STAT_LIKE,
5};
6use crate::syscall_info::RetCode;
7use anyhow::bail;
8use clap::{Parser, Subcommand};
9use libc::pid_t;
10use regex::Regex;
11use std::collections::HashMap;
12use std::path::PathBuf;
13use std::str::FromStr;
14use syscalls::{Sysno, SysnoSet};
15
16#[derive(Parser, Debug, Default)]
17#[command(name = "lurk", about, version, allow_external_subcommands = true)]
18pub struct Args {
19 #[arg(short = 'n', long)]
21 pub syscall_number: bool,
22 #[arg(short = 'p', long)]
24 pub attach: Option<pid_t>,
25 #[arg(short = 'v', long)]
27 pub no_abbrev: bool,
28 #[arg(short, long, conflicts_with = "no_abbrev")]
30 pub string_limit: Option<usize>,
31 #[arg(short = 'o', long)]
33 pub file: Option<PathBuf>,
34 #[arg(short = 'c', long)]
36 pub summary_only: bool,
37 #[arg(short = 'C', long, conflicts_with = "summary_only")]
39 pub summary: bool,
40 #[arg(short = 'z', long)]
42 pub successful_only: bool,
43 #[arg(short = 'Z', long, conflicts_with = "successful_only")]
45 pub failed_only: bool,
46 #[arg(short = 'E', long)]
48 pub env: Vec<String>,
49 #[arg(short, long)]
51 pub username: Option<String>,
52 #[arg(short, long)]
54 pub follow_forks: bool,
55 #[arg(short = 'T', long)]
57 pub syscall_times: bool,
58 #[arg(short, long)]
60 pub expr: Vec<String>,
61 #[arg(short, long)]
63 pub json: bool,
64 #[arg(long)]
66 pub collapse_exec_retries: bool,
67 #[command(subcommand)]
68 pub command: Option<ArgCommand>,
69}
70
71#[derive(Subcommand, Debug, PartialEq)]
75pub enum ArgCommand {
76 #[command(external_subcommand)]
78 Command(Vec<String>),
79}
80
81#[derive(Parser, Debug, PartialEq)]
82pub struct ArgAttach {
83 #[arg(short = 'p', long)]
85 pub attach: pid_t,
86}
87
88impl Args {
89 pub fn create_filter(&self) -> anyhow::Result<Filter> {
90 let all_syscall_names: HashMap<&'static str, Sysno> =
91 SysnoSet::all().iter().map(|v| (v.name(), v)).collect();
92 let mut expr_negation = false;
93 let mut system_calls = SysnoSet::empty();
94
95 for token in &self.expr {
97 let mut tokens = token.splitn(2, '=');
98 match (tokens.next(), tokens.next()) {
99 (Some(token_key), Some(mut token_value))
100 if token_key == "t" || token_key == "trace" =>
101 {
102 if let Some(v) = token_value.strip_prefix('!') {
103 token_value = v;
104 expr_negation = true;
105 }
106
107 for part in token_value.split(',') {
108 if let Some(part) = part.strip_prefix('/') {
109 if let Ok(pattern) = Regex::new(part) {
111 for (syscall, sysno) in &all_syscall_names {
112 if pattern.is_match(syscall) {
113 system_calls.insert(*sysno);
114 }
115 }
116 } else {
117 bail!("Invalid regex pattern: {part}");
118 }
119 } else if let Some(part) = part.strip_prefix('%') {
120 system_calls = system_calls.union(match part {
122 "file" => &TRACE_FILE,
123 "process" => &TRACE_PROCESS,
124 "network" | "net" => &TRACE_NETWORK,
125 "signal" => &TRACE_SIGNAL,
126 "ipc" => &TRACE_IPC,
127 "desc" => &TRACE_DESC,
128 "memory" => &TRACE_MEMORY,
129 "creds" => &TRACE_CREDS,
130 "stat" => &TRACE_STAT,
131 "lstat" => &TRACE_LSTAT,
132 "fstat" => &TRACE_FSTAT,
133 "%stat" => &TRACE_STAT_LIKE,
134 "statfs" => &TRACE_STATFS,
135 "fstatfs" => &TRACE_FSTATFS,
136 "%statfs" => &TRACE_STATFS_LIKE,
137 "clock" => &TRACE_CLOCK,
138 "pure" => &TRACE_PURE,
139 v => bail!("Category '{v}' is not valid!"),
140 });
141 } else {
142 let mut ignore_unknown = false;
144 if let Some(v) = token_value.strip_prefix('?') {
145 token_value = v;
146 ignore_unknown = true;
147 }
148 if let Ok(val) = Sysno::from_str(part) {
149 system_calls.insert(val);
150 } else if !ignore_unknown {
151 bail!("System call '{part}' is not valid!");
152 }
153 }
154 }
155 }
156 _ => bail!("expr {token} is not supported. Please have a look at the syntax."),
157 }
158 }
159 Ok(Filter {
160 ret_code_filter: if self.successful_only {
161 FilterRetCode::Oks
162 } else if self.failed_only {
163 FilterRetCode::Errs
164 } else {
165 FilterRetCode::All
166 },
167 sysno_filter: if system_calls.count() == 0 {
168 FilterSysno::All
169 } else if expr_negation {
170 FilterSysno::Except(system_calls)
171 } else {
172 FilterSysno::Only(system_calls)
173 },
174 })
175 }
176}
177
178enum FilterRetCode {
179 All,
180 Oks,
181 Errs,
182}
183
184enum FilterSysno {
185 All,
186 Only(SysnoSet),
187 Except(SysnoSet),
188}
189
190pub struct Filter {
191 ret_code_filter: FilterRetCode,
192 sysno_filter: FilterSysno,
193}
194
195impl Filter {
196 pub fn matches(&mut self, sys_no: Sysno, res: RetCode) -> bool {
197 (
198 match self.ret_code_filter {
200 FilterRetCode::All => true,
201 FilterRetCode::Oks => matches!(res, RetCode::Ok(_) | RetCode::Address(_)),
202 FilterRetCode::Errs => matches!(res, RetCode::Err(_)),
203 }
204 ) && (
205 match &self.sysno_filter {
207 FilterSysno::All => true,
208 FilterSysno::Only(sysno_set) => sysno_set.contains(sys_no),
209 FilterSysno::Except(sysno_set) => !sysno_set.contains(sys_no),
210 }
211 )
212 }
213
214 pub fn all_enabled(&self) -> SysnoSet {
215 match &self.sysno_filter {
216 FilterSysno::All => SysnoSet::all(),
217 FilterSysno::Only(sysno_set) => sysno_set.clone(),
218 FilterSysno::Except(sysno_set) => SysnoSet::all().difference(sysno_set),
219 }
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn test_args_simple() {
229 let args = Args::parse_from(["lurk", "app"]);
230 assert_eq!(
231 args.command,
232 Some(ArgCommand::Command(vec!["app".to_string()])),
233 );
234 }
235}