use std::ffi::{CStr, CString};
use std::fmt;
use std::ptr::read;
use std::sync::Arc;
use libc::{uid_t, gid_t};
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
use libc::{c_char, time_t};
#[cfg(target_os = "linux")]
use libc::c_char;
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
#[repr(C)]
pub struct c_passwd {
pw_name: *const c_char, pw_passwd: *const c_char, pw_uid: uid_t, pw_gid: gid_t, pw_change: time_t, pw_class: *const c_char,
pw_gecos: *const c_char,
pw_dir: *const c_char, pw_shell: *const c_char, pw_expire: time_t, }
#[cfg(target_os = "linux")]
#[repr(C)]
pub struct c_passwd {
pw_name: *const c_char, pw_passwd: *const c_char, pw_uid: uid_t, pw_gid: gid_t, pw_gecos: *const c_char,
pw_dir: *const c_char, pw_shell: *const c_char, }
#[repr(C)]
pub struct c_group {
gr_name: *const c_char, gr_passwd: *const c_char, gr_gid: gid_t, gr_mem: *const *const c_char, }
extern {
fn getpwuid(uid: uid_t) -> *const c_passwd;
fn getpwnam(user_name: *const c_char) -> *const c_passwd;
fn getgrgid(gid: gid_t) -> *const c_group;
fn getgrnam(group_name: *const c_char) -> *const c_group;
fn getuid() -> uid_t;
fn geteuid() -> uid_t;
fn getgid() -> gid_t;
fn getegid() -> gid_t;
fn setpwent();
fn getpwent() -> *const c_passwd;
fn endpwent();
}
#[derive(Clone)]
pub struct User {
uid: uid_t,
primary_group: gid_t,
extras: os::UserExtras,
pub name_arc: Arc<String>,
}
impl User {
pub fn new(uid: uid_t, name: &str, primary_group: gid_t) -> User {
User {
uid: uid,
name_arc: Arc::new(name.to_owned()),
primary_group: primary_group,
extras: os::UserExtras::default(),
}
}
pub fn uid(&self) -> uid_t {
self.uid.clone()
}
pub fn name(&self) -> &str {
&**self.name_arc
}
pub fn primary_group_id(&self) -> gid_t {
self.primary_group.clone()
}
}
impl fmt::Debug for User {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
f.debug_struct("User")
.field("uid", &self.uid)
.field("name_arc", &self.name_arc)
.field("primary_group", &self.primary_group)
.field("extras", &self.extras)
.finish()
}
else {
write!(f, "User({}, {})", self.uid(), self.name())
}
}
}
#[derive(Clone)]
pub struct Group {
gid: gid_t,
extras: os::GroupExtras,
pub name_arc: Arc<String>,
}
impl Group {
pub fn new(gid: gid_t, name: &str) -> Self {
Group {
gid: gid,
name_arc: Arc::new(String::from(name)),
extras: os::GroupExtras::default(),
}
}
pub fn gid(&self) -> gid_t {
self.gid.clone()
}
pub fn name(&self) -> &str {
&**self.name_arc
}
}
impl fmt::Debug for Group {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if f.alternate() {
f.debug_struct("Group")
.field("gid", &self.gid)
.field("name_arc", &self.name_arc)
.field("extras", &self.extras)
.finish()
}
else {
write!(f, "Group({}, {})", self.gid(), self.name())
}
}
}
unsafe fn from_raw_buf(p: *const c_char) -> String {
CStr::from_ptr(p).to_string_lossy().into_owned()
}
unsafe fn ptr_as_ref<T>(pointer: *const T) -> Option<T> {
if pointer.is_null() {
None
}
else {
Some(read(pointer))
}
}
unsafe fn passwd_to_user(pointer: *const c_passwd) -> Option<User> {
if let Some(passwd) = ptr_as_ref(pointer) {
let name = Arc::new(from_raw_buf(passwd.pw_name));
Some(User {
uid: passwd.pw_uid,
name_arc: name,
primary_group: passwd.pw_gid,
extras: os::UserExtras::from_passwd(passwd),
})
}
else {
None
}
}
unsafe fn struct_to_group(pointer: *const c_group) -> Option<Group> {
if let Some(group) = ptr_as_ref(pointer) {
let name = Arc::new(from_raw_buf(group.gr_name));
Some(Group {
gid: group.gr_gid,
name_arc: name,
extras: os::GroupExtras::from_struct(group),
})
}
else {
None
}
}
unsafe fn members(groups: *const *const c_char) -> Vec<String> {
let mut members = Vec::new();
for i in 0.. {
let username = groups.offset(i);
if username.is_null() || (*username).is_null() {
break;
}
else {
members.push(from_raw_buf(*username));
}
}
members
}
pub fn get_user_by_uid(uid: uid_t) -> Option<User> {
unsafe {
let passwd = getpwuid(uid);
passwd_to_user(passwd)
}
}
pub fn get_user_by_name(username: &str) -> Option<User> {
if let Ok(username) = CString::new(username) {
unsafe {
let passwd = getpwnam(username.as_ptr());
passwd_to_user(passwd)
}
}
else {
None
}
}
pub fn get_group_by_gid(gid: gid_t) -> Option<Group> {
unsafe {
let group = getgrgid(gid);
struct_to_group(group)
}
}
pub fn get_group_by_name(group_name: &str) -> Option<Group> {
if let Ok(group_name) = CString::new(group_name) {
unsafe {
let group = getgrnam(group_name.as_ptr());
struct_to_group(group)
}
}
else {
None
}
}
pub fn get_current_uid() -> uid_t {
unsafe { getuid() }
}
pub fn get_current_username() -> Option<String> {
let uid = get_current_uid();
get_user_by_uid(uid).map(|u| Arc::try_unwrap(u.name_arc).unwrap())
}
pub fn get_effective_uid() -> uid_t {
unsafe { geteuid() }
}
pub fn get_effective_username() -> Option<String> {
let uid = get_effective_uid();
get_user_by_uid(uid).map(|u| Arc::try_unwrap(u.name_arc).unwrap())
}
pub fn get_current_gid() -> gid_t {
unsafe { getgid() }
}
pub fn get_current_groupname() -> Option<String> {
let gid = get_current_gid();
get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name_arc).unwrap())
}
pub fn get_effective_gid() -> gid_t {
unsafe { getegid() }
}
pub fn get_effective_groupname() -> Option<String> {
let gid = get_effective_gid();
get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name_arc).unwrap())
}
pub struct AllUsers(());
impl AllUsers {
pub unsafe fn new() -> AllUsers {
setpwent();
AllUsers(())
}
}
impl Drop for AllUsers {
fn drop(&mut self) {
unsafe { endpwent() };
}
}
impl Iterator for AllUsers {
type Item = User;
fn next(&mut self) -> Option<User> {
unsafe { passwd_to_user(getpwent()) }
}
}
pub mod os {
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
pub mod unix {
use std::path::Path;
use super::super::{c_passwd, c_group, members, from_raw_buf, Group};
pub trait UserExt {
fn home_dir(&self) -> &Path;
fn with_home_dir(self, home_dir: &str) -> Self;
fn shell(&self) -> &Path;
fn with_shell(self, shell: &str) -> Self;
}
pub trait GroupExt {
fn members(&self) -> &[String];
fn add_member(self, name: &str) -> Self;
}
#[derive(Clone, Debug)]
pub struct UserExtras {
pub home_dir: String,
pub shell: String,
}
impl Default for UserExtras {
fn default() -> UserExtras {
UserExtras {
home_dir: String::from("/var/empty"),
shell: String::from("/bin/false"),
}
}
}
impl UserExtras {
pub unsafe fn from_passwd(passwd: c_passwd) -> UserExtras {
let home_dir = from_raw_buf(passwd.pw_dir);
let shell = from_raw_buf(passwd.pw_shell);
UserExtras {
home_dir: home_dir,
shell: shell,
}
}
}
#[cfg(any(target_os = "linux"))]
use super::super::User;
#[cfg(any(target_os = "linux"))]
impl UserExt for User {
fn home_dir(&self) -> &Path {
Path::new(&self.extras.home_dir)
}
fn with_home_dir(mut self, home_dir: &str) -> User {
self.extras.home_dir = home_dir.to_owned();
self
}
fn shell(&self) -> &Path {
Path::new(&self.extras.shell)
}
fn with_shell(mut self, shell: &str) -> User {
self.extras.shell = shell.to_owned();
self
}
}
#[derive(Clone, Default, Debug)]
pub struct GroupExtras {
pub members: Vec<String>,
}
impl GroupExtras {
pub unsafe fn from_struct(group: c_group) -> GroupExtras {
let members = members(group.gr_mem);
GroupExtras {
members: members,
}
}
}
impl GroupExt for Group {
fn members(&self) -> &[String] {
&*self.extras.members
}
fn add_member(mut self, member: &str) -> Group {
self.extras.members.push(member.to_owned());
self
}
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
pub mod bsd {
use std::path::Path;
use libc::time_t;
use super::super::{c_passwd, User};
#[derive(Clone, Debug)]
pub struct UserExtras {
pub extras: super::unix::UserExtras,
pub change: time_t,
pub expire: time_t,
}
impl UserExtras {
pub unsafe fn from_passwd(passwd: c_passwd) -> UserExtras {
UserExtras {
change: passwd.pw_change,
expire: passwd.pw_expire,
extras: super::unix::UserExtras::from_passwd(passwd),
}
}
}
impl super::unix::UserExt for User {
fn home_dir(&self) -> &Path {
Path::new(&self.extras.extras.home_dir)
}
fn with_home_dir(mut self, home_dir: &str) -> User {
self.extras.extras.home_dir = home_dir.to_owned();
self
}
fn shell(&self) -> &Path {
Path::new(&self.extras.extras.shell)
}
fn with_shell(mut self, shell: &str) -> User {
self.extras.extras.shell = shell.to_owned();
self
}
}
pub trait UserExt {
fn password_change_time(&self) -> time_t;
fn password_expire_time(&self) -> time_t;
}
impl UserExt for User {
fn password_change_time(&self) -> time_t {
self.extras.change.clone()
}
fn password_expire_time(&self) -> time_t {
self.extras.expire.clone()
}
}
impl Default for UserExtras {
fn default() -> UserExtras {
UserExtras {
extras: super::unix::UserExtras::default(),
change: 0,
expire: 0,
}
}
}
}
#[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
pub type UserExtras = bsd::UserExtras;
#[cfg(any(target_os = "linux"))]
pub type UserExtras = unix::UserExtras;
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd"))]
pub type GroupExtras = unix::GroupExtras;
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn uid() {
get_current_uid();
}
#[test]
fn username() {
let uid = get_current_uid();
assert_eq!(&*get_current_username().unwrap(), &*get_user_by_uid(uid).unwrap().name());
}
#[test]
fn uid_for_username() {
let uid = get_current_uid();
let user = get_user_by_uid(uid).unwrap();
assert_eq!(user.uid, uid);
}
#[test]
fn username_for_uid_for_username() {
let uid = get_current_uid();
let user = get_user_by_uid(uid).unwrap();
let user2 = get_user_by_uid(user.uid).unwrap();
assert_eq!(user2.uid, uid);
}
#[test]
fn user_info() {
use base::os::unix::UserExt;
let uid = get_current_uid();
let user = get_user_by_uid(uid).unwrap();
println!("HOME={:?}, SHELL={:?}", user.home_dir(), user.shell());
}
#[test]
fn user_by_name() {
let name = get_current_username().unwrap();
let user_by_name = get_user_by_name(&name);
assert!(user_by_name.is_some());
assert_eq!(user_by_name.unwrap().name(), &*name);
let user = get_user_by_name("user\0");
assert!(user.is_none());
}
#[test]
fn group_by_name() {
let cur_uid = get_current_uid();
let cur_user = get_user_by_uid(cur_uid).unwrap();
let cur_group = get_group_by_gid(cur_user.primary_group).unwrap();
let group_by_name = get_group_by_name(&cur_group.name());
assert!(group_by_name.is_some());
assert_eq!(group_by_name.unwrap().name(), cur_group.name());
let group = get_group_by_name("users\0");
assert!(group.is_none());
}
}