#![doc(html_root_url = "https://docs.rs/etc-passwd/0.2.2")]
#![warn(missing_docs)]
#![warn(unused)]
#![warn(unused_crate_dependencies)]
use std::{
ffi::{CStr, CString},
io::{Error, Result},
mem, ptr,
};
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Passwd {
pub name: CString,
pub passwd: CString,
pub uid: libc::uid_t,
pub gid: libc::gid_t,
pub gecos: CString,
pub dir: CString,
pub shell: CString,
}
impl Passwd {
pub fn from_name(name: impl AsRef<CStr>) -> Result<Option<Self>> {
let name = name.as_ref();
getpw_r(name.as_ptr(), libc::getpwnam_r)
}
pub fn from_uid(uid: libc::uid_t) -> Result<Option<Self>> {
getpw_r(uid, libc::getpwuid_r)
}
pub fn current_user() -> Result<Option<Self>> {
Self::from_uid(unsafe { libc::getuid() })
}
unsafe fn from_c_struct(passwd: &libc::passwd) -> Self {
Self {
name: CStr::from_ptr(passwd.pw_name).to_owned(),
passwd: CStr::from_ptr(passwd.pw_passwd).to_owned(),
uid: passwd.pw_uid,
gid: passwd.pw_gid,
gecos: CStr::from_ptr(passwd.pw_gecos).to_owned(),
dir: CStr::from_ptr(passwd.pw_dir).to_owned(),
shell: CStr::from_ptr(passwd.pw_shell).to_owned(),
}
}
}
fn getpw_r<T>(
key: T,
f: unsafe extern "C" fn(
key: T,
pwd: *mut libc::passwd,
buf: *mut libc::c_char,
buflen: libc::size_t,
result: *mut *mut libc::passwd,
) -> libc::c_int,
) -> Result<Option<Passwd>>
where
T: Copy,
{
let mut passwd = unsafe { mem::zeroed() };
let amt = unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) };
let mut amt = libc::c_long::max(amt, 512) as usize;
let mut buf = vec![0; amt];
loop {
let mut result = ptr::null_mut();
unsafe {
f(key, &mut passwd, buf.as_mut_ptr(), buf.len(), &mut result);
}
if !result.is_null() {
return Ok(Some(unsafe { Passwd::from_c_struct(&passwd) }));
}
let e = Error::last_os_error();
let errno = e.raw_os_error().unwrap();
match errno {
libc::EINTR => continue,
libc::ERANGE => {
amt *= 2;
buf.resize(amt, 0);
continue;
}
0 | libc::ENOENT | libc::ESRCH | libc::EBADF | libc::EPERM => return Ok(None),
_ => return Err(e),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::{fs::File, io::prelude::*, path::Path};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[test]
fn root() -> Result<()> {
let by_name = Passwd::from_name(CString::new("root")?)?.unwrap();
let by_uid = Passwd::from_uid(0)?.unwrap();
assert_eq!(by_name.uid, 0);
assert_eq!(by_name.gid, 0);
assert_eq!(by_name.name.to_str()?, "root");
#[cfg(not(target_os = "macos"))]
assert_eq!(by_name.dir.to_str()?, "/root");
#[cfg(target_os = "macos")]
assert_eq!(by_name.dir.to_str()?, "/var/root");
assert_eq!(by_uid, by_name);
Ok(())
}
#[test]
fn current_user() -> Result<()> {
let uid = unsafe { libc::getuid() };
let by_cu = Passwd::current_user()?.unwrap();
let by_name = Passwd::from_name(&by_cu.name)?.unwrap();
assert_eq!(by_cu.uid, uid);
assert_eq!(by_cu.dir.to_str()?, std::env::var("HOME")?);
assert_eq!(by_cu, by_name);
Ok(())
}
#[test]
fn user_not_exist() -> Result<()> {
assert!(Passwd::from_uid(u32::MAX)?.is_none());
assert!(Passwd::from_name(CString::new("")?)?.is_none());
Ok(())
}
#[test]
fn test_readme_deps() {
version_sync::assert_markdown_deps_updated!("README.md");
}
#[test]
fn test_html_root_url() {
version_sync::assert_html_root_url_updated!("src/lib.rs");
}
#[test]
fn test_readme_up_to_date() -> Result<()> {
let mut source = File::open("src/lib.rs")?;
let mut template = File::open("README.tpl")?;
let mut expected = cargo_readme::generate_readme(
Path::new("."),
&mut source,
Some(&mut template),
true,
true,
true,
true,
)?;
expected.push('\n');
let mut readme = String::new();
let mut file = File::open("README.md")?;
file.read_to_string(&mut readme)?;
for (l, r) in readme.lines().zip(expected.lines()) {
assert_eq!(l, r);
}
assert_eq!(readme, expected);
Ok(())
}
}