Przeglądaj źródła

Highlight groups the user is a member of

First non-trivial change for a while... because this involves reading from the OS, we should cache as much as we can in memory. So, group membership checking is done immediately after reading a group name, as the group structure has already been populated.
Ben S 11 lat temu
rodzic
commit
b337f9174d
4 zmienionych plików z 96 dodań i 27 usunięć
  1. 2 2
      column.rs
  2. 9 5
      file.rs
  3. 1 2
      options.rs
  4. 84 18
      unix.rs

+ 2 - 2
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",

+ 9 - 5
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()),
         }
     }
 

+ 1 - 2
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);

+ 84 - 18
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<i32, Option<String>>,
-    group_names: HashMap<u32, Option<String>>,
+    user_names:    HashMap<u32, Option<String>>,  // mapping of user IDs to user names
+    group_names:   HashMap<u32, Option<String>>,  // mapping of groups IDs to group names
+    groups:        HashMap<u32, bool>,            // 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<String> {
+    pub fn is_group_member(&self, gid: u32) -> bool {
+        *self.groups.get(&gid)
+    }
+
+    pub fn get_user_name(&mut self, uid: u32) -> Option<String> {
         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<String> {
-        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<String> {
+        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 }
-}