diff --git a/crates/cardwire-daemon/src/analyzer/dynamic_analysis.rs b/crates/cardwire-daemon/src/analyzer/dynamic_analysis.rs index 6b0064a..0d53936 100644 --- a/crates/cardwire-daemon/src/analyzer/dynamic_analysis.rs +++ b/crates/cardwire-daemon/src/analyzer/dynamic_analysis.rs @@ -10,23 +10,42 @@ pub fn check_steam_environ(environ: &[u8]) -> bool { } /// Read the proc `maps` file to find the gamemodeauto.so -pub fn check_gamemode(environ: &[u8]) -> bool { - environ - .windows(18) +pub fn check_gamemode(map: &[u8]) -> bool { + map.windows(18) .any(|window| window == b"libgamemodeauto.so") } -/// Read the environ map to file the FLATPAK_ID and compare with .desktop apps -pub fn check_flatpak_environ(environ: &[u8], xdg_list: &HashMap) -> bool { - // Check if the byte array contains the substring - 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) +/// Check if the comm is in the xdg list +pub fn check_fdo_app_id(comm: &str, xdg_list: &HashMap) -> bool { + xdg_list.contains_key(comm) +} + +pub fn check_for_flatpak_run(cmdline: &str, xdg_list: &HashMap) -> bool { + let mut args = cmdline.split('\0').filter(|s| !s.is_empty()); + + if let Some(arg0) = args.next() { + // Ensure the actual executable is flatpak or bwrap, not a wrapper like 'niri msg' + if !arg0.ends_with("flatpak") + && !arg0.ends_with(".flatpak-wrapped") + && !arg0.ends_with("bwrap") { + return false; + } + } else { + return false; + } + + // Now check if any of the arguments match our allowed app + for arg in args { + if let Some(exec) = arg.strip_prefix("--command=") { + if xdg_list.contains_key(exec) { + return true; + } + } else if xdg_list.contains_key(arg) { return true; } } + false } diff --git a/crates/cardwire-daemon/src/analyzer/models.rs b/crates/cardwire-daemon/src/analyzer/models.rs index 1de73f6..514dc74 100644 --- a/crates/cardwire-daemon/src/analyzer/models.rs +++ b/crates/cardwire-daemon/src/analyzer/models.rs @@ -8,7 +8,7 @@ use tokio::{ use crate::analyzer::{ dynamic_analysis::{ - check_cardwire_allow, check_flatpak_environ, check_gamemode, check_gpu_env, check_steam_environ + check_cardwire_allow, check_fdo_app_id, check_for_flatpak_run, check_gamemode, check_gpu_env, check_steam_environ }, static_analysis }; #[repr(C)] @@ -150,7 +150,8 @@ impl CardwireAnalyzer { Some(name) => name, None => return, }; - if let Some(result) = self_clone.evaluate_app(event.pid).await + if let Some(result) = + self_clone.evaluate_app(event.pid, &real_app_name).await && result { info!( @@ -190,26 +191,39 @@ 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 { + async fn evaluate_app(&self, pid: u32, comm: &str) -> 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(&environ) { return Some(allow); } let xdg_list = self.xdg_list.read().await; - let result = check_flatpak_environ(&environ, &xdg_list) - || check_gamemode(&environ) + let mut result = check_fdo_app_id(comm, &xdg_list) || check_steam_environ(&environ) || check_gpu_env(&environ); - if result { - info!("time to read environ for pid {}: {}", pid, time); + // if no result with environ file, read cmdline + // The goal is to reduce unnecessary reads + if !result { + let path_cmd = format!("/proc/{}/cmdline", pid); + let cmdline = match fs::read_to_string(path_cmd) { + Ok(content) => content, + Err(_) => return None, + }; + result = check_for_flatpak_run(&cmdline, &xdg_list); + } + // reading map is slow, should be done if every test are false + if !result { + let path_map = format!("/proc/{}/map", pid); + let map = match fs::read(path_map) { + Ok(content) => content, + Err(_) => return None, + }; + result = check_gamemode(&map); } Some(result) } diff --git a/crates/cardwire-daemon/src/analyzer/static_analysis.rs b/crates/cardwire-daemon/src/analyzer/static_analysis.rs index 3c097bf..81cb4a1 100644 --- a/crates/cardwire-daemon/src/analyzer/static_analysis.rs +++ b/crates/cardwire-daemon/src/analyzer/static_analysis.rs @@ -68,14 +68,18 @@ pub async fn get_fdo_apps() -> anyhow::Result> { // ignore if app doesnt end with .desktop if let Some(ext) = path.extension() && ext == "desktop" + && let Ok(app_fdo) = DesktopEntry::from_path(path, Some(&locales)) + && app_fdo.prefers_non_default_gpu() + && let Some(name) = app_fdo.name(&locales) { - // for now, only keep flatpak apps that prefers a non default gpu - // the reason we only keep flatpak apps is because i can match a process with - // it's FLATPAK_ID env - if let Ok(app_fdo) = DesktopEntry::from_path(path, Some(&locales)) - && app_fdo.prefers_non_default_gpu() - && let Some(flatpak_id) = app_fdo.flatpak() - { + // Push both lowercase and normal name to the hashmap + // the RPCS3 .desktop contain the name `RPCS3` but the comm is `rpcs3`, so + // we need to lowercase it On the other, Ryujinx + // .desktop's name is `Ryujinx` and the comm is `Ryujinx`, so we also push + // the default name + app_list.insert(name.to_ascii_lowercase(), true); + app_list.insert(name.to_string(), true); + if let Some(flatpak_id) = app_fdo.flatpak() { app_list.insert(flatpak_id.to_string(), true); } } diff --git a/crates/cardwire-ebpf/src/bpf.c b/crates/cardwire-ebpf/src/bpf.c index 20ab3c1..2c8e5c6 100644 --- a/crates/cardwire-ebpf/src/bpf.c +++ b/crates/cardwire-ebpf/src/bpf.c @@ -102,14 +102,25 @@ int trace_process_exit(void *ctx) if (!is_smart()) return 0; - // Now create and send a close event containing the pid to the userspace + // we get the pid_tgdi + __u64 pid_tgid = bpf_get_current_pid_tgid(); + // extract the tgid + __u32 tgid = pid_tgid >> 32; + // and the pid + __u32 pid = pid_tgid & 0xFFFFFFFF; + + // Only send close event if the main thread is exiting + if (pid != tgid) + return 0; + struct close_t *rb_data = {}; rb_data = bpf_ringbuf_reserve(&cw_close_events, sizeof(struct close_t), 0); + // if struct error, exit if (!rb_data) { return 0; } - rb_data->pid = bpf_get_current_pid_tgid() >> 32; + rb_data->pid = tgid; bpf_ringbuf_submit(rb_data, 0); return 0;