From 7fff6d51aacc1a957c95873fc603b7945a934199 Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 27 Aug 2017 19:55:48 +0200
Subject: [PATCH 01/13] unistd: Remove trailing whitespace

---
 src/unistd.rs | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/unistd.rs b/src/unistd.rs
index 401357d3da..42c6d5835d 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -804,7 +804,7 @@ pub enum Whence {
     SeekCur = libc::SEEK_CUR,
     /// Specify an offset relative to the end of the file.
     SeekEnd = libc::SEEK_END,
-    /// Specify an offset relative to the next location in the file greater than or 
+    /// Specify an offset relative to the next location in the file greater than or
     /// equal to offset that contains some data. If offset points to
     /// some data, then the file offset is set to offset.
     #[cfg(any(target_os = "dragonfly", target_os = "freebsd",
@@ -813,7 +813,7 @@ pub enum Whence {
                                            target_arch = "mips64")))))]
     SeekData = libc::SEEK_DATA,
     /// Specify an offset relative to the next hole in the file greater than
-    /// or equal to offset. If offset points into the middle of a hole, then 
+    /// or equal to offset. If offset points into the middle of a hole, then
     /// the file offset should be set to offset. If there is no hole past offset,
     /// then the file offset should be adjusted to the end of the file (i.e., there
     /// is an implicit hole at the end of any file).
@@ -1361,7 +1361,7 @@ pub enum SysconfVar {
     OPEN_MAX = libc::_SC_OPEN_MAX,
     #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios",
               target_os="linux", target_os = "macos", target_os="openbsd"))]
-    /// The implementation supports the Advisory Information option. 
+    /// The implementation supports the Advisory Information option.
     _POSIX_ADVISORY_INFO = libc::_SC_ADVISORY_INFO,
     #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios",
               target_os="linux", target_os = "macos", target_os="netbsd",
@@ -1380,7 +1380,7 @@ pub enum SysconfVar {
               target_os="openbsd"))]
     /// The implementation supports the Process CPU-Time Clocks option.
     _POSIX_CPUTIME = libc::_SC_CPUTIME,
-    /// The implementation supports the File Synchronization option. 
+    /// The implementation supports the File Synchronization option.
     _POSIX_FSYNC = libc::_SC_FSYNC,
     #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios",
               target_os="linux", target_os = "macos", target_os="openbsd"))]
@@ -1495,7 +1495,7 @@ pub enum SysconfVar {
               target_os="linux", target_os = "macos", target_os="openbsd"))]
     /// The implementation supports timeouts.
     _POSIX_TIMEOUTS = libc::_SC_TIMEOUTS,
-    /// The implementation supports timers. 
+    /// The implementation supports timers.
     _POSIX_TIMERS = libc::_SC_TIMERS,
     #[cfg(any(target_os="dragonfly", target_os="freebsd", target_os = "ios",
               target_os="linux", target_os = "macos", target_os="openbsd"))]

From a154b26e49062ecd4975b690378327969d73192e Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 27 Aug 2017 20:07:12 +0200
Subject: [PATCH 02/13] unistd: Add setgroups()

---
 src/unistd.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 46 insertions(+), 1 deletion(-)

diff --git a/src/unistd.rs b/src/unistd.rs
index 42c6d5835d..28220e5d14 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -464,7 +464,7 @@ pub fn mkdir<P: ?Sized + NixPath>(path: &P, mode: Mode) -> Result<()> {
 /// fn main() {
 ///     let tmp_dir = TempDir::new("test_fifo").unwrap();
 ///     let fifo_path = tmp_dir.path().join("foo.pipe");
-/// 
+///
 ///     // create new fifo and give read, write and execute rights to the owner
 ///     match unistd::mkfifo(&fifo_path, stat::S_IRWXU) {
 ///        Ok(_) => println!("created {:?}", fifo_path),
@@ -1047,6 +1047,51 @@ pub fn setgid(gid: Gid) -> Result<()> {
     Errno::result(res).map(drop)
 }
 
+/// Set the list of supplementary group IDs for the calling process.
+///
+/// *Note:* On macOS, `getgroups()` may not return the same group list set by
+/// calling `setgroups()`. The use of `setgroups()` on macOS is 'highly
+/// discouraged' by Apple. Developers are referred to the `opendirectoryd`
+/// daemon and its set of APIs.
+///
+/// [Further reading](http://man7.org/linux/man-pages/man2/getgroups.2.html)
+///
+/// # Examples
+///
+/// `setgroups` can be used when dropping privileges from the root user to a
+/// specific user and group. For example, given the user `www-data` with UID
+/// `33` and the group `backup` with the GID `34`, one could switch user as
+/// follows:
+/// ```
+/// let uid = Uid::from_raw(33);
+/// let gid = Gid::from_raw(34);
+/// setgroups(&[gid])?;
+/// setgid(gid)?;
+/// setuid(uid)?;
+/// ```
+pub fn setgroups(groups: &[Gid]) -> Result<()> {
+    cfg_if! {
+        if #[cfg(any(target_os = "dragonfly",
+                     target_os = "freebsd",
+                     target_os = "ios",
+                     target_os = "macos",
+                     target_os = "netbsd",
+                     target_os = "openbsd"))] {
+            type setgroups_ngroups_t = c_int;
+        } else {
+            type setgroups_ngroups_t = size_t;
+        }
+    }
+    // FIXME: On the platforms we currently support, the `Gid` struct has the
+    // same representation in memory as a bare `gid_t`. This is not necessarily
+    // the case on all Rust platforms, though. See RFC 1785.
+    let res = unsafe {
+        libc::setgroups(groups.len() as setgroups_ngroups_t, groups.as_ptr() as *const gid_t)
+    };
+
+    Errno::result(res).map(|_| ())
+}
+
 /// Suspend the thread until a signal is received
 ///
 /// See also [pause(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html)

From 5ff0c35998e1bfd9907e2e9c73681acc99143431 Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 27 Aug 2017 20:07:56 +0200
Subject: [PATCH 03/13] unistd: Add getgroups()

---
 src/unistd.rs | 43 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/src/unistd.rs b/src/unistd.rs
index 28220e5d14..38f52e4ec2 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -1047,6 +1047,49 @@ pub fn setgid(gid: Gid) -> Result<()> {
     Errno::result(res).map(drop)
 }
 
+/// Get the list of supplementary group IDs of the calling process.
+///
+/// *Note:* On macOS, `getgroups()` behavior differs somewhat from other Unix
+/// platforms. It returns the current group access list for the user associated
+/// with the effective user id of the process; the group access list may change
+/// over the lifetime of the process, and it is not affected by calls to
+/// `setgroups()`.
+///
+/// [Further reading](http://pubs.opengroup.org/onlinepubs/009695399/functions/getgroups.html)
+pub fn getgroups() -> Result<Vec<Gid>> {
+    // First get the number of groups so we can size our Vec
+    use std::ptr;
+    let ret = unsafe { libc::getgroups(0, ptr::null_mut()) };
+    let mut size = Errno::result(ret)?;
+
+    // Now actually get the groups. We try multiple times in case the number of
+    // groups has changed since the first call to getgroups() and the buffer is
+    // now too small
+    let mut groups = Vec::<Gid>::with_capacity(size as usize);
+    loop {
+        // FIXME: On the platforms we currently support, the `Gid` struct has
+        // the same representation in memory as a bare `gid_t`. This is not
+        // necessarily the case on all Rust platforms, though. See RFC 1785.
+        let ret = unsafe { libc::getgroups(size, groups.as_mut_ptr() as *mut gid_t) };
+
+        match Errno::result(ret) {
+            Ok(s) => {
+                unsafe { groups.set_len(s as usize) };
+                return Ok(groups);
+            },
+            Err(Error::Sys(Errno::EINVAL)) => {
+                // EINVAL indicates that size was too small, so trigger a
+                // resize of the groups Vec and try again...
+                let cap = groups.capacity();
+                unsafe { groups.set_len(cap) };
+                groups.reserve(1);
+                size = groups.capacity() as c_int;
+            },
+            Err(e) => return Err(e)
+        }
+    }
+}
+
 /// Set the list of supplementary group IDs for the calling process.
 ///
 /// *Note:* On macOS, `getgroups()` may not return the same group list set by

From 89b705519e34396319fb2869a7102c511543387c Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 27 Aug 2017 20:08:24 +0200
Subject: [PATCH 04/13] test_unistd: Add test for getgroups/setgroups()

---
 test/test_unistd.rs | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/test/test_unistd.rs b/test/test_unistd.rs
index 627eb09b0e..365ec00d8c 100644
--- a/test/test_unistd.rs
+++ b/test/test_unistd.rs
@@ -122,6 +122,29 @@ mod linux_android {
     }
 }
 
+#[test]
+// `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
+#[cfg(not(any(target_os = "ios", target_os = "macos")))]
+fn test_setgroups() {
+    // Skip this test when not run as root as `setgroups()` requires root.
+    if !Uid::current().is_root() {
+        return
+    }
+
+    // Save the existing groups
+    let old_groups = getgroups().unwrap();
+
+    // Set some new made up groups
+    let groups = [Gid::from_raw(123), Gid::from_raw(456)];
+    setgroups(&groups).unwrap();
+
+    let new_groups = getgroups().unwrap();
+    assert_eq!(new_groups, groups);
+
+    // Revert back to the old groups
+    setgroups(&old_groups).unwrap();
+}
+
 macro_rules! execve_test_factory(
     ($test_name:ident, $syscall:ident, $exe: expr) => (
     #[test]

From 7263c5a185e3b3f4991719d417995711f0cb8745 Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 27 Aug 2017 20:09:18 +0200
Subject: [PATCH 05/13] unistd: Add initgroups()

---
 src/unistd.rs | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/src/unistd.rs b/src/unistd.rs
index 38f52e4ec2..ea4862c288 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -1135,6 +1135,41 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> {
     Errno::result(res).map(|_| ())
 }
 
+/// Initialize the supplementary group access list. Sets the supplementary
+/// group IDs for the calling process using all groups that `user` is a member
+/// of. The additional group `group` is also added to the list.
+///
+/// [Further reading](http://man7.org/linux/man-pages/man3/initgroups.3.html)
+///
+/// # Examples
+///
+/// `initgroups` can be used when dropping privileges from the root user to
+/// another user. For example, given the user `www-data`, we could look up the
+/// UID and GID for the user in the system's password database (usually found
+/// in `/etc/passwd`). If the `www-data` user's UID and GID were `33` and `33`,
+/// respectively, one could switch user as follows:
+/// ```
+/// let user = CString::new("www-data").unwrap();
+/// let uid = Uid::from_raw(33);
+/// let gid = Gid::from_raw(33);
+/// initgroups(&user, gid)?;
+/// setgid(gid)?;
+/// setuid(uid)?;
+/// ```
+pub fn initgroups(user: &CString, group: Gid) -> Result<()> {
+    cfg_if! {
+        if #[cfg(any(target_os = "ios", target_os = "macos"))] {
+            type initgroups_group_t = c_int;
+        } else {
+            type initgroups_group_t = gid_t;
+        }
+    }
+    let gid: gid_t = group.into();
+    let res = unsafe { libc::initgroups(user.as_ptr(), gid as initgroups_group_t) };
+
+    Errno::result(res).map(|_| ())
+}
+
 /// Suspend the thread until a signal is received
 ///
 /// See also [pause(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/pause.html)

From 4a2d65ea65b0394921eb81f051e1efc6b89f5c93 Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 27 Aug 2017 20:09:51 +0200
Subject: [PATCH 06/13] unistd: Add getgrouplist()

---
 src/unistd.rs | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/src/unistd.rs b/src/unistd.rs
index ea4862c288..21a398990f 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -1135,6 +1135,70 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> {
     Errno::result(res).map(|_| ())
 }
 
+/// Calculate the supplementary group access list. Gets the group IDs of all
+/// groups that `user` is a member of. The additional group `group` is also
+/// added to the list.
+///
+/// [Further reading](http://man7.org/linux/man-pages/man3/getgrouplist.3.html)
+///
+/// # Errors
+///
+/// Although the `getgrouplist()` call does not return any specific
+/// errors on any known platforms, this implementation will return a system
+/// error of `EINVAL` if the number of groups to be fetched exceeds the
+/// `NGROUPS_MAX` sysconf value. This mimics the behaviour of `getgroups()`
+/// and `setgroups()`. Additionally, while some implementations will return a
+/// partial list of groups when `NGROUPS_MAX` is exceeded, this implementation
+/// will only ever return the complete list or else an error.
+pub fn getgrouplist(user: &CString, group: Gid) -> Result<Vec<Gid>> {
+    let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) {
+        Ok(Some(n)) => n as c_int,
+        Ok(None) | Err(_) => <c_int>::max_value(),
+    };
+    use std::cmp::min;
+    let mut ngroups = min(ngroups_max, 8);
+    let mut groups = Vec::<Gid>::with_capacity(ngroups as usize);
+    cfg_if! {
+        if #[cfg(any(target_os = "ios", target_os = "macos"))] {
+            type getgrouplist_group_t = c_int;
+        } else {
+            type getgrouplist_group_t = gid_t;
+        }
+    }
+    let gid: gid_t = group.into();
+    loop {
+        let ret = unsafe {
+            libc::getgrouplist(user.as_ptr(),
+                               gid as getgrouplist_group_t,
+                               groups.as_mut_ptr() as *mut getgrouplist_group_t,
+                               &mut ngroups)
+        };
+        // BSD systems only return 0 or -1, Linux returns ngroups on success.
+        if ret >= 0 {
+            unsafe { groups.set_len(ngroups as usize) };
+            return Ok(groups);
+        } else if ret == -1 {
+            // Returns -1 if ngroups is too small, but does not set errno.
+            // BSD systems will still fill the groups buffer with as many
+            // groups as possible, but Linux manpages do not mention this
+            // behavior.
+
+            let cap = groups.capacity();
+            if cap >= ngroups_max as usize {
+                // We already have the largest capacity we can, give up
+                return Err(Error::invalid_argument());
+            }
+
+            // Reserve space for at least ngroups
+            groups.reserve(ngroups as usize);
+
+            // Even if the buffer gets resized to bigger than ngroups_max,
+            // don't ever ask for more than ngroups_max groups
+            ngroups = min(ngroups_max, groups.capacity() as c_int);
+        }
+    }
+}
+
 /// Initialize the supplementary group access list. Sets the supplementary
 /// group IDs for the calling process using all groups that `user` is a member
 /// of. The additional group `group` is also added to the list.

From 688667eff8fe2fd9c5d3d91e4bf53e99284d894e Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 27 Aug 2017 20:10:27 +0200
Subject: [PATCH 07/13] test_unistd: Add test for getgrouplist/initgroups()

---
 test/test_unistd.rs | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/test/test_unistd.rs b/test/test_unistd.rs
index 365ec00d8c..73e8b8ab31 100644
--- a/test/test_unistd.rs
+++ b/test/test_unistd.rs
@@ -145,6 +145,37 @@ fn test_setgroups() {
     setgroups(&old_groups).unwrap();
 }
 
+#[test]
+// `getgroups()` and `setgroups()` do not behave as expected on Apple platforms
+#[cfg(not(any(target_os = "ios", target_os = "macos")))]
+fn test_initgroups() {
+    // Skip this test when not run as root as `initgroups()` and `setgroups()`
+    // require root.
+    if !Uid::current().is_root() {
+        return
+    }
+
+    // Save the existing groups
+    let old_groups = getgroups().unwrap();
+
+    // It doesn't matter if the root user is not called "root" or if a user
+    // called "root" doesn't exist. We are just checking that the extra,
+    // made-up group, `123`, is set.
+    // FIXME: This only tests half of initgroups' functionality.
+    let user = CString::new("root").unwrap();
+    let group = Gid::from_raw(123);
+    let group_list = getgrouplist(&user, group).unwrap();
+    assert!(group_list.contains(&group));
+
+    initgroups(&user, group).unwrap();
+
+    let new_groups = getgroups().unwrap();
+    assert_eq!(new_groups, group_list);
+
+    // Revert back to the old groups
+    setgroups(&old_groups).unwrap();
+}
+
 macro_rules! execve_test_factory(
     ($test_name:ident, $syscall:ident, $exe: expr) => (
     #[test]

From e36d4afc34e0c97b99ac7f1cbb4a3aeaf14e0ab4 Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 27 Aug 2017 20:13:06 +0200
Subject: [PATCH 08/13] Add changelog entry for #733

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 351f7deca7..f09b4bed34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
   ([#771](https://github.com/nix-rust/nix/pull/771))
 - Added `nix::sys::uio::{process_vm_readv, process_vm_writev}` on Linux
   ([#568](https://github.com/nix-rust/nix/pull/568))
+- Added `nix::unistd::{getgroups, setgroups, getgrouplist, initgroups}`. ([#733](https://github.com/nix-rust/nix/pull/733))
 
 ### Changed
 - Renamed existing `ptrace` wrappers to encourage namespacing ([#692](https://github.com/nix-rust/nix/pull/692))

From a7ebd89b428930c492d7cd2905cc0741c108a4ed Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sat, 9 Sep 2017 21:18:44 +0200
Subject: [PATCH 09/13] groups tests: Add groups mutex and print message when
 tests skipped

Fix groups mutex name
---
 test/test.rs        |  3 +++
 test/test_unistd.rs | 14 ++++++++++++++
 2 files changed, 17 insertions(+)

diff --git a/test/test.rs b/test/test.rs
index 1b73c4ffa4..f4942d3dc1 100644
--- a/test/test.rs
+++ b/test/test.rs
@@ -43,6 +43,9 @@ lazy_static! {
     /// Any test that changes the process's current working directory must grab
     /// this mutex
     pub static ref CWD_MTX: Mutex<()> = Mutex::new(());
+    /// Any test that changes the process's supplementary groups must grab this
+    /// mutex
+    pub static ref GROUPS_MTX: Mutex<()> = Mutex::new(());
     /// Any test that creates child processes must grab this mutex, regardless
     /// of what it does with those children.
     pub static ref FORK_MTX: Mutex<()> = Mutex::new(());
diff --git a/test/test_unistd.rs b/test/test_unistd.rs
index 73e8b8ab31..2bb432d0d1 100644
--- a/test/test_unistd.rs
+++ b/test/test_unistd.rs
@@ -128,9 +128,16 @@ mod linux_android {
 fn test_setgroups() {
     // Skip this test when not run as root as `setgroups()` requires root.
     if !Uid::current().is_root() {
+        use std::io;
+        let stderr = io::stderr();
+        let mut handle = stderr.lock();
+        writeln!(handle, "test_setgroups requires root privileges. Skipping test.").unwrap();
         return
     }
 
+    #[allow(unused_variables)]
+    let m = ::GROUPS_MTX.lock().expect("Mutex got poisoned by another test");
+
     // Save the existing groups
     let old_groups = getgroups().unwrap();
 
@@ -152,9 +159,16 @@ fn test_initgroups() {
     // Skip this test when not run as root as `initgroups()` and `setgroups()`
     // require root.
     if !Uid::current().is_root() {
+        use std::io;
+        let stderr = io::stderr();
+        let mut handle = stderr.lock();
+        writeln!(handle, "test_initgroups requires root privileges. Skipping test.").unwrap();
         return
     }
 
+    #[allow(unused_variables)]
+    let m = ::GROUPS_MTX.lock().expect("Mutex got poisoned by another test");
+
     // Save the existing groups
     let old_groups = getgroups().unwrap();
 

From 57246cfe56559e067fa9ee507746c42e4a92a958 Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Thu, 21 Sep 2017 12:53:06 +0200
Subject: [PATCH 10/13] unistd: Make getgrouplist and initgroups take &CStr

Don't need to call as_c_str()
---
 src/unistd.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/unistd.rs b/src/unistd.rs
index 21a398990f..8feb70ee22 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -1150,7 +1150,7 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> {
 /// and `setgroups()`. Additionally, while some implementations will return a
 /// partial list of groups when `NGROUPS_MAX` is exceeded, this implementation
 /// will only ever return the complete list or else an error.
-pub fn getgrouplist(user: &CString, group: Gid) -> Result<Vec<Gid>> {
+pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> {
     let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) {
         Ok(Some(n)) => n as c_int,
         Ok(None) | Err(_) => <c_int>::max_value(),
@@ -1220,7 +1220,7 @@ pub fn getgrouplist(user: &CString, group: Gid) -> Result<Vec<Gid>> {
 /// setgid(gid)?;
 /// setuid(uid)?;
 /// ```
-pub fn initgroups(user: &CString, group: Gid) -> Result<()> {
+pub fn initgroups(user: &CStr, group: Gid) -> Result<()> {
     cfg_if! {
         if #[cfg(any(target_os = "ios", target_os = "macos"))] {
             type initgroups_group_t = c_int;

From a75f3fa04c12d5190b39fb9c03f67c301f61f8a8 Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sat, 30 Sep 2017 21:09:06 +0200
Subject: [PATCH 11/13] unistd: groups: Respond to minor PR feedback items

---
 src/unistd.rs       | 41 ++++++++++++++++++++++-------------------
 test/test_unistd.rs | 15 +++++++--------
 2 files changed, 29 insertions(+), 27 deletions(-)

diff --git a/src/unistd.rs b/src/unistd.rs
index 8feb70ee22..f596b75178 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -6,14 +6,13 @@ use fcntl::{fcntl, OFlag, O_CLOEXEC, FD_CLOEXEC};
 use fcntl::FcntlArg::F_SETFD;
 use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t,
            uid_t, gid_t, mode_t};
-use std::mem;
+use std::{self, fmt, mem};
 use std::ffi::{CString, CStr, OsString, OsStr};
 use std::os::unix::ffi::{OsStringExt, OsStrExt};
 use std::os::unix::io::RawFd;
 use std::path::{PathBuf};
 use void::Void;
 use sys::stat::Mode;
-use std::fmt;
 
 #[cfg(any(target_os = "android", target_os = "linux"))]
 pub use self::pivot_root::*;
@@ -1058,19 +1057,19 @@ pub fn setgid(gid: Gid) -> Result<()> {
 /// [Further reading](http://pubs.opengroup.org/onlinepubs/009695399/functions/getgroups.html)
 pub fn getgroups() -> Result<Vec<Gid>> {
     // First get the number of groups so we can size our Vec
-    use std::ptr;
-    let ret = unsafe { libc::getgroups(0, ptr::null_mut()) };
-    let mut size = Errno::result(ret)?;
+    let ret = unsafe { libc::getgroups(0, std::ptr::null_mut()) };
 
     // Now actually get the groups. We try multiple times in case the number of
     // groups has changed since the first call to getgroups() and the buffer is
-    // now too small
-    let mut groups = Vec::<Gid>::with_capacity(size as usize);
+    // now too small.
+    let mut groups = Vec::<Gid>::with_capacity(Errno::result(ret)? as usize);
     loop {
         // FIXME: On the platforms we currently support, the `Gid` struct has
         // the same representation in memory as a bare `gid_t`. This is not
         // necessarily the case on all Rust platforms, though. See RFC 1785.
-        let ret = unsafe { libc::getgroups(size, groups.as_mut_ptr() as *mut gid_t) };
+        let ret = unsafe {
+            libc::getgroups(groups.capacity() as c_int, groups.as_mut_ptr() as *mut gid_t)
+        };
 
         match Errno::result(ret) {
             Ok(s) => {
@@ -1078,12 +1077,12 @@ pub fn getgroups() -> Result<Vec<Gid>> {
                 return Ok(groups);
             },
             Err(Error::Sys(Errno::EINVAL)) => {
-                // EINVAL indicates that size was too small, so trigger a
-                // resize of the groups Vec and try again...
+                // EINVAL indicates that the buffer size was too small. Trigger
+                // the internal buffer resizing logic of `Vec` by requiring
+                // more space than the current capacity.
                 let cap = groups.capacity();
                 unsafe { groups.set_len(cap) };
                 groups.reserve(1);
-                size = groups.capacity() as c_int;
             },
             Err(e) => return Err(e)
         }
@@ -1103,7 +1102,7 @@ pub fn getgroups() -> Result<Vec<Gid>> {
 ///
 /// `setgroups` can be used when dropping privileges from the root user to a
 /// specific user and group. For example, given the user `www-data` with UID
-/// `33` and the group `backup` with the GID `34`, one could switch user as
+/// `33` and the group `backup` with the GID `34`, one could switch the user as
 /// follows:
 /// ```
 /// let uid = Uid::from_raw(33);
@@ -1135,9 +1134,10 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> {
     Errno::result(res).map(|_| ())
 }
 
-/// Calculate the supplementary group access list. Gets the group IDs of all
-/// groups that `user` is a member of. The additional group `group` is also
-/// added to the list.
+/// Calculate the supplementary group access list.
+///
+/// Gets the group IDs of all groups that `user` is a member of. The additional
+/// group `group` is also added to the list.
 ///
 /// [Further reading](http://man7.org/linux/man-pages/man3/getgrouplist.3.html)
 ///
@@ -1173,6 +1173,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> {
                                groups.as_mut_ptr() as *mut getgrouplist_group_t,
                                &mut ngroups)
         };
+
         // BSD systems only return 0 or -1, Linux returns ngroups on success.
         if ret >= 0 {
             unsafe { groups.set_len(ngroups as usize) };
@@ -1199,9 +1200,11 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> {
     }
 }
 
-/// Initialize the supplementary group access list. Sets the supplementary
-/// group IDs for the calling process using all groups that `user` is a member
-/// of. The additional group `group` is also added to the list.
+/// Initialize the supplementary group access list.
+///
+/// Sets the supplementary group IDs for the calling process using all groups
+/// that `user` is a member of. The additional group `group` is also added to
+/// the list.
 ///
 /// [Further reading](http://man7.org/linux/man-pages/man3/initgroups.3.html)
 ///
@@ -1211,7 +1214,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> {
 /// another user. For example, given the user `www-data`, we could look up the
 /// UID and GID for the user in the system's password database (usually found
 /// in `/etc/passwd`). If the `www-data` user's UID and GID were `33` and `33`,
-/// respectively, one could switch user as follows:
+/// respectively, one could switch the user as follows:
 /// ```
 /// let user = CString::new("www-data").unwrap();
 /// let uid = Uid::from_raw(33);
diff --git a/test/test_unistd.rs b/test/test_unistd.rs
index 2bb432d0d1..672cbb9c69 100644
--- a/test/test_unistd.rs
+++ b/test/test_unistd.rs
@@ -4,7 +4,7 @@ use nix::unistd::*;
 use nix::unistd::ForkResult::*;
 use nix::sys::wait::*;
 use nix::sys::stat;
-use std::{env, iter};
+use std::{self, env, iter};
 use std::ffi::CString;
 use std::fs::File;
 use std::io::Write;
@@ -128,11 +128,10 @@ mod linux_android {
 fn test_setgroups() {
     // Skip this test when not run as root as `setgroups()` requires root.
     if !Uid::current().is_root() {
-        use std::io;
-        let stderr = io::stderr();
+        let stderr = std::io::stderr();
         let mut handle = stderr.lock();
         writeln!(handle, "test_setgroups requires root privileges. Skipping test.").unwrap();
-        return
+        return;
     }
 
     #[allow(unused_variables)]
@@ -159,11 +158,10 @@ fn test_initgroups() {
     // Skip this test when not run as root as `initgroups()` and `setgroups()`
     // require root.
     if !Uid::current().is_root() {
-        use std::io;
-        let stderr = io::stderr();
+        let stderr = std::io::stderr();
         let mut handle = stderr.lock();
         writeln!(handle, "test_initgroups requires root privileges. Skipping test.").unwrap();
-        return
+        return;
     }
 
     #[allow(unused_variables)]
@@ -175,7 +173,8 @@ fn test_initgroups() {
     // It doesn't matter if the root user is not called "root" or if a user
     // called "root" doesn't exist. We are just checking that the extra,
     // made-up group, `123`, is set.
-    // FIXME: This only tests half of initgroups' functionality.
+    // FIXME: Test the other half of initgroups' functionality: whether the
+    // groups that the user belongs to are also set.
     let user = CString::new("root").unwrap();
     let group = Gid::from_raw(123);
     let group_list = getgrouplist(&user, group).unwrap();

From d4e7761923b71aac253675814ae6da723d74b59c Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 12 Nov 2017 11:48:26 +0200
Subject: [PATCH 12/13] unistd: groups: Disable functions on Apple platforms

---
 src/unistd.rs | 31 ++++++++++++++++++++-----------
 1 file changed, 20 insertions(+), 11 deletions(-)

diff --git a/src/unistd.rs b/src/unistd.rs
index f596b75178..2b2f3751b3 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -1048,13 +1048,12 @@ pub fn setgid(gid: Gid) -> Result<()> {
 
 /// Get the list of supplementary group IDs of the calling process.
 ///
-/// *Note:* On macOS, `getgroups()` behavior differs somewhat from other Unix
-/// platforms. It returns the current group access list for the user associated
-/// with the effective user id of the process; the group access list may change
-/// over the lifetime of the process, and it is not affected by calls to
-/// `setgroups()`.
-///
 /// [Further reading](http://pubs.opengroup.org/onlinepubs/009695399/functions/getgroups.html)
+///
+/// **Note:** This function is not available for Apple platforms. On those
+/// platforms, checking group membership should be achieved via communication
+/// with the `opendirectoryd` service.
+#[cfg(not(any(target_os = "ios", target_os = "macos")))]
 pub fn getgroups() -> Result<Vec<Gid>> {
     // First get the number of groups so we can size our Vec
     let ret = unsafe { libc::getgroups(0, std::ptr::null_mut()) };
@@ -1091,13 +1090,12 @@ pub fn getgroups() -> Result<Vec<Gid>> {
 
 /// Set the list of supplementary group IDs for the calling process.
 ///
-/// *Note:* On macOS, `getgroups()` may not return the same group list set by
-/// calling `setgroups()`. The use of `setgroups()` on macOS is 'highly
-/// discouraged' by Apple. Developers are referred to the `opendirectoryd`
-/// daemon and its set of APIs.
-///
 /// [Further reading](http://man7.org/linux/man-pages/man2/getgroups.2.html)
 ///
+/// **Note:** This function is not available for Apple platforms. On those
+/// platforms, group membership management should be achieved via communication
+/// with the `opendirectoryd` service.
+///
 /// # Examples
 ///
 /// `setgroups` can be used when dropping privileges from the root user to a
@@ -1111,6 +1109,7 @@ pub fn getgroups() -> Result<Vec<Gid>> {
 /// setgid(gid)?;
 /// setuid(uid)?;
 /// ```
+#[cfg(not(any(target_os = "ios", target_os = "macos")))]
 pub fn setgroups(groups: &[Gid]) -> Result<()> {
     cfg_if! {
         if #[cfg(any(target_os = "dragonfly",
@@ -1141,6 +1140,10 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> {
 ///
 /// [Further reading](http://man7.org/linux/man-pages/man3/getgrouplist.3.html)
 ///
+/// **Note:** This function is not available for Apple platforms. On those
+/// platforms, checking group membership should be achieved via communication
+/// with the `opendirectoryd` service.
+///
 /// # Errors
 ///
 /// Although the `getgrouplist()` call does not return any specific
@@ -1150,6 +1153,7 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> {
 /// and `setgroups()`. Additionally, while some implementations will return a
 /// partial list of groups when `NGROUPS_MAX` is exceeded, this implementation
 /// will only ever return the complete list or else an error.
+#[cfg(not(any(target_os = "ios", target_os = "macos")))]
 pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> {
     let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) {
         Ok(Some(n)) => n as c_int,
@@ -1208,6 +1212,10 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> {
 ///
 /// [Further reading](http://man7.org/linux/man-pages/man3/initgroups.3.html)
 ///
+/// **Note:** This function is not available for Apple platforms. On those
+/// platforms, group membership management should be achieved via communication
+/// with the `opendirectoryd` service.
+///
 /// # Examples
 ///
 /// `initgroups` can be used when dropping privileges from the root user to
@@ -1223,6 +1231,7 @@ pub fn getgrouplist(user: &CStr, group: Gid) -> Result<Vec<Gid>> {
 /// setgid(gid)?;
 /// setuid(uid)?;
 /// ```
+#[cfg(not(any(target_os = "ios", target_os = "macos")))]
 pub fn initgroups(user: &CStr, group: Gid) -> Result<()> {
     cfg_if! {
         if #[cfg(any(target_os = "ios", target_os = "macos"))] {

From 0c786dfe62dc43b7a463d00fd9c37475ee5a1ce3 Mon Sep 17 00:00:00 2001
From: Jamie Hewland <jhewland@gmail.com>
Date: Sun, 12 Nov 2017 12:10:41 +0200
Subject: [PATCH 13/13] unistd: Rework import of std::ptr

---
 src/unistd.rs | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/src/unistd.rs b/src/unistd.rs
index 2b2f3751b3..dc0d5d077c 100644
--- a/src/unistd.rs
+++ b/src/unistd.rs
@@ -6,7 +6,7 @@ use fcntl::{fcntl, OFlag, O_CLOEXEC, FD_CLOEXEC};
 use fcntl::FcntlArg::F_SETFD;
 use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t,
            uid_t, gid_t, mode_t};
-use std::{self, fmt, mem};
+use std::{fmt, mem, ptr};
 use std::ffi::{CString, CStr, OsString, OsStr};
 use std::os::unix::ffi::{OsStringExt, OsStrExt};
 use std::os::unix::io::RawFd;
@@ -553,9 +553,6 @@ pub fn chown<P: ?Sized + NixPath>(path: &P, owner: Option<Uid>, group: Option<Gi
 }
 
 fn to_exec_array(args: &[CString]) -> Vec<*const c_char> {
-    use std::ptr;
-    use libc::c_char;
-
     let mut args_p: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect();
     args_p.push(ptr::null());
     args_p
@@ -1056,7 +1053,7 @@ pub fn setgid(gid: Gid) -> Result<()> {
 #[cfg(not(any(target_os = "ios", target_os = "macos")))]
 pub fn getgroups() -> Result<Vec<Gid>> {
     // First get the number of groups so we can size our Vec
-    let ret = unsafe { libc::getgroups(0, std::ptr::null_mut()) };
+    let ret = unsafe { libc::getgroups(0, ptr::null_mut()) };
 
     // Now actually get the groups. We try multiple times in case the number of
     // groups has changed since the first call to getgroups() and the buffer is