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 #[command(subcommand)]
65 pub command: Option<ArgCommand>,
66}
67
68#[derive(Subcommand, Debug, PartialEq)]
72pub enum ArgCommand {
73 #[command(external_subcommand)]
75 Command(Vec<String>),
76}
77
78#[derive(Parser, Debug, PartialEq)]
79pub struct ArgAttach {
80 #[arg(short = 'p', long)]
82 pub attach: pid_t,
83}
84
85impl Args {
86 pub fn create_filter(&self) -> anyhow::Result<Filter> {
87 let all_syscall_names: HashMap<&'static str, Sysno> =
88 SysnoSet::all().iter().map(|v| (v.name(), v)).collect();
89 let mut expr_negation = false;
90 let mut system_calls = SysnoSet::empty();
91
92 for token in &self.expr {
94 let mut tokens = token.splitn(2, '=');
95 match (tokens.next(), tokens.next()) {
96 (Some(token_key), Some(mut token_value))
97 if token_key == "t" || token_key == "trace" =>
98 {
99 if let Some(v) = token_value.strip_prefix('!') {
100 token_value = v;
101 expr_negation = true;
102 }
103
104 for part in token_value.split(',') {
105 if let Some(part) = part.strip_prefix('/') {
106 if let Ok(pattern) = Regex::new(part) {
108 for (syscall, sysno) in &all_syscall_names {
109 if pattern.is_match(syscall) {
110 system_calls.insert(*sysno);
111 }
112 }
113 } else {
114 bail!("Invalid regex pattern: {part}");
115 }
116 } else if let Some(part) = part.strip_prefix('%') {
117 system_calls = system_calls.union(match part {
119 "file" => &TRACE_FILE,
120 "process" => &TRACE_PROCESS,
121 "network" | "net" => &TRACE_NETWORK,
122 "signal" => &TRACE_SIGNAL,
123 "ipc" => &TRACE_IPC,
124 "desc" => &TRACE_DESC,
125 "memory" => &TRACE_MEMORY,
126 "creds" => &TRACE_CREDS,
127 "stat" => &TRACE_STAT,
128 "lstat" => &TRACE_LSTAT,
129 "fstat" => &TRACE_FSTAT,
130 "%stat" => &TRACE_STAT_LIKE,
131 "statfs" => &TRACE_STATFS,
132 "fstatfs" => &TRACE_FSTATFS,
133 "%statfs" => &TRACE_STATFS_LIKE,
134 "clock" => &TRACE_CLOCK,
135 "pure" => &TRACE_PURE,
136 v => bail!("Category '{v}' is not valid!"),
137 });
138 } else {
139 let mut ignore_unknown = false;
141 if let Some(v) = token_value.strip_prefix('?') {
142 token_value = v;
143 ignore_unknown = true;
144 }
145 if let Ok(val) = Sysno::from_str(part) {
146 system_calls.insert(val);
147 } else if !ignore_unknown {
148 bail!("System call '{part}' is not valid!");
149 }
150 }
151 }
152 }
153 _ => bail!("expr {token} is not supported. Please have a look at the syntax."),
154 }
155 }
156 Ok(Filter {
157 ret_code_filter: if self.successful_only {
158 FilterRetCode::Oks
159 } else if self.failed_only {
160 FilterRetCode::Errs
161 } else {
162 FilterRetCode::All
163 },
164 sysno_filter: if system_calls.count() == 0 {
165 FilterSysno::All
166 } else if expr_negation {
167 FilterSysno::Except(system_calls)
168 } else {
169 FilterSysno::Only(system_calls)
170 },
171 })
172 }
173}
174
175enum FilterRetCode {
176 All,
177 Oks,
178 Errs,
179}
180
181enum FilterSysno {
182 All,
183 Only(SysnoSet),
184 Except(SysnoSet),
185}
186
187pub struct Filter {
188 ret_code_filter: FilterRetCode,
189 sysno_filter: FilterSysno,
190}
191
192impl Filter {
193 pub fn matches(&mut self, sys_no: Sysno, res: RetCode) -> bool {
194 (
195 match self.ret_code_filter {
197 FilterRetCode::All => true,
198 FilterRetCode::Oks => matches!(res, RetCode::Ok(_) | RetCode::Address(_)),
199 FilterRetCode::Errs => matches!(res, RetCode::Err(_)),
200 }
201 ) && (
202 match &self.sysno_filter {
204 FilterSysno::All => true,
205 FilterSysno::Only(sysno_set) => sysno_set.contains(sys_no),
206 FilterSysno::Except(sysno_set) => !sysno_set.contains(sys_no),
207 }
208 )
209 }
210
211 pub fn all_enabled(&self) -> SysnoSet {
212 match &self.sysno_filter {
213 FilterSysno::All => SysnoSet::all(),
214 FilterSysno::Only(sysno_set) => sysno_set.clone(),
215 FilterSysno::Except(sysno_set) => SysnoSet::all().difference(sysno_set),
216 }
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_args_simple() {
226 let args = Args::parse_from(["lurk", "app"]);
227 assert_eq!(
228 args.command,
229 Some(ArgCommand::Command(vec!["app".to_string()])),
230 );
231 }
232}