1use std::collections::HashMap;
4use std::path::PathBuf;
5use std::sync::OnceLock;
6use windows::Win32::Foundation::{CloseHandle, HANDLE, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT};
7use windows::Win32::Storage::FileSystem::QueryDosDeviceW;
8use windows::Win32::System::ProcessStatus::GetProcessImageFileNameW;
9use windows::Win32::System::Threading::{
10 GetCurrentProcess, GetExitCodeProcess, OpenProcess, PROCESS_QUERY_INFORMATION,
11 PROCESS_TERMINATE, TerminateProcess, WaitForSingleObject,
12};
13use windows::core::PCWSTR;
14
15use super::types::{ProcessAccess, ProcessId};
16use crate::error::{Error, ProcessError, ProcessOpenError, Result};
17use crate::utils::to_utf16_nul;
18use crate::wait::Wait;
19
20const STILL_ACTIVE: u32 = 259;
22const DEVICE_PREFIX: &[u16] = &[92, 68, 101, 118, 105, 99, 101, 92];
23
24static DEVICE_PATH_CACHE: OnceLock<HashMap<Vec<u16>, char>> = OnceLock::new();
27
28fn init_device_path_cache() -> HashMap<Vec<u16>, char> {
30 let mut cache = HashMap::new();
31 let mut device_path_buffer = vec![0u16; 32768];
32
33 for drive_char in 'A'..='Z' {
34 let drive = format!("{}:", drive_char);
35 let drive_wide = to_utf16_nul(&drive);
36
37 let len =
40 unsafe { QueryDosDeviceW(PCWSTR(drive_wide.as_ptr()), Some(&mut device_path_buffer)) };
41
42 if len > 0 {
43 let mut device_path_vec: Vec<u16> = device_path_buffer[..len as usize].to_vec();
44 while device_path_vec.last() == Some(&0) {
46 device_path_vec.pop();
47 }
48 cache.insert(device_path_vec, drive_char);
49 }
50 }
51
52 cache
53}
54
55fn device_path_to_drive_path_u16(buffer_u16: &[u16]) -> String {
58 if buffer_u16.is_empty() {
60 return String::new();
61 }
62
63 const BACKSLASH: u16 = b'\\' as u16;
65 if buffer_u16[0] != BACKSLASH || buffer_u16.len() < 8 {
66 let path_str = String::from_utf16_lossy(buffer_u16);
68 return path_str;
69 }
70
71 if buffer_u16.len() < DEVICE_PREFIX.len()
73 || !buffer_u16[..DEVICE_PREFIX.len()].eq(DEVICE_PREFIX)
74 {
75 let path_str = String::from_utf16_lossy(buffer_u16);
76 return path_str;
77 }
78
79 let mut device_root_end = DEVICE_PREFIX.len();
81 while device_root_end < buffer_u16.len() && buffer_u16[device_root_end] != BACKSLASH {
82 device_root_end += 1;
83 }
84
85 let cache = DEVICE_PATH_CACHE.get_or_init(init_device_path_cache);
87
88 if let Some(&drive_char) = cache.get(&buffer_u16[..device_root_end]) {
89 if device_root_end >= buffer_u16.len() {
91 return format!("{}:\\", drive_char);
93 }
94
95 let mut rest_str = String::with_capacity(device_root_end + 3);
97 rest_str.push(drive_char);
98 rest_str.push_str(":\\");
99 let rest_slice = &buffer_u16[device_root_end + 1..];
100 for c in char::decode_utf16(rest_slice.iter().copied()).flatten() {
101 rest_str.push(c);
102 }
103 return rest_str;
104 }
105
106 String::from_utf16_lossy(buffer_u16)
108}
109
110pub struct Process {
112 handle: HANDLE,
113 pid: ProcessId,
114 close_on_drop: bool,
115}
116
117impl Process {
118 pub fn open(pid: ProcessId) -> Result<Self> {
120 Self::open_with_access(pid, ProcessAccess::QueryInformation)
121 }
122
123 pub fn open_with_access(pid: ProcessId, access: ProcessAccess) -> Result<Self> {
125 let handle =
126 unsafe { OpenProcess(access.to_windows(), false, pid.as_u32()) }.map_err(|e| {
127 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
128 pid.as_u32(),
129 "Failed to open process",
130 e.code().0,
131 )))
132 })?;
133
134 Ok(Process {
135 handle,
136 pid,
137 close_on_drop: true,
138 })
139 }
140
141 pub fn current() -> Self {
145 Process {
146 handle: unsafe { GetCurrentProcess() },
147 pid: ProcessId::new(std::process::id()),
148 close_on_drop: false,
149 }
150 }
151
152 pub fn with_access(&self, access: ProcessAccess) -> Result<Self> {
164 Self::open_with_access(self.pid, access)
165 }
166
167 pub fn id(&self) -> ProcessId {
169 self.pid
170 }
171
172 pub fn name(&self) -> Result<String> {
174 let mut buffer = Vec::with_capacity(260);
175 self.name_with_buffer(&mut buffer)
176 }
177
178 pub fn name_with_buffer(&self, out_buffer: &mut Vec<u8>) -> Result<String> {
180 let path = self.path_with_buffer(out_buffer)?;
181 Ok(path
182 .file_name()
183 .and_then(|s| s.to_str())
184 .unwrap_or("")
185 .to_string())
186 }
187
188 pub fn path(&self) -> Result<PathBuf> {
190 let mut buffer = Vec::with_capacity(260);
191 self.path_with_buffer(&mut buffer)
192 }
193
194 pub fn path_with_buffer(&self, out_buffer: &mut Vec<u8>) -> Result<PathBuf> {
196 out_buffer.clear();
198 if out_buffer.capacity() < 1024 {
199 out_buffer.reserve(1024);
200 }
201 unsafe {
202 out_buffer.set_len(1024);
203 }
204
205 let buffer_u16 = unsafe {
206 std::slice::from_raw_parts_mut(
207 out_buffer.as_mut_ptr() as *mut u16,
208 out_buffer.len() / 2,
209 )
210 };
211
212 let len = unsafe { GetProcessImageFileNameW(self.handle, buffer_u16) } as usize;
213
214 if len == 0 {
215 return Err(Error::Process(ProcessError::OpenFailed(
216 ProcessOpenError::new(self.pid.as_u32(), "Failed to get process image path"),
217 )));
218 }
219
220 let path = device_path_to_drive_path_u16(&buffer_u16[..len]);
222
223 Ok(PathBuf::from(path))
224 }
225
226 pub fn is_running(&self) -> Result<bool> {
228 match self.exit_code() {
229 Ok(Some(_)) => Ok(false),
230 Ok(None) => Ok(true),
231 Err(e) => Err(e),
232 }
233 }
234
235 pub fn exit_code(&self) -> Result<Option<u32>> {
239 let exit_code = self.get_exit_code_value()?;
240
241 if exit_code == STILL_ACTIVE {
242 Ok(None)
243 } else {
244 Ok(Some(exit_code))
245 }
246 }
247
248 pub fn wait_for_exit(&self) -> Result<u32> {
250 let wait_result = unsafe { WaitForSingleObject(self.handle, u32::MAX) };
251 if wait_result == WAIT_OBJECT_0 {
252 let exit_code = self.get_exit_code_value()?;
253 if exit_code == STILL_ACTIVE {
254 return Err(Error::Process(ProcessError::OpenFailed(
255 ProcessOpenError::new(
256 self.pid.as_u32(),
257 "Process wait completed but exit code is still active",
258 ),
259 )));
260 }
261 return Ok(exit_code);
262 }
263
264 if wait_result == WAIT_FAILED {
265 return Err(Error::Process(ProcessError::OpenFailed(
266 ProcessOpenError::new(self.pid.as_u32(), "Failed to wait for process exit"),
267 )));
268 }
269
270 Err(Error::Process(ProcessError::OpenFailed(
271 ProcessOpenError::new(
272 self.pid.as_u32(),
273 "Unexpected wait result while waiting for process exit",
274 ),
275 )))
276 }
277
278 pub fn wait_for_exit_timeout(&self, timeout: std::time::Duration) -> Result<Option<u32>> {
282 let wait_result = unsafe {
283 WaitForSingleObject(
284 self.handle,
285 timeout.as_millis().min(u32::MAX as u128) as u32,
286 )
287 };
288
289 if wait_result == WAIT_TIMEOUT {
290 return Ok(None);
291 }
292
293 if wait_result == WAIT_OBJECT_0 {
294 let exit_code = self.get_exit_code_value()?;
295 if exit_code == STILL_ACTIVE {
296 return Err(Error::Process(ProcessError::OpenFailed(
297 ProcessOpenError::new(
298 self.pid.as_u32(),
299 "Process wait completed but exit code is still active",
300 ),
301 )));
302 }
303 return Ok(Some(exit_code));
304 }
305
306 if wait_result == WAIT_FAILED {
307 return Err(Error::Process(ProcessError::OpenFailed(
308 ProcessOpenError::new(
309 self.pid.as_u32(),
310 "Failed to wait for process exit with timeout",
311 ),
312 )));
313 }
314
315 Err(Error::Process(ProcessError::OpenFailed(
316 ProcessOpenError::new(
317 self.pid.as_u32(),
318 "Unexpected wait result while waiting for process exit",
319 ),
320 )))
321 }
322
323 pub fn as_wait(&self) -> Wait {
327 Wait::from_handle_borrowed(self.handle)
328 }
329
330 pub fn kill(&self) -> Result<()> {
332 self.terminate(1)
333 }
334
335 pub fn terminate(&self, exit_code: u32) -> Result<()> {
337 unsafe { TerminateProcess(self.handle, exit_code) }.map_err(|e| {
338 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
339 self.pid.as_u32(),
340 "Failed to terminate process",
341 e.code().0,
342 )))
343 })
344 }
345
346 pub fn kill_by_id(pid: ProcessId) -> Result<()> {
348 let process = Process::open_with_access(
349 pid,
350 ProcessAccess::Custom(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION),
351 )?;
352 process.kill()
353 }
354
355 pub unsafe fn as_raw_handle(&self) -> HANDLE {
361 self.handle
362 }
363
364 fn get_exit_code_value(&self) -> Result<u32> {
365 let mut exit_code = 0u32;
366 unsafe { GetExitCodeProcess(self.handle, &mut exit_code) }.map_err(|e| {
367 Error::Process(ProcessError::OpenFailed(ProcessOpenError::with_code(
368 self.pid.as_u32(),
369 "Failed to get exit code",
370 e.code().0,
371 )))
372 })?;
373 Ok(exit_code)
374 }
375}
376
377impl Drop for Process {
378 fn drop(&mut self) {
379 if self.close_on_drop {
380 unsafe {
381 let _ = CloseHandle(self.handle);
382 }
383 }
384 }
385}
386
387unsafe impl Send for Process {}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
396 fn test_process_current() {
397 let current = Process::current();
399 assert_eq!(current.id().as_u32(), std::process::id());
400 }
401
402 #[test]
403 fn test_process_with_access_same_pid() {
404 let current = Process::current();
406 let original_pid = current.id();
407
408 let _ = current.with_access(ProcessAccess::QueryInformation);
411
412 assert_eq!(current.id(), original_pid);
414 }
415
416 #[test]
417 fn test_process_with_access_different_rights() {
418 let current = Process::current();
420
421 let _ = current.with_access(ProcessAccess::VmRead);
423 let _ = current.with_access(ProcessAccess::Terminate);
424 let _ = current.with_access(ProcessAccess::AllAccess);
425
426 assert_eq!(current.id().as_u32(), std::process::id());
428 }
429
430 #[test]
432 fn test_device_path_to_drive_path_passthrough_non_device_path() {
433 let path = "C:\\Windows\\System32\\file.exe";
435 let u16_path: Vec<u16> = path.encode_utf16().collect();
436 let result = device_path_to_drive_path_u16(&u16_path);
437 assert_eq!(result, path);
438 }
439
440 #[test]
441 fn test_device_path_to_drive_path_initializes_cache() {
442 let path = r"\Device\HarddiskVolume1\Windows\System32\kernel32.dll";
444 let u16_path: Vec<u16> = path.encode_utf16().collect();
445 let result = device_path_to_drive_path_u16(&u16_path);
446
447 assert!(!result.is_empty(), "Should return a valid path");
449 }
450
451 #[test]
452 fn test_device_path_to_drive_path_consistent_mapping() {
453 let path1 = r"\Device\HarddiskVolume1\Windows\System32\kernel32.dll";
455 let path2 = r"\Device\HarddiskVolume1\Program Files\app.exe";
456
457 let u16_path1: Vec<u16> = path1.encode_utf16().collect();
458 let u16_path2: Vec<u16> = path2.encode_utf16().collect();
459
460 let result1 = device_path_to_drive_path_u16(&u16_path1);
461 let result2 = device_path_to_drive_path_u16(&u16_path2);
462
463 let is_device_1 = result1.starts_with(r"\Device\");
465 let is_device_2 = result2.starts_with(r"\Device\");
466
467 assert_eq!(
468 is_device_1, is_device_2,
469 "Consistent mapping for same device"
470 );
471 }
472
473 #[test]
474 fn test_device_path_to_drive_path_root_path() {
475 let path = r"\Device\HarddiskVolume1";
477 let u16_path: Vec<u16> = path.encode_utf16().collect();
478 let result = device_path_to_drive_path_u16(&u16_path);
479
480 assert!(!result.is_empty(), "Should return a valid path");
481 }
482
483 #[test]
484 fn test_device_path_to_drive_path_long_path() {
485 let path = r"\Device\HarddiskVolume1\Windows\System32\Drivers\etc\hosts";
487 let u16_path: Vec<u16> = path.encode_utf16().collect();
488 let result = device_path_to_drive_path_u16(&u16_path);
489
490 assert!(
492 result.contains("hosts") || result.starts_with(r"\Device\"),
493 "Should process path correctly"
494 );
495 }
496
497 #[test]
498 fn test_device_path_to_drive_path_multiple_backslashes() {
499 let path = r"\Device\HarddiskVolume2\Users\Admin\Documents\file.txt";
501 let u16_path: Vec<u16> = path.encode_utf16().collect();
502 let result = device_path_to_drive_path_u16(&u16_path);
503
504 assert!(!result.is_empty(), "Should return a valid path");
506 }
507
508 #[test]
509 fn test_device_path_to_drive_path_preserves_case() {
510 let path = r"\Device\HarddiskVolume1\Program Files\MyApp\config.ini";
512 let u16_path: Vec<u16> = path.encode_utf16().collect();
513 let result = device_path_to_drive_path_u16(&u16_path);
514
515 assert!(
517 result.to_lowercase().contains("program files") || result.starts_with(r"\Device\"),
518 "Should handle case appropriately"
519 );
520 }
521
522 #[test]
523 fn test_init_device_path_cache_returns_valid_mappings() {
524 let cache = init_device_path_cache();
526
527 assert!(!cache.is_empty(), "Cache should have entries");
529
530 for &drive_char in cache.values() {
532 assert!(
533 drive_char.is_ascii_uppercase(),
534 "Drive letter should be A-Z"
535 );
536 }
537 }
538
539 #[test]
540 fn test_init_device_path_cache_has_device_path_keys() {
541 let cache = init_device_path_cache();
543
544 for key in cache.keys() {
545 assert!(
546 key.starts_with(DEVICE_PREFIX),
547 "Cache key should be a device path"
548 );
549 }
550 }
551
552 #[test]
553 fn test_device_path_cache_is_singleton() {
554 let cache1 = DEVICE_PATH_CACHE.get_or_init(init_device_path_cache);
556 let cache2 = DEVICE_PATH_CACHE.get_or_init(init_device_path_cache);
557
558 assert_eq!(cache1.len(), cache2.len(), "Cache should be consistent");
560 }
561
562 #[test]
563 fn test_device_path_conversion_with_special_characters() {
564 let path = r"\Device\HarddiskVolume1\Program Files (x86)\app.exe";
566 let u16_path: Vec<u16> = path.encode_utf16().collect();
567 let result = device_path_to_drive_path_u16(&u16_path);
568
569 assert!(!result.is_empty(), "Should handle special characters");
570 }
571
572 #[test]
573 fn test_device_path_unknown_device_fallback() {
574 let path = r"\Device\HarddiskVolume999\unknown\path";
576 let u16_path: Vec<u16> = path.encode_utf16().collect();
577 let result = device_path_to_drive_path_u16(&u16_path);
578
579 assert!(
581 result.contains("unknown") || result.starts_with(r"\Device\"),
582 "Should handle unknown device gracefully"
583 );
584 }
585
586 #[test]
587 fn test_device_path_empty_subdirectory() {
588 let path = r"\Device\HarddiskVolume1\";
590 let u16_path: Vec<u16> = path.encode_utf16().collect();
591 let result = device_path_to_drive_path_u16(&u16_path);
592
593 assert!(!result.is_empty(), "Should handle trailing backslash");
594 }
595
596 #[test]
597 fn test_device_path_c_drive_common_paths() {
598 let paths = vec![
600 r"\Device\HarddiskVolume1\Windows\System32\kernel32.dll",
601 r"\Device\HarddiskVolume1\Program Files\app.exe",
602 r"\Device\HarddiskVolume1\Users\Admin\Desktop\file.txt",
603 ];
604
605 for path in paths {
606 let u16_path: Vec<u16> = path.encode_utf16().collect();
607 let result = device_path_to_drive_path_u16(&u16_path);
608
609 assert!(!result.is_empty(), "Should process path without error");
611 }
612 }
613}