mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-22 10:53:36 +08:00
110 lines
4.2 KiB
Rust
110 lines
4.2 KiB
Rust
use anyhow::{Context as _, Result, bail};
|
|
use std::os::windows::io::{AsRawHandle as _, FromRawHandle as _, OwnedHandle};
|
|
use windows_service::{
|
|
Error as WindowsServiceError,
|
|
service::{ServiceAccess, ServiceState},
|
|
service_manager::{ServiceManager, ServiceManagerAccess},
|
|
};
|
|
use windows_sys::Win32::{
|
|
Foundation::{ERROR_NO_MORE_FILES, ERROR_SERVICE_DOES_NOT_EXIST, INVALID_HANDLE_VALUE},
|
|
System::Diagnostics::ToolHelp::{
|
|
CreateToolhelp32Snapshot, PROCESSENTRY32W, Process32FirstW, Process32NextW, TH32CS_SNAPPROCESS,
|
|
},
|
|
};
|
|
|
|
pub(crate) async fn confirm_idle(location_refused: bool) -> Result<()> {
|
|
if location_refused {
|
|
let credentials = crate::core::owner_identity::current_owner_credentials()?;
|
|
let response = clash_verge_service_ipc::get_status(&credentials).await?;
|
|
if response.code != 0 {
|
|
bail!(
|
|
"cannot confirm idle service: code {} ({})",
|
|
response.code,
|
|
response.message
|
|
);
|
|
}
|
|
let status = response.data.context("service did not return its status")?;
|
|
if status.is_active
|
|
|| status.core_pid.is_some()
|
|
|| status.service_state != clash_verge_service_ipc::ServiceLifecycleState::Running
|
|
{
|
|
bail!("service still has an active or recovering core session");
|
|
}
|
|
}
|
|
tokio::task::spawn_blocking(move || {
|
|
if !location_refused {
|
|
require_stopped_service()?;
|
|
}
|
|
require_no_core_process(!location_refused)?;
|
|
if !location_refused {
|
|
require_stopped_service()?;
|
|
}
|
|
Ok(())
|
|
})
|
|
.await
|
|
.context("service fallback probe did not finish")?
|
|
}
|
|
|
|
fn require_stopped_service() -> Result<()> {
|
|
let manager = ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT)?;
|
|
match manager.open_service(
|
|
clash_verge_service_ipc::WINDOWS_SERVICE_NAME,
|
|
ServiceAccess::QUERY_STATUS,
|
|
) {
|
|
Ok(service) => {
|
|
let status = service.query_status()?;
|
|
if status.current_state != ServiceState::Stopped || status.process_id.is_some_and(|pid| pid != 0) {
|
|
bail!(
|
|
"service is {:?}; Sidecar fallback requires a stopped service",
|
|
status.current_state
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(WindowsServiceError::Winapi(error))
|
|
if error.raw_os_error() == Some(ERROR_SERVICE_DOES_NOT_EXIST as i32) =>
|
|
{
|
|
Ok(())
|
|
}
|
|
Err(error) => Err(error).context("cannot confirm that the service has stopped"),
|
|
}
|
|
}
|
|
|
|
fn require_no_core_process(include_service: bool) -> Result<()> {
|
|
let raw = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
|
|
if raw == INVALID_HANDLE_VALUE {
|
|
return Err(std::io::Error::last_os_error()).context("cannot inspect existing core processes");
|
|
}
|
|
let snapshot = unsafe { OwnedHandle::from_raw_handle(raw) };
|
|
let mut entry = PROCESSENTRY32W {
|
|
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
|
|
..unsafe { std::mem::zeroed() }
|
|
};
|
|
let mut found = unsafe { Process32FirstW(snapshot.as_raw_handle(), &mut entry) };
|
|
while found != 0 {
|
|
let length = entry
|
|
.szExeFile
|
|
.iter()
|
|
.position(|value| *value == 0)
|
|
.unwrap_or(entry.szExeFile.len());
|
|
let name = String::from_utf16_lossy(&entry.szExeFile[..length]);
|
|
// SCM can be stopped while a core from an earlier service process remains alive.
|
|
if crate::config::IVerge::VALID_CLASH_CORES
|
|
.iter()
|
|
.any(|core| name.eq_ignore_ascii_case(&format!("{core}.exe")))
|
|
|| (include_service && name.eq_ignore_ascii_case("clash-verge-service.exe"))
|
|
{
|
|
bail!(
|
|
"process {name} (PID {}) is still running; refusing a second core",
|
|
entry.th32ProcessID
|
|
);
|
|
}
|
|
found = unsafe { Process32NextW(snapshot.as_raw_handle(), &mut entry) };
|
|
}
|
|
let error = std::io::Error::last_os_error();
|
|
if error.raw_os_error() != Some(ERROR_NO_MORE_FILES as i32) {
|
|
return Err(error).context("core process enumeration did not complete");
|
|
}
|
|
Ok(())
|
|
}
|