summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/unistd.rs64
1 files changed, 64 insertions, 0 deletions
diff --git a/src/unistd.rs b/src/unistd.rs
index ea4862c2..21a39899 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.