1use std::path::PathBuf;
4
5pub mod action_family;
6pub mod config;
7pub mod distro;
8
9pub use distro::{detect, detect_distro, parse_os_release, DistroFamily, DistroId};
10
11pub const PRODUCTION_LISTEN_URI: &str = "unix:///run/sysknife/daemon.sock";
15
16pub const PRODUCTION_DATABASE_PATH: &str = "/var/lib/sysknife/daemon.sqlite";
20
21pub fn default_listen_uri() -> String {
31 if let Ok(uri) = std::env::var("SYSKNIFE_LISTEN_URI") {
32 return uri;
33 }
34 if let Ok(runtime) = std::env::var("XDG_RUNTIME_DIR") {
35 let p = PathBuf::from(runtime).join("sysknife/daemon.sock");
36 return format!("unix://{}", p.display());
37 }
38 let uid = current_uid();
39 format!("unix:///tmp/sysknife-{uid}.sock")
40}
41
42pub fn default_database_path() -> PathBuf {
51 if let Ok(path) = std::env::var("SYSKNIFE_DATABASE_PATH") {
52 return PathBuf::from(path);
53 }
54 if let Ok(state) = std::env::var("XDG_STATE_HOME") {
55 return PathBuf::from(state).join("sysknife/daemon.sqlite");
56 }
57 if let Ok(home) = std::env::var("HOME") {
58 return PathBuf::from(home).join(".local/state/sysknife/daemon.sqlite");
59 }
60 PathBuf::from(PRODUCTION_DATABASE_PATH)
61}
62
63fn current_uid() -> u32 {
70 std::fs::read_to_string("/proc/self/status")
71 .ok()
72 .and_then(|s| {
73 s.lines()
74 .find(|l| l.starts_with("Uid:"))
75 .and_then(|l| l.split_whitespace().nth(1))
76 .and_then(|v| v.parse::<u32>().ok())
77 })
78 .unwrap_or(0)
79}
80
81#[cfg(test)]
82mod tests {
83 use super::{
84 default_database_path, default_listen_uri, PRODUCTION_DATABASE_PATH, PRODUCTION_LISTEN_URI,
85 };
86 use std::sync::Mutex;
87
88 static ENV_LOCK: Mutex<()> = Mutex::new(());
89
90 #[test]
91 fn production_constants_are_absolute() {
92 assert!(PRODUCTION_LISTEN_URI.starts_with("unix:///run/"));
93 assert!(PRODUCTION_DATABASE_PATH.starts_with("/var/lib/"));
94 }
95
96 #[test]
97 fn database_env_var_wins() {
98 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
99 unsafe {
100 std::env::set_var("SYSKNIFE_DATABASE_PATH", "/explicit/path/db.sqlite");
101 }
102 let p = default_database_path();
103 unsafe {
104 std::env::remove_var("SYSKNIFE_DATABASE_PATH");
105 }
106 assert_eq!(p.to_str(), Some("/explicit/path/db.sqlite"));
107 }
108
109 #[test]
110 fn database_xdg_state_used_when_set() {
111 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
112 unsafe {
113 std::env::remove_var("SYSKNIFE_DATABASE_PATH");
114 std::env::set_var("XDG_STATE_HOME", "/xdg/state");
115 }
116 let p = default_database_path();
117 unsafe {
118 std::env::remove_var("XDG_STATE_HOME");
119 }
120 assert_eq!(p.to_str(), Some("/xdg/state/sysknife/daemon.sqlite"));
121 }
122
123 #[test]
124 fn database_falls_back_to_home_local_state() {
125 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
126 unsafe {
127 std::env::remove_var("SYSKNIFE_DATABASE_PATH");
128 std::env::remove_var("XDG_STATE_HOME");
129 std::env::set_var("HOME", "/home/contributor");
130 }
131 let p = default_database_path();
132 unsafe {
133 std::env::remove_var("HOME");
134 }
135 assert_eq!(
136 p.to_str(),
137 Some("/home/contributor/.local/state/sysknife/daemon.sqlite")
138 );
139 }
140
141 #[test]
142 fn database_last_resort_is_production_path() {
143 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
144 let saved_home = std::env::var("HOME").ok();
146 unsafe {
147 std::env::remove_var("SYSKNIFE_DATABASE_PATH");
148 std::env::remove_var("XDG_STATE_HOME");
149 std::env::remove_var("HOME");
150 }
151 let p = default_database_path();
152 unsafe {
153 if let Some(h) = saved_home {
154 std::env::set_var("HOME", h);
155 }
156 }
157 assert_eq!(p.to_str(), Some(PRODUCTION_DATABASE_PATH));
158 }
159
160 #[test]
161 fn listen_env_var_wins() {
162 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
163 unsafe {
164 std::env::set_var("SYSKNIFE_LISTEN_URI", "unix:///explicit.sock");
165 }
166 let u = default_listen_uri();
167 unsafe {
168 std::env::remove_var("SYSKNIFE_LISTEN_URI");
169 }
170 assert_eq!(u, "unix:///explicit.sock");
171 }
172
173 #[test]
174 fn listen_uses_xdg_runtime_dir_when_set() {
175 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
176 unsafe {
177 std::env::remove_var("SYSKNIFE_LISTEN_URI");
178 std::env::set_var("XDG_RUNTIME_DIR", "/run/user/1000");
179 }
180 let u = default_listen_uri();
181 unsafe {
182 std::env::remove_var("XDG_RUNTIME_DIR");
183 }
184 assert_eq!(u, "unix:///run/user/1000/sysknife/daemon.sock");
185 }
186
187 #[test]
188 fn listen_last_resort_is_per_uid_tmp() {
189 let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
190 unsafe {
191 std::env::remove_var("SYSKNIFE_LISTEN_URI");
192 std::env::remove_var("XDG_RUNTIME_DIR");
193 }
194 let u = default_listen_uri();
195 assert!(u.starts_with("unix:///tmp/sysknife-"));
196 assert!(u.ends_with(".sock"));
197 }
198}