From 36125265ea9b2f7d57f6a4936a3a6184e3421589 Mon Sep 17 00:00:00 2001 From: Fredrick Brennan Date: Sat, 3 Mar 2018 11:16:15 +0000 Subject: [PATCH] Add `Users` and `Group` iterators (re-entrant) 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. --- Cargo.toml | 2 +- src/unistd.rs | 147 ++++++++++++++++++++++++++++++-------------- test/test_unistd.rs | 31 ++++++---- 3 files changed, 120 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 651c2184ce..17e80ef180 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/unistd.rs b/src/unistd.rs index b395164347..f060fb9a5e 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -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 { @@ -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 { @@ -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, @@ -2260,49 +2262,6 @@ pub fn getpwuid(uid: Uid) -> Result> { } } -/// 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> { - 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] @@ -2334,3 +2293,99 @@ pub fn getgrgid(gid: Gid) -> Result> { 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::>(); +/// +/// ``` +#[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 { + + 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 { + + 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() }; + } +} diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 5dafb82e4f..ebe0ef041c 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -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 = entries.collect(); + let entries2 = Users::new(); + let users2: Vec = 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 = entries.collect(); + let entries2 = Groups::new(); + let groups2: Vec = 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 = entries.collect(); + assert!(users.len() == 0); } #[test]