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