mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-30 10:42:09 +08:00
fix(macos): discard a core cache the current user cannot write (#7883)
* fix(macos): discard a core cache the current user cannot write * docs(changelog): add the unwritable core cache fix * docs: update changelog to include Linux in cache.db ownership fix --------- Co-authored-by: oomeow <oomeow@outlook.com>
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
修复 i18n 清理脚本因导入方式错误而无法运行的问题
|
||||
修复 Linux 桌面环境下主题切换时应用外观不跟随的问题
|
||||
修复 Gemini 测试正确性
|
||||
修复 macOS/Linux 用户模式下旧版服务遗留的 cache.db 属主为 root 且当前用户不可写,导致无法记住上次选择节点的问题
|
||||
修复代理组节点 `PASS-RULE` 被过滤丢失的问题
|
||||
|
||||
<details>
|
||||
|
||||
@@ -108,6 +108,8 @@ impl CoreManager {
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
let clash_core = Config::verge().await.latest_arc().get_valid_clash_core();
|
||||
let config_dir = dirs::app_home_dir()?;
|
||||
#[cfg(unix)]
|
||||
discard_unwritable_core_cache(&config_dir);
|
||||
|
||||
#[cfg(unix)]
|
||||
let previous_mask = unsafe { tauri_plugin_clash_verge_sysinfo::libc::umask(0o077) };
|
||||
@@ -322,6 +324,107 @@ impl CoreManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops a `cache.db` the current user cannot write before handing the directory to the core.
|
||||
///
|
||||
/// mihomo keeps `profile.store-selected` in `cache.db` inside its data directory. Service builds
|
||||
/// before the runtime staging rework ran the core as root against this same directory without a
|
||||
/// umask, leaving the file as `root:staff 0644`: still readable, so the core loads stale
|
||||
/// selections, but never writable again, so it silently stops recording new ones. Nothing
|
||||
/// repairs it either, because the service-side cleanup only runs inside the service. Removing it
|
||||
/// lets the core recreate the cache under the current user; the fake-ip leases and frozen
|
||||
/// selections that go with it could not be updated anyway.
|
||||
#[cfg(unix)]
|
||||
fn discard_unwritable_core_cache(config_dir: &Path) {
|
||||
let cache = config_dir.join("cache.db");
|
||||
// Appending neither creates nor truncates, so this only asks whether a write would be allowed.
|
||||
match std::fs::OpenOptions::new().append(true).open(&cache) {
|
||||
Ok(_) => {}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
|
||||
Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => {
|
||||
// Unlinking is governed by the directory, which the current user owns.
|
||||
match std::fs::remove_file(&cache) {
|
||||
Ok(()) => logging!(
|
||||
info,
|
||||
Type::Core,
|
||||
"Discarded a core cache the current user cannot write: {}",
|
||||
cache.display()
|
||||
),
|
||||
Err(error) => logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to discard the unwritable core cache {}: {error}",
|
||||
cache.display()
|
||||
),
|
||||
}
|
||||
}
|
||||
Err(error) => logging!(
|
||||
warn,
|
||||
Type::Core,
|
||||
"Failed to probe the core cache {}: {error}",
|
||||
cache.display()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
mod core_cache_tests {
|
||||
use super::discard_unwritable_core_cache;
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
|
||||
fn scratch(name: &str) -> anyhow::Result<std::path::PathBuf> {
|
||||
let root = std::env::temp_dir().join(format!("clash-verge-core-cache-{name}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
std::fs::create_dir_all(&root)?;
|
||||
Ok(root)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unwritable_cache_is_discarded() -> anyhow::Result<()> {
|
||||
// root ignores the permission bits, so the probe cannot fail there.
|
||||
if unsafe { tauri_plugin_clash_verge_sysinfo::libc::geteuid() } == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let root = scratch("unwritable")?;
|
||||
let cache = root.join("cache.db");
|
||||
std::fs::write(&cache, b"stale")?;
|
||||
std::fs::set_permissions(&cache, std::fs::Permissions::from_mode(0o444))?;
|
||||
|
||||
discard_unwritable_core_cache(&root);
|
||||
|
||||
assert!(!cache.exists(), "an unwritable cache must not be handed to the core");
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_writable_cache_is_kept() -> anyhow::Result<()> {
|
||||
let root = scratch("writable")?;
|
||||
let cache = root.join("cache.db");
|
||||
std::fs::write(&cache, b"live")?;
|
||||
|
||||
discard_unwritable_core_cache(&root);
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read(&cache)?,
|
||||
b"live",
|
||||
"a writable cache carries the stored selections and must survive"
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_missing_cache_is_not_an_error() -> anyhow::Result<()> {
|
||||
let root = scratch("missing")?;
|
||||
|
||||
discard_unwritable_core_cache(&root);
|
||||
|
||||
assert!(!root.join("cache.db").exists());
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod readiness_tests {
|
||||
use super::{claim_core_readiness_generation, poll_sidecar_readiness, should_clear_terminated_sidecar};
|
||||
|
||||
Reference in New Issue
Block a user