diff --git a/src/unistd.rs b/src/unistd.rs index 3edbfb0e78..197762cdf8 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -1010,6 +1010,64 @@ pub fn setgroups(groups: &[Gid]) -> Result<()> { Errno::result(res).map(drop) } +/// 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) +pub fn getgrouplist(user: &CString, group: Gid) -> Result> { + let ngroups_max = match sysconf(SysconfVar::NGROUPS_MAX) { + Ok(Some(n)) => n as c_int, + Ok(None) | Err(_) => ::max_value(), + }; + use std::cmp::min; + let mut ngroups = min(ngroups_max, 8); + let mut groups = Vec::::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) }; + break + } + + // 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. + if ret == -1 { + let cap = groups.capacity(); + if cap >= ngroups_max as usize { + // We already have the largest capacity we can, give up + // FIXME: What error should be returned? + return Err(Error::invalid_argument()) + } + + // Trigger buffer resizing + unsafe { groups.set_len(cap) }; + groups.reserve(ngroups as usize - cap); + + // 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); + } + } + + Ok(groups) +} + /// 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. diff --git a/test/test_unistd.rs b/test/test_unistd.rs index 45840b3d32..a9072580b6 100644 --- a/test/test_unistd.rs +++ b/test/test_unistd.rs @@ -143,10 +143,13 @@ fn test_initgroups() { // 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!(new_groups.contains(&group)); + assert_eq!(new_groups, group_list); // Revert back to the old groups setgroups(&old_groups).unwrap();