]> git.proxmox.com Git - rustc.git/blame - vendor/sysinfo/src/windows/system.rs
New upstream version 1.67.1+dfsg1
[rustc.git] / vendor / sysinfo / src / windows / system.rs
CommitLineData
923072b8
FG
1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use crate::{
4 CpuRefreshKind, LoadAvg, Networks, Pid, ProcessExt, ProcessRefreshKind, RefreshKind, SystemExt,
5 User,
6};
7use winapi::um::winreg::HKEY_LOCAL_MACHINE;
8
9use crate::sys::component::{self, Component};
10use crate::sys::cpu::*;
11use crate::sys::disk::{get_disks, Disk};
487cf647 12use crate::sys::process::{get_start_time, update_memory, Process};
923072b8
FG
13use crate::sys::tools::*;
14use crate::sys::users::get_users;
15use crate::sys::utils::get_now;
16
17use crate::utils::into_iter;
18
19use std::cell::UnsafeCell;
20use std::collections::HashMap;
21use std::ffi::OsStr;
22use std::mem::{size_of, zeroed};
23use std::os::windows::ffi::OsStrExt;
24use std::slice::from_raw_parts;
25use std::time::SystemTime;
26
27use ntapi::ntexapi::{
28 NtQuerySystemInformation, SystemProcessInformation, SYSTEM_PROCESS_INFORMATION,
29};
30use winapi::ctypes::wchar_t;
31use winapi::shared::minwindef::{DWORD, FALSE, HKEY, LPBYTE, TRUE};
32use winapi::shared::ntdef::{PVOID, ULONG};
33use winapi::shared::ntstatus::STATUS_INFO_LENGTH_MISMATCH;
34use winapi::shared::winerror;
35use winapi::um::minwinbase::STILL_ACTIVE;
36use winapi::um::processthreadsapi::GetExitCodeProcess;
37use winapi::um::psapi::{GetPerformanceInfo, PERFORMANCE_INFORMATION};
38use winapi::um::sysinfoapi::{
39 ComputerNamePhysicalDnsHostname, GetComputerNameExW, GetTickCount64, GlobalMemoryStatusEx,
40 MEMORYSTATUSEX,
41};
42use winapi::um::winnt::{HANDLE, KEY_READ};
43use winapi::um::winreg::{RegOpenKeyExW, RegQueryValueExW};
44
45declare_signals! {
46 (),
47 Signal::Kill => (),
48 _ => None,
49}
50
51#[doc = include_str!("../../md_doc/system.md")]
52pub struct System {
53 process_list: HashMap<Pid, Process>,
54 mem_total: u64,
55 mem_available: u64,
56 swap_total: u64,
57 swap_used: u64,
58 cpus: CpusWrapper,
59 components: Vec<Component>,
60 disks: Vec<Disk>,
61 query: Option<Query>,
62 networks: Networks,
63 boot_time: u64,
64 users: Vec<User>,
65}
66
487cf647
FG
67static WINDOWS_ELEVEN_BUILD_NUMBER: u32 = 22000;
68
69impl System {
70 fn is_windows_eleven(&self) -> bool {
71 WINDOWS_ELEVEN_BUILD_NUMBER
72 <= self
73 .kernel_version()
74 .unwrap_or_default()
75 .parse()
76 .unwrap_or(0)
77 }
78}
79
923072b8
FG
80// Useful for parallel iterations.
81struct Wrap<T>(T);
82
83#[allow(clippy::non_send_fields_in_send_ty)]
84unsafe impl<T> Send for Wrap<T> {}
85unsafe impl<T> Sync for Wrap<T> {}
86
87unsafe fn boot_time() -> u64 {
88 match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
487cf647 89 Ok(n) => n.as_secs().saturating_sub(GetTickCount64() / 1_000),
923072b8
FG
90 Err(_e) => {
91 sysinfo_debug!("Failed to compute boot time: {:?}", _e);
92 0
93 }
94 }
95}
96
97impl SystemExt for System {
98 const IS_SUPPORTED: bool = true;
99 const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals();
100
101 #[allow(non_snake_case)]
102 fn new_with_specifics(refreshes: RefreshKind) -> System {
103 let mut s = System {
104 process_list: HashMap::with_capacity(500),
105 mem_total: 0,
106 mem_available: 0,
107 swap_total: 0,
108 swap_used: 0,
109 cpus: CpusWrapper::new(),
110 components: Vec::new(),
111 disks: Vec::with_capacity(2),
112 query: None,
113 networks: Networks::new(),
114 boot_time: unsafe { boot_time() },
115 users: Vec::new(),
116 };
117 s.refresh_specifics(refreshes);
118 s
119 }
120
121 fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) {
122 if self.query.is_none() {
123 self.query = Query::new();
124 if let Some(ref mut query) = self.query {
125 add_english_counter(
064997fb 126 r"\Processor(_Total)\% Processor Time".to_string(),
923072b8
FG
127 query,
128 get_key_used(self.cpus.global_cpu_mut()),
129 "tot_0".to_owned(),
130 );
131 for (pos, proc_) in self.cpus.iter_mut(refresh_kind).enumerate() {
132 add_english_counter(
487cf647 133 format!(r"\Processor({pos})\% Processor Time"),
923072b8
FG
134 query,
135 get_key_used(proc_),
487cf647 136 format!("{pos}_0"),
923072b8
FG
137 );
138 }
139 }
140 }
141 if let Some(ref mut query) = self.query {
142 query.refresh();
143 let mut used_time = None;
144 if let Some(ref key_used) = *get_key_used(self.cpus.global_cpu_mut()) {
145 used_time = Some(
146 query
147 .get(&key_used.unique_id)
148 .expect("global_key_idle disappeared"),
149 );
150 }
151 if let Some(used_time) = used_time {
152 self.cpus.global_cpu_mut().set_cpu_usage(used_time);
153 }
154 for p in self.cpus.iter_mut(refresh_kind) {
155 let mut used_time = None;
156 if let Some(ref key_used) = *get_key_used(p) {
157 used_time = Some(
158 query
159 .get(&key_used.unique_id)
160 .expect("key_used disappeared"),
161 );
162 }
163 if let Some(used_time) = used_time {
164 p.set_cpu_usage(used_time);
165 }
166 }
167 if refresh_kind.frequency() {
168 self.cpus.get_frequencies();
169 }
170 }
171 }
172
173 fn refresh_memory(&mut self) {
174 unsafe {
175 let mut mem_info: MEMORYSTATUSEX = zeroed();
176 mem_info.dwLength = size_of::<MEMORYSTATUSEX>() as u32;
177 GlobalMemoryStatusEx(&mut mem_info);
487cf647
FG
178 self.mem_total = mem_info.ullTotalPhys as _;
179 self.mem_available = mem_info.ullAvailPhys as _;
923072b8
FG
180 let mut perf_info: PERFORMANCE_INFORMATION = zeroed();
181 if GetPerformanceInfo(&mut perf_info, size_of::<PERFORMANCE_INFORMATION>() as u32)
182 == TRUE
183 {
184 let swap_total = perf_info.PageSize.saturating_mul(
185 perf_info
186 .CommitLimit
187 .saturating_sub(perf_info.PhysicalTotal),
188 );
189 let swap_used = perf_info.PageSize.saturating_mul(
190 perf_info
191 .CommitTotal
192 .saturating_sub(perf_info.PhysicalTotal),
193 );
487cf647
FG
194 self.swap_total = swap_total as _;
195 self.swap_used = swap_used as _;
923072b8
FG
196 }
197 }
198 }
199
200 fn refresh_components_list(&mut self) {
201 self.components = component::get_components();
202 }
203
204 #[allow(clippy::map_entry)]
205 fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool {
923072b8 206 let now = get_now();
487cf647
FG
207 let nb_cpus = self.cpus.len() as u64;
208
209 if let Some(proc_) = self.process_list.get_mut(&pid) {
210 if let Some(ret) = refresh_existing_process(proc_, nb_cpus, now, refresh_kind) {
211 return ret;
212 }
213 // We need to re-make the process because the PID owner changed.
214 }
923072b8 215 if let Some(mut p) = Process::new_from_pid(pid, now, refresh_kind) {
487cf647 216 p.update(refresh_kind, nb_cpus, now);
923072b8
FG
217 p.updated = false;
218 self.process_list.insert(pid, p);
219 true
220 } else {
221 false
222 }
223 }
224
225 #[allow(clippy::cast_ptr_alignment)]
226 fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) {
227 // Windows 10 notebook requires at least 512KiB of memory to make it in one go
228 let mut buffer_size: usize = 512 * 1024;
229 let now = get_now();
230
231 loop {
232 let mut process_information: Vec<u8> = Vec::with_capacity(buffer_size);
233 let mut cb_needed = 0;
234
235 unsafe {
236 process_information.set_len(buffer_size);
237 let ntstatus = NtQuerySystemInformation(
238 SystemProcessInformation,
239 process_information.as_mut_ptr() as PVOID,
240 buffer_size as ULONG,
241 &mut cb_needed,
242 );
243
244 if ntstatus != STATUS_INFO_LENGTH_MISMATCH {
245 if ntstatus < 0 {
246 sysinfo_debug!(
247 "Couldn't get process infos: NtQuerySystemInformation returned {}",
248 ntstatus
249 );
250 }
251
252 // Parse the data block to get process information
253 let mut process_ids = Vec::with_capacity(500);
254 let mut process_information_offset = 0;
255 loop {
256 let p = process_information
257 .as_ptr()
258 .offset(process_information_offset)
259 as *const SYSTEM_PROCESS_INFORMATION;
260 let pi = &*p;
261
262 process_ids.push(Wrap(p));
263
264 if pi.NextEntryOffset == 0 {
265 break;
266 }
267
268 process_information_offset += pi.NextEntryOffset as isize;
269 }
270 let process_list = Wrap(UnsafeCell::new(&mut self.process_list));
271 let nb_cpus = if refresh_kind.cpu() {
272 self.cpus.len() as u64
273 } else {
274 0
275 };
276
277 #[cfg(feature = "multithread")]
278 use rayon::iter::ParallelIterator;
279
280 // TODO: instead of using parallel iterator only here, would be better to be
281 // able to run it over `process_information` directly!
282 let processes = into_iter(process_ids)
283 .filter_map(|pi| {
284 let pi = *pi.0;
285 let pid = Pid(pi.UniqueProcessId as _);
286 if let Some(proc_) = (*process_list.0.get()).get_mut(&pid) {
487cf647
FG
287 if proc_
288 .get_start_time()
289 .map(|start| start == proc_.start_time())
290 .unwrap_or(true)
291 {
292 proc_.memory = pi.WorkingSetSize as _;
293 proc_.virtual_memory = pi.VirtualSize as _;
294 proc_.update(refresh_kind, nb_cpus, now);
295 return None;
296 }
297 // If the PID owner changed, we need to recompute the whole process.
298 sysinfo_debug!("owner changed for PID {}", proc_.pid());
923072b8
FG
299 }
300 let name = get_process_name(&pi, pid);
301 let mut p = Process::new_full(
302 pid,
303 if pi.InheritedFromUniqueProcessId as usize != 0 {
304 Some(Pid(pi.InheritedFromUniqueProcessId as _))
305 } else {
306 None
307 },
487cf647
FG
308 pi.WorkingSetSize as _,
309 pi.VirtualSize as _,
923072b8
FG
310 name,
311 now,
312 refresh_kind,
313 );
314 p.update(refresh_kind, nb_cpus, now);
315 Some(p)
316 })
317 .collect::<Vec<_>>();
318 for p in processes.into_iter() {
319 self.process_list.insert(p.pid(), p);
320 }
321 self.process_list.retain(|_, v| {
322 let x = v.updated;
323 v.updated = false;
324 x
325 });
326
327 break;
328 }
329
330 // GetNewBufferSize
331 if cb_needed == 0 {
332 buffer_size *= 2;
333 continue;
334 }
335 // allocating a few more kilo bytes just in case there are some new process
336 // kicked in since new call to NtQuerySystemInformation
337 buffer_size = (cb_needed + (1024 * 10)) as usize;
338 }
339 }
340 }
341
342 fn refresh_disks_list(&mut self) {
343 self.disks = unsafe { get_disks() };
344 }
345
346 fn refresh_users_list(&mut self) {
347 self.users = unsafe { get_users() };
348 }
349
350 fn processes(&self) -> &HashMap<Pid, Process> {
351 &self.process_list
352 }
353
354 fn process(&self, pid: Pid) -> Option<&Process> {
355 self.process_list.get(&pid)
356 }
357
358 fn global_cpu_info(&self) -> &Cpu {
359 self.cpus.global_cpu()
360 }
361
362 fn cpus(&self) -> &[Cpu] {
363 self.cpus.cpus()
364 }
365
366 fn physical_core_count(&self) -> Option<usize> {
367 get_physical_core_count()
368 }
369
370 fn total_memory(&self) -> u64 {
371 self.mem_total
372 }
373
374 fn free_memory(&self) -> u64 {
375 // MEMORYSTATUSEX doesn't report free memory
376 self.mem_available
377 }
378
379 fn available_memory(&self) -> u64 {
380 self.mem_available
381 }
382
383 fn used_memory(&self) -> u64 {
384 self.mem_total - self.mem_available
385 }
386
387 fn total_swap(&self) -> u64 {
388 self.swap_total
389 }
390
391 fn free_swap(&self) -> u64 {
392 self.swap_total - self.swap_used
393 }
394
395 fn used_swap(&self) -> u64 {
396 self.swap_used
397 }
398
399 fn components(&self) -> &[Component] {
400 &self.components
401 }
402
403 fn components_mut(&mut self) -> &mut [Component] {
404 &mut self.components
405 }
406
407 fn disks(&self) -> &[Disk] {
408 &self.disks
409 }
410
411 fn disks_mut(&mut self) -> &mut [Disk] {
412 &mut self.disks
413 }
414
487cf647
FG
415 fn sort_disks_by<F>(&mut self, compare: F)
416 where
417 F: FnMut(&Disk, &Disk) -> std::cmp::Ordering,
418 {
419 self.disks.sort_unstable_by(compare);
420 }
421
923072b8
FG
422 fn users(&self) -> &[User] {
423 &self.users
424 }
425
426 fn networks(&self) -> &Networks {
427 &self.networks
428 }
429
430 fn networks_mut(&mut self) -> &mut Networks {
431 &mut self.networks
432 }
433
434 fn uptime(&self) -> u64 {
487cf647 435 unsafe { GetTickCount64() / 1_000 }
923072b8
FG
436 }
437
438 fn boot_time(&self) -> u64 {
439 self.boot_time
440 }
441
442 fn load_average(&self) -> LoadAvg {
443 get_load_average()
444 }
445
446 fn name(&self) -> Option<String> {
447 Some("Windows".to_owned())
448 }
449
450 fn long_os_version(&self) -> Option<String> {
487cf647
FG
451 if self.is_windows_eleven() {
452 return get_reg_string_value(
453 HKEY_LOCAL_MACHINE,
454 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
455 "ProductName",
456 )
457 .map(|product_name| product_name.replace("Windows 10 ", "Windows 11 "));
458 }
923072b8
FG
459 get_reg_string_value(
460 HKEY_LOCAL_MACHINE,
461 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
462 "ProductName",
463 )
464 }
465
466 fn host_name(&self) -> Option<String> {
467 get_dns_hostname()
468 }
469
470 fn kernel_version(&self) -> Option<String> {
471 get_reg_string_value(
472 HKEY_LOCAL_MACHINE,
473 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
474 "CurrentBuildNumber",
475 )
476 }
477
478 fn os_version(&self) -> Option<String> {
923072b8
FG
479 let build_number = get_reg_string_value(
480 HKEY_LOCAL_MACHINE,
481 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
482 "CurrentBuildNumber",
487cf647
FG
483 )
484 .unwrap_or_default();
485 let major = if self.is_windows_eleven() {
486 11u32
487 } else {
488 u32::from_le_bytes(
489 get_reg_value_u32(
490 HKEY_LOCAL_MACHINE,
491 "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
492 "CurrentMajorVersionNumber",
493 )
494 .unwrap_or_default(),
495 )
496 };
497 Some(format!("{major} ({build_number})"))
498 }
923072b8 499
487cf647
FG
500 fn distribution_id(&self) -> String {
501 std::env::consts::OS.to_owned()
923072b8
FG
502 }
503}
504
505impl Default for System {
506 fn default() -> System {
507 System::new()
508 }
509}
510
487cf647 511pub(crate) fn is_proc_running(handle: HANDLE) -> bool {
923072b8
FG
512 let mut exit_code = 0;
513 unsafe {
514 let ret = GetExitCodeProcess(handle, &mut exit_code);
515 !(ret == FALSE || exit_code != STILL_ACTIVE)
516 }
517}
518
487cf647
FG
519/// If it returns `None`, it means that the PID owner changed and that the `Process` must be
520/// completely recomputed.
521fn refresh_existing_process(
522 proc_: &mut Process,
523 nb_cpus: u64,
524 now: u64,
525 refresh_kind: ProcessRefreshKind,
526) -> Option<bool> {
527 if let Some(handle) = proc_.get_handle() {
528 if get_start_time(handle) != proc_.start_time() {
529 sysinfo_debug!("owner changed for PID {}", proc_.pid());
530 // PID owner changed!
531 return None;
532 }
533 if !is_proc_running(handle) {
534 return Some(false);
923072b8 535 }
923072b8 536 } else {
487cf647 537 return Some(false);
923072b8 538 }
487cf647
FG
539 update_memory(proc_);
540 proc_.update(refresh_kind, nb_cpus, now);
541 proc_.updated = false;
542 Some(true)
923072b8
FG
543}
544
545#[allow(clippy::size_of_in_element_count)]
546//^ needed for "name.Length as usize / std::mem::size_of::<u16>()"
547pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: Pid) -> String {
548 let name = &process.ImageName;
549 if name.Buffer.is_null() {
550 match process_id.0 {
551 0 => "Idle".to_owned(),
552 4 => "System".to_owned(),
487cf647 553 _ => format!("<no name> Process {process_id}"),
923072b8
FG
554 }
555 } else {
556 unsafe {
557 let slice = std::slice::from_raw_parts(
558 name.Buffer,
559 // The length is in bytes, not the length of string
560 name.Length as usize / std::mem::size_of::<u16>(),
561 );
562
563 String::from_utf16_lossy(slice)
564 }
565 }
566}
567
568fn utf16_str<S: AsRef<OsStr> + ?Sized>(text: &S) -> Vec<u16> {
569 OsStr::new(text)
570 .encode_wide()
571 .chain(Some(0).into_iter())
572 .collect::<Vec<_>>()
573}
574
575fn get_reg_string_value(hkey: HKEY, path: &str, field_name: &str) -> Option<String> {
576 let c_path = utf16_str(path);
577 let c_field_name = utf16_str(field_name);
578
579 let mut new_hkey: HKEY = std::ptr::null_mut();
580 unsafe {
581 if RegOpenKeyExW(hkey, c_path.as_ptr(), 0, KEY_READ, &mut new_hkey) != 0 {
582 return None;
583 }
584
585 let mut buf_len: DWORD = 2048;
586 let mut buf_type: DWORD = 0;
587 let mut buf: Vec<u8> = Vec::with_capacity(buf_len as usize);
588 loop {
589 match RegQueryValueExW(
590 new_hkey,
591 c_field_name.as_ptr(),
592 std::ptr::null_mut(),
593 &mut buf_type,
594 buf.as_mut_ptr() as LPBYTE,
595 &mut buf_len,
596 ) as DWORD
597 {
598 0 => break,
599 winerror::ERROR_MORE_DATA => {
600 buf.reserve(buf_len as _);
601 }
602 _ => return None,
603 }
604 }
605
606 buf.set_len(buf_len as _);
607
608 let words = from_raw_parts(buf.as_ptr() as *const u16, buf.len() / 2);
609 let mut s = String::from_utf16_lossy(words);
610 while s.ends_with('\u{0}') {
611 s.pop();
612 }
613 Some(s)
614 }
615}
616
617fn get_reg_value_u32(hkey: HKEY, path: &str, field_name: &str) -> Option<[u8; 4]> {
618 let c_path = utf16_str(path);
619 let c_field_name = utf16_str(field_name);
620
621 let mut new_hkey: HKEY = std::ptr::null_mut();
622 unsafe {
623 if RegOpenKeyExW(hkey, c_path.as_ptr(), 0, KEY_READ, &mut new_hkey) != 0 {
624 return None;
625 }
626
627 let mut buf_len: DWORD = 4;
628 let mut buf_type: DWORD = 0;
629 let mut buf = [0u8; 4];
630
631 match RegQueryValueExW(
632 new_hkey,
633 c_field_name.as_ptr(),
634 std::ptr::null_mut(),
635 &mut buf_type,
636 buf.as_mut_ptr() as LPBYTE,
637 &mut buf_len,
638 ) as DWORD
639 {
640 0 => Some(buf),
641 _ => None,
642 }
643 }
644}
645
646fn get_dns_hostname() -> Option<String> {
647 let mut buffer_size = 0;
648 // Running this first to get the buffer size since the DNS name can be longer than MAX_COMPUTERNAME_LENGTH
649 // setting the `lpBuffer` to null will return the buffer size
650 // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
651 unsafe {
652 GetComputerNameExW(
653 ComputerNamePhysicalDnsHostname,
654 std::ptr::null_mut(),
655 &mut buffer_size,
656 );
657
658 // Setting the buffer with the new length
659 let mut buffer = vec![0_u16; buffer_size as usize];
660
661 // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-computer_name_format
662 if GetComputerNameExW(
663 ComputerNamePhysicalDnsHostname,
664 buffer.as_mut_ptr() as *mut wchar_t,
665 &mut buffer_size,
666 ) == TRUE
667 {
668 if let Some(pos) = buffer.iter().position(|c| *c == 0) {
669 buffer.resize(pos, 0);
670 }
671
672 return String::from_utf16(&buffer).ok();
673 }
674 }
675
676 sysinfo_debug!("Failed to get computer hostname");
677 None
678}