sysinfo 0.24.4

Library to get system information such as processes, CPUs, disks, components and networks
Documentation
// Take a look at the license at the top of the repository in the LICENSE file.

use crate::{
    common::{Gid, Uid},
    User,
};

use crate::sys::utils;
use libc::{c_char, endpwent, getgrgid, getgrouplist, getpwent, gid_t, setpwent, strlen};

fn get_user_groups(name: *const c_char, group_id: gid_t) -> Vec<String> {
    let mut add = 0;

    loop {
        let mut nb_groups = 256 + add;
        let mut groups = Vec::with_capacity(nb_groups as _);
        unsafe {
            if getgrouplist(name, group_id as _, groups.as_mut_ptr(), &mut nb_groups) == -1 {
                add += 100;
                continue;
            }
            groups.set_len(nb_groups as _);
            return groups
                .into_iter()
                .filter_map(|g| {
                    let group = getgrgid(g as _);
                    if group.is_null() {
                        return None;
                    }
                    utils::cstr_to_rust((*group).gr_name)
                })
                .collect();
        }
    }
}

fn endswith(s1: *const c_char, s2: &[u8]) -> bool {
    if s1.is_null() {
        return false;
    }
    unsafe {
        let mut len = strlen(s1) as isize - 1;
        let mut i = s2.len() as isize - 1;
        while len >= 0 && i >= 0 && *s1.offset(len) == s2[i as usize] as _ {
            i -= 1;
            len -= 1;
        }
        i == -1
    }
}

fn users_list<F>(filter: F) -> Vec<User>
where
    F: Fn(*const c_char, u32) -> bool,
{
    let mut users = Vec::new();

    unsafe {
        setpwent();
        loop {
            let pw = getpwent();
            if pw.is_null() {
                break;
            }

            if !filter((*pw).pw_shell, (*pw).pw_uid) {
                // This is not a "real" or "local" user.
                continue;
            }

            let groups = get_user_groups((*pw).pw_name, (*pw).pw_gid);
            let uid = (*pw).pw_uid;
            let gid = (*pw).pw_gid;
            if let Some(name) = utils::cstr_to_rust((*pw).pw_name) {
                users.push(User {
                    uid: Uid(uid),
                    gid: Gid(gid),
                    name,
                    groups,
                });
            }
        }
        endpwent();
    }
    users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap());
    users.dedup_by(|a, b| a.name == b.name);
    users
}

pub(crate) fn get_users_list() -> Vec<User> {
    users_list(|shell, uid| {
        !endswith(shell, b"/false") && !endswith(shell, b"/uucico") && uid < 65536
    })
}

// This was the OSX-based solution. It provides enough information, but what a mess!
// pub fn get_users_list() -> Vec<User> {
//     let mut users = Vec::new();
//     let node_name = b"/Local/Default\0";

//     unsafe {
//         let node_name = ffi::CFStringCreateWithCStringNoCopy(
//             std::ptr::null_mut(),
//             node_name.as_ptr() as *const c_char,
//             ffi::kCFStringEncodingMacRoman,
//             ffi::kCFAllocatorNull as *mut c_void,
//         );
//         let node_ref = ffi::ODNodeCreateWithName(
//             ffi::kCFAllocatorDefault,
//             ffi::kODSessionDefault,
//             node_name,
//             std::ptr::null_mut(),
//         );
//         let query = ffi::ODQueryCreateWithNode(
//             ffi::kCFAllocatorDefault,
//             node_ref,
//             ffi::kODRecordTypeUsers as _, // kODRecordTypeGroups
//             std::ptr::null(),
//             0,
//             std::ptr::null(),
//             std::ptr::null(),
//             0,
//             std::ptr::null_mut(),
//         );
//         if query.is_null() {
//             return users;
//         }
//         let results = ffi::ODQueryCopyResults(
//             query,
//             false as _,
//             std::ptr::null_mut(),
//         );
//         let len = ffi::CFArrayGetCount(results);
//         for i in 0..len {
//             let name = match get_user_name(ffi::CFArrayGetValueAtIndex(results, i)) {
//                 Some(n) => n,
//                 None => continue,
//             };
//             let groups = get_user_groups(&name);
//             users.push(User { name });
//         }

//         ffi::CFRelease(results as *const c_void);
//         ffi::CFRelease(query as *const c_void);
//         ffi::CFRelease(node_ref as *const c_void);
//         ffi::CFRelease(node_name as *const c_void);
//     }
//     users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap());
//     return users;
// }

// fn get_user_name(result: *const c_void) -> Option<String> {
//     let user_name = ffi::ODRecordGetRecordName(result as _);
//     let ptr = ffi::CFStringGetCharactersPtr(user_name);
//     String::from_utf16(&if ptr.is_null() {
//         let len = ffi::CFStringGetLength(user_name); // It returns the len in UTF-16 code pairs.
//         if len == 0 {
//             continue;
//         }
//         let mut v = Vec::with_capacity(len as _);
//         for x in 0..len {
//             v.push(ffi::CFStringGetCharacterAtIndex(user_name, x));
//         }
//         v
//     } else {
//         let mut v: Vec<u16> = Vec::new();
//         let mut x = 0;
//         loop {
//             let letter = *ptr.offset(x);
//             if letter == 0 {
//                 break;
//             }
//             v.push(letter);
//             x += 1;
//         }
//         v
//     }.ok()
// }