Skip to content

Commit

Permalink
Add Users and Group iterators
Browse files Browse the repository at this point in the history
This was a collaborative work between Johannes Schilling
<dario@deaktualisierung.org>, Fredrick Brennan <copypaste@kittens.ph>
and myself.

This is a continuation of nix-rust#1139.

Signed-off-by: Otavio Salvador <otavio@ossystems.com.br>
  • Loading branch information
otavio committed Oct 31, 2019
1 parent b14415a commit adcec6a
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 0 deletions.
173 changes: 173 additions & 0 deletions src/unistd.rs
Expand Up @@ -20,6 +20,11 @@ pub use self::pivot_root::*;
#[cfg(any(target_os = "android", target_os = "freebsd",
target_os = "linux", target_os = "openbsd"))]
pub use self::setres::*;
#[cfg(not(any(target_os = "android",
target_os = "ios",
target_os = "macos",
target_env = "musl")))]
pub use self::usergroupiter::*;

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

#[cfg(not(any(target_os = "android",
target_os = "ios",
target_os = "macos",
target_env = "musl")))]
/// Default buffer size for system user and group querying functions
const PWGRP_BUFSIZE: usize = 1024;

/// Representation of a User, based on `libc::passwd`
///
/// The reason some fields in this struct are `String` and others are `CString` is because some
Expand Down Expand Up @@ -2676,3 +2688,164 @@ impl Group {
})
}
}

#[cfg(not(any(target_os = "android",
target_os = "ios",
target_os = "macos",
target_env = "musl")))]
mod usergroupiter {
use libc::{self, c_char};
use Result;
use errno::Errno;
use super::{Error, User, Group, PWGRP_BUFSIZE};
use std::{mem, ptr};

/// Used to get all of the users on the system.
///
/// # Examples
///
/// ```
/// # use nix::unistd::Users;
/// Users::default()
/// .map(|e| e.map(|pw| println!("{}\t{}", pw.name, pw.uid)))
/// .collect::<Vec<_>>();
///
/// ```
///
/// # Safety
///
/// This iterator should not be used in different threads without synchronization; while doing so
/// will not cause undefined behavior, because modern systems lack re-entrant versions of
/// `setpwent` and `endpwent`, it is very likely that iterators running in different threads will
/// yield different numbers of items.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Users(usize);

impl Default for Users {
fn default() -> Self {
Users::with_capacity(PWGRP_BUFSIZE)
}
}

impl Users {
/// Create a new `Users` instance with given capacity.
pub fn with_capacity(bufsize: usize) -> Self {
unsafe { libc::setpwent(); }
Users(bufsize)
}

/// Get the buffer size this `Users` instance was created with.
pub fn bufsize(&self) -> usize {
self.0
}
}

impl Iterator for Users {
type Item = Result<User>;
fn next(&mut self) -> Option<Result<User>> {
let mut cbuf = vec![0 as c_char; self.0];
let mut pwd = mem::MaybeUninit::<libc::passwd>::uninit();
let mut res = ptr::null_mut();

let error = unsafe {
Errno::clear();
libc::getpwent_r(
pwd.as_mut_ptr(),
cbuf.as_mut_ptr(),
self.0,
&mut res
)
};

let pwd = unsafe { pwd.assume_init() };

if error == 0 && !res.is_null() {
Some(Ok(User::from(&pwd)))
} else if error == libc::ERANGE {
Some(Err(Error::Sys(Errno::last())))
} else {
None
}
}
}

impl Drop for Users {
fn drop(&mut self) {
unsafe { libc::endpwent() };
}
}

/// Used to get all of the groups on the system.
///
/// # Examples
///
/// ```
/// # use nix::unistd::Groups;
/// Groups::default()
/// .map(|e| e.map(|gr| println!("{}\t{}", gr.name, gr.gid)))
/// .collect::<Vec<_>>();
/// ```
///
/// # Safety
///
/// This iterator should not be used in different threads without synchronization; while doing so
/// will not cause undefined behavior, because modern systems lack re-entrant versions of
/// `setgrent` and `endgrent`, it is very likely that iterators running in different threads will
/// yield different numbers of items.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Groups(usize);

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

impl Groups {
/// Create a new `Groups` instance with given capacity.
pub fn with_capacity(bufsize: usize) -> Self {
unsafe { libc::setgrent(); }
Groups(bufsize)
}

/// Get the buffer size this `Users` instance was created with.
pub fn bufsize(&self) -> usize {
self.0
}
}

impl Iterator for Groups {
type Item = Result<Group>;
fn next(&mut self) -> Option<Result<Group>> {
let mut cbuf = vec![0 as c_char; self.0];
let mut grp = mem::MaybeUninit::<libc::group>::uninit();
let mut res = ptr::null_mut();

let error = unsafe {
Errno::clear();
libc::getgrent_r(
grp.as_mut_ptr(),
cbuf.as_mut_ptr(),
self.0,
&mut res
)
};

let grp = unsafe { grp.assume_init() };

if error == 0 && !res.is_null() {
Some(Ok(Group::from(&grp)))
} else if error == libc::ERANGE {
Some(Err(Error::Sys(Errno::last())))
} else {
None
}
}
}

impl Drop for Groups {
fn drop(&mut self) {
unsafe { libc::endgrent() };
}
}
}
2 changes: 2 additions & 0 deletions test/test.rs
Expand Up @@ -159,6 +159,8 @@ lazy_static! {
pub static ref PTSNAME_MTX: Mutex<()> = Mutex::new(());
/// Any test that alters signal handling must grab this mutex.
pub static ref SIGNAL_MTX: Mutex<()> = Mutex::new(());
/// Any test that uses the `Users` or `Groups` iterators must grab this mutex.
pub static ref USER_GRP_ITER_MTX: Mutex<()> = Mutex::new(());
}

/// RAII object that restores a test's original directory on drop
Expand Down
44 changes: 44 additions & 0 deletions test/test_unistd.rs
Expand Up @@ -660,6 +660,50 @@ fn test_symlinkat() {
);
}

#[cfg(not(any(target_os = "android",
target_os = "ios",
target_os = "macos",
target_env = "musl")))]

#[test]
fn test_users_iterator() {
let _m = ::USER_GRP_ITER_MTX.lock().expect("Mutex got poisoned by another test");

let entries = Users::default();
let users: Vec<Result<User, _>> = entries.collect();
let entries2 = Users::default();
let users2: Vec<Result<User, _>> = entries2.collect();
assert!(users == users2 && users.len() > 0);
}

#[cfg(not(any(target_os = "android",
target_os = "ios",
target_os = "macos",
target_env = "musl")))]

#[test]
fn test_groups_iterator() {
let _m = ::USER_GRP_ITER_MTX.lock().expect("Mutex got poisoned by another test");

let entries = Groups::default();
let groups: Vec<Result<Group, _>> = entries.collect();
let entries2 = Groups::default();
let groups2: Vec<Result<Group, _>> = entries2.collect();
assert!(groups == groups2 && groups.len() > 0);
}

#[cfg(not(any(target_os = "android",
target_os = "ios",
target_os = "macos",
target_env = "musl")))]
#[test]
/// This test sees what happens when we use a ridiculously small buffer.
fn test_users_iterator_smallbuf() {
let _m = ::USER_GRP_ITER_MTX.lock().expect("Mutex got poisoned by another test");

let bufsize = 2;
assert!(Users::with_capacity(bufsize).next().unwrap().is_err());
}

#[test]
fn test_unlinkat_dir_noremovedir() {
Expand Down

0 comments on commit adcec6a

Please sign in to comment.