Skip to content

Commit

Permalink
Add sysinfo::Groups implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sisungo committed Feb 22, 2024
1 parent 4e06037 commit 7e61eb7
Show file tree
Hide file tree
Showing 12 changed files with 513 additions and 12 deletions.
181 changes: 175 additions & 6 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use crate::{
ComponentInner, ComponentsInner, CpuInner, NetworkDataInner, NetworksInner, ProcessInner,
SystemInner, UserInner,
ComponentInner, ComponentsInner, CpuInner, GroupInner, NetworkDataInner, NetworksInner, ProcessInner, SystemInner, UserInner
};

use std::cmp::Ordering;
Expand Down Expand Up @@ -2843,6 +2842,177 @@ impl Users {
}
}

/// Interacting with groups.
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new();
/// for group in groups.list() {
/// println!("{}", group.name());
/// }
/// ```
pub struct Groups {
groups: Vec<Group>,
}

impl Default for Groups {
fn default() -> Self {
Self::new()
}
}

impl From<Groups> for Vec<Group> {
fn from(groups: Groups) -> Self {
groups.groups
}
}

impl From<Vec<Group>> for Groups {
fn from(groups: Vec<Group>) -> Self {
Self { groups }
}
}

impl std::ops::Deref for Groups {
type Target = [Group];

fn deref(&self) -> &Self::Target {
self.list()
}
}

impl std::ops::DerefMut for Groups {
fn deref_mut(&mut self) -> &mut Self::Target {
self.list_mut()
}
}

impl<'a> IntoIterator for &'a Groups {
type Item = &'a Group;
type IntoIter = std::slice::Iter<'a, Group>;

fn into_iter(self) -> Self::IntoIter {
self.list().iter()
}
}

impl<'a> IntoIterator for &'a mut Groups {
type Item = &'a mut Group;
type IntoIter = std::slice::IterMut<'a, Group>;

fn into_iter(self) -> Self::IntoIter {
self.list_mut().iter_mut()
}
}

impl Groups {
/// Creates a new empty [`Groups`][crate::Groups] type.
///
/// If you want it to be filled directly, take a look at [`Groups::new_with_refreshed_list`].
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new();
/// groups.refresh_list();
/// for group in groups.list() {
/// println!("{group:?}");
/// }
/// ```
pub fn new() -> Self {
Self { groups: Vec::new() }
}

/// Creates a new [`Groups`][crate::Groups] type with the user list loaded.
/// It is a combination of [`Groups::new`] and [`Groups::refresh_list`].
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new_with_refreshed_list();
/// for group in groups.list() {
/// println!("{group:?}");
/// }
/// ```
pub fn new_with_refreshed_list() -> Self {
let mut groups = Self::new();
groups.refresh_list();
groups
}

/// Returns the users list.
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let groups = Groups::new_with_refreshed_list();
/// for group in groups.list() {
/// println!("{group:?}");
/// }
/// ```
pub fn list(&self) -> &[Group] {
&self.groups
}

/// Returns the groups list.
///
/// ```no_run
/// use sysinfo::Groups;
///
/// let mut groups = Groups::new_with_refreshed_list();
/// groups.list_mut().sort_by(|user1, user2| {
/// user1.name().partial_cmp(user2.name()).unwrap()
/// });
/// ```
pub fn list_mut(&mut self) -> &mut [Group] {
&mut self.groups
}

/// The group list will be emptied then completely recomputed.
///
/// ```no_run
/// use sysinfo::Users;
///
/// let mut users = Users::new();
/// users.refresh_list();
/// ```
pub fn refresh_list(&mut self) {
crate::sys::get_groups(&mut self.groups);
}

/// Returns the [`Group`] matching the given `group_id`.
///
/// **Important**: The group list must be filled before using this method, otherwise it will
/// always return `None` (through the `refresh_*` methods).
///
/// It is a shorthand for:
///
/// ```ignore
/// # use sysinfo::Groups;
/// let groups = Groups::new_with_refreshed_list();
/// groups.list().find(|user| user.id() == user_id);
/// ```
///
/// Full example:
///
/// ```no_run
/// use sysinfo::{Pid, System, Groups};
///
/// let mut s = System::new_all();
/// let groups = Groups::new_with_refreshed_list();
///
/// if let Some(process) = s.process(Pid::from(1337)) {
/// if let Some(group_id) = process.group_id() {
/// println!("User for process 1337: {:?}", groups.get_group_by_id(group_id));
/// }
/// }
/// ```
pub fn get_group_by_id(&self, group_id: &Gid) -> Option<&Group> {
self.groups.iter().find(|group| group.id() == group_id)
}
}

/// An enum representing signals on UNIX-like systems.
///
/// On non-unix systems, this enum is mostly useless and is only there to keep coherency between
Expand Down Expand Up @@ -3213,8 +3383,7 @@ impl User {
/// ```
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Group {
pub(crate) id: Gid,
pub(crate) name: String,
pub(crate) inner: GroupInner,
}

impl Group {
Expand All @@ -3234,7 +3403,7 @@ impl Group {
/// }
/// ```
pub fn id(&self) -> &Gid {
&self.id
&self.inner.id()
}

/// Returns the name of the group.
Expand All @@ -3251,7 +3420,7 @@ impl Group {
/// }
/// ```
pub fn name(&self) -> &str {
&self.name
&self.inner.name()
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub use crate::common::{

pub(crate) use crate::sys::{
ComponentInner, ComponentsInner, CpuInner, DiskInner, DisksInner, NetworkDataInner,
NetworksInner, ProcessInner, SystemInner, UserInner,
NetworksInner, ProcessInner, SystemInner, UserInner, GroupInner,
};
pub use crate::sys::{IS_SUPPORTED_SYSTEM, MINIMUM_CPU_UPDATE_INTERVAL, SUPPORTED_SIGNALS};

Expand Down
127 changes: 127 additions & 0 deletions src/unix/apple/groups.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use crate::{
common::Gid,
Group, GroupInner,
};

use libc::{endgrent, getgrent, setgrent};
use std::collections::HashMap;

pub(crate) fn get_groups(groups: &mut Vec<Group>) {
groups.clear();

let mut groups_map = HashMap::with_capacity(10);

unsafe {
setgrent();
loop {
let gr = getgrent();
if gr.is_null() {
// The call was interrupted by a signal, retrying.
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
continue;
}
break;
}

if let Some(name) = crate::unix::utils::cstr_to_rust((*gr).gr_name) {
if groups_map.contains_key(&name) {
continue;
}

let gid = (*gr).gr_gid;
groups_map.insert(name, Gid(gid));
}
}
endgrent();
}
for (name, gid) in groups_map {
groups.push(Group {
inner: GroupInner::new(gid, name),
});
}
}

// 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,
// };
// 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()
// }
2 changes: 2 additions & 0 deletions src/unix/apple/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod network;
pub mod process;
pub mod system;
pub mod users;
pub mod groups;
mod utils;

pub(crate) use self::component::{ComponentInner, ComponentsInner};
Expand All @@ -31,6 +32,7 @@ pub(crate) use self::network::{NetworkDataInner, NetworksInner};
pub(crate) use self::process::ProcessInner;
pub(crate) use self::system::SystemInner;
pub(crate) use crate::unix::users::{get_users, UserInner};
pub(crate) use crate::unix::groups::{get_groups, GroupInner};
pub(crate) use crate::unix::DisksInner;

use std::time::Duration;
Expand Down
1 change: 1 addition & 0 deletions src/unix/freebsd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub(crate) use self::network::{NetworkDataInner, NetworksInner};
pub(crate) use self::process::ProcessInner;
pub(crate) use self::system::SystemInner;
pub(crate) use crate::unix::users::{get_users, UserInner};
pub(crate) use crate::unix::groups::{get_groups, GroupInner};
pub(crate) use crate::unix::DisksInner;

use libc::c_int;
Expand Down

0 comments on commit 7e61eb7

Please sign in to comment.