//! Special daemon with root privileges. Since GUI should not have (and sometimes can't have) root //! privileges and service processes are designed to be as small as possible this is proxy. //! //! It is responsible for: //! * Loading all amdfand processes. In order to do this process needs to be killed with signal 0 to check if it still is alive //! * Reload amdfand process with signal SIGHUP //! * Save changed config file //! //! It is using `/tmp/amdgui-helper.sock` file and `ron` serialization for communication. //! After each operation connection is terminated so each command needs new connection. #![allow(clippy::non_octal_unix_permissions)] use amdgpu::helper_cmd::{Command, Pid, Response}; use amdgpu::IoFailure; use std::ffi::OsStr; use std::fs::Permissions; use std::io::{Read, Write}; use std::net::Shutdown; use std::os::unix::fs::PermissionsExt; use std::os::unix::net::{UnixListener, UnixStream}; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("{0}")] Io(#[from] amdgpu::IoFailure), #[error("{0}")] Lock(#[from] amdgpu::AmdGpuError), } pub type Result = std::result::Result; fn main() -> Result<()> { if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", "DEBUG"); } pretty_env_logger::init(); let mut lock = amdgpu::lock_file::PidLock::new("amdgui", String::from("helper"))?; lock.acquire()?; let sock_path = amdgpu::helper_cmd::sock_file(); let listener = { let _ = std::fs::remove_file(&sock_path); UnixListener::bind(&sock_path).map_err(|io| IoFailure { io, path: sock_path.clone(), })? }; if let Err(e) = std::fs::set_permissions(&sock_path, Permissions::from_mode(0x777)) { log::error!("Failed to change gui helper socket file mode. {:?}", e); } while let Ok((stream, _addr)) = listener.accept() { handle_connection(stream); } lock.release()?; Ok(()) } pub struct Service(UnixStream); impl Service { /// Serialize and send command pub fn write_response(&mut self, res: amdgpu::helper_cmd::Response) { match ron::to_string(&res) { Ok(buffer) => match self.0.write_all(buffer.as_bytes()) { Ok(_) => { log::info!("Response successfully written") } Err(e) => log::warn!("Failed to write response. {:?}", e), }, Err(e) => { log::warn!("Failed to serialize response {:?}. {:?}", res, e) } } } /// Read from `.sock` file new line separated commands pub fn read_command(&mut self) -> Option { let mut command = String::with_capacity(100); log::info!("Reading stream..."); read_line(&mut self.0, &mut command); if command.is_empty() { return None; } Some(command) } /// Close connection with no operation response pub fn kill(mut self) { self.write_response(Response::NoOp); self.close(); } pub fn close(self) { let _ = self.0.shutdown(Shutdown::Both); } } fn handle_connection(stream: UnixStream) { let mut service = Service(stream); let command = match service.read_command() { Some(s) => s, _ => return service.kill(), }; log::info!("Incoming {:?}", command); let cmd = match ron::from_str::(command.trim()) { Ok(cmd) => cmd, Err(e) => { log::warn!("Invalid message {:?}. {:?}", command, e); return service.kill(); } }; handle_command(service, cmd); } fn handle_command(mut service: Service, cmd: Command) { match cmd { Command::ReloadConfig { pid } => { log::info!("Reloading config file for pid {:?}", pid); handle_reload_config(service, pid); } Command::FanServices => handle_fan_services(service), Command::SaveFanConfig { path, content } => { handle_save_fan_config(&mut service, path, content) } } } fn handle_save_fan_config(service: &mut Service, path: String, content: String) { match std::fs::write(path, content) { Err(e) => service.write_response(Response::ConfigFileSaveFailed(format!("{:?}", e))), Ok(..) => service.write_response(Response::ConfigFileSaved), } } fn handle_fan_services(mut service: Service) { log::info!("Loading fan services"); let services = read_fan_services(); log::info!("Loaded fan services pid {:?}", services); service.write_response(Response::Services(services)); } fn read_line(stream: &mut UnixStream, command: &mut String) { let mut buffer = [0]; while stream.read_exact(&mut buffer).is_ok() { if buffer[0] == b'\n' { break; } match std::str::from_utf8(&buffer) { Ok(s) => { command.push_str(s); } Err(e) => { log::error!("Failed to read from client. {:?}", e); let _ = stream.shutdown(Shutdown::Both); continue; } } } } fn handle_reload_config(service: Service, pid: Pid) { unsafe { nix::libc::kill(pid.0, nix::sys::signal::Signal::SIGHUP as i32); } service.kill(); } fn read_fan_services() -> Vec { if let Ok(entry) = std::fs::read_dir("/var/lib/amdfand") { entry .filter(|e| { e.as_ref() .map(|e| { log::info!("Extension is {:?}", e.path().extension()); e.path().extension().and_then(OsStr::to_str) == Some("pid") }) .ok() .unwrap_or_default() }) .filter_map(|e| { log::info!("Found entry {:?}", e); match e { Ok(entry) => std::fs::read_to_string(entry.path()) .ok() .and_then(|s| s.parse::().ok()) .filter(|pid| unsafe { nix::libc::kill(*pid, 0) } == 0), _ => None, } }) .map(Pid) .collect() } else { log::warn!("Directory /var/lib/amdfand not found"); vec![] } }