Skip to content

Commit adcec6a

Browse files
committed
Add Users and Group iterators
This was a collaborative work between Johannes Schilling <[email protected]>, Fredrick Brennan <[email protected]> and myself. This is a continuation of nix-rust#1139. Signed-off-by: Otavio Salvador <[email protected]>
1 parent b14415a commit adcec6a

File tree

3 files changed

+219
-0
lines changed

3 files changed

+219
-0
lines changed

src/unistd.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ pub use self::pivot_root::*;
2020
#[cfg(any(target_os = "android", target_os = "freebsd",
2121
target_os = "linux", target_os = "openbsd"))]
2222
pub use self::setres::*;
23+
#[cfg(not(any(target_os = "android",
24+
target_os = "ios",
25+
target_os = "macos",
26+
target_env = "musl")))]
27+
pub use self::usergroupiter::*;
2328

2429
/// User identifier
2530
///
@@ -2425,6 +2430,13 @@ pub fn access<P: ?Sized + NixPath>(path: &P, amode: AccessFlags) -> Result<()> {
24252430
Errno::result(res).map(drop)
24262431
}
24272432

2433+
#[cfg(not(any(target_os = "android",
2434+
target_os = "ios",
2435+
target_os = "macos",
2436+
target_env = "musl")))]
2437+
/// Default buffer size for system user and group querying functions
2438+
const PWGRP_BUFSIZE: usize = 1024;
2439+
24282440
/// Representation of a User, based on `libc::passwd`
24292441
///
24302442
/// The reason some fields in this struct are `String` and others are `CString` is because some
@@ -2676,3 +2688,164 @@ impl Group {
26762688
})
26772689
}
26782690
}
2691+
2692+
#[cfg(not(any(target_os = "android",
2693+
target_os = "ios",
2694+
target_os = "macos",
2695+
target_env = "musl")))]
2696+
mod usergroupiter {
2697+
use libc::{self, c_char};
2698+
use Result;
2699+
use errno::Errno;
2700+
use super::{Error, User, Group, PWGRP_BUFSIZE};
2701+
use std::{mem, ptr};
2702+
2703+
/// Used to get all of the users on the system.
2704+
///
2705+
/// # Examples
2706+
///
2707+
/// ```
2708+
/// # use nix::unistd::Users;
2709+
/// Users::default()
2710+
/// .map(|e| e.map(|pw| println!("{}\t{}", pw.name, pw.uid)))
2711+
/// .collect::<Vec<_>>();
2712+
///
2713+
/// ```
2714+
///
2715+
/// # Safety
2716+
///
2717+
/// This iterator should not be used in different threads without synchronization; while doing so
2718+
/// will not cause undefined behavior, because modern systems lack re-entrant versions of
2719+
/// `setpwent` and `endpwent`, it is very likely that iterators running in different threads will
2720+
/// yield different numbers of items.
2721+
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
2722+
pub struct Users(usize);
2723+
2724+
impl Default for Users {
2725+
fn default() -> Self {
2726+
Users::with_capacity(PWGRP_BUFSIZE)
2727+
}
2728+
}
2729+
2730+
impl Users {
2731+
/// Create a new `Users` instance with given capacity.
2732+
pub fn with_capacity(bufsize: usize) -> Self {
2733+
unsafe { libc::setpwent(); }
2734+
Users(bufsize)
2735+
}
2736+
2737+
/// Get the buffer size this `Users` instance was created with.
2738+
pub fn bufsize(&self) -> usize {
2739+
self.0
2740+
}
2741+
}
2742+
2743+
impl Iterator for Users {
2744+
type Item = Result<User>;
2745+
fn next(&mut self) -> Option<Result<User>> {
2746+
let mut cbuf = vec![0 as c_char; self.0];
2747+
let mut pwd = mem::MaybeUninit::<libc::passwd>::uninit();
2748+
let mut res = ptr::null_mut();
2749+
2750+
let error = unsafe {
2751+
Errno::clear();
2752+
libc::getpwent_r(
2753+
pwd.as_mut_ptr(),
2754+
cbuf.as_mut_ptr(),
2755+
self.0,
2756+
&mut res
2757+
)
2758+
};
2759+
2760+
let pwd = unsafe { pwd.assume_init() };
2761+
2762+
if error == 0 && !res.is_null() {
2763+
Some(Ok(User::from(&pwd)))
2764+
} else if error == libc::ERANGE {
2765+
Some(Err(Error::Sys(Errno::last())))
2766+
} else {
2767+
None
2768+
}
2769+
}
2770+
}
2771+
2772+
impl Drop for Users {
2773+
fn drop(&mut self) {
2774+
unsafe { libc::endpwent() };
2775+
}
2776+
}
2777+
2778+
/// Used to get all of the groups on the system.
2779+
///
2780+
/// # Examples
2781+
///
2782+
/// ```
2783+
/// # use nix::unistd::Groups;
2784+
/// Groups::default()
2785+
/// .map(|e| e.map(|gr| println!("{}\t{}", gr.name, gr.gid)))
2786+
/// .collect::<Vec<_>>();
2787+
/// ```
2788+
///
2789+
/// # Safety
2790+
///
2791+
/// This iterator should not be used in different threads without synchronization; while doing so
2792+
/// will not cause undefined behavior, because modern systems lack re-entrant versions of
2793+
/// `setgrent` and `endgrent`, it is very likely that iterators running in different threads will
2794+
/// yield different numbers of items.
2795+
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
2796+
pub struct Groups(usize);
2797+
2798+
impl Default for Groups {
2799+
fn default() -> Self {
2800+
Groups::with_capacity(PWGRP_BUFSIZE)
2801+
}
2802+
}
2803+
2804+
impl Groups {
2805+
/// Create a new `Groups` instance with given capacity.
2806+
pub fn with_capacity(bufsize: usize) -> Self {
2807+
unsafe { libc::setgrent(); }
2808+
Groups(bufsize)
2809+
}
2810+
2811+
/// Get the buffer size this `Users` instance was created with.
2812+
pub fn bufsize(&self) -> usize {
2813+
self.0
2814+
}
2815+
}
2816+
2817+
impl Iterator for Groups {
2818+
type Item = Result<Group>;
2819+
fn next(&mut self) -> Option<Result<Group>> {
2820+
let mut cbuf = vec![0 as c_char; self.0];
2821+
let mut grp = mem::MaybeUninit::<libc::group>::uninit();
2822+
let mut res = ptr::null_mut();
2823+
2824+
let error = unsafe {
2825+
Errno::clear();
2826+
libc::getgrent_r(
2827+
grp.as_mut_ptr(),
2828+
cbuf.as_mut_ptr(),
2829+
self.0,
2830+
&mut res
2831+
)
2832+
};
2833+
2834+
let grp = unsafe { grp.assume_init() };
2835+
2836+
if error == 0 && !res.is_null() {
2837+
Some(Ok(Group::from(&grp)))
2838+
} else if error == libc::ERANGE {
2839+
Some(Err(Error::Sys(Errno::last())))
2840+
} else {
2841+
None
2842+
}
2843+
}
2844+
}
2845+
2846+
impl Drop for Groups {
2847+
fn drop(&mut self) {
2848+
unsafe { libc::endgrent() };
2849+
}
2850+
}
2851+
}

test/test.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ lazy_static! {
159159
pub static ref PTSNAME_MTX: Mutex<()> = Mutex::new(());
160160
/// Any test that alters signal handling must grab this mutex.
161161
pub static ref SIGNAL_MTX: Mutex<()> = Mutex::new(());
162+
/// Any test that uses the `Users` or `Groups` iterators must grab this mutex.
163+
pub static ref USER_GRP_ITER_MTX: Mutex<()> = Mutex::new(());
162164
}
163165

164166
/// RAII object that restores a test's original directory on drop

test/test_unistd.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,50 @@ fn test_symlinkat() {
660660
);
661661
}
662662

663+
#[cfg(not(any(target_os = "android",
664+
target_os = "ios",
665+
target_os = "macos",
666+
target_env = "musl")))]
667+
668+
#[test]
669+
fn test_users_iterator() {
670+
let _m = ::USER_GRP_ITER_MTX.lock().expect("Mutex got poisoned by another test");
671+
672+
let entries = Users::default();
673+
let users: Vec<Result<User, _>> = entries.collect();
674+
let entries2 = Users::default();
675+
let users2: Vec<Result<User, _>> = entries2.collect();
676+
assert!(users == users2 && users.len() > 0);
677+
}
678+
679+
#[cfg(not(any(target_os = "android",
680+
target_os = "ios",
681+
target_os = "macos",
682+
target_env = "musl")))]
683+
684+
#[test]
685+
fn test_groups_iterator() {
686+
let _m = ::USER_GRP_ITER_MTX.lock().expect("Mutex got poisoned by another test");
687+
688+
let entries = Groups::default();
689+
let groups: Vec<Result<Group, _>> = entries.collect();
690+
let entries2 = Groups::default();
691+
let groups2: Vec<Result<Group, _>> = entries2.collect();
692+
assert!(groups == groups2 && groups.len() > 0);
693+
}
694+
695+
#[cfg(not(any(target_os = "android",
696+
target_os = "ios",
697+
target_os = "macos",
698+
target_env = "musl")))]
699+
#[test]
700+
/// This test sees what happens when we use a ridiculously small buffer.
701+
fn test_users_iterator_smallbuf() {
702+
let _m = ::USER_GRP_ITER_MTX.lock().expect("Mutex got poisoned by another test");
703+
704+
let bufsize = 2;
705+
assert!(Users::with_capacity(bufsize).next().unwrap().is_err());
706+
}
663707

664708
#[test]
665709
fn test_unlinkat_dir_noremovedir() {

0 commit comments

Comments
 (0)