#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
use libc::time_t;
use libc::{c_char, c_int, gid_t, uid_t};
use libc::{getgrgid, getgrnam, getgroups};
use libc::{getpwnam, getpwuid, group, passwd};
use std::ffi::{CStr, CString};
use std::io::Error as IOError;
use std::io::ErrorKind;
use std::io::Result as IOResult;
use std::ptr;
use std::sync::{LazyLock, Mutex};
unsafe extern "C" {
fn getgrouplist(
name: *const c_char,
gid: gid_t,
groups: *mut gid_t,
ngroups: *mut c_int,
) -> c_int;
}
pub fn get_groups() -> IOResult<Vec<gid_t>> {
let mut groups = Vec::new();
loop {
let ngroups = match unsafe { getgroups(0, ptr::null_mut()) } {
-1 => return Err(IOError::last_os_error()),
0 => return Ok(Vec::new()),
n => n,
};
groups.resize(ngroups.try_into().unwrap(), 0);
let res = unsafe { getgroups(ngroups, groups.as_mut_ptr()) };
if res == -1 {
let err = IOError::last_os_error();
if err.raw_os_error() == Some(libc::EINVAL) {
} else {
return Err(err);
}
} else {
groups.truncate(res.try_into().unwrap());
return Ok(groups);
}
}
}
#[cfg(all(unix, not(target_os = "redox"), feature = "process"))]
pub fn get_groups_gnu(arg_id: Option<u32>) -> IOResult<Vec<gid_t>> {
let groups = get_groups()?;
let egid = arg_id.unwrap_or_else(crate::features::process::getegid);
Ok(sort_groups(groups, egid))
}
#[cfg(all(unix, not(target_os = "redox"), feature = "process"))]
fn sort_groups(mut groups: Vec<gid_t>, egid: gid_t) -> Vec<gid_t> {
if let Some(index) = groups.iter().position(|&x| x == egid) {
groups[..=index].rotate_right(1);
} else {
groups.insert(0, egid);
}
groups
}
#[derive(Clone, Debug)]
pub struct Passwd {
pub name: String,
pub uid: uid_t,
pub gid: gid_t,
pub user_info: Option<String>,
pub user_shell: Option<String>,
pub user_dir: Option<String>,
pub user_passwd: Option<String>,
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
pub user_access_class: Option<String>,
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
pub passwd_change_time: time_t,
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
pub expiration: time_t,
}
fn cstr2string(ptr: *const c_char) -> Option<String> {
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() })
}
}
impl Passwd {
unsafe fn from_raw(raw: passwd) -> Self {
Self {
name: cstr2string(raw.pw_name).expect("passwd without name"),
uid: raw.pw_uid,
gid: raw.pw_gid,
#[cfg(not(all(
target_os = "android",
any(target_arch = "x86", target_arch = "arm")
)))]
user_info: cstr2string(raw.pw_gecos),
#[cfg(all(target_os = "android", any(target_arch = "x86", target_arch = "arm")))]
user_info: None,
user_shell: cstr2string(raw.pw_shell),
user_dir: cstr2string(raw.pw_dir),
user_passwd: cstr2string(raw.pw_passwd),
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
user_access_class: cstr2string(raw.pw_class),
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
passwd_change_time: raw.pw_change,
#[cfg(any(target_os = "freebsd", target_vendor = "apple"))]
expiration: raw.pw_expire,
}
}
pub fn belongs_to(&self) -> Vec<gid_t> {
let mut ngroups: c_int = 8;
let mut ngroups_old: c_int;
let mut groups = vec![0; ngroups.try_into().unwrap()];
let name = CString::new(self.name.as_bytes()).unwrap();
loop {
ngroups_old = ngroups;
if unsafe {
getgrouplist(
name.as_ptr(),
self.gid,
groups.as_mut_ptr(),
&raw mut ngroups,
)
} == -1
{
if ngroups == ngroups_old {
ngroups *= 2;
}
groups.resize(ngroups.try_into().unwrap(), 0);
} else {
break;
}
}
let ngroups = ngroups.try_into().unwrap();
assert!(ngroups <= groups.len());
groups.truncate(ngroups);
groups
}
}
#[derive(Clone, Debug)]
pub struct Group {
pub name: String,
pub gid: gid_t,
}
impl Group {
unsafe fn from_raw(raw: group) -> Self {
Self {
name: cstr2string(raw.gr_name).expect("group without name"),
gid: raw.gr_gid,
}
}
}
pub trait Locate<K> {
fn locate(key: K) -> IOResult<Self>
where
Self: ::std::marker::Sized;
}
static PW_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
macro_rules! f {
($fnam:ident, $fid:ident, $t:ident, $st:ident) => {
impl Locate<$t> for $st {
fn locate(k: $t) -> IOResult<Self> {
let _guard = PW_LOCK.lock();
unsafe {
let data = $fid(k);
if !data.is_null() {
Ok($st::from_raw(ptr::read(data as *const _)))
} else {
Err(IOError::new(
ErrorKind::NotFound,
format!("No such id: {k}"),
))
}
}
}
}
impl<'a> Locate<&'a str> for $st {
fn locate(k: &'a str) -> IOResult<Self> {
let _guard = PW_LOCK.lock();
unsafe {
let cstring = CString::new(k)?;
let data = $fnam(cstring.as_ptr());
if !data.is_null() {
return Ok($st::from_raw(ptr::read(data as *const _)));
}
if let Ok(id) = k.parse::<$t>() {
let data = $fid(id);
if !data.is_null() {
Ok($st::from_raw(ptr::read(data as *const _)))
} else {
Err(IOError::new(
ErrorKind::NotFound,
format!("No such id: {id}"),
))
}
} else {
Err(IOError::new(ErrorKind::NotFound, format!("Not found: {k}")))
}
}
}
}
};
}
f!(getpwnam, getpwuid, uid_t, Passwd);
f!(getgrnam, getgrgid, gid_t, Group);
#[inline]
pub fn uid2usr(id: uid_t) -> IOResult<String> {
Passwd::locate(id).map(|p| p.name)
}
#[inline]
pub fn gid2grp(id: gid_t) -> IOResult<String> {
Group::locate(id).map(|p| p.name)
}
#[inline]
pub fn usr2uid(name: &str) -> IOResult<uid_t> {
Passwd::locate(name).map(|p| p.uid)
}
#[inline]
pub fn grp2gid(name: &str) -> IOResult<gid_t> {
Group::locate(name).map(|p| p.gid)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_sort_groups() {
assert_eq!(sort_groups(vec![1, 2, 3], 4), vec![4, 1, 2, 3]);
assert_eq!(sort_groups(vec![1, 2, 3], 3), vec![3, 1, 2]);
assert_eq!(sort_groups(vec![1, 2, 3], 2), vec![2, 1, 3]);
assert_eq!(sort_groups(vec![1, 2, 3], 1), vec![1, 2, 3]);
assert_eq!(sort_groups(vec![1, 2, 3], 0), vec![0, 1, 2, 3]);
}
#[test]
fn test_entries_get_groups_gnu() {
if let Ok(mut groups) = get_groups() {
if let Some(last) = groups.pop() {
groups.insert(0, last);
assert_eq!(get_groups_gnu(Some(last)).unwrap(), groups);
}
}
}
}