From ed5e05fc8b7a6b5b386e2f528ecc42429f06734e Mon Sep 17 00:00:00 2001 From: luytan Date: Tue, 7 Jul 2026 00:26:35 +0200 Subject: [PATCH] refactor: replace any fs:: read with sync fs read and share environ bytes to multiple functions --- .../src/analyzer/dynamic_analysis.rs | 43 ++++----------- crates/cardwire-daemon/src/analyzer/models.rs | 52 +++++++++++-------- 2 files changed, 40 insertions(+), 55 deletions(-) diff --git a/crates/cardwire-daemon/src/analyzer/dynamic_analysis.rs b/crates/cardwire-daemon/src/analyzer/dynamic_analysis.rs index 6691875..6b0064a 100644 --- a/crates/cardwire-daemon/src/analyzer/dynamic_analysis.rs +++ b/crates/cardwire-daemon/src/analyzer/dynamic_analysis.rs @@ -2,37 +2,24 @@ //! - gamemoderun analysis //! - library analysis use std::collections::HashMap; -use tokio::fs; /// Read the proc `environ` file to find the `SteamAppId=` string /// used to identify both native and proton games -pub async fn check_steam_environ(pid: u32) -> bool { - let path = format!("/proc/{}/environ", pid); - let Ok(bytes) = fs::read(path).await else { - return false; - }; - bytes.windows(11).any(|window| window == b"SteamAppId=") +pub fn check_steam_environ(environ: &[u8]) -> bool { + environ.windows(11).any(|window| window == b"SteamAppId=") } /// Read the proc `maps` file to find the gamemodeauto.so -pub async fn check_gamemode(pid: u32) -> bool { - let path = format!("/proc/{}/maps", pid); - let Ok(bytes) = fs::read(path).await else { - return false; - }; - bytes +pub fn check_gamemode(environ: &[u8]) -> bool { + environ .windows(18) .any(|window| window == b"libgamemodeauto.so") } /// Read the environ map to file the FLATPAK_ID and compare with .desktop apps -pub async fn check_flatpak_environ(pid: u32, xdg_list: &HashMap) -> bool { - let path = format!("/proc/{}/environ", pid); - let Ok(bytes) = fs::read(path).await else { - return false; - }; +pub fn check_flatpak_environ(environ: &[u8], xdg_list: &HashMap) -> bool { // Check if the byte array contains the substring - for var in bytes.split(|&b| b == 0) { + for var in environ.split(|&b| b == 0) { if var.starts_with(b"FLATPAK_ID=") && let Ok(str) = std::str::from_utf8(&var[11..]) && xdg_list.contains_key(str) @@ -43,13 +30,8 @@ pub async fn check_flatpak_environ(pid: u32, xdg_list: &HashMap) - false } -pub async fn check_cardwire_allow(pid: u32) -> Option { - let path = format!("/proc/{}/environ", pid); - let Ok(bytes) = fs::read(path).await else { - return None; - }; - - for var in bytes.split(|&b| b == 0) { +pub fn check_cardwire_allow(environ: &[u8]) -> Option { + for var in environ.split(|&b| b == 0) { if var.starts_with(b"CARDWIRE_ALLOW=") { if var.get(15) == Some(&b'1') { return Some(true); // CARDWIRE_ALLOW=1 @@ -61,13 +43,8 @@ pub async fn check_cardwire_allow(pid: u32) -> Option { // Not present None } -pub async fn check_gpu_env(pid: u32) -> bool { - let path = format!("/proc/{}/environ", pid); - let Ok(bytes) = fs::read(path).await else { - return false; - }; - - for var in bytes.split(|&b| b == 0) { +pub fn check_gpu_env(environ: &[u8]) -> bool { + for var in environ.split(|&b| b == 0) { if var.starts_with(b"DRI_PRIME==") { if var.get(11) == Some(&b'1') { return true; // DRI_PRIME=1 diff --git a/crates/cardwire-daemon/src/analyzer/models.rs b/crates/cardwire-daemon/src/analyzer/models.rs index 0a6d6aa..1de73f6 100644 --- a/crates/cardwire-daemon/src/analyzer/models.rs +++ b/crates/cardwire-daemon/src/analyzer/models.rs @@ -1,9 +1,9 @@ use aya::maps::{HashMap as AyaHashMap, RingBuf}; use cardwire_ebpf::EbpfBlocker; -use log::{debug, warn}; -use std::{collections::HashMap, ptr, sync::Arc}; +use log::{debug, info, warn}; +use std::{collections::HashMap, fs, ptr, sync::Arc}; use tokio::{ - fs, io::{Interest, unix::AsyncFd}, sync::{Mutex, RwLock, mpsc} + io::{Interest, unix::AsyncFd}, sync::{Mutex, RwLock, mpsc}, time::Instant }; use crate::analyzer::{ @@ -140,19 +140,25 @@ impl CardwireAnalyzer { EventMsg::Exec(event) => { let self_clone = self.clone(); tokio::spawn(async move { + let time = Instant::now(); let pid_map = self_clone.pid_map.read().await; if pid_map.get(&event.pid, 0).is_ok() { return; } drop(pid_map); - let real_app_name = match get_real_process_name(event.pid).await { + let real_app_name = match get_real_process_name(event.pid) { Some(name) => name, None => return, }; if let Some(result) = self_clone.evaluate_app(event.pid).await && result { - debug!("ALLOW: pid: {}, name: {}", event.pid, real_app_name); + info!( + "ALLOW: pid: {} process: {} in {}us", + event.pid, + &real_app_name, + time.elapsed().as_micros() + ); let mut pid_map = self_clone.pid_map.write().await; if let Err(e) = pid_map.insert(event.pid, 1, 0) { warn!("Failed to insert into eBPF map: {}", e); @@ -165,7 +171,7 @@ impl CardwireAnalyzer { EventMsg::Close(event) => { let self_clone = self.clone(); tokio::spawn(async move { - let real_app_name = match get_real_process_name(event.pid).await { + let real_app_name = match get_real_process_name(event.pid) { Some(name) => name, None => "unknown".to_string(), }; @@ -185,31 +191,33 @@ impl CardwireAnalyzer { /// Default app are blocked, try to find if it's a game or a gpu intensive app async fn evaluate_app(&self, pid: u32) -> Option { + let path = format!("/proc/{}/environ", pid); + let time = Instant::now(); + let environ = match fs::read(path) { + Ok(content) => content, + Err(_) => return None, + }; + let time = time.elapsed().as_micros(); // First check CARDWIRE_ALLOW, if None continue - if let Some(allow) = check_cardwire_allow(pid).await { + if let Some(allow) = check_cardwire_allow(&environ) { return Some(allow); } - let xdg_list = self.xdg_list.read().await; - // experimentation - // TODO: Replace - let check = |check: bool| if check { Err(()) } else { Ok(()) }; - let result = tokio::try_join!( - async { check(check_steam_environ(pid).await) }, - async { check(check_gamemode(pid).await) }, - async { check(check_flatpak_environ(pid, &xdg_list).await) }, - async { check(check_gpu_env(pid).await) }, - ); - match result.is_err() { - true => Some(true), - false => None, + + let result = check_flatpak_environ(&environ, &xdg_list) + || check_gamemode(&environ) + || check_steam_environ(&environ) + || check_gpu_env(&environ); + if result { + info!("time to read environ for pid {}: {}", pid, time); } + Some(result) } } -async fn get_real_process_name(pid: u32) -> Option { +fn get_real_process_name(pid: u32) -> Option { let cmdline_path = format!("/proc/{}/cmdline", pid); - let cmdline_bytes = match fs::read(&cmdline_path).await { + let cmdline_bytes = match fs::read(&cmdline_path) { Ok(b) => b, Err(_) => return None, // process died };