Skip to content

Commit

Permalink
Add Users and Group iterators (re-entrant)
Browse files Browse the repository at this point in the history
Other functions still need to be edited to use the re-entrant versions,
however while working on this I found a problem (actually, @asomers
mentioned it first, but I did not realize its severity until I coded it
myself -- I learned the hard way..)

Tests will only pass if you do run `cargo test` with the
`--test-threads=1` option. I have no idea what to do about this, because
it seems like no modern systems include `setpwent_r` or
`endpwent_r`...only `getpwent_r` (same for groups).

After we work out how we want to deal with this (perhaps exempting some
functions from the tests, or adding the flag), I will fix the other
functions, e.g. `getpwnam`, to use the re-entrant versions. However, I'm
concerned that this problem will cause all my hard work to be wasted :-(

So I will wait until we get a consensus around these iterators before
continuing to fix the other functions.
  • Loading branch information
ctrlcctrlv committed Mar 3, 2018
1 parent 4e0de01 commit 3612526
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 60 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ exclude = [
]

[dependencies]
libc = { git = "https://github.com/rust-lang/libc" }
libc = { version = ">=0.2.38", git = "https://github.com/rust-lang/libc", branch = "master" }
bitflags = "1.0"
cfg-if = "0.1.0"
void = "1.0.2"
Expand Down
147 changes: 101 additions & 46 deletions src/unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub use self::setres::*;
///
/// Newtype pattern around `uid_t` (which is just alias). It prevents bugs caused by accidentally
/// passing wrong value.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub struct Uid(uid_t);

impl Uid {
Expand Down Expand Up @@ -69,7 +69,7 @@ pub const ROOT: Uid = Uid(0);
///
/// Newtype pattern around `gid_t` (which is just alias). It prevents bugs caused by accidentally
/// passing wrong value.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub struct Gid(gid_t);

impl Gid {
Expand Down Expand Up @@ -2137,7 +2137,9 @@ mod setres {
}
}

#[derive(Debug, Clone, PartialEq)]
const PWGRP_BUFSIZE: usize = 1024;

#[derive(Debug, Clone, PartialEq, Default)]
pub struct Passwd {
/// username
pub name: CString,
Expand Down Expand Up @@ -2260,49 +2262,6 @@ pub fn getpwuid(uid: Uid) -> Result<Option<Passwd>> {
}
}

/// Gets a user DB entry (see
/// [getpwent(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getpwent.html))
#[cfg(not(target_os = "android"))]
#[inline]
pub fn getpwent() -> Result<Option<Passwd>> {
let pw = unsafe { libc::getpwent().as_ref() };

if errno::errno() != 0 {
return Err(Error::Sys(Errno::last()));
}

match pw {
None => {return Ok(None)},
Some(pw) => Ok(Some(Passwd::from(*pw)))
}
}

/// Returns a the cursor to the top of the user DB (see
/// [setpwent(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/setpwent.html))
#[cfg(not(target_os = "android"))]
#[inline]
pub fn setpwent() -> Result<()> {
unsafe { libc::setpwent() };
if errno::errno() != 0 {
Err(Error::Sys(Errno::last()))
} else {
Ok(())
}
}

/// Closes the user DB (see
/// [endpwent(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/endpwent.html))
#[cfg(not(target_os = "android"))]
#[inline]
pub fn endpwent() -> Result<()> {
unsafe { libc::endpwent() };
if errno::errno() != 0 {
Err(Error::Sys(Errno::last()))
} else {
Ok(())
}
}

/// Gets a group DB entry based on group name (see
/// [getgrnam(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/getgrnam.html))
#[inline]
Expand Down Expand Up @@ -2334,3 +2293,99 @@ pub fn getgrgid(gid: Gid) -> Result<Option<Group>> {
Some(gr) => Ok(Some(Group::from(*gr)))
}
}


/// This iterator can be used to get all of the users on the system. For example:
///
/// ```
/// use nix::unistd::Users;
/// eprintln!("Users on this system:\nname\tuid");
/// Users::new()
/// .map(
/// |pw| println!("{}\t{}",
/// pw.name.to_string_lossy().into_owned(),
/// format!("{}", pw.uid)))
/// .collect::<Vec<_>>();
///
/// ```
#[derive(Debug)]
#[cfg(not(target_os = "android"))]
pub struct Users(pub usize);

impl Users {
pub fn new() -> Self {
unsafe { libc::setpwent(); }
Users(PWGRP_BUFSIZE)
}

pub fn with_bufsize(bufsize: usize) -> Self {
unsafe { libc::setpwent(); }
Users(bufsize)
}
}

impl Iterator for Users {
type Item = Passwd;
fn next(&mut self) -> Option<Passwd> {

let mut cbuf = vec![0i8; self.0];
let mut pwd: libc::passwd = unsafe { mem::zeroed() };
let mut res = ptr::null_mut();

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

match i {
0 if !res.is_null() => {
unsafe { Some(Passwd::from(*res)) }
},
_ => None
}
}
}

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

#[derive(Debug)]
#[cfg(not(target_os = "android"))]
pub struct Groups(pub usize);

impl Groups {
pub fn new() -> Self {
unsafe { libc::setgrent(); }
Groups(PWGRP_BUFSIZE)
}

pub fn with_bufsize(bufsize: usize) -> Self {
unsafe { libc::setgrent(); }
Groups(bufsize)
}
}

impl Iterator for Groups {
type Item = Group;
fn next(&mut self) -> Option<Group> {

let mut cbuf = vec![0i8; self.0];
let mut grp: libc::group = unsafe { mem::zeroed() };
let mut res = ptr::null_mut();

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

match i {
0 if !res.is_null() => {
unsafe { Some(Group::from(*res)) }
},
_ => None
}
}
}

impl Drop for Groups {
fn drop(&mut self) {
unsafe { libc::endgrent() };
}
}
31 changes: 18 additions & 13 deletions test/test_unistd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,25 +471,30 @@ fn test_getpwuid() {
}

#[test]
// This test will work if there are at least 2 users on the system (including root)
fn test_getpwent() {
let u1 = getpwent();
let u2 = getpwent();
assert!(u1.unwrap() != u2.unwrap());
fn test_users_iterator() {
let entries = Users::new();
let users: Vec<Passwd> = entries.collect();
let entries2 = Users::new();
let users2: Vec<Passwd> = entries2.collect();
assert!(users == users2);
}

#[test]
fn test_setpwent() {
let _ = getpwent();
let res = setpwent();
assert!(res.is_ok());
fn test_groups_iterator() {
let entries = Groups::new();
let groups: Vec<Group> = entries.collect();
let entries2 = Groups::new();
let groups2: Vec<Group> = entries2.collect();
assert!(groups == groups2);
}

#[test]
fn test_endpwent() {
let _ = getpwent();
let res = endpwent();
assert!(res.is_ok());
/// This test sees what happens when we use a ridiculously small buffer.
fn test_users_iterator_smallbuf() {
let bufsize = 2;
let entries = Users(bufsize);
let users: Vec<Passwd> = entries.collect();
assert!(users.len() == 0);
}

#[test]
Expand Down

0 comments on commit 3612526

Please sign in to comment.