andrewdavidmackenzie / libproc-rs

A rust library for getting information about running processes for Mac and Linux
MIT License
58 stars 17 forks source link

I was rewriting a process tree mapping program from C, and noticed th… #113

Closed jthomas-dd closed 8 months ago

jthomas-dd commented 8 months ago

…at for a ppid with a single child, I would get an empty list using 'let pids = pids_by_type(libproc::processes::ProcFilter::ByParentProcess { ppid: MY_PROC_PID }).unwrap();'. Where the same direct call in C would yield a list with 1 PID. Digging into libproc-rs, I noticed that the size was being adjusted -1, and when corrected I now see the correct single child pid.

One worrisome thing is what is happening now when there are actually zero children for a PID? I think it's producing an error.

andrewdavidmackenzie commented 8 months ago

Thanks very much for the investigation and proposed fix. I have mentioned the original code contributor to see if he recalls where that came from.

Looking at this:

    match ret {
        value if value <= 0 => Err(io::Error::last_os_error()),      <<<<<<<<<<<<<<<<<<<<<<<<<<<
        _ => {
            let items_count = ret as usize / mem::size_of::<u32>() - 1;
            unsafe {
                pids.set_len(items_count);
            }
            Ok(pids)
        }
    }

and the C source:

int 
proc_listpids(uint32_t type, uint32_t typeinfo, void *buffer, int buffersize) 
{
    int retval;

    if ((type == PROC_ALL_PIDS) || (type == PROC_PGRP_ONLY)  || (type == PROC_TTY_ONLY) || (type == PROC_UID_ONLY) || (type == PROC_RUID_ONLY)) {
        if ((retval = __proc_info(1, type, typeinfo,(uint64_t)0, buffer, buffersize)) == -1)
            return(0); <<<<<<<<<<<<<<<<<<<<<<<<<<<<
    } else {
        errno = EINVAL;
        retval = 0; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
    }
    return(retval);
}

So, I think this line: (fyi @mjpieters )

value if value <= 0 => Err(io::Error::last_os_error()),

can't distinguish the two cases and we need to look at errno to know if the 0 indicates

Something like:

match ret {
        value if value < 0 || errno => Err(io::Error::last_os_error()), // psuedo-code
        _ => {
            let items_count = ret as usize / mem::size_of::<u32>();  // 0 / x = 0
            unsafe {
                pids.set_len(items_count);
            }
            Ok(pids)
        }
    }

WDYT?

Testing

Any ideas for tests we could add for the:

I see test_listpids_parent_pid() exists, but need to study it.

I suspect this: let pids = listpids(ProcFilter::ByParentProcess { ppid: *ppid }).unwrap_or_default(); maybe avoiding/handling any error during running the test?

And I think this:

  if bsdinfo_pids.len() <= 1 {
                    continue;
                }

will skip the call to listpids(parent)

andrewdavidmackenzie commented 8 months ago

I took an initial shot an bigger changes to the off by one, the zero case (error or not) and refactoring the tests here:

https://github.com/andrewdavidmackenzie/libproc-rs/pull/114

FYI @mjpieters @jthomas-dd could you take a look?

If OK, I think I can factor out almost all the repitition of code in the tests and just pass a closure for the small difference to a common test...

andrewdavidmackenzie commented 8 months ago

I just realized that we will have to handle the two ret = 0 cases in this part also:

    if buffer_size <= 0 {
        return Err(io::Error::last_os_error());
    }
andrewdavidmackenzie commented 8 months ago

Closed in favor of https://github.com/andrewdavidmackenzie/libproc-rs/pull/114

andrewdavidmackenzie commented 8 months ago

FYI https://github.com/andrewdavidmackenzie/libproc-rs/releases/tag/v0.14.3 :-)

andrewdavidmackenzie commented 8 months ago

I included a small improvement to errno checking that was nagging at me after last release:

https://github.com/andrewdavidmackenzie/libproc-rs/releases/tag/v0.14.4