tracexec_core/cli/
config.rs

1use std::{io, path::PathBuf};
2
3use directories::ProjectDirs;
4use serde::{Deserialize, Deserializer, Serialize};
5use snafu::{ResultExt, Snafu};
6use tracing::warn;
7
8use crate::timestamp::TimestampFormat;
9
10use super::options::{ActivePane, AppLayout, SeccompBpf};
11
12#[derive(Debug, Default, Clone, Deserialize, Serialize)]
13pub struct Config {
14  pub log: Option<LogModeConfig>,
15  pub tui: Option<TuiModeConfig>,
16  pub modifier: Option<ModifierConfig>,
17  pub ptrace: Option<PtraceConfig>,
18  pub debugger: Option<DebuggerConfig>,
19}
20
21#[derive(Debug, Snafu)]
22pub enum ConfigLoadError {
23  #[snafu(display("Config file not found."))]
24  NotFound,
25  #[snafu(display("Failed to load config file."))]
26  IoError { source: io::Error },
27  #[snafu(display("Failed to parse config file."))]
28  TomlError { source: toml::de::Error },
29}
30
31impl Config {
32  pub fn load(path: Option<PathBuf>) -> Result<Self, ConfigLoadError> {
33    let config_text = match path {
34      Some(path) => std::fs::read_to_string(path).context(IoSnafu)?, // if manually specified config doesn't exist, return a hard error
35      None => {
36        let Some(project_dirs) = project_directory() else {
37          warn!("No valid home directory found! Not loading config.toml.");
38          return Err(ConfigLoadError::NotFound);
39        };
40        // ~/.config/tracexec/config.toml
41        let config_path = project_dirs.config_dir().join("config.toml");
42
43        std::fs::read_to_string(config_path).map_err(|e| match e.kind() {
44          io::ErrorKind::NotFound => ConfigLoadError::NotFound,
45          _ => ConfigLoadError::IoError { source: e },
46        })?
47      }
48    };
49
50    let config: Self = toml::from_str(&config_text).context(TomlSnafu)?;
51    Ok(config)
52  }
53}
54
55#[derive(Debug, Default, Clone, Deserialize, Serialize)]
56pub struct ModifierConfig {
57  pub seccomp_bpf: Option<SeccompBpf>,
58  pub successful_only: Option<bool>,
59  pub fd_in_cmdline: Option<bool>,
60  pub stdio_in_cmdline: Option<bool>,
61  pub resolve_proc_self_exe: Option<bool>,
62  pub hide_cloexec_fds: Option<bool>,
63  pub timestamp: Option<TimestampConfig>,
64}
65
66#[derive(Debug, Default, Clone, Deserialize, Serialize)]
67pub struct TimestampConfig {
68  pub enable: bool,
69  pub inline_format: Option<TimestampFormat>,
70}
71
72#[derive(Debug, Default, Clone, Deserialize, Serialize)]
73pub struct PtraceConfig {
74  pub seccomp_bpf: Option<SeccompBpf>,
75}
76
77#[derive(Debug, Default, Clone, Deserialize, Serialize)]
78pub struct TuiModeConfig {
79  pub follow: Option<bool>,
80  pub exit_handling: Option<ExitHandling>,
81  pub active_pane: Option<ActivePane>,
82  pub layout: Option<AppLayout>,
83  #[serde(default, deserialize_with = "deserialize_frame_rate")]
84  pub frame_rate: Option<f64>,
85  pub max_events: Option<u64>,
86}
87
88#[derive(Debug, Default, Clone, Deserialize, Serialize)]
89pub struct DebuggerConfig {
90  pub default_external_command: Option<String>,
91}
92
93fn deserialize_frame_rate<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
94where
95  D: Deserializer<'de>,
96{
97  let value = Option::<f64>::deserialize(deserializer)?;
98  if value.is_some_and(|v| v.is_nan() || v <= 0. || v.is_infinite()) {
99    return Err(serde::de::Error::invalid_value(
100      serde::de::Unexpected::Float(value.unwrap()),
101      &"a positive floating-point number",
102    ));
103  }
104  Ok(value)
105}
106
107#[derive(Debug, Default, Clone, Deserialize, Serialize)]
108pub struct LogModeConfig {
109  pub show_interpreter: Option<bool>,
110  pub color_level: Option<ColorLevel>,
111  pub foreground: Option<bool>,
112  pub fd_display: Option<FileDescriptorDisplay>,
113  pub env_display: Option<EnvDisplay>,
114  pub show_comm: Option<bool>,
115  pub show_argv: Option<bool>,
116  pub show_filename: Option<bool>,
117  pub show_cwd: Option<bool>,
118  pub show_cmdline: Option<bool>,
119  pub decode_errno: Option<bool>,
120}
121
122#[derive(Debug, Default, Clone, Deserialize, Serialize)]
123pub enum ColorLevel {
124  Less,
125  #[default]
126  Normal,
127  More,
128}
129
130#[derive(Debug, Default, Clone, Deserialize, Serialize)]
131pub enum FileDescriptorDisplay {
132  Hide,
133  Show,
134  #[default]
135  Diff,
136}
137
138#[derive(Debug, Default, Clone, Deserialize, Serialize)]
139pub enum EnvDisplay {
140  Hide,
141  Show,
142  #[default]
143  Diff,
144}
145
146#[derive(Debug, Default, Clone, Deserialize, Serialize)]
147pub enum ExitHandling {
148  #[default]
149  Wait,
150  Kill,
151  Terminate,
152}
153
154pub fn project_directory() -> Option<ProjectDirs> {
155  ProjectDirs::from("dev", "kxxt", env!("CARGO_PKG_NAME"))
156}