feat(windows): run as a real SCM service, not a Run-key autostart #107

Merged
razvandimescu merged 16 commits from feat/windows-service into main 2026-04-17 07:02:43 +08:00
Showing only changes of commit fe9f31616e - Show all commits

View File

@@ -912,16 +912,19 @@ fn delete_service_scm() {
#[cfg(windows)] #[cfg(windows)]
fn is_service_registered() -> bool { fn is_service_registered() -> bool {
run_sc(&["query", crate::windows_service::SERVICE_NAME]) run_sc(&["query", crate::windows_service::SERVICE_NAME])
.map(|o| { .map(|o| parse_sc_registered(o.status.success(), &String::from_utf8_lossy(&o.stdout)))
// sc query exits 0 if the service exists (running or stopped). .unwrap_or(false)
// Error 1060 = "service does not exist". }
if o.status.success() {
/// Parse `sc query` output to determine if a service is registered.
/// Extracted for testability — the actual `sc` call is in `is_service_registered`.
#[cfg(any(windows, test))]
fn parse_sc_registered(exit_success: bool, stdout: &str) -> bool {
if exit_success {
return true; return true;
} }
let text = String::from_utf8_lossy(&o.stdout); // Error 1060 = "The specified service does not exist as an installed service."
!text.contains("1060") !stdout.contains("1060")
})
.unwrap_or(false)
} }
/// Print service state from SCM. /// Print service state from SCM.
@@ -929,18 +932,23 @@ fn is_service_registered() -> bool {
fn service_status_windows() -> Result<(), String> { fn service_status_windows() -> Result<(), String> {
let out = run_sc(&["query", crate::windows_service::SERVICE_NAME])?; let out = run_sc(&["query", crate::windows_service::SERVICE_NAME])?;
let text = String::from_utf8_lossy(&out.stdout); let text = String::from_utf8_lossy(&out.stdout);
if text.contains("1060") { let display = parse_sc_state(&text);
eprintln!(" Service is not installed.\n"); eprintln!(" {}\n", display);
return Ok(()); Ok(())
} }
// Parse STATE line, e.g. "STATE : 4 RUNNING"
let state = text /// Parse the STATE line from `sc query` output. Returns a human-readable
/// string like "STATE : 4 RUNNING" or "Service is not installed."
#[cfg(any(windows, test))]
fn parse_sc_state(sc_output: &str) -> String {
if sc_output.contains("1060") {
return "Service is not installed.".to_string();
}
sc_output
.lines() .lines()
.find(|l| l.contains("STATE")) .find(|l| l.contains("STATE"))
.map(|l| l.trim().to_string()) .map(|l| l.trim().to_string())
.unwrap_or_else(|| "unknown".to_string()); .unwrap_or_else(|| "unknown".to_string())
eprintln!(" {}\n", state);
Ok(())
} }
#[cfg(windows)] #[cfg(windows)]
@@ -2132,4 +2140,57 @@ Wireless LAN adapter Wi-Fi:
let err = std::io::Error::from(std::io::ErrorKind::AddrInUse); let err = std::io::Error::from(std::io::ErrorKind::AddrInUse);
assert!(try_port53_advisory("not-an-address", &err).is_none()); assert!(try_port53_advisory("not-an-address", &err).is_none());
} }
#[test]
fn sc_query_running_service_is_registered() {
assert!(parse_sc_registered(true, ""));
}
#[test]
fn sc_query_stopped_service_is_registered() {
let output = "SERVICE_NAME: Numa\n TYPE: 10 WIN32_OWN\n STATE: 1 STOPPED\n";
assert!(parse_sc_registered(true, output));
}
#[test]
fn sc_query_missing_service_not_registered() {
let output = "[SC] EnumQueryServicesStatus:OpenService FAILED 1060:\n\nThe specified service does not exist as an installed service.\n";
assert!(!parse_sc_registered(false, output));
}
#[test]
fn sc_query_other_error_assumes_registered() {
// Permission denied or other errors — don't assume unregistered.
let output = "[SC] OpenService FAILED 5:\n\nAccess is denied.\n";
assert!(parse_sc_registered(false, output));
}
#[test]
fn parse_sc_state_running() {
let output = "SERVICE_NAME: Numa\n TYPE : 10 WIN32_OWN_PROCESS\n STATE : 4 RUNNING\n WIN32_EXIT_CODE : 0\n";
assert!(parse_sc_state(output).contains("RUNNING"));
}
#[test]
fn parse_sc_state_stopped() {
let output = "SERVICE_NAME: Numa\n TYPE : 10 WIN32_OWN_PROCESS\n STATE : 1 STOPPED\n";
assert!(parse_sc_state(output).contains("STOPPED"));
}
#[test]
fn parse_sc_state_not_installed() {
let output = "[SC] EnumQueryServicesStatus:OpenService FAILED 1060:\n\n";
assert_eq!(parse_sc_state(output), "Service is not installed.");
}
#[test]
fn parse_sc_state_empty_output() {
assert_eq!(parse_sc_state(""), "unknown");
}
#[cfg(windows)]
#[test]
fn windows_config_dir_equals_data_dir() {
assert_eq!(crate::config_dir(), crate::data_dir());
}
} }