diff --git a/column.rs b/column.rs index 46f56dd..fea06c4 100644 --- a/column.rs +++ b/column.rs @@ -3,7 +3,7 @@ pub enum Column { FileName, FileSize(bool), Blocks, - User(u64), + User, Group, HardLinks, Inode, @@ -33,7 +33,7 @@ impl Column { FileName => "Name", FileSize(_) => "Size", Blocks => "Blocks", - User(_) => "User", + User => "User", Group => "Group", HardLinks => "Links", Inode => "inode", diff --git a/file.rs b/file.rs index f591292..a98b7fe 100644 --- a/file.rs +++ b/file.rs @@ -115,12 +115,16 @@ impl<'a> File<'a> { // Display the ID if the user/group doesn't exist, which // usually means it was deleted but its files weren't. - User(uid) => { - let style = if uid == self.stat.unstable.uid { Yellow.bold() } else { Plain }; - let string = unix.get_user_name(self.stat.unstable.uid as i32).unwrap_or(self.stat.unstable.uid.to_str()); - return style.paint(string.as_slice()); + User => { + let style = if unix.uid == self.stat.unstable.uid as u32 { Yellow.bold() } else { Plain }; + let string = unix.get_user_name(self.stat.unstable.uid as u32).unwrap_or(self.stat.unstable.uid.to_str()); + style.paint(string.as_slice()) + }, + Group => { + let name = unix.get_group_name(self.stat.unstable.gid as u32).unwrap_or(self.stat.unstable.gid.to_str()); + let style = if unix.is_group_member(self.stat.unstable.gid as u32) { Yellow.normal() } else { Plain }; + style.paint(name.as_slice()) }, - Group => unix.get_group_name(self.stat.unstable.gid as u32).unwrap_or(self.stat.unstable.gid.to_str()), } } diff --git a/options.rs b/options.rs index 4de8f28..e34b6f6 100644 --- a/options.rs +++ b/options.rs @@ -3,7 +3,6 @@ extern crate getopts; use file::File; use std::cmp::lexical_ordering; use column::{Column, Permissions, FileName, FileSize, User, Group, HardLinks, Inode, Blocks}; -use unix::get_current_user_id; use std::ascii::StrAsciiExt; pub enum SortField { @@ -76,7 +75,7 @@ impl Options { columns.push(Blocks); } - columns.push(User(get_current_user_id())); + columns.push(User); if matches.opt_present("group") { columns.push(Group); diff --git a/unix.rs b/unix.rs index 14c110c..785adbd 100644 --- a/unix.rs +++ b/unix.rs @@ -5,10 +5,11 @@ use std::collections::hashmap::HashMap; mod c { #![allow(non_camel_case_types)] extern crate libc; - use self::libc::{ + pub use self::libc::{ c_char, c_int, uid_t, + gid_t, time_t }; @@ -26,7 +27,10 @@ mod c { } pub struct c_group { - pub gr_name: *c_char // group name + pub gr_name: *c_char, // group name + pub gr_passwd: *c_char, // password + pub gr_gid: gid_t, // group id + pub gr_mem: **c_char, // names of users in the group } extern { @@ -36,21 +40,46 @@ mod c { } } pub struct Unix { - user_names: HashMap>, - group_names: HashMap>, + user_names: HashMap>, // mapping of user IDs to user names + group_names: HashMap>, // mapping of groups IDs to group names + groups: HashMap, // mapping of group IDs to whether the current user is a member + pub uid: u32, // current user's ID + pub username: String, // current user's name } impl Unix { pub fn empty_cache() -> Unix { + let uid = unsafe { c::getuid() }; + let info = unsafe { c::getpwuid(uid as i32).to_option().unwrap() }; // the user has to have a name + + let username = unsafe { from_c_str(info.pw_name) }; + + let mut user_names = HashMap::new(); + user_names.insert(uid as u32, Some(username.clone())); + + // Unix groups work like this: every group has a list of + // users, referred to by their names. But, every user also has + // a primary group, which isn't in this list. So handle this + // case immediately after we look up the user's details. + let mut groups = HashMap::new(); + groups.insert(info.pw_gid as u32, true); + Unix { - user_names: HashMap::new(), + user_names: user_names, group_names: HashMap::new(), + uid: uid as u32, + username: username, + groups: groups, } } - pub fn get_user_name<'a> (&'a mut self, uid: i32) -> Option { + pub fn is_group_member(&self, gid: u32) -> bool { + *self.groups.get(&gid) + } + + pub fn get_user_name(&mut self, uid: u32) -> Option { self.user_names.find_or_insert_with(uid, |&u| { - let pw = unsafe { c::getpwuid(u) }; + let pw = unsafe { c::getpwuid(u as i32) }; if pw.is_not_null() { return unsafe { Some(from_c_str(read(pw).pw_name)) }; } @@ -60,19 +89,56 @@ impl Unix { }).clone() } - pub fn get_group_name<'a>(&'a mut self, gid: u32) -> Option { - self.group_names.find_or_insert_with(gid, |&gid| { - let gr = unsafe { c::getgrgid(gid) }; - if gr.is_not_null() { - return unsafe { Some(from_c_str(read(gr).gr_name)) }; + fn group_membership(group: **i8, uname: &String) -> bool { + let mut i = 0; + + // The list of members is a pointer to a pointer of + // characters, terminated by a null pointer. So the first call + // to `to_option` will always succeed, as that memory is + // guaranteed to be there (unless we go past the end of RAM). + // The second call will return None if it's a null pointer. + + loop { + match unsafe { group.offset(i).to_option().unwrap().to_option() } { + Some(username) => { + if unsafe { from_c_str(username) } == *uname { + return true; + } + } + None => { + return false; + } } - else { - return None; + i += 1; + } + } + + pub fn get_group_name(&mut self, gid: u32) -> Option { + match self.group_names.find_copy(&gid) { + Some(name) => name, + None => { + match unsafe { c::getgrgid(gid).to_option() } { + None => { + self.group_names.insert(gid, None); + return None; + }, + Some(r) => { + let group_name = unsafe { Some(from_c_str(r.gr_name)) }; + self.group_names.insert(gid, group_name.clone()); + + // Calculate whether we are a member of the + // group. Now's as good a time as any as we've + // just retrieved the group details. + + if !self.groups.contains_key(&gid) { + self.groups.insert(gid, Unix::group_membership(r.gr_mem, &self.username)); + } + + return group_name; + } + } } - }).clone() + } } } -pub fn get_current_user_id() -> u64 { - unsafe { c::getuid() as u64 } -}