auto_cpufreq/config/
settings.rs1use anyhow::{bail, Result};
4use std::path::{Path, PathBuf};
5use std::process::Command;
6use std::sync::{Arc, Mutex};
7use std::time::{Duration, Instant};
8use std::thread;
9
10use configparser::ini::Ini;
11
12pub struct Config {
13 path: Arc<Mutex<PathBuf>>,
14 config: Arc<Mutex<Ini>>,
15 last_reload: Arc<Mutex<Instant>>,
16}
17
18impl Config {
19 pub fn new() -> Self {
20 Config {
21 path: Arc::new(Mutex::new(PathBuf::new())),
22 config: Arc::new(Mutex::new(Ini::new())),
23 last_reload: Arc::new(Mutex::new(Instant::now())),
24 }
25 }
26
27 pub fn set_path(&self, path: PathBuf) -> Result<()> {
28 *self.path.lock().unwrap() = path.clone();
29
30 if path.exists() {
31 self.update_config()?;
32 }
33
34 self.setup_watcher(&path)?;
35
36 Ok(())
37 }
38
39 fn setup_watcher(&self, path: &Path) -> Result<()> {
40 let config_clone = Arc::clone(&self.config);
41 let path_clone = path.to_path_buf();
42 let last_reload_clone = Arc::clone(&self.last_reload);
43
44 let parent = match path.parent() {
45 Some(p) => p.to_path_buf(),
46 None => return Ok(()),
47 };
48
49 thread::spawn(move || {
50 unsafe {
51 let fd = libc::inotify_init1(libc::IN_CLOEXEC | libc::IN_NONBLOCK);
52 if fd < 0 {
53 eprintln!("inotify_init1 failed");
54 return;
55 }
56
57 let parent_str = match parent.to_str() {
58 Some(s) => s.to_string(),
59 None => { libc::close(fd); return; }
60 };
61
62 let cpath = std::ffi::CString::new(parent_str).unwrap();
63 let wd = libc::inotify_add_watch(
64 fd,
65 cpath.as_ptr(),
66 libc::IN_CREATE | libc::IN_MODIFY | libc::IN_DELETE | libc::IN_MOVED_TO,
67 );
68
69 if wd < 0 {
70 eprintln!("inotify_add_watch failed");
71 libc::close(fd);
72 return;
73 }
74
75 let buf_size = std::mem::size_of::<libc::inotify_event>() + 256 + 1;
77 let mut buf = vec![0u8; buf_size * 16];
78
79 loop {
80 let mut pfd = libc::pollfd {
82 fd,
83 events: libc::POLLIN,
84 revents: 0,
85 };
86 let ret = libc::poll(&mut pfd, 1, 1000); if ret <= 0 {
88 continue;
89 }
90
91 let n = libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len());
92 if n <= 0 {
93 continue;
94 }
95
96 let mut offset = 0isize;
97 while offset < n {
98 let event = &*(buf.as_ptr().offset(offset) as *const libc::inotify_event);
99 let name_ptr = buf.as_ptr().offset(offset + std::mem::size_of::<libc::inotify_event>() as isize);
100 let name_len = (0..event.len as usize).take_while(|&i| *name_ptr.add(i) != 0).count();
101 let name = std::str::from_utf8(std::slice::from_raw_parts(name_ptr, name_len))
102 .unwrap_or("");
103
104 if path_clone.file_name().and_then(|n| n.to_str()) == Some(name) {
106 let should_reload = {
107 let mut last = last_reload_clone.lock().unwrap();
108 let now = Instant::now();
109 if now.duration_since(*last) > Duration::from_millis(500) {
110 *last = now;
111 true
112 } else {
113 false
114 }
115 };
116
117 if should_reload {
118 let mut new_config = Ini::new();
119 if let Some(path_str) = path_clone.to_str() {
120 if new_config.load(path_str).is_ok() {
121 if let Ok(mut config) = config_clone.lock() {
122 *config = new_config;
123 }
124 }
125 }
126 }
127 }
128
129 offset += (std::mem::size_of::<libc::inotify_event>() + event.len as usize) as isize;
130 }
131 }
132 }
133 });
134
135 Ok(())
136 }
137
138 pub fn has_config(&self) -> bool {
139 self.path.lock().unwrap().exists()
140 }
141
142 pub fn get_path(&self) -> PathBuf {
143 self.path.lock().unwrap().clone()
144 }
145
146 pub fn update_config(&self) -> Result<()> {
147 let path = self.path.lock().unwrap().clone();
148
149 let mut new_config = Ini::new();
150 match new_config.load(path.to_str().unwrap_or("")) {
151 Ok(_) => {
152 *self.config.lock().unwrap() = new_config;
153 Ok(())
154 }
155 Err(e) => {
156 eprintln!(
157 "The following error occurred while parsing the config file:\n{}",
158 e
159 );
160 Ok(())
161 }
162 }
163 }
164
165 pub fn get_string(&self, section: &str, key: &str) -> Result<Option<String>> {
166 let config = self.config.lock().unwrap();
167 Ok(config.get(section, key))
168 }
169
170 pub fn get_bool(&self, section: &str, key: &str) -> Result<bool> {
171 let value = self.get_string(section, key)?;
172
173 match value.as_deref() {
174 Some("true") | Some("True") | Some("1") | Some("yes") | Some("Yes") => Ok(true),
175 Some("false") | Some("False") | Some("0") | Some("no") | Some("No") => Ok(false),
176 Some(v) => bail!("Invalid boolean value: {}", v),
177 None => Ok(false),
178 }
179 }
180
181 pub fn get_int(&self, section: &str, key: &str) -> Result<Option<i32>> {
182 let value = self.get_string(section, key)?;
183
184 match value {
185 Some(s) => match s.parse() {
186 Ok(v) => Ok(Some(v)),
187 Err(e) => bail!("Failed to parse integer: {}", e),
188 },
189 None => Ok(None),
190 }
191 }
192
193 pub fn get_threshold(&self, mode: &str) -> Result<u8> {
194 let key = match mode {
195 "start" => "charging_start_threshold",
196 "stop" => "charging_stop_threshold",
197 _ => bail!("Invalid threshold mode: {}", mode),
198 };
199
200 let value = self.get_int("battery", key)?;
201
202 match value {
203 Some(v) if (0..=100).contains(&v) => Ok(v as u8),
204 Some(v) => bail!("Threshold value out of range (0-100): {}", v),
205 None => Ok(if mode == "start" { 0 } else { 100 }),
206 }
207 }
208
209 pub fn has_option(&self, section: &str, key: &str) -> bool {
210 self.config.lock().unwrap().get(section, key).is_some()
211 }
212
213 pub fn get(&self, section: &str, key: &str, fallback: &str) -> String {
214 self.get_string(section, key)
215 .ok()
216 .flatten()
217 .unwrap_or_else(|| fallback.to_string())
218 }
219}
220
221impl Default for Config {
222 fn default() -> Self {
223 Self::new()
224 }
225}
226
227unsafe impl Send for Config {}
228unsafe impl Sync for Config {}
229
230lazy_static::lazy_static! {
231 pub static ref CONFIG: Config = Config::new();
232}
233
234pub fn find_config_file(args_config_file: Option<&str>) -> PathBuf {
235 let home = get_home_dir();
236
237 let user_config_dir = std::env::var("XDG_CONFIG_HOME")
238 .map(PathBuf::from)
239 .unwrap_or_else(|_| home.join(".config"));
240
241 let user_config_file = user_config_dir.join("auto-cpufreq/auto-cpufreq.conf");
242 let system_config_file = PathBuf::from("/etc/auto-cpufreq.conf");
243
244 if let Some(config_path) = args_config_file {
245 let path = PathBuf::from(config_path);
246 if path.is_file() {
247 return path;
248 } else {
249 eprintln!(
250 "Config file specified with '--config {}' not found.",
251 config_path
252 );
253 std::process::exit(1);
254 }
255 }
256
257 if user_config_file.is_file() {
258 return user_config_file;
259 }
260
261 system_config_file
262}
263
264fn get_home_dir() -> PathBuf {
265 let output = Command::new("sh")
266 .arg("-c")
267 .arg("getent passwd ${SUDO_USER:-$USER} | cut -d: -f6")
268 .output();
269
270 match output {
271 Ok(output) if output.status.success() => {
272 let home = String::from_utf8_lossy(&output.stdout);
273 PathBuf::from(home.trim())
274 }
275 _ => std::env::var("HOME")
276 .map(PathBuf::from)
277 .unwrap_or_else(|_| PathBuf::from("/root")),
278 }
279}