Skip to content

Commit c44d137

Browse files
committed
fix: Improve cache cleanup, error sanitization, and log batching
- cache.rs: Add cleanup_expired() call to pgrep cache functions to ensure all cache entries are periodically cleaned up - cli.rs: Sanitize error messages to avoid exposing sensitive system details; log full errors for debugging, return user-friendly messages - log.rs: Batch log lines into single event emission to reduce IPC overhead and improve UI performance - constants.ts, logs.ts: Update frontend to handle batched log-lines event
1 parent 0544bf1 commit c44d137

5 files changed

Lines changed: 70 additions & 18 deletions

File tree

src-tauri/src/cache.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,9 @@ pub fn get_mount_output() -> Option<Output> {
122122
pub fn is_krun_running() -> bool {
123123
let cache_key = "pgrep_krun".to_string();
124124

125-
// Check cache first
125+
// Check cache first (also cleanup expired entries periodically)
126126
let cached = with_cache(|cache| {
127+
cache.cleanup_expired(MAX_CACHE_AGE);
127128
cache.get(&cache_key, PGREP_CACHE_DURATION).cloned()
128129
}).flatten();
129130

@@ -147,8 +148,9 @@ pub fn is_krun_running() -> bool {
147148
pub fn is_libkrun_running() -> bool {
148149
let cache_key = "pgrep_libkrun".to_string();
149150

150-
// Check cache first
151+
// Check cache first (also cleanup expired entries periodically)
151152
let cached = with_cache(|cache| {
153+
cache.cleanup_expired(MAX_CACHE_AGE);
152154
cache.get(&cache_key, PGREP_CACHE_DURATION).cloned()
153155
}).flatten();
154156

src-tauri/src/cli.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,50 @@ use std::path::{Path, PathBuf};
55
use std::time::{Duration, Instant};
66
use std::sync::OnceLock;
77

8+
/// Sanitize error output to avoid exposing sensitive system details
9+
/// Logs the full error for debugging but returns a user-friendly message
10+
fn sanitize_error(stdout: &str, stderr: &str) -> String {
11+
// Log full details for debugging
12+
if !stdout.is_empty() || !stderr.is_empty() {
13+
log::debug!("Command failed - stdout: {}, stderr: {}", stdout, stderr);
14+
}
15+
16+
// Check for common error patterns and return user-friendly messages
17+
let combined = format!("{}{}", stdout, stderr);
18+
19+
if combined.contains("not mounted") || combined.contains("No such file") {
20+
return "Filesystem is not mounted".to_string();
21+
}
22+
if combined.contains("Permission denied") {
23+
return "Permission denied - try running with administrator privileges".to_string();
24+
}
25+
if combined.contains("Device busy") || combined.contains("resource busy") {
26+
return "Device is busy - close any applications using it and try again".to_string();
27+
}
28+
if combined.contains("Invalid argument") {
29+
return "Invalid operation or unsupported filesystem".to_string();
30+
}
31+
if combined.contains("No space left") {
32+
return "No space left on device".to_string();
33+
}
34+
if combined.contains("Read-only") {
35+
return "Filesystem is read-only".to_string();
36+
}
37+
38+
// For anylinuxfs-specific errors, extract the message after "Error:"
39+
if let Some(pos) = combined.find("Error:") {
40+
let error_msg = combined[pos + 6..].trim();
41+
// Take first line only, limit length
42+
let first_line = error_msg.lines().next().unwrap_or(error_msg);
43+
if first_line.len() <= 200 {
44+
return first_line.to_string();
45+
}
46+
}
47+
48+
// Generic fallback - don't expose raw output
49+
"Operation failed - check logs for details".to_string()
50+
}
51+
852
/// Common locations to search for anylinuxfs
953
const SEARCH_PATHS: &[&str] = &[
1054
"/opt/homebrew/bin/anylinuxfs",
@@ -115,7 +159,7 @@ fn execute_direct(args: &[&str], passphrase: Option<&str>) -> Result<String, Str
115159
} else {
116160
let stderr = String::from_utf8_lossy(&output.stderr);
117161
let stdout = String::from_utf8_lossy(&output.stdout);
118-
Err(format!("{}{}", stdout, stderr))
162+
Err(sanitize_error(&stdout, &stderr))
119163
}
120164
}
121165

@@ -177,7 +221,7 @@ fn execute_with_sudo(args: &[&str], passphrase: Option<&str>) -> Result<String,
177221
} else if stderr.contains("no askpass program") || stderr.contains("no password was provided") {
178222
return Err("Authentication cancelled".to_string());
179223
} else {
180-
return Err(format!("{}{}", stdout, stderr));
224+
return Err(sanitize_error(&stdout, &stderr));
181225
}
182226
}
183227
}

src-tauri/src/commands/log.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ pub fn start_log_stream(app: AppHandle) -> Result<(), String> {
115115
if is_our_file {
116116
match event.kind {
117117
EventKind::Modify(_) | EventKind::Create(_) => {
118-
// Read new lines
118+
// Read new lines and batch them
119119
if let Ok(mut file) = File::open(&log_path) {
120120
let file_len = file.metadata()
121121
.map(|m| m.len())
@@ -124,21 +124,25 @@ pub fn start_log_stream(app: AppHandle) -> Result<(), String> {
124124
if file_len > last_pos {
125125
if file.seek(SeekFrom::Start(last_pos)).is_ok() {
126126
let reader = BufReader::new(&file);
127-
for line in reader.lines() {
128-
if let Ok(line) = line {
129-
let _ = app.emit("log-line", line);
130-
}
127+
// Batch lines to reduce IPC overhead
128+
let lines: Vec<String> = reader.lines()
129+
.filter_map(|l| l.ok())
130+
.collect();
131+
if !lines.is_empty() {
132+
let _ = app.emit("log-lines", lines);
131133
}
132134
}
133135
last_pos = file_len;
134136
} else if file_len < last_pos {
135137
// File was truncated, read from beginning
136138
if file.seek(SeekFrom::Start(0)).is_ok() {
137139
let reader = BufReader::new(&file);
138-
for line in reader.lines() {
139-
if let Ok(line) = line {
140-
let _ = app.emit("log-line", line);
141-
}
140+
// Batch lines to reduce IPC overhead
141+
let lines: Vec<String> = reader.lines()
142+
.filter_map(|l| l.ok())
143+
.collect();
144+
if !lines.is_empty() {
145+
let _ = app.emit("log-lines", lines);
142146
}
143147
}
144148
last_pos = file_len;

src/lib/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Tauri event names
22
export const Events = {
3-
LOG_LINE: 'log-line',
3+
LOG_LINES: 'log-lines', // Batched log lines for better performance
44
SHELL_OUTPUT: 'shell-output',
55
SHELL_EXIT: 'shell-exit',
66
DISKS_CHANGED: 'disks-changed',

src/lib/stores/logs.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,13 @@ function createLogsStore() {
5555
// Start the backend log watcher
5656
await startLogStream();
5757

58-
// Listen for log events
59-
unlisten = await listen<string>(Events.LOG_LINE, (event) => {
58+
// Listen for batched log events (more efficient than per-line)
59+
unlisten = await listen<string[]>(Events.LOG_LINES, (event) => {
6060
update((s) => {
61-
const newLine = processLine(event.payload);
62-
const newLines = [...s.lines, newLine];
61+
const newLines = [...s.lines];
62+
for (const line of event.payload) {
63+
newLines.push(processLine(line));
64+
}
6365
// Keep only last MAX_LINES
6466
if (newLines.length > Limits.MAX_LOG_LINES) {
6567
newLines.splice(0, newLines.length - Limits.MAX_LOG_LINES);

0 commit comments

Comments
 (0)