1 // Take a look at the license at the top of the repository in the LICENSE file.
4 use std
::mem
::{self, MaybeUninit}
;
6 use std
::path
::{Path, PathBuf}
;
8 use std
::borrow
::Borrow
;
10 use libc
::{c_int, c_void, kill, size_t}
;
12 use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}
;
14 use crate::sys
::process
::ThreadStatus
;
15 use crate::sys
::system
::Wrap
;
17 #[doc = include_str!("../../../md_doc/process.md")]
19 pub(crate) name
: String
,
20 pub(crate) cmd
: Vec
<String
>,
21 pub(crate) exe
: PathBuf
,
24 pub(crate) environ
: Vec
<String
>,
26 pub(crate) root
: PathBuf
,
27 pub(crate) memory
: u64,
28 pub(crate) virtual_memory
: u64,
33 pub(crate) updated
: bool
,
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.
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,
50 pub(crate) fn new_empty(pid
: Pid
, exe
: PathBuf
, name
: String
, cwd
: PathBuf
) -> Process
{
70 process_status
: ProcessStatus
::Unknown(0),
79 pub(crate) fn new(pid
: Pid
, parent
: Option
<Pid
>, start_time
: u64, run_time
: u64) -> Process
{
99 process_status
: ProcessStatus
::Unknown(0),
102 old_written_bytes
: 0,
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) }
115 fn name(&self) -> &str {
119 fn cmd(&self) -> &[String
] {
123 fn exe(&self) -> &Path
{
127 fn pid(&self) -> Pid
{
131 fn environ(&self) -> &[String
] {
135 fn cwd(&self) -> &Path
{
139 fn root(&self) -> &Path
{
143 fn memory(&self) -> u64 {
147 fn virtual_memory(&self) -> u64 {
151 fn parent(&self) -> Option
<Pid
> {
155 fn status(&self) -> ProcessStatus
{
159 fn start_time(&self) -> u64 {
163 fn run_time(&self) -> u64 {
167 fn cpu_usage(&self) -> f32 {
171 fn disk_usage(&self) -> 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
,
180 fn user_id(&self) -> Option
<&Uid
> {
181 self.user_id
.as_ref()
184 fn group_id(&self) -> Option
<Gid
> {
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
);
203 #[allow(deprecated)] // Because of libc::mach_absolute_time.
204 pub(crate) fn compute_cpu_usage(
206 task_info
: libc
::proc_taskinfo
,
209 time_interval
: Option
<f64>,
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
216 .saturating_add(task_info
.pti_total_user
);
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;
225 p
.old_stime
= task_info
.pti_total_system
;
226 p
.old_utime
= task_info
.pti_total_user
;
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
);
236 let system_time_delta
= if task_time
< p
.old_utime
{
239 task_time
.saturating_sub(p
.old_utime
)
241 let time_delta
= if time
< p
.old_stime
{
244 time
.saturating_sub(p
.old_stime
)
246 p
.old_utime
= task_time
;
248 p
.cpu_usage
= if time_delta
== 0 {
251 (system_time_delta
as f64 * 100f64 / time_delta
as f64) as f32
257 /*pub fn set_time(p: &mut Process, utime: u64, stime: u64) {
258 p.old_utime = p.utime;
259 p.old_stime = p.stime;
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
271 libc
::PROC_PIDTASKINFO
,
273 &mut task_info
as *mut libc
::proc_taskinfo
as *mut c_void
,
274 mem
::size_of
::<libc
::proc_taskinfo
>() as _
,
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.
288 if kill(pid
.0, 0) == 0 {
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
299 fn do_not_get_env_path(_
: &str, _
: &mut PathBuf
, _
: &mut bool
) {}
302 fn do_get_env_path(env
: &str, root
: &mut PathBuf
, check
: &mut bool
) {
303 if *check
&& env
.starts_with("PATH=") {
305 *root
= Path
::new(&env
[5..]).to_path_buf();
309 unsafe fn get_bsd_info(pid
: Pid
) -> Option
<libc
::proc_bsdinfo
> {
310 let mut info
= mem
::zeroed
::<libc
::proc_bsdinfo
>();
312 if libc
::proc_pidinfo(
314 libc
::PROC_PIDTBSDINFO
,
316 &mut info
as *mut _
as *mut _
,
317 mem
::size_of
::<libc
::proc_bsdinfo
>() as _
,
318 ) != mem
::size_of
::<libc
::proc_bsdinfo
>() as _
326 unsafe fn create_new_process(
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(
336 libc
::PROC_PIDVNODEPATHINFO
,
338 &mut vnodepathinfo
as *mut _
as *mut _
,
339 mem
::size_of
::<libc
::proc_vnodepathinfo
>() as _
,
341 let cwd
= if result
> 0 {
342 let buffer
= vnodepathinfo
.pvi_cdir
.vip_path
;
343 let buffer
= CStr
::from_ptr(buffer
.as_ptr() as _
);
347 .unwrap_or_else(|_
| PathBuf
::new())
352 let info
= match info
{
355 let mut buffer
: Vec
<u8> = Vec
::with_capacity(libc
::PROC_PIDPATHINFO_MAXSIZE
as _
);
356 match libc
::proc_pidpath(
358 buffer
.as_mut_ptr() as *mut _
,
359 libc
::PROC_PIDPATHINFO_MAXSIZE
as _
,
362 buffer
.set_len(x
as _
);
363 let tmp
= String
::from_utf8_unchecked(buffer
);
364 let exe
= PathBuf
::from(tmp
);
367 .and_then(|x
| x
.to_str())
370 return Ok(Some(Process
::new_empty(pid
, exe
, name
, cwd
)));
377 let parent
= match info
.pbi_ppid
as i32 {
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 _
];
386 * /---------------\ 0x00000000
388 * |---------------| <-- Beginning of data returned by sysctl() is here.
410 * |---------------| <-- Top of stack.
413 * \---------------/ 0xffffffff
420 std
::ptr
::null_mut(),
424 return Err(()); // not enough rights I assume?
426 let mut n_args
: c_int
= 0;
428 (&mut n_args
) as *mut c_int
as *mut c_void
,
429 ptr
as *const c_void
,
430 mem
::size_of
::<c_int
>(),
433 let mut cp
= ptr
.add(mem
::size_of
::<c_int
>());
436 let start_time
= info
.pbi_start_tvsec
;
437 let run_time
= now
.saturating_sub(start_time
);
439 let mut p
= if cp
< ptr
.add(size
) {
440 while cp
< ptr
.add(size
) && *cp
!= 0 {
443 let exe
= Path
::new(get_unchecked_str(cp
, start
).as_str()).to_path_buf();
446 .and_then(|x
| x
.to_str())
449 while cp
< ptr
.add(size
) && *cp
== 0 {
454 let mut cmd
= Vec
::with_capacity(n_args
as usize);
455 while c
< n_args
&& cp
< ptr
.add(size
) {
458 cmd
.push(get_unchecked_str(cp
, start
));
459 start
= cp
.offset(1);
465 unsafe fn get_environ
<F
: Fn(&str, &mut PathBuf
, &mut bool
)>(
471 ) -> (Vec
<String
>, PathBuf
) {
472 let mut environ
= Vec
::with_capacity(10);
474 let mut check
= true;
475 while cp
< ptr
.add(size
) {
480 let e
= get_unchecked_str(cp
, start
);
481 callback(&e
, &mut root
, &mut check
);
483 start
= cp
.offset(1);
490 let (environ
, root
) = if exe
.is_absolute() {
491 if let Some(parent_path
) = exe
.parent() {
496 parent_path
.to_path_buf(),
500 get_environ(ptr
, cp
, size
, PathBuf
::new(), do_get_env_path
)
503 get_environ(ptr
, cp
, size
, PathBuf
::new(), do_get_env_path
)
505 let mut p
= Process
::new(pid
, parent
, start_time
, run_time
);
510 p
.cmd
= parse_command_line(&cmd
);
515 Process
::new(pid
, parent
, start_time
, run_time
)
518 let task_info
= get_task_info(pid
);
520 p
.memory
= task_info
.pti_resident_size
;
521 p
.virtual_memory
= task_info
.pti_virtual_size
;
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
);
532 pub(crate) fn update_process(
536 time_interval
: Option
<f64>,
538 refresh_kind
: ProcessRefreshKind
,
539 check_if_alive
: bool
,
540 ) -> Result
<Option
<Process
>, ()> {
542 if let Some(ref mut p
) = (*wrap
.0.get()).get_mut(&pid
) {
544 // We don't have access to this process' information.
545 return if check_if_pid_is_alive(pid
, check_if_alive
) {
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.
556 // The owner of this PID changed.
557 return create_new_process(pid
, size
, now
, refresh_kind
, Some(info
));
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(
564 libc
::PROC_PIDTHREADINFO
,
566 &mut thread_info
as *mut libc
::proc_threadinfo
as *mut c_void
,
567 mem
::size_of
::<libc
::proc_threadinfo
>() as _
,
571 thread_info
.pth_user_time
,
572 thread_info
.pth_system_time
,
573 Some(ThreadStatus
::from(thread_info
.pth_run_state
)),
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
))
583 p
.status
= thread_status
;
585 if refresh_kind
.cpu() {
586 compute_cpu_usage(p
, task_info
, system_time
, user_time
, time_interval
);
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
);
597 create_new_process(pid
, size
, now
, refresh_kind
, get_bsd_info(pid
))
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
;
605 let mut pidrusage
= MaybeUninit
::<libc
::rusage_info_v2
>::uninit();
608 let retval
= libc
::proc_pid_rusage(
610 libc
::RUSAGE_INFO_V2
,
611 pidrusage
.as_mut_ptr() as _
,
615 sysinfo_debug
!("proc_pid_rusage failed: {:?}", retval
);
617 let pidrusage
= pidrusage
.assume_init();
618 p
.read_bytes
= pidrusage
.ri_diskio_bytesread
;
619 p
.written_bytes
= pidrusage
.ri_diskio_byteswritten
;
624 #[allow(unknown_lints)]
625 #[allow(clippy::uninit_vec)]
626 pub(crate) fn get_proc_list() -> Option
<Vec
<Pid
>> {
628 let count
= libc
::proc_listallpids(::std
::ptr
::null_mut(), 0);
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
);
637 if x
< 1 || x
as usize >= pids
.len() {
640 pids
.set_len(x
as usize);
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());
654 fn parse_command_line
<T
: Deref
<Target
= str> + Borrow
<str>>(cmd
: &[T
]) -> Vec
<String
> {
656 let mut command
= Vec
::with_capacity(cmd
.len());
657 while x
< cmd
.len() {
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) {
664 command.push(cmd[x..y].join(" "));
667 command.push(cmd[x].to_owned());
680 let mut path = PathBuf::new();
681 let mut check = true;
683 do_get_env_path("PATH
=tadam
", &mut path, &mut check);
686 assert_eq!(path, PathBuf::from("tadam
"));