fix(daemon,scanner,crypto): harden lifecycle, widen Windows page scan, fix SQLCipher short read (#54)

- daemon: write pid file only after IPC bound; clean sock+pid on normal return
- transport: PidFile JSON metadata + identity verification (ps/QueryFullProcessImageNameW); SIGTERM with poll-timeout; backward-compat read for plain-text pid
- daemon_cmd: status/stop work with both new JSON and legacy plain-text pid file
- config: cwd → exe_dir → ~/.wx-cli config precedence matches `wx init` write order; Windows DB auto-detect picks newest by latest mtime
- crypto: full_decrypt uses read_exact for intermediate pages, zero-pads only the final partial page; tests cover short-chunk reads and early EOF
- scanner/windows: page protect check covers PAGE_READWRITE / PAGE_WRITECOPY / PAGE_EXECUTE_*WRITE* with modifier-bit stripping

Cross-reviewed by @wx-cli-coder. Windows verified via `cargo check --target x86_64-pc-windows-gnu` (no Windows runtime test).
This commit is contained in:
jakevin
2026-05-14 17:11:42 +08:00
committed by GitHub
parent d4587b1c68
commit 70aa3a44e3
6 changed files with 601 additions and 206 deletions
+21 -10
View File
@@ -25,9 +25,7 @@ async fn async_run() -> Result<()> {
tokio::fs::create_dir_all(&cli_dir).await?;
tokio::fs::create_dir_all(config::cache_dir()).await?;
// 写 PID 文件
let pid = std::process::id();
tokio::fs::write(config::pid_path(), pid.to_string()).await?;
// 注册 SIGTERM / SIGINT 处理
setup_signal_handler().await;
@@ -39,7 +37,8 @@ async fn async_run() -> Result<()> {
eprintln!("[daemon] DB_DIR: {}", cfg.db_dir.display());
// 加载密钥
let keys_content = tokio::fs::read_to_string(&cfg.keys_file).await
let keys_content = tokio::fs::read_to_string(&cfg.keys_file)
.await
.map_err(|e| anyhow::anyhow!("读取密钥文件 {:?} 失败: {}", cfg.keys_file, e))?;
let keys_raw: serde_json::Value = serde_json::from_str(&keys_content)?;
let all_keys = extract_keys(&keys_raw);
@@ -49,11 +48,14 @@ async fn async_run() -> Result<()> {
let db = Arc::new(cache::DbCache::new(cfg.db_dir.clone(), all_keys.clone()).await?);
// 收集消息 DB 列表
let msg_db_keys: Vec<String> = all_keys.keys()
let msg_db_keys: Vec<String> = all_keys
.keys()
.filter(|k| {
let k = k.replace('\\', "/");
k.contains("message/message_") && k.ends_with(".db")
&& !k.contains("_fts") && !k.contains("_resource")
k.contains("message/message_")
&& k.ends_with(".db")
&& !k.contains("_fts")
&& !k.contains("_resource")
})
.cloned()
.collect();
@@ -82,7 +84,9 @@ async fn async_run() -> Result<()> {
let names_arc = Arc::new(tokio::sync::RwLock::new(Arc::new(names)));
// 启动 IPC server(阻塞)
server::serve(Arc::clone(&db), Arc::clone(&names_arc)).await?;
let serve_result = server::serve(Arc::clone(&db), Arc::clone(&names_arc)).await;
cleanup_ipc_files();
serve_result?;
Ok(())
}
@@ -96,7 +100,9 @@ fn extract_keys(json: &serde_json::Value) -> HashMap<String, String> {
let mut result = HashMap::new();
if let Some(obj) = json.as_object() {
for (k, v) in obj {
if k.starts_with('_') { continue; }
if k.starts_with('_') {
continue;
}
let enc_key = if let Some(s) = v.as_str() {
s.to_string()
} else if let Some(obj2) = v.as_object() {
@@ -132,8 +138,13 @@ async fn setup_signal_handler() {
});
}
#[cfg(unix)]
fn cleanup_and_exit() {
let _ = std::fs::remove_file(config::sock_path());
let _ = std::fs::remove_file(config::pid_path());
cleanup_ipc_files();
std::process::exit(0);
}
fn cleanup_ipc_files() {
let _ = std::fs::remove_file(config::sock_path());
let _ = std::fs::remove_file(config::pid_path());
}