use std::collections::HashSet;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::ffi::{CStr, CString};
use std::path::PathBuf;
use crate::os::paths::constants;
use crate::os::unistd::{Pid, getuid};
use super::{Uid, Gid, SubUidRange, SubGidRange, COMMON_ADMIN_GROUPS};
pub fn username_by_uid(uid: Uid) -> Option<String> {
let passwd = unsafe { libc::getpwuid(uid.0) };
if passwd.is_null() {
return None;
}
unsafe {
let name = CStr::from_ptr((*passwd).pw_name);
Some(name.to_string_lossy().into_owned())
}
}
pub fn uid_by_username(username: &str) -> Option<Uid> {
let username = CString::new(username).ok()?;
let passwd = unsafe { libc::getpwnam(username.as_ptr()) };
if passwd.is_null() {
return None;
}
unsafe { Some(Uid((*passwd).pw_uid)) }
}
pub fn groupname_by_gid(gid: Gid) -> Option<String> {
let group = unsafe { libc::getgrgid(gid.0) };
if group.is_null() {
return None;
}
unsafe {
let name = CStr::from_ptr((*group).gr_name);
Some(name.to_string_lossy().into_owned())
}
}
pub fn gid_by_groupname(groupname: &str) -> Option<Gid> {
let groupname = CString::new(groupname).ok()?;
let group = unsafe { libc::getgrnam(groupname.as_ptr()) };
if group.is_null() {
return None;
}
unsafe { Some(Gid((*group).gr_gid)) }
}
pub fn primary_gid_by_uid(uid: Uid) -> Option<Gid> {
let passwd = unsafe { libc::getpwuid(uid.0) };
if passwd.is_null() {
return None;
}
unsafe { Some(Gid((*passwd).pw_gid)) }
}
pub fn subuid_by_username(username: &str) -> Option<SubUidRange> {
let username = CString::new(username).ok()?;
let file = File::open(constants::PATH_SYSTEM_SUBUID_FILE).ok()?;
let reader = BufReader::new(file);
for line in reader.lines().map_while(Result::ok) {
let parts: Vec<&str> = line.split(constants::SUBID_SEPARATOR).collect();
if parts.len() != 3 {
continue;
}
if parts[0] == username.to_str().unwrap() {
if let (Ok(start), Ok(count)) = (parts[1].parse::<u32>(), parts[2].parse::<u32>()) {
return Some(SubUidRange { start: Uid::from_raw(start), count });
}
}
}
None
}
pub fn subuid_by_username_raw(username: &str) -> Option<(u32, u32)> {
subuid_by_username(username).and_then(|range| range.as_raw())
}
pub fn subgid_by_groupname(groupname: &str) -> Option<SubGidRange> {
let groupname = CString::new(groupname).ok()?;
let file = File::open(constants::PATH_SYSTEM_SUBGID_FILE).ok()?;
let reader = BufReader::new(file);
for line in reader.lines().map_while(Result::ok) {
let parts: Vec<&str> = line.split(constants::SUBID_SEPARATOR).collect();
if parts.len() != 3 {
continue;
}
if parts[0] == groupname.to_str().unwrap() {
if let (Ok(start), Ok(count)) = (parts[1].parse::<u32>(), parts[2].parse::<u32>()) {
return Some(SubGidRange { start: Gid::from_raw(start), count });
}
}
}
None
}
pub fn subgid_by_groupname_raw(groupname: &str) -> Option<(u32, u32)> {
subgid_by_groupname(groupname).and_then(|range| range.as_raw())
}
pub fn groups_for_uid(uid: Uid) -> HashSet<String> {
let mut groups = HashSet::new();
if let Some(primary_gid) = primary_gid_by_uid(uid)
&& let Some(group_name) = groupname_by_gid(primary_gid)
{
groups.insert(group_name);
}
let Some(username) = username_by_uid(uid) else {
return groups;
};
let Ok(file) = File::open(constants::PATH_SYSTEM_GROUPS_FILE) else {
return groups;
};
let reader = BufReader::new(file);
for line in reader.lines().map_while(Result::ok) {
let parts: Vec<&str> = line.split(':').collect();
if parts.len() < 4 {
continue;
}
let group_name = parts[0];
let members = parts[3];
if members.split(',').any(|member| member.trim() == username) {
groups.insert(group_name.to_string());
}
}
groups
}
pub fn user_in_group(uid: Uid, group: &str) -> bool {
groups_for_uid(uid).contains(group)
}
pub fn user_in_admin_group(uid: Uid) -> bool {
COMMON_ADMIN_GROUPS
.iter()
.any(|group| user_in_group(uid, group))
}
pub fn invoking_username() -> Option<String> {
let uid = super::invoking_uid();
username_by_uid(uid)
}
pub fn has_common_admin_group() -> bool {
let Ok(file) = File::open(constants::PATH_SYSTEM_SUDOERS_FILE) else {
return false;
};
let reader = BufReader::new(file);
for line in reader.lines().map_while(Result::ok) {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
for group in COMMON_ADMIN_GROUPS {
let token = format!("%{group}");
if line.split_whitespace().any(|part| part == token) {
return true;
}
}
}
false
}
pub fn gethostname() -> io::Result<String> {
let mut buffer = vec![0u8; 256];
loop {
let result =
unsafe { libc::gethostname(buffer.as_mut_ptr() as *mut libc::c_char, buffer.len()) };
if result != 0 {
return Err(io::Error::last_os_error());
}
if let Some(nul_pos) = buffer.iter().position(|&byte| byte == 0) {
buffer.truncate(nul_pos);
return Ok(String::from_utf8_lossy(&buffer).into_owned());
}
if buffer.len() >= 4096 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"hostname was not null-terminated",
));
}
buffer.resize(buffer.len() * 2, 0);
}
}
pub fn getcwd() -> io::Result<PathBuf> {
std::env::current_dir()
}
pub fn chdir(path: impl AsRef<std::path::Path>) -> io::Result<()> {
std::env::set_current_dir(path)
}
pub fn pid_is_alive(pid: Pid) -> bool {
let result = unsafe { libc::kill(pid.0, 0) };
if result == 0 {
return true;
}
(unsafe { libc::__errno_location().read() }) == libc::EPERM
}
pub fn home_dir() -> Option<PathBuf> {
if let Some(home) = std::env::var_os(constants::USER_HOME_ENV) {
return Some(PathBuf::from(home));
}
let uid = getuid();
let passwd = unsafe { libc::getpwuid(uid.0) };
if passwd.is_null() {
return None;
}
unsafe {
let dir = CStr::from_ptr((*passwd).pw_dir);
Some(PathBuf::from(dir.to_string_lossy().into_owned()))
}
}