]> git.proxmox.com Git - rustc.git/blob - vendor/sysinfo/src/apple/macos/process.rs
fff9c1f712d02c01c144d20d6c9ff816e8a43574
[rustc.git] / vendor / sysinfo / src / apple / macos / process.rs
1 // Take a look at the license at the top of the repository in the LICENSE file.
2
3 use std::ffi::CStr;
4 use std::mem::{self, MaybeUninit};
5 use std::ops::Deref;
6 use std::path::{Path, PathBuf};
7
8 use std::borrow::Borrow;
9
10 use libc::{c_int, c_void, kill, size_t};
11
12 use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid};
13
14 use crate::sys::process::ThreadStatus;
15 use crate::sys::system::Wrap;
16
17 #[doc = include_str!("../../../md_doc/process.md")]
18 pub struct Process {
19 pub(crate) name: String,
20 pub(crate) cmd: Vec<String>,
21 pub(crate) exe: PathBuf,
22 pid: Pid,
23 parent: Option<Pid>,
24 pub(crate) environ: Vec<String>,
25 cwd: PathBuf,
26 pub(crate) root: PathBuf,
27 pub(crate) memory: u64,
28 pub(crate) virtual_memory: u64,
29 old_utime: u64,
30 old_stime: u64,
31 start_time: u64,
32 run_time: u64,
33 pub(crate) updated: bool,
34 cpu_usage: f32,
35 user_id: Option<Uid>,
36 group_id: Option<Gid>,
37 pub(crate) process_status: ProcessStatus,
38 /// Status of process (running, stopped, waiting, etc). `None` means `sysinfo` doesn't have
39 /// enough rights to get this information.
40 ///
41 /// This is very likely this one that you want instead of `process_status`.
42 pub status: Option<ThreadStatus>,
43 pub(crate) old_read_bytes: u64,
44 pub(crate) old_written_bytes: u64,
45 pub(crate) read_bytes: u64,
46 pub(crate) written_bytes: u64,
47 }
48
49 impl Process {
50 pub(crate) fn new_empty(pid: Pid, exe: PathBuf, name: String, cwd: PathBuf) -> Process {
51 Process {
52 name,
53 pid,
54 parent: None,
55 cmd: Vec::new(),
56 environ: Vec::new(),
57 exe,
58 cwd,
59 root: PathBuf::new(),
60 memory: 0,
61 virtual_memory: 0,
62 cpu_usage: 0.,
63 old_utime: 0,
64 old_stime: 0,
65 updated: true,
66 start_time: 0,
67 run_time: 0,
68 user_id: None,
69 group_id: None,
70 process_status: ProcessStatus::Unknown(0),
71 status: None,
72 old_read_bytes: 0,
73 old_written_bytes: 0,
74 read_bytes: 0,
75 written_bytes: 0,
76 }
77 }
78
79 pub(crate) fn new(pid: Pid, parent: Option<Pid>, start_time: u64, run_time: u64) -> Process {
80 Process {
81 name: String::new(),
82 pid,
83 parent,
84 cmd: Vec::new(),
85 environ: Vec::new(),
86 exe: PathBuf::new(),
87 cwd: PathBuf::new(),
88 root: PathBuf::new(),
89 memory: 0,
90 virtual_memory: 0,
91 cpu_usage: 0.,
92 old_utime: 0,
93 old_stime: 0,
94 updated: true,
95 start_time,
96 run_time,
97 user_id: None,
98 group_id: None,
99 process_status: ProcessStatus::Unknown(0),
100 status: None,
101 old_read_bytes: 0,
102 old_written_bytes: 0,
103 read_bytes: 0,
104 written_bytes: 0,
105 }
106 }
107 }
108
109 impl ProcessExt for Process {
110 fn kill_with(&self, signal: Signal) -> Option<bool> {
111 let c_signal = crate::sys::system::convert_signal(signal)?;
112 unsafe { Some(kill(self.pid.0, c_signal) == 0) }
113 }
114
115 fn name(&self) -> &str {
116 &self.name
117 }
118
119 fn cmd(&self) -> &[String] {
120 &self.cmd
121 }
122
123 fn exe(&self) -> &Path {
124 self.exe.as_path()
125 }
126
127 fn pid(&self) -> Pid {
128 self.pid
129 }
130
131 fn environ(&self) -> &[String] {
132 &self.environ
133 }
134
135 fn cwd(&self) -> &Path {
136 self.cwd.as_path()
137 }
138
139 fn root(&self) -> &Path {
140 self.root.as_path()
141 }
142
143 fn memory(&self) -> u64 {
144 self.memory
145 }
146
147 fn virtual_memory(&self) -> u64 {
148 self.virtual_memory
149 }
150
151 fn parent(&self) -> Option<Pid> {
152 self.parent
153 }
154
155 fn status(&self) -> ProcessStatus {
156 self.process_status
157 }
158
159 fn start_time(&self) -> u64 {
160 self.start_time
161 }
162
163 fn run_time(&self) -> u64 {
164 self.run_time
165 }
166
167 fn cpu_usage(&self) -> f32 {
168 self.cpu_usage
169 }
170
171 fn disk_usage(&self) -> DiskUsage {
172 DiskUsage {
173 read_bytes: self.read_bytes - self.old_read_bytes,
174 total_read_bytes: self.read_bytes,
175 written_bytes: self.written_bytes - self.old_written_bytes,
176 total_written_bytes: self.written_bytes,
177 }
178 }
179
180 fn user_id(&self) -> Option<&Uid> {
181 self.user_id.as_ref()
182 }
183
184 fn group_id(&self) -> Option<Gid> {
185 self.group_id
186 }
187
188 fn wait(&self) {
189 let mut status = 0;
190 // attempt waiting
191 unsafe {
192 if libc::waitpid(self.pid.0, &mut status, 0) < 0 {
193 // attempt failed (non-child process) so loop until process ends
194 let duration = std::time::Duration::from_millis(10);
195 while kill(self.pid.0, 0) == 0 {
196 std::thread::sleep(duration);
197 }
198 }
199 }
200 }
201 }
202
203 #[allow(deprecated)] // Because of libc::mach_absolute_time.
204 pub(crate) fn compute_cpu_usage(
205 p: &mut Process,
206 task_info: libc::proc_taskinfo,
207 system_time: u64,
208 user_time: u64,
209 time_interval: Option<f64>,
210 ) {
211 if let Some(time_interval) = time_interval {
212 let total_existing_time = p.old_stime.saturating_add(p.old_utime);
213 if time_interval > 0.000001 && total_existing_time > 0 {
214 let total_current_time = task_info
215 .pti_total_system
216 .saturating_add(task_info.pti_total_user);
217
218 let total_time_diff = total_current_time.saturating_sub(total_existing_time);
219 if total_time_diff > 0 {
220 p.cpu_usage = (total_time_diff as f64 / time_interval * 100.) as f32;
221 }
222 } else {
223 p.cpu_usage = 0.;
224 }
225 p.old_stime = task_info.pti_total_system;
226 p.old_utime = task_info.pti_total_user;
227 } else {
228 unsafe {
229 // This is the "backup way" of CPU computation.
230 let time = libc::mach_absolute_time();
231 let task_time = user_time
232 .saturating_add(system_time)
233 .saturating_add(task_info.pti_total_user)
234 .saturating_add(task_info.pti_total_system);
235
236 let system_time_delta = if task_time < p.old_utime {
237 task_time
238 } else {
239 task_time.saturating_sub(p.old_utime)
240 };
241 let time_delta = if time < p.old_stime {
242 time
243 } else {
244 time.saturating_sub(p.old_stime)
245 };
246 p.old_utime = task_time;
247 p.old_stime = time;
248 p.cpu_usage = if time_delta == 0 {
249 0f32
250 } else {
251 (system_time_delta as f64 * 100f64 / time_delta as f64) as f32
252 };
253 }
254 }
255 }
256
257 /*pub fn set_time(p: &mut Process, utime: u64, stime: u64) {
258 p.old_utime = p.utime;
259 p.old_stime = p.stime;
260 p.utime = utime;
261 p.stime = stime;
262 p.updated = true;
263 }*/
264
265 unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo {
266 let mut task_info = mem::zeroed::<libc::proc_taskinfo>();
267 // If it doesn't work, we just don't have memory information for this process
268 // so it's "fine".
269 libc::proc_pidinfo(
270 pid.0,
271 libc::PROC_PIDTASKINFO,
272 0,
273 &mut task_info as *mut libc::proc_taskinfo as *mut c_void,
274 mem::size_of::<libc::proc_taskinfo>() as _,
275 );
276 task_info
277 }
278
279 #[inline]
280 fn check_if_pid_is_alive(pid: Pid, check_if_alive: bool) -> bool {
281 // In case we are iterating all pids we got from `proc_listallpids`, then
282 // there is no point checking if the process is alive since it was returned
283 // from this function.
284 if !check_if_alive {
285 return true;
286 }
287 unsafe {
288 if kill(pid.0, 0) == 0 {
289 return true;
290 }
291 // `kill` failed but it might not be because the process is dead.
292 let errno = libc::__error();
293 // If errno is equal to ESCHR, it means the process is dead.
294 !errno.is_null() && *errno != libc::ESRCH
295 }
296 }
297
298 #[inline]
299 fn do_not_get_env_path(_: &str, _: &mut PathBuf, _: &mut bool) {}
300
301 #[inline]
302 fn do_get_env_path(env: &str, root: &mut PathBuf, check: &mut bool) {
303 if *check && env.starts_with("PATH=") {
304 *check = false;
305 *root = Path::new(&env[5..]).to_path_buf();
306 }
307 }
308
309 unsafe fn get_bsd_info(pid: Pid) -> Option<libc::proc_bsdinfo> {
310 let mut info = mem::zeroed::<libc::proc_bsdinfo>();
311
312 if libc::proc_pidinfo(
313 pid.0,
314 libc::PROC_PIDTBSDINFO,
315 0,
316 &mut info as *mut _ as *mut _,
317 mem::size_of::<libc::proc_bsdinfo>() as _,
318 ) != mem::size_of::<libc::proc_bsdinfo>() as _
319 {
320 None
321 } else {
322 Some(info)
323 }
324 }
325
326 unsafe fn create_new_process(
327 pid: Pid,
328 mut size: size_t,
329 now: u64,
330 refresh_kind: ProcessRefreshKind,
331 info: Option<libc::proc_bsdinfo>,
332 ) -> Result<Option<Process>, ()> {
333 let mut vnodepathinfo = mem::zeroed::<libc::proc_vnodepathinfo>();
334 let result = libc::proc_pidinfo(
335 pid.0,
336 libc::PROC_PIDVNODEPATHINFO,
337 0,
338 &mut vnodepathinfo as *mut _ as *mut _,
339 mem::size_of::<libc::proc_vnodepathinfo>() as _,
340 );
341 let cwd = if result > 0 {
342 let buffer = vnodepathinfo.pvi_cdir.vip_path;
343 let buffer = CStr::from_ptr(buffer.as_ptr() as _);
344 buffer
345 .to_str()
346 .map(PathBuf::from)
347 .unwrap_or_else(|_| PathBuf::new())
348 } else {
349 PathBuf::new()
350 };
351
352 let info = match info {
353 Some(info) => info,
354 None => {
355 let mut buffer: Vec<u8> = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _);
356 match libc::proc_pidpath(
357 pid.0,
358 buffer.as_mut_ptr() as *mut _,
359 libc::PROC_PIDPATHINFO_MAXSIZE as _,
360 ) {
361 x if x > 0 => {
362 buffer.set_len(x as _);
363 let tmp = String::from_utf8_unchecked(buffer);
364 let exe = PathBuf::from(tmp);
365 let name = exe
366 .file_name()
367 .and_then(|x| x.to_str())
368 .unwrap_or("")
369 .to_owned();
370 return Ok(Some(Process::new_empty(pid, exe, name, cwd)));
371 }
372 _ => {}
373 }
374 return Err(());
375 }
376 };
377 let parent = match info.pbi_ppid as i32 {
378 0 => None,
379 p => Some(Pid(p)),
380 };
381
382 let mut proc_args = Vec::with_capacity(size as _);
383 let ptr: *mut u8 = proc_args.as_mut_slice().as_mut_ptr();
384 let mut mib = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid.0 as _];
385 /*
386 * /---------------\ 0x00000000
387 * | ::::::::::::: |
388 * |---------------| <-- Beginning of data returned by sysctl() is here.
389 * | argc |
390 * |---------------|
391 * | exec_path |
392 * |---------------|
393 * | 0 |
394 * |---------------|
395 * | arg[0] |
396 * |---------------|
397 * | 0 |
398 * |---------------|
399 * | arg[n] |
400 * |---------------|
401 * | 0 |
402 * |---------------|
403 * | env[0] |
404 * |---------------|
405 * | 0 |
406 * |---------------|
407 * | env[n] |
408 * |---------------|
409 * | ::::::::::::: |
410 * |---------------| <-- Top of stack.
411 * : :
412 * : :
413 * \---------------/ 0xffffffff
414 */
415 if libc::sysctl(
416 mib.as_mut_ptr(),
417 mib.len() as _,
418 ptr as *mut c_void,
419 &mut size,
420 std::ptr::null_mut(),
421 0,
422 ) == -1
423 {
424 return Err(()); // not enough rights I assume?
425 }
426 let mut n_args: c_int = 0;
427 libc::memcpy(
428 (&mut n_args) as *mut c_int as *mut c_void,
429 ptr as *const c_void,
430 mem::size_of::<c_int>(),
431 );
432
433 let mut cp = ptr.add(mem::size_of::<c_int>());
434 let mut start = cp;
435
436 let start_time = info.pbi_start_tvsec;
437 let run_time = now.saturating_sub(start_time);
438
439 let mut p = if cp < ptr.add(size) {
440 while cp < ptr.add(size) && *cp != 0 {
441 cp = cp.offset(1);
442 }
443 let exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf();
444 let name = exe
445 .file_name()
446 .and_then(|x| x.to_str())
447 .unwrap_or("")
448 .to_owned();
449 while cp < ptr.add(size) && *cp == 0 {
450 cp = cp.offset(1);
451 }
452 start = cp;
453 let mut c = 0;
454 let mut cmd = Vec::with_capacity(n_args as usize);
455 while c < n_args && cp < ptr.add(size) {
456 if *cp == 0 {
457 c += 1;
458 cmd.push(get_unchecked_str(cp, start));
459 start = cp.offset(1);
460 }
461 cp = cp.offset(1);
462 }
463
464 #[inline]
465 unsafe fn get_environ<F: Fn(&str, &mut PathBuf, &mut bool)>(
466 ptr: *mut u8,
467 mut cp: *mut u8,
468 size: size_t,
469 mut root: PathBuf,
470 callback: F,
471 ) -> (Vec<String>, PathBuf) {
472 let mut environ = Vec::with_capacity(10);
473 let mut start = cp;
474 let mut check = true;
475 while cp < ptr.add(size) {
476 if *cp == 0 {
477 if cp == start {
478 break;
479 }
480 let e = get_unchecked_str(cp, start);
481 callback(&e, &mut root, &mut check);
482 environ.push(e);
483 start = cp.offset(1);
484 }
485 cp = cp.offset(1);
486 }
487 (environ, root)
488 }
489
490 let (environ, root) = if exe.is_absolute() {
491 if let Some(parent_path) = exe.parent() {
492 get_environ(
493 ptr,
494 cp,
495 size,
496 parent_path.to_path_buf(),
497 do_not_get_env_path,
498 )
499 } else {
500 get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path)
501 }
502 } else {
503 get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path)
504 };
505 let mut p = Process::new(pid, parent, start_time, run_time);
506
507 p.exe = exe;
508 p.name = name;
509 p.cwd = cwd;
510 p.cmd = parse_command_line(&cmd);
511 p.environ = environ;
512 p.root = root;
513 p
514 } else {
515 Process::new(pid, parent, start_time, run_time)
516 };
517
518 let task_info = get_task_info(pid);
519
520 p.memory = task_info.pti_resident_size;
521 p.virtual_memory = task_info.pti_virtual_size;
522
523 p.user_id = Some(Uid(info.pbi_uid));
524 p.group_id = Some(Gid(info.pbi_gid));
525 p.process_status = ProcessStatus::from(info.pbi_status);
526 if refresh_kind.disk_usage() {
527 update_proc_disk_activity(&mut p);
528 }
529 Ok(Some(p))
530 }
531
532 pub(crate) fn update_process(
533 wrap: &Wrap,
534 pid: Pid,
535 size: size_t,
536 time_interval: Option<f64>,
537 now: u64,
538 refresh_kind: ProcessRefreshKind,
539 check_if_alive: bool,
540 ) -> Result<Option<Process>, ()> {
541 unsafe {
542 if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) {
543 if p.memory == 0 {
544 // We don't have access to this process' information.
545 return if check_if_pid_is_alive(pid, check_if_alive) {
546 p.updated = true;
547 Ok(None)
548 } else {
549 Err(())
550 };
551 }
552 if let Some(info) = get_bsd_info(pid) {
553 if info.pbi_start_tvsec != p.start_time {
554 // We don't it to be removed, just replaced.
555 p.updated = true;
556 // The owner of this PID changed.
557 return create_new_process(pid, size, now, refresh_kind, Some(info));
558 }
559 }
560 let task_info = get_task_info(pid);
561 let mut thread_info = mem::zeroed::<libc::proc_threadinfo>();
562 let (user_time, system_time, thread_status) = if libc::proc_pidinfo(
563 pid.0,
564 libc::PROC_PIDTHREADINFO,
565 0,
566 &mut thread_info as *mut libc::proc_threadinfo as *mut c_void,
567 mem::size_of::<libc::proc_threadinfo>() as _,
568 ) != 0
569 {
570 (
571 thread_info.pth_user_time,
572 thread_info.pth_system_time,
573 Some(ThreadStatus::from(thread_info.pth_run_state)),
574 )
575 } else {
576 // It very likely means that the process is dead...
577 if check_if_pid_is_alive(pid, check_if_alive) {
578 (0, 0, Some(ThreadStatus::Running))
579 } else {
580 return Err(());
581 }
582 };
583 p.status = thread_status;
584
585 if refresh_kind.cpu() {
586 compute_cpu_usage(p, task_info, system_time, user_time, time_interval);
587 }
588
589 p.memory = task_info.pti_resident_size;
590 p.virtual_memory = task_info.pti_virtual_size;
591 if refresh_kind.disk_usage() {
592 update_proc_disk_activity(p);
593 }
594 p.updated = true;
595 return Ok(None);
596 }
597 create_new_process(pid, size, now, refresh_kind, get_bsd_info(pid))
598 }
599 }
600
601 fn update_proc_disk_activity(p: &mut Process) {
602 p.old_read_bytes = p.read_bytes;
603 p.old_written_bytes = p.written_bytes;
604
605 let mut pidrusage = MaybeUninit::<libc::rusage_info_v2>::uninit();
606
607 unsafe {
608 let retval = libc::proc_pid_rusage(
609 p.pid().0 as _,
610 libc::RUSAGE_INFO_V2,
611 pidrusage.as_mut_ptr() as _,
612 );
613
614 if retval < 0 {
615 sysinfo_debug!("proc_pid_rusage failed: {:?}", retval);
616 } else {
617 let pidrusage = pidrusage.assume_init();
618 p.read_bytes = pidrusage.ri_diskio_bytesread;
619 p.written_bytes = pidrusage.ri_diskio_byteswritten;
620 }
621 }
622 }
623
624 #[allow(unknown_lints)]
625 #[allow(clippy::uninit_vec)]
626 pub(crate) fn get_proc_list() -> Option<Vec<Pid>> {
627 unsafe {
628 let count = libc::proc_listallpids(::std::ptr::null_mut(), 0);
629 if count < 1 {
630 return None;
631 }
632 let mut pids: Vec<Pid> = Vec::with_capacity(count as usize);
633 pids.set_len(count as usize);
634 let count = count * mem::size_of::<Pid>() as i32;
635 let x = libc::proc_listallpids(pids.as_mut_ptr() as *mut c_void, count);
636
637 if x < 1 || x as usize >= pids.len() {
638 None
639 } else {
640 pids.set_len(x as usize);
641 Some(pids)
642 }
643 }
644 }
645
646 unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String {
647 let len = cp as usize - start as usize;
648 let part = Vec::from_raw_parts(start, len, len);
649 let tmp = String::from_utf8_unchecked(part.clone());
650 mem::forget(part);
651 tmp
652 }
653
654 fn parse_command_line<T: Deref<Target = str> + Borrow<str>>(cmd: &[T]) -> Vec<String> {
655 let mut x = 0;
656 let mut command = Vec::with_capacity(cmd.len());
657 while x < cmd.len() {
658 let mut y = x;
659 if cmd[y].starts_with('\'') || cmd[y].starts_with('"') {
660 let c = if cmd[y].starts_with('\'') { '\'' } else { '"' };
661 while y < cmd.len() && !cmd[y].ends_with(c) {
662 y += 1;
663 }
664 command.push(cmd[x..y].join(" "));
665 x = y;
666 } else {
667 command.push(cmd[x].to_owned());
668 }
669 x += 1;
670 }
671 command
672 }
673
674 #[cfg(test)]
675 mod test {
676 use super::*;
677
678 #[test]
679 fn test_get_path() {
680 let mut path = PathBuf::new();
681 let mut check = true;
682
683 do_get_env_path("PATH=tadam", &mut path, &mut check);
684
685 assert!(!check);
686 assert_eq!(path, PathBuf::from("tadam"));
687 }
688 }