use std::ffi::{c_char, CStr, OsStr, OsString};
use std::fmt;
use std::io;
use std::mem;
use std::ops;
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::ptr;
use crate::os::unix::GroupidExt;
use crate::private;
use crate::Error;
pub trait UseridExt: private::Sealed {
fn as_raw_uid(&self) -> libc::uid_t;
fn from_raw_uid(uid: &libc::uid_t) -> &Self;
fn lookup_passwd(&self) -> Result<Passwd, Error>;
}
#[derive(PartialEq, Eq)]
pub(crate) struct Userid {
raw_uid: libc::uid_t,
}
impl Userid {
#[inline]
pub fn try_clone(&self) -> Result<UseridBuf, io::Error> {
Ok(UseridBuf {
raw_uid: self.raw_uid,
})
}
pub fn name(&self) -> Result<OsString, Error> {
get_name_by_uid(self.raw_uid)
}
}
impl fmt::Display for Userid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.raw_uid)
}
}
impl fmt::Debug for Userid {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.raw_uid)
}
}
impl PartialEq<UseridBuf> for Userid {
fn eq(&self, other: &UseridBuf) -> bool {
self.raw_uid == other.raw_uid
}
}
impl private::Sealed for Userid {}
impl UseridExt for Userid {
fn as_raw_uid(&self) -> libc::uid_t {
self.raw_uid
}
fn from_raw_uid(uid: &libc::uid_t) -> &Self {
unsafe { &*(uid as *const libc::uid_t as *const Self) }
}
fn lookup_passwd(&self) -> Result<Passwd, Error> {
get_pw_by_uid(self.raw_uid)
}
}
#[derive(PartialEq, Eq)]
pub(crate) struct UseridBuf {
raw_uid: libc::uid_t,
}
impl fmt::Display for UseridBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.raw_uid)
}
}
impl fmt::Debug for UseridBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.raw_uid)
}
}
impl PartialEq<Userid> for UseridBuf {
fn eq(&self, other: &Userid) -> bool {
self.raw_uid == other.raw_uid
}
}
impl ops::Deref for UseridBuf {
type Target = Userid;
fn deref(&self) -> &Self::Target {
Userid::from_raw_uid(&self.raw_uid)
}
}
pub struct Passwd {
raw_pwd: libc::passwd,
buf: Vec<c_char>,
}
impl Passwd {
pub fn name(&self) -> &OsStr {
let pw_name = unsafe { CStr::from_ptr(self.raw_pwd.pw_name) };
OsStr::from_bytes(pw_name.to_bytes())
}
#[inline]
pub fn uid(&self) -> &crate::Userid {
crate::Userid::from_raw_uid(&self.raw_pwd.pw_uid)
}
#[inline]
pub fn gid(&self) -> &crate::Groupid {
crate::Groupid::from_raw_gid(&self.raw_pwd.pw_gid)
}
pub fn dir(&self) -> &OsStr {
let pw_dir = unsafe { CStr::from_ptr(self.raw_pwd.pw_dir) };
OsStr::from_bytes(pw_dir.to_bytes())
}
pub fn shell(&self) -> &OsStr {
let pw_shell = unsafe { CStr::from_ptr(self.raw_pwd.pw_shell) };
OsStr::from_bytes(pw_shell.to_bytes())
}
#[inline]
pub fn as_raw_passwd(&self) -> &libc::passwd {
&self.raw_pwd
}
}
impl fmt::Debug for Passwd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Passwd")
.field("pw_name", &self.name())
.field("pw_uid", &self.uid())
.field("pw_dir", &self.dir())
.field("pw_shell", &self.shell())
.finish_non_exhaustive()
}
}
pub fn get_name_by_uid(uid: libc::uid_t) -> Result<OsString, Error> {
let pwd = get_pw_by_uid(uid)?;
let pw_name = unsafe { CStr::from_ptr(pwd.raw_pwd.pw_name) };
let vec = pw_name.to_bytes().to_vec();
Ok(OsString::from_vec(vec))
}
pub fn get_pw_by_uid(uid: libc::uid_t) -> Result<Passwd, Error> {
let mut buflen = unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) };
if buflen == -1 {
buflen = 1024;
}
let mut passwd = Passwd {
raw_pwd: unsafe { mem::zeroed() },
buf: vec![0; buflen as usize],
};
let mut result: *mut libc::passwd = ptr::null_mut();
unsafe {
let return_code = libc::getpwuid_r(
uid,
&mut passwd.raw_pwd,
passwd.buf.as_mut_ptr(),
buflen as usize,
&mut result,
);
if return_code == 0 {
if result == &mut passwd.raw_pwd {
Ok(passwd)
} else {
Err(Error::NoRecord)
}
} else {
Err(Error::last_os_error())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::process::Command;
#[test]
fn test_userid_username_ok() {
let uid = unsafe { libc::getuid() };
let userid = Userid::from_raw_uid(&uid);
if let Ok(username) = userid.name() {
let id_un_stdout = Command::new("id").arg("-un").output().unwrap().stdout;
assert_eq!(
username.as_bytes(),
&id_un_stdout[0..id_un_stdout.len() - 1]
);
} else {
panic!()
}
}
#[test]
fn test_userid_username_norecord() {
let uid = libc::uid_t::MAX - 3;
let userid = Userid::from_raw_uid(&uid);
let result = userid.name();
assert!(matches!(result, Err(Error::NoRecord)));
}
#[test]
fn test_get_pwd_by_uid_ok() {
if let Ok(pwd) = get_pw_by_uid(unsafe { libc::getuid() }) {
let id_un_stdout = Command::new("id").arg("-un").output().unwrap().stdout;
assert_eq!(
pwd.name().as_bytes(),
&id_un_stdout[0..id_un_stdout.len() - 1]
);
assert_eq!(pwd.uid().as_raw_uid(), unsafe { libc::getuid() });
assert_eq!(pwd.gid().as_raw_gid(), unsafe { libc::getgid() });
} else {
panic!();
}
}
#[test]
fn test_get_pw_by_uid_norecord() {
let result = get_pw_by_uid(libc::uid_t::MAX - 3);
assert!(matches!(result, Err(Error::NoRecord)));
}
}