//! Forked from . //! //! Get user information stored in the password file `/etc/passwd`. //! //! This crate provides a safe wrapper for libc functions such as [`getpwnam_r(3)`] and [`getpwuid_r(3)`]. //! //! # Usage //! //! Add this to your `Cargo.toml`: //! //! ```toml //! [dependencies] //! etc-passwd = "0.2.0" //! ``` //! //! # Examples //! //! Get a current user information: //! //! ``` //! use passwd::Passwd; //! //! # fn main() -> Result<(), Box> { //! if let Some(passwd) = Passwd::current_user()? { //! println!("current user name is: {}", passwd.name.to_str()?); //! println!("your user id is: {}", passwd.uid); //! println!("your group id is: {}", passwd.gid); //! println!("your full name is: {}", passwd.gecos.to_str()?); //! println!("your home directory is: {}", passwd.dir.to_str()?); //! println!("your login shell is: {}", passwd.shell.to_str()?); //! } else { //! println!("oops! current user is not found... who are you?"); //! } //! # Ok(()) //! # } //! ``` //! //! [`getpwnam_r(3)`]: https://man7.org/linux/man-pages/man3/getpwnam_r.3.html //! [`getpwuid_r(3)`]: https://man7.org/linux/man-pages/man3/getpwuid_r.3.html use std::ffi::CStr; use std::io::{Error, Result}; use std::mem::MaybeUninit; use nix::unistd::{Gid, Uid}; /// Representation of a user information stored in the password file `/etc/passwd`. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Passwd<'a> { /// A username. pub name: &'a CStr, /// A user password. pub passwd: &'a CStr, /// A user ID. pub uid: Uid, /// A group ID. pub gid: Gid, /// A user full name or a comment. pub gecos: &'a CStr, /// A home directory. pub dir: &'a CStr, /// A shell program. pub shell: &'a CStr, } impl<'a> Passwd<'a> { /// Looks up the username in the password file and returns a `Passwd` with user information, if the user is found. pub fn from_name(name: impl AsRef, buf: &'a mut Vec) -> Result> { let name = name.as_ref(); getpw_r(name.as_ptr(), buf, libc::getpwnam_r) } /// Looks up the user ID and returns a `Passwd` with user information, if the user is found. pub fn from_uid(uid: Uid, buf: &'a mut Vec) -> Result> { getpw_r(uid.as_raw(), buf, libc::getpwuid_r) } /// Looks up current user's information in the password file and return a `Passwd` with user information, if the user is found. /// /// This is a shortcut for `Passwd::from_uid(libc::getuid())`. pub fn current_user(buf: &'a mut Vec) -> Result> { Self::from_uid(nix::unistd::getuid(), buf) } unsafe fn from_c_struct(passwd: libc::passwd) -> Self { let libc::passwd { pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell, .. } = passwd; Self { name: CStr::from_ptr(pw_name), passwd: CStr::from_ptr(pw_passwd), uid: Uid::from_raw(pw_uid), gid: Gid::from_raw(pw_gid), gecos: CStr::from_ptr(pw_gecos), dir: CStr::from_ptr(pw_dir), shell: CStr::from_ptr(pw_shell), } } } fn getpw_r<'a, T>( key: T, buf: &'a mut Vec, 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>> where T: Copy, { let mut passwd = MaybeUninit::uninit(); let amt = unsafe { libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) }; let mut amt = libc::c_long::max(amt, 512) as usize; buf.clear(); buf.reserve(amt); loop { buf.reserve(amt); let mut result = std::ptr::null_mut(); let code = unsafe { f( key, passwd.as_mut_ptr(), buf.as_mut_ptr() as _, buf.capacity(), &mut result, ) }; return if !result.is_null() { Ok(Some(unsafe { Passwd::from_c_struct(passwd.assume_init()) })) } else if code == 0 { Ok(None) } else { let e = Error::last_os_error(); let errno = e.raw_os_error().unwrap(); match errno { // A signal was caught libc::EINTR => continue, // Insufficient buffer space libc::ERANGE => { amt *= 2; continue; } // The given name or uid was not found. // see https://man7.org/linux/man-pages/man3/getpwnam_r.3.html 0 | libc::ENOENT | libc::ESRCH | libc::EBADF | libc::EPERM => Ok(None), // Other errors _ => Err(e), } }; } } #[cfg(test)] mod test { use std::ffi::CString; use super::*; type Result = std::result::Result>; #[test] fn root() -> Result<()> { let mut buf_a = Vec::new(); let mut buf_b = Vec::new(); let by_name = Passwd::from_name(CStr::from_bytes_with_nul(b"root\0")?, &mut buf_a)?.unwrap(); let by_uid = Passwd::from_uid(0.into(), &mut buf_b)?.unwrap(); assert_eq!(by_name.uid, Uid::from_raw(0)); assert_eq!(by_name.gid, Gid::from_raw(0)); assert_eq!(by_name.name.to_str()?, "root"); assert_eq!(by_name.dir.to_str()?, "/root"); assert_eq!(by_uid, by_name); Ok(()) } #[test] fn current_user() -> Result<()> { let uid = unsafe { nix::unistd::getuid() }; let mut buf_a = Vec::new(); let mut buf_b = Vec::new(); let by_cu = Passwd::current_user(&mut buf_a)?.unwrap(); let by_name = Passwd::from_name(&by_cu.name, &mut buf_b)?.unwrap(); assert_eq!(by_cu.uid, uid); // Assume $HOME is not modified assert_eq!(by_cu.dir.to_str()?, std::env::var("HOME")?); assert_eq!(by_cu, by_name); Ok(()) } #[test] fn user_not_exist() -> Result<()> { let mut buf_a = Vec::new(); let mut buf_b = Vec::new(); assert!(Passwd::from_uid(u32::MAX.into(), &mut buf_a)?.is_none()); assert!(Passwd::from_name(CString::new("")?, &mut buf_b)?.is_none()); Ok(()) } }