fix: preserve windows sessions after ssh logout (#2098)
* fix: preserve windows sessions after ssh logout refs #2008 * chore: document windows wmi safety refs #2008
This commit is contained in:
parent
a07871a55e
commit
0732039eaf
|
|
@ -520,6 +520,94 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
|
|
@ -609,6 +697,7 @@ dependencies = [
|
|||
"tracing-subscriber",
|
||||
"unicode-width",
|
||||
"windows-sys",
|
||||
"wmi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1566,6 +1655,12 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
|
|
@ -2184,12 +2279,107 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
|
|
@ -2199,6 +2389,15 @@ dependencies = [
|
|||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
|
|
@ -2305,6 +2504,20 @@ dependencies = [
|
|||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wmi"
|
||||
version = "0.18.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c81b85c57a57500e56669586496bf2abd5cf082b9d32995251185d105208b64"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"log",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
"windows",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
|
|
|
|||
|
|
@ -48,9 +48,11 @@ schemars = { version = "1.2.1", features = ["derive"] }
|
|||
portable-pty = { path = "vendor/portable-pty" }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
wmi = { version = "0.18.4", default-features = false }
|
||||
windows-sys = { version = "0.61.2", features = [
|
||||
"Wdk_System_Threading",
|
||||
"Win32_Foundation",
|
||||
"Win32_Globalization",
|
||||
"Win32_System_DataExchange",
|
||||
"Win32_System_Diagnostics_Debug",
|
||||
"Win32_System_Diagnostics_ToolHelp",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
- Relicensed Herdr from AGPL-3.0-or-later to Apache-2.0.
|
||||
|
||||
### Fixed
|
||||
- Detached Windows servers and pane processes now survive logout from the OpenSSH session that started them. (#2008)
|
||||
- Windows `agent start` now launches agents without native arguments instead of timing out on an invalid empty PowerShell argument list. (#2072)
|
||||
- Headless servers now resume restored agent sessions without waiting for a TUI client to attach. (#2064)
|
||||
- Vibe and other Kitty-keyboard pane applications now receive shifted letters and punctuation when they request associated text. (#2020)
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ pub(crate) const fn capabilities() -> PlatformCapabilities {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
pub fn launch_server_daemon_command(command: &mut std::process::Command) -> std::io::Result<u32> {
|
||||
command.spawn().map(|child| child.id())
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub fn detach_server_daemon_command(command: &mut std::process::Command) {
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{HashMap, HashSet, VecDeque},
|
||||
ffi::c_void,
|
||||
ffi::{c_void, OsStr},
|
||||
mem::{size_of, MaybeUninit},
|
||||
path::PathBuf,
|
||||
ptr::{copy_nonoverlapping, null_mut},
|
||||
|
|
@ -15,6 +16,7 @@ use windows_sys::{
|
|||
CloseHandle, GlobalFree, LocalFree, HANDLE, HWND, INVALID_HANDLE_VALUE, NTSTATUS,
|
||||
STATUS_SUCCESS, UNICODE_STRING,
|
||||
},
|
||||
Globalization::{CompareStringOrdinal, CSTR_EQUAL, CSTR_GREATER_THAN, CSTR_LESS_THAN},
|
||||
System::{
|
||||
Console::GetConsoleWindow,
|
||||
DataExchange::{CloseClipboard, EmptyClipboard, OpenClipboard, SetClipboardData},
|
||||
|
|
@ -25,7 +27,10 @@ use windows_sys::{
|
|||
TH32CS_SNAPPROCESS,
|
||||
},
|
||||
},
|
||||
JobObjects::IsProcessInJob,
|
||||
JobObjects::{
|
||||
IsProcessInJob, JobObjectExtendedLimitInformation, QueryInformationJobObject,
|
||||
JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
|
||||
},
|
||||
Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE},
|
||||
Ole::CF_UNICODETEXT,
|
||||
Threading::{
|
||||
|
|
@ -248,6 +253,178 @@ pub(crate) fn configure_background_command_platform(command: &mut std::process::
|
|||
command.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
pub fn launch_server_daemon_command(command: &mut std::process::Command) -> std::io::Result<u32> {
|
||||
if current_job_kills_processes_on_close()? {
|
||||
launch_server_daemon_with_wmi(command)
|
||||
} else {
|
||||
command.spawn().map(|child| child.id())
|
||||
}
|
||||
}
|
||||
|
||||
fn launch_server_daemon_with_wmi(command: &std::process::Command) -> std::io::Result<u32> {
|
||||
// WMI resolves the class from this Rust type name, including CIM casing.
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Win32_Process;
|
||||
|
||||
// WMI serializes this embedded object using the matching CIM class name.
|
||||
#[allow(non_camel_case_types)]
|
||||
#[derive(serde::Serialize)]
|
||||
struct Win32_ProcessStartup {
|
||||
#[serde(rename = "CreateFlags")]
|
||||
create_flags: u32,
|
||||
#[serde(rename = "EnvironmentVariables")]
|
||||
environment_variables: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
struct CreateInput {
|
||||
#[serde(rename = "CommandLine")]
|
||||
command_line: String,
|
||||
#[serde(rename = "CurrentDirectory")]
|
||||
current_directory: String,
|
||||
#[serde(rename = "ProcessStartupInformation")]
|
||||
process_startup_information: Win32_ProcessStartup,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct CreateOutput {
|
||||
#[serde(rename = "ProcessId")]
|
||||
process_id: Option<u32>,
|
||||
#[serde(rename = "ReturnValue")]
|
||||
return_value: u32,
|
||||
}
|
||||
|
||||
let current_directory = command
|
||||
.get_current_dir()
|
||||
.map(std::path::Path::to_path_buf)
|
||||
.map(Ok)
|
||||
.unwrap_or_else(std::env::current_dir)?;
|
||||
let input = CreateInput {
|
||||
command_line: windows_command_line(command)?,
|
||||
current_directory: unicode_windows_value(
|
||||
¤t_directory.into_os_string(),
|
||||
"working directory",
|
||||
)?,
|
||||
process_startup_information: Win32_ProcessStartup {
|
||||
create_flags: DETACHED_PROCESS,
|
||||
environment_variables: effective_command_environment(command)?,
|
||||
},
|
||||
};
|
||||
|
||||
let connection = wmi::WMIConnection::new()
|
||||
.map_err(|err| std::io::Error::other(format!("failed to connect to WMI: {err}")))?;
|
||||
let output: CreateOutput = connection
|
||||
.exec_class_method::<Win32_Process, _>("Create", &input)
|
||||
.map_err(|err| std::io::Error::other(format!("WMI Win32_Process.Create failed: {err}")))?;
|
||||
if output.return_value != 0 {
|
||||
return Err(std::io::Error::other(format!(
|
||||
"WMI Win32_Process.Create returned error {}",
|
||||
output.return_value
|
||||
)));
|
||||
}
|
||||
output.process_id.ok_or_else(|| {
|
||||
std::io::Error::other("WMI Win32_Process.Create succeeded without a process id")
|
||||
})
|
||||
}
|
||||
|
||||
fn windows_command_line(command: &std::process::Command) -> std::io::Result<String> {
|
||||
std::iter::once(command.get_program())
|
||||
.chain(command.get_args())
|
||||
.map(|value| {
|
||||
unicode_windows_value(value, "server command argument")
|
||||
.map(|value| quote_windows_command_line_arg(&value))
|
||||
})
|
||||
.collect::<std::io::Result<Vec<_>>>()
|
||||
.map(|parts| parts.join(" "))
|
||||
}
|
||||
|
||||
fn effective_command_environment(command: &std::process::Command) -> std::io::Result<Vec<String>> {
|
||||
let mut environment = std::env::vars_os()
|
||||
.map(|(key, value)| {
|
||||
Ok((
|
||||
unicode_windows_value(&key, "inherited environment variable name")?,
|
||||
unicode_windows_value(&value, "inherited environment variable value")?,
|
||||
))
|
||||
})
|
||||
.collect::<std::io::Result<Vec<(String, String)>>>()?;
|
||||
for (key, value) in command.get_envs() {
|
||||
let key = unicode_windows_value(key, "environment variable name")?;
|
||||
environment.retain(|(inherited, _)| windows_environment_key_cmp(inherited, &key).is_ne());
|
||||
if let Some(value) = value {
|
||||
environment.push((
|
||||
key,
|
||||
unicode_windows_value(value, "environment variable value")?,
|
||||
));
|
||||
}
|
||||
}
|
||||
environment.sort_unstable_by(|(left, _), (right, _)| windows_environment_key_cmp(left, right));
|
||||
Ok(environment
|
||||
.into_iter()
|
||||
.map(|(key, value)| format!("{key}={value}"))
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn windows_environment_key_cmp(left: &str, right: &str) -> Ordering {
|
||||
let left_wide: Vec<u16> = left.encode_utf16().collect();
|
||||
let right_wide: Vec<u16> = right.encode_utf16().collect();
|
||||
// SAFETY: both pointers remain valid for the call and lengths count UTF-16 units.
|
||||
match unsafe {
|
||||
CompareStringOrdinal(
|
||||
left_wide.as_ptr(),
|
||||
left_wide.len() as i32,
|
||||
right_wide.as_ptr(),
|
||||
right_wide.len() as i32,
|
||||
1,
|
||||
)
|
||||
} {
|
||||
CSTR_LESS_THAN => Ordering::Less,
|
||||
CSTR_EQUAL => Ordering::Equal,
|
||||
CSTR_GREATER_THAN => Ordering::Greater,
|
||||
_ => left.cmp(right),
|
||||
}
|
||||
}
|
||||
|
||||
fn unicode_windows_value(value: &OsStr, label: &str) -> std::io::Result<String> {
|
||||
value.to_str().map(str::to_owned).ok_or_else(|| {
|
||||
std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidData,
|
||||
format!("{label} is not valid Unicode"),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn current_process_is_in_job() -> std::io::Result<bool> {
|
||||
let mut in_job = 0;
|
||||
// SAFETY: `in_job` is a valid writable BOOL for the duration of the call.
|
||||
if unsafe { IsProcessInJob(GetCurrentProcess(), null_mut(), &mut in_job) } == 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(in_job != 0)
|
||||
}
|
||||
|
||||
fn current_job_kills_processes_on_close() -> std::io::Result<bool> {
|
||||
if !current_process_is_in_job()? {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut limits = JOBOBJECT_EXTENDED_LIMIT_INFORMATION::default();
|
||||
// SAFETY: `limits` is writable and its exact buffer size is supplied.
|
||||
if unsafe {
|
||||
QueryInformationJobObject(
|
||||
null_mut(),
|
||||
JobObjectExtendedLimitInformation,
|
||||
&mut limits as *mut _ as *mut c_void,
|
||||
size_of::<JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
|
||||
null_mut(),
|
||||
)
|
||||
} == 0
|
||||
{
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(limits.BasicLimitInformation.LimitFlags & JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE != 0)
|
||||
}
|
||||
|
||||
pub fn detach_server_daemon_command(command: &mut std::process::Command) {
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
|
|
@ -259,8 +436,7 @@ pub fn current_process_is_detached_server_daemon() -> bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
let mut in_job = 0;
|
||||
unsafe { IsProcessInJob(GetCurrentProcess(), null_mut(), &mut in_job) != 0 && in_job == 0 }
|
||||
matches!(current_process_is_in_job(), Ok(false))
|
||||
}
|
||||
|
||||
pub fn foreground_job(child_pid: u32) -> Option<ForegroundJob> {
|
||||
|
|
@ -1333,6 +1509,70 @@ mod tests {
|
|||
|
||||
const CONSOLE_TEST_CHILD_ENV: &str = "HERDR_TEST_CONSOLE_CHILD_MODE";
|
||||
const CONSOLE_TEST_PARENT_PID_ENV: &str = "HERDR_TEST_CONSOLE_PARENT_PID";
|
||||
const WMI_DAEMON_TEST_CHILD_ENV: &str = "HERDR_TEST_WMI_DAEMON_CHILD";
|
||||
|
||||
#[test]
|
||||
fn windows_environment_keys_use_unicode_case_insensitive_ordering() {
|
||||
assert_eq!(
|
||||
super::windows_environment_key_cmp("hérdr", "HÉRDR"),
|
||||
std::cmp::Ordering::Equal
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_wmi_daemon_preserves_environment_and_working_directory() {
|
||||
if let Some(capture) = std::env::var_os(WMI_DAEMON_TEST_CHILD_ENV) {
|
||||
let cwd = std::env::current_dir().expect("WMI daemon test working directory");
|
||||
fs::write(
|
||||
capture,
|
||||
format!(
|
||||
"{}\n{}",
|
||||
cwd.display(),
|
||||
super::current_process_is_detached_server_daemon()
|
||||
),
|
||||
)
|
||||
.expect("write WMI daemon test capture");
|
||||
return;
|
||||
}
|
||||
|
||||
let base = std::env::temp_dir().join(format!(
|
||||
"herdr-wmi-daemon-test-{}-{}",
|
||||
std::process::id(),
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis()
|
||||
));
|
||||
fs::create_dir_all(&base).unwrap();
|
||||
let capture = base.join("capture.txt");
|
||||
let test_exe = std::env::current_exe().expect("resolve test executable");
|
||||
let mut child = Command::new(test_exe);
|
||||
child
|
||||
.arg("windows_wmi_daemon_preserves_environment_and_working_directory")
|
||||
.current_dir(&base)
|
||||
.env(WMI_DAEMON_TEST_CHILD_ENV, &capture)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
let pid = super::launch_server_daemon_with_wmi(&child)
|
||||
.expect("launch detached process through WMI");
|
||||
assert_ne!(pid, 0, "WMI returned an invalid process id");
|
||||
|
||||
let expected = format!("{}\ntrue", base.display());
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
loop {
|
||||
if fs::read_to_string(&capture).is_ok_and(|captured| captured == expected) {
|
||||
break;
|
||||
}
|
||||
assert!(
|
||||
Instant::now() < deadline,
|
||||
"WMI daemon child did not write the expected capture"
|
||||
);
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
|
||||
fn console_process_ids() -> Vec<u32> {
|
||||
let mut process_ids = vec![0; 8];
|
||||
|
|
|
|||
|
|
@ -198,11 +198,10 @@ pub fn spawn_server_daemon() -> io::Result<u32> {
|
|||
|
||||
let mut command = build_server_daemon_command(exe);
|
||||
|
||||
let child = command.spawn().map_err(|err: io::Error| {
|
||||
io::Error::new(err.kind(), format!("failed to spawn herdr server: {err}"))
|
||||
})?;
|
||||
|
||||
let pid = child.id();
|
||||
let pid =
|
||||
crate::platform::launch_server_daemon_command(&mut command).map_err(|err: io::Error| {
|
||||
io::Error::new(err.kind(), format!("failed to spawn herdr server: {err}"))
|
||||
})?;
|
||||
info!(pid, "server daemon spawned");
|
||||
|
||||
Ok(pid)
|
||||
|
|
|
|||
Loading…
Reference in New Issue