diff --git a/examples/example.rs b/examples/example.rs index 97741b8..204122a 100644 --- a/examples/example.rs +++ b/examples/example.rs @@ -2,14 +2,14 @@ extern crate users; use users::{Users, OSUsers}; fn main() { - let mut cache = OSUsers::empty_cache(); - + let cache = OSUsers::empty_cache(); + let current_uid = cache.get_current_uid(); println!("Your UID is {}", current_uid); - + let you = cache.get_user_by_uid(current_uid).expect("No entry for current user!"); println!("Your username is {}", you.name); - + let primary_group = cache.get_group_by_gid(you.primary_group).expect("No entry for your primary group!"); println!("Your primary group has ID {} and name {}", primary_group.gid, primary_group.name); diff --git a/examples/threading.rs b/examples/threading.rs new file mode 100644 index 0000000..24ac1a3 --- /dev/null +++ b/examples/threading.rs @@ -0,0 +1,61 @@ +//! This example demonstrates how to use an `OSUsers` cache in a +//! multi-threaded situation. The cache uses `RefCell`s internally, so it +//! is distinctly not thread-safe. Instead, you’ll need to place it within +//! some kind of lock in order to have threads access it one-at-a-time. +//! +//! It queries all the users it can find in the range 500..510. This is the +//! default uid range on my Apple laptop -- Linux starts counting from 1000, +//! but I can’t include both in the range! It spawns one thread per user to +//! query, with each thread accessing the same cache. +//! +//! Then, afterwards, it retrieves references to the users that had been +//! cached earlier. + +// For extra fun, try uncommenting some of the lines of code below, making +// the code try to access the users cache *without* a Mutex, and see it +// spew compile errors at you. + +extern crate users; +use users::{Users, OSUsers, uid_t}; + +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use std::thread; + +const LO: uid_t = 500; +const HI: uid_t = 510; + +fn main() { + + // For thread-safely, our users cache needs to be within a Mutex, so + // only one thread can access it once. This Mutex needs to be within an + // Arc, so multiple threads can access the Mutex. + let cache = Arc::new(Mutex::new(OSUsers::empty_cache())); + // let cache = OSUsers::empty_cache(); + + // Loop over the range and query all the users in the range. Although we + // could use the `&User` values returned, we just ignore them. + for uid in LO .. HI { + let cache = cache.clone(); + + thread::spawn(move || { + let cache = cache.lock().unwrap(); // Unlock the mutex + let _ = cache.get_user_by_uid(uid); // Query our users cache! + }); + } + + // Wait for all the threads to finish. + thread::sleep(Duration::from_millis(100)); + + // Loop over the same range and print out all the users we find. + // These users will be retrieved from the cache. + for uid in LO .. HI { + let cache = cache.lock().unwrap(); // Re-unlock the mutex + if let Some(u) = cache.get_user_by_uid(uid) { // Re-query our cache! + println!("User #{} is {}", u.uid, u.name) + } + else { + println!("User #{} does not exist", uid); + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index d38ff9d..e7a558b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ //! let mut cache = OSUsers::empty_cache(); //! let group = cache.get_group_by_name("admin").expect("No such group 'admin'!"); //! println!("The '{}' group has the ID {}", group.name, group.gid); -//! for member in group.members.into_iter() { +//! for member in &group.members { //! println!("{} is a member of the group", member); //! } //! ``` @@ -112,6 +112,7 @@ use std::ffi::{CStr, CString}; use std::io::{Error as IOError, Result as IOResult}; use std::ptr::read; use std::str::from_utf8_unchecked; +use std::sync::Arc; extern crate libc; pub use libc::{uid_t, gid_t, c_int}; @@ -130,40 +131,40 @@ pub use os::OSUsers; pub trait Users { /// Return a User object if one exists for the given user ID; otherwise, return None. - fn get_user_by_uid(&mut self, uid: uid_t) -> Option; + fn get_user_by_uid(&self, uid: uid_t) -> Option>; /// Return a User object if one exists for the given username; otherwise, return None. - fn get_user_by_name(&mut self, username: &str) -> Option; + fn get_user_by_name(&self, username: &str) -> Option>; /// Return a Group object if one exists for the given group ID; otherwise, return None. - fn get_group_by_gid(&mut self, gid: gid_t) -> Option; + fn get_group_by_gid(&self, gid: gid_t) -> Option>; /// Return a Group object if one exists for the given groupname; otherwise, return None. - fn get_group_by_name(&mut self, group_name: &str) -> Option; + fn get_group_by_name(&self, group_name: &str) -> Option>; /// Return the user ID for the user running the process. - fn get_current_uid(&mut self) -> uid_t; + fn get_current_uid(&self) -> uid_t; /// Return the username of the user running the process. - fn get_current_username(&mut self) -> Option; + fn get_current_username(&self) -> Option>; /// Return the group ID for the user running the process. - fn get_current_gid(&mut self) -> gid_t; + fn get_current_gid(&self) -> gid_t; /// Return the group name of the user running the process. - fn get_current_groupname(&mut self) -> Option; + fn get_current_groupname(&self) -> Option>; /// Return the effective user id. - fn get_effective_uid(&mut self) -> uid_t; + fn get_effective_uid(&self) -> uid_t; /// Return the effective group id. - fn get_effective_gid(&mut self) -> gid_t; + fn get_effective_gid(&self) -> gid_t; /// Return the effective username. - fn get_effective_username(&mut self) -> Option; + fn get_effective_username(&self) -> Option>; /// Return the effective group name. - fn get_effective_groupname(&mut self) -> Option; + fn get_effective_groupname(&self) -> Option>; } #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly"))] @@ -232,7 +233,7 @@ pub struct User { pub uid: uid_t, /// This user's name - pub name: String, + pub name: Arc, /// The ID of this user's primary group pub primary_group: gid_t, @@ -252,7 +253,7 @@ pub struct Group { pub gid: uid_t, /// This group's name - pub name: String, + pub name: Arc, /// Vector of the names of the users who belong to this group as a non-primary member pub members: Vec, @@ -267,7 +268,7 @@ unsafe fn passwd_to_user(pointer: *const c_passwd) -> Option { let pw = read(pointer); Some(User { uid: pw.pw_uid as uid_t, - name: from_raw_buf(pw.pw_name as *const i8), + name: Arc::new(from_raw_buf(pw.pw_name as *const i8)), primary_group: pw.pw_gid as gid_t, home_dir: from_raw_buf(pw.pw_dir as *const i8), shell: from_raw_buf(pw.pw_shell as *const i8) @@ -283,7 +284,7 @@ unsafe fn struct_to_group(pointer: *const c_group) -> Option { let gr = read(pointer); let name = from_raw_buf(gr.gr_name as *const i8); let members = members(gr.gr_mem); - Some(Group { gid: gr.gr_gid, name: name, members: members }) + Some(Group { gid: gr.gr_gid, name: Arc::new(name), members: members }) } else { None @@ -355,7 +356,7 @@ pub fn get_current_uid() -> uid_t { /// Return the username of the user running the process. pub fn get_current_username() -> Option { let uid = get_current_uid(); - get_user_by_uid(uid).map(|u| u.name) + get_user_by_uid(uid).map(|u| Arc::try_unwrap(u.name).unwrap()) } /// Return the user ID for the effective user running the process. @@ -366,7 +367,7 @@ pub fn get_effective_uid() -> uid_t { /// Return the username of the effective user running the process. pub fn get_effective_username() -> Option { let uid = get_effective_uid(); - get_user_by_uid(uid).map(|u| u.name) + get_user_by_uid(uid).map(|u| Arc::try_unwrap(u.name).unwrap()) } /// Return the group ID for the user running the process. @@ -377,7 +378,7 @@ pub fn get_current_gid() -> gid_t { /// Return the groupname of the user running the process. pub fn get_current_groupname() -> Option { let gid = get_current_gid(); - get_group_by_gid(gid).map(|g| g.name) + get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name).unwrap()) } /// Return the group ID for the effective user running the process. @@ -388,7 +389,7 @@ pub fn get_effective_gid() -> gid_t { /// Return the groupname of the effective user running the process. pub fn get_effective_groupname() -> Option { let gid = get_effective_gid(); - get_group_by_gid(gid).map(|g| g.name) + get_group_by_gid(gid).map(|g| Arc::try_unwrap(g.name).unwrap()) } /// Set current user for the running process, requires root priviledges. @@ -497,7 +498,7 @@ mod test { #[test] fn username() { let uid = get_current_uid(); - assert_eq!(get_current_username().unwrap(), get_user_by_uid(uid).unwrap().name); + assert_eq!(&*get_current_username().unwrap(), &*get_user_by_uid(uid).unwrap().name); } #[test] @@ -531,7 +532,7 @@ mod test { 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); + assert_eq!(&**user_by_name.unwrap().name, &*name); // User names containing '\0' cannot be used (for now) let user = get_user_by_name("user\0"); diff --git a/src/mock.rs b/src/mock.rs index 9fbb5e6..a5c613f 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -18,9 +18,11 @@ //! //! ```rust //! use users::mock::{MockUsers, User, Group}; +//! use std::sync::Arc; +//! //! let mut users = MockUsers::with_current_uid(1000); -//! users.add_user(User { uid: 1000, name: "Bobbins".to_string(), primary_group: 100, home_dir: "/home/bobbins".to_string(), shell: "/bin/bash".to_string() }); -//! users.add_group(Group { gid: 100, name: "funkyppl".to_string(), members: vec![ "other_person".to_string() ] }); +//! users.add_user(User { uid: 1000, name: Arc::new("Bobbins".to_string()), primary_group: 100, home_dir: "/home/bobbins".to_string(), shell: "/bin/bash".to_string() }); +//! users.add_group(Group { gid: 100, name: Arc::new("funkyppl".to_string()), members: vec![ "other_person".to_string() ] }); //! ``` //! //! The exports get re-exported into the mock module, for simpler `use` lines. @@ -37,13 +39,14 @@ //! ```rust //! use users::{Users, OSUsers, User}; //! use users::mock::MockUsers; +//! use std::sync::Arc; //! //! fn print_current_username(users: &mut U) { //! println!("Current user: {:?}", users.get_current_username()); //! } //! //! let mut users = MockUsers::with_current_uid(1001); -//! users.add_user(User { uid: 1001, name: "fred".to_string(), primary_group: 101 , home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string()}); +//! users.add_user(User { uid: 1001, name: Arc::new("fred".to_string()), primary_group: 101 , home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string()}); //! print_current_username(&mut users); //! //! let mut actual_users = OSUsers::empty_cache(); @@ -52,12 +55,13 @@ pub use super::{Users, User, Group}; use std::collections::HashMap; +use std::sync::Arc; use libc::{uid_t, gid_t}; /// A mocking users object that you can add your own users and groups to. pub struct MockUsers { - users: HashMap, - groups: HashMap, + users: HashMap>, + groups: HashMap>, uid: uid_t, } @@ -73,62 +77,62 @@ impl MockUsers { } /// Add a user to the users table. - pub fn add_user(&mut self, user: User) -> Option { - self.users.insert(user.uid, user) + pub fn add_user(&mut self, user: User) -> Option> { + self.users.insert(user.uid, Arc::new(user)) } /// Add a group to the groups table. - pub fn add_group(&mut self, group: Group) -> Option { - self.groups.insert(group.gid, group) + pub fn add_group(&mut self, group: Group) -> Option> { + self.groups.insert(group.gid, Arc::new(group)) } } impl Users for MockUsers { - fn get_user_by_uid(&mut self, uid: uid_t) -> Option { + fn get_user_by_uid(&self, uid: uid_t) -> Option> { self.users.get(&uid).cloned() } - fn get_user_by_name(&mut self, username: &str) -> Option { - self.users.values().find(|u| u.name == username).cloned() + fn get_user_by_name(&self, username: &str) -> Option> { + self.users.values().find(|u| &*u.name == username).cloned() } - fn get_group_by_gid(&mut self, gid: gid_t) -> Option { + fn get_group_by_gid(&self, gid: gid_t) -> Option> { self.groups.get(&gid).cloned() } - fn get_group_by_name(&mut self, group_name: &str) -> Option { - self.groups.values().find(|g| g.name == group_name).cloned() + fn get_group_by_name(&self, group_name: &str) -> Option> { + self.groups.values().find(|g| &*g.name == group_name).cloned() } - fn get_current_uid(&mut self) -> uid_t { + fn get_current_uid(&self) -> uid_t { self.uid } - fn get_current_username(&mut self) -> Option { + fn get_current_username(&self) -> Option> { self.users.get(&self.uid).map(|u| u.name.clone()) } - fn get_current_gid(&mut self) -> uid_t { + fn get_current_gid(&self) -> uid_t { self.uid } - fn get_current_groupname(&mut self) -> Option { + fn get_current_groupname(&self) -> Option> { self.groups.get(&self.uid).map(|u| u.name.clone()) } - fn get_effective_uid(&mut self) -> uid_t { + fn get_effective_uid(&self) -> uid_t { self.uid } - fn get_effective_username(&mut self) -> Option { + fn get_effective_username(&self) -> Option> { self.users.get(&self.uid).map(|u| u.name.clone()) } - fn get_effective_gid(&mut self) -> uid_t { + fn get_effective_gid(&self) -> uid_t { self.uid } - fn get_effective_groupname(&mut self) -> Option { + fn get_effective_groupname(&self) -> Option> { self.groups.get(&self.uid).map(|u| u.name.clone()) } } @@ -136,71 +140,72 @@ impl Users for MockUsers { #[cfg(test)] mod test { use super::{Users, User, Group, MockUsers}; + use std::sync::Arc; #[test] fn current_username() { let mut users = MockUsers::with_current_uid(1337); - users.add_user(User { uid: 1337, name: "fred".to_string(), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); - assert_eq!(Some("fred".to_string()), users.get_current_username()) + users.add_user(User { uid: 1337, name: Arc::new("fred".to_string()), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); + assert_eq!(Some(Arc::new("fred".into())), users.get_current_username()) } #[test] fn no_current_username() { - let mut users = MockUsers::with_current_uid(1337); + let users = MockUsers::with_current_uid(1337); assert_eq!(None, users.get_current_username()) } #[test] fn uid() { let mut users = MockUsers::with_current_uid(0); - users.add_user(User { uid: 1337, name: "fred".to_string(), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); - assert_eq!(Some("fred".to_string()), users.get_user_by_uid(1337).map(|u| u.name)) + users.add_user(User { uid: 1337, name: Arc::new("fred".to_string()), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); + assert_eq!(Some(Arc::new("fred".into())), users.get_user_by_uid(1337).map(|u| u.name.clone())) } #[test] fn username() { let mut users = MockUsers::with_current_uid(1337); - users.add_user(User { uid: 1440, name: "fred".to_string(), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); + users.add_user(User { uid: 1440, name: Arc::new("fred".to_string()), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); assert_eq!(Some(1440), users.get_user_by_name("fred").map(|u| u.uid)) } #[test] fn no_username() { let mut users = MockUsers::with_current_uid(1337); - users.add_user(User { uid: 1440, name: "fred".to_string(), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); + users.add_user(User { uid: 1440, name: Arc::new("fred".to_string()), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); assert_eq!(None, users.get_user_by_name("criminy").map(|u| u.uid)) } #[test] fn no_uid() { - let mut users = MockUsers::with_current_uid(0); - assert_eq!(None, users.get_user_by_uid(1337).map(|u| u.name)) + let users = MockUsers::with_current_uid(0); + assert_eq!(None, users.get_user_by_uid(1337).map(|u| u.name.clone())) } #[test] fn gid() { let mut users = MockUsers::with_current_uid(0); - users.add_group(Group { gid: 1337, name: "fred".to_string(), members: vec![], }); - assert_eq!(Some("fred".to_string()), users.get_group_by_gid(1337).map(|g| g.name)) + users.add_group(Group { gid: 1337, name: Arc::new("fred".to_string()), members: vec![], }); + assert_eq!(Some(Arc::new("fred".into())), users.get_group_by_gid(1337).map(|g| g.name.clone())) } #[test] fn group_name() { let mut users = MockUsers::with_current_uid(0); - users.add_group(Group { gid: 1337, name: "fred".to_string(), members: vec![], }); + users.add_group(Group { gid: 1337, name: Arc::new("fred".to_string()), members: vec![], }); assert_eq!(Some(1337), users.get_group_by_name("fred").map(|g| g.gid)) } #[test] fn no_group_name() { let mut users = MockUsers::with_current_uid(0); - users.add_group(Group { gid: 1337, name: "fred".to_string(), members: vec![], }); + users.add_group(Group { gid: 1337, name: Arc::new("fred".to_string()), members: vec![], }); assert_eq!(None, users.get_group_by_name("santa").map(|g| g.gid)) } #[test] fn no_gid() { - let mut users = MockUsers::with_current_uid(0); - assert_eq!(None, users.get_group_by_gid(1337).map(|g| g.name)) + let users = MockUsers::with_current_uid(0); + assert_eq!(None, users.get_group_by_gid(1337).map(|g| g.name.clone())) } } diff --git a/src/os.rs b/src/os.rs index 8cc6805..707fff9 100644 --- a/src/os.rs +++ b/src/os.rs @@ -1,21 +1,85 @@ +//! A cache for users and groups provided by the OS. +//! +//! ## Caching, multiple threads, and mutability +//! +//! The `OSUsers` type is caught between a rock and a hard place when it comes +//! to providing references to users and groups. +//! +//! Instead of returning a fresh `User` struct each time, for example, it will +//! return a reference to the version it currently has in its cache. So you can +//! ask for User #501 twice, and you’ll get a reference to the same value both +//! time. Its methods are *idempotent* -- calling one multiple times has the +//! same effect as calling one once. +//! +//! This works fine in theory, but in practice, the cache has to update its own +//! state somehow: it contains several `HashMap`s that hold the result of user +//! and group lookups. Rust provides mutability in two ways: +//! +//! 1. Have its methods take `&mut self`, instead of `&self`, allowing the +//! internal maps to be mutated (“inherited mutability”) +//! 2. Wrap the internal maps in a `RefCell`, allowing them to be modified +//! (“interior mutability”). +//! +//! Unfortunately, Rust is also very protective of references to a mutable +//! value. In this case, switching to `&mut self` would only allow for one user +//! to be read at a time! +//! +//! ``norun +//! let mut cache = OSUsers::empty_cache(); +//! let uid = cache.get_current_uid(); // OK... +//! let user = cache.get_user_by_uid(uid).unwrap() // OK... +//! let group = cache.get_group_by_gid(user.primary_group); // No! +//! ``` +//! +//! When we get the `user`, it returns an optional reference (which we unwrap) +//! to the user’s entry in the cache. This is a reference to something contained +//! in a mutable value. Then, when we want to get the user’s primary group, it +//! will return *another* reference to the same mutable value. This is something +//! that Rust explicitly disallows! +//! +//! The compiler wasn’t on our side with Option 1, so let’s try Option 2: +//! changing the methods back to `&self` instead of `&mut self`, and using +//! `RefCell`s internally. However, Rust is smarter than this, and knows that +//! we’re just trying the same trick as earlier. A simplified implementation of +//! a user cache lookup would look something like this: +//! +//! ``norun +//! fn get_user_by_uid(&self, uid: uid_t) -> Option<&User> { +//! let users = self.users.borrow_mut(); +//! users.get(uid) +//! } +//! ``` +//! +//! Rust won’t allow us to return a reference like this because the `Ref` of the +//! `RefCell` just gets dropped at the end of the method, meaning that our +//! reference does not live long enough. +//! +//! So instead of doing any of that, we use `Arc` everywhere in order to get +//! around all the lifetime restrictions. Returning reference-counted users and +//! groups mean that we don’t have to worry about further uses of the cache, as +//! the values themselves don’t count as being stored *in* the cache anymore. So +//! it can be queried multiple times or go out of scope and the values it +//! produces are not affected. + use libc::{uid_t, gid_t}; use std::borrow::ToOwned; +use std::cell::{Cell, RefCell}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::HashMap; +use std::sync::Arc; use super::{User, Group, Users}; /// A producer of user and group instances that caches every result. -#[derive(Clone)] pub struct OSUsers { - users: BiMap, + users: BiMap, groups: BiMap, - uid: Option, - gid: Option, - euid: Option, - egid: Option, + uid: Cell>, + gid: Cell>, + euid: Cell>, + egid: Cell>, } /// A kinda-bi-directional HashMap that associates keys to values, and then @@ -23,10 +87,9 @@ pub struct OSUsers { /// *values*-to-keys lookup, because we only want to search based on /// usernames and group names. There wouldn’t be much point offering a “User /// to uid” map, as the uid is present in the user struct! -#[derive(Clone)] struct BiMap { - forward: HashMap>, - backward: HashMap>, + forward: RefCell< HashMap>> >, + backward: RefCell< HashMap, Option> >, } impl OSUsers { @@ -34,33 +97,38 @@ impl OSUsers { pub fn empty_cache() -> OSUsers { OSUsers { users: BiMap { - forward: HashMap::new(), - backward: HashMap::new(), + forward: RefCell::new(HashMap::new()), + backward: RefCell::new(HashMap::new()), }, groups: BiMap { - forward: HashMap::new(), - backward: HashMap::new(), + forward: RefCell::new(HashMap::new()), + backward: RefCell::new(HashMap::new()), }, - uid: None, - gid: None, - euid: None, - egid: None, + uid: Cell::new(None), + gid: Cell::new(None), + euid: Cell::new(None), + egid: Cell::new(None), } } } impl Users for OSUsers { - fn get_user_by_uid(&mut self, uid: uid_t) -> Option { - match self.users.forward.entry(uid) { + fn get_user_by_uid(&self, uid: uid_t) -> Option> { + let mut users_forward = self.users.forward.borrow_mut(); + + match users_forward.entry(uid) { Vacant(entry) => { - let user = super::get_user_by_uid(uid); - match user { + match super::get_user_by_uid(uid) { Some(user) => { - entry.insert(Some(user.clone())); - self.users.backward.insert(user.name.clone(), Some(user.uid)); - Some(user) + let newsername = user.name.clone(); + let mut users_backward = self.users.backward.borrow_mut(); + users_backward.insert(newsername, Some(uid)); + + let user_arc = Arc::new(user); + entry.insert(Some(user_arc.clone())); + Some(user_arc) }, None => { entry.insert(None); @@ -72,17 +140,23 @@ impl Users for OSUsers { } } - fn get_user_by_name(&mut self, username: &str) -> Option { + fn get_user_by_name(&self, username: &str) -> Option> { + let mut users_backward = self.users.backward.borrow_mut(); + // to_owned() could change here: // https://github.com/rust-lang/rfcs/blob/master/text/0509-collections-reform-part-2.md#alternatives-to-toowned-on-entries - match self.users.backward.entry(username.to_owned()) { + match users_backward.entry(Arc::new(username.to_owned())) { Vacant(entry) => { - let user = super::get_user_by_name(username); - match user { + match super::get_user_by_name(username) { Some(user) => { - entry.insert(Some(user.uid)); - self.users.forward.insert(user.uid, Some(user.clone())); - Some(user) + let uid = user.uid; + let user_arc = Arc::new(user); + + let mut users_forward = self.users.forward.borrow_mut(); + users_forward.insert(uid, Some(user_arc.clone())); + entry.insert(Some(uid)); + + Some(user_arc) }, None => { entry.insert(None); @@ -90,22 +164,31 @@ impl Users for OSUsers { } } }, - Occupied(entry) => match entry.get() { - &Some(uid) => self.users.forward[&uid].clone(), - &None => None, + Occupied(entry) => match *entry.get() { + Some(uid) => { + let users_forward = self.users.forward.borrow_mut(); + users_forward[&uid].clone() + } + None => None, } } } - fn get_group_by_gid(&mut self, gid: gid_t) -> Option { - match self.groups.forward.entry(gid) { + fn get_group_by_gid(&self, gid: gid_t) -> Option> { + let mut groups_forward = self.groups.forward.borrow_mut(); + + match groups_forward.entry(gid) { Vacant(entry) => { let group = super::get_group_by_gid(gid); match group { Some(group) => { - entry.insert(Some(group.clone())); - self.groups.backward.insert(group.name.clone(), Some(group.gid)); - Some(group) + let new_group_name = group.name.clone(); + let mut groups_backward = self.groups.backward.borrow_mut(); + groups_backward.insert(new_group_name, Some(gid)); + + let group_arc = Arc::new(group); + entry.insert(Some(group_arc.clone())); + Some(group_arc) }, None => { entry.insert(None); @@ -117,17 +200,24 @@ impl Users for OSUsers { } } - fn get_group_by_name(&mut self, group_name: &str) -> Option { + fn get_group_by_name(&self, group_name: &str) -> Option> { + let mut groups_backward = self.groups.backward.borrow_mut(); + // to_owned() could change here: // https://github.com/rust-lang/rfcs/blob/master/text/0509-collections-reform-part-2.md#alternatives-to-toowned-on-entries - match self.groups.backward.entry(group_name.to_owned()) { + match groups_backward.entry(Arc::new(group_name.to_owned())) { Vacant(entry) => { let user = super::get_group_by_name(group_name); match user { Some(group) => { - entry.insert(Some(group.gid)); - self.groups.forward.insert(group.gid, Some(group.clone())); - Some(group) + let group_arc = Arc::new(group.clone()); + let gid = group.gid; + + let mut groups_forward = self.groups.forward.borrow_mut(); + groups_forward.insert(gid, Some(group_arc.clone())); + entry.insert(Some(gid)); + + Some(group_arc) }, None => { entry.insert(None); @@ -135,75 +225,77 @@ impl Users for OSUsers { } } }, - Occupied(entry) => match entry.get() { - &Some(gid) => self.groups.forward[&gid].clone(), - &None => None, + Occupied(entry) => match *entry.get() { + Some(gid) => { + let groups_forward = self.groups.forward.borrow_mut(); + groups_forward[&gid].as_ref().cloned() + } + None => None, } } } - fn get_current_uid(&mut self) -> uid_t { - match self.uid { + fn get_current_uid(&self) -> uid_t { + match self.uid.get() { Some(uid) => uid, None => { let uid = super::get_current_uid(); - self.uid = Some(uid); + self.uid.set(Some(uid)); uid } } } - /// Return the username of the user running the process. - fn get_current_username(&mut self) -> Option { + fn get_current_username(&self) -> Option> { let uid = self.get_current_uid(); - self.get_user_by_uid(uid).map(|u| u.name) + self.get_user_by_uid(uid).map(|u| u.name.clone()) } - fn get_current_gid(&mut self) -> gid_t { - match self.gid { + fn get_current_gid(&self) -> gid_t { + match self.gid.get() { Some(gid) => gid, None => { let gid = super::get_current_gid(); - self.gid = Some(gid); + self.gid.set(Some(gid)); gid } } } - fn get_current_groupname(&mut self) -> Option { + fn get_current_groupname(&self) -> Option> { let gid = self.get_current_gid(); - self.get_group_by_gid(gid).map(|g| g.name) + self.get_group_by_gid(gid).map(|g| g.name.clone()) } - fn get_effective_gid(&mut self) -> gid_t { - match self.egid { + fn get_effective_gid(&self) -> gid_t { + match self.egid.get() { Some(gid) => gid, None => { let gid = super::get_effective_gid(); - self.egid = Some(gid); + self.egid.set(Some(gid)); gid } } } - fn get_effective_groupname(&mut self) -> Option { + fn get_effective_groupname(&self) -> Option> { let gid = self.get_effective_gid(); - self.get_group_by_gid(gid).map(|g| g.name) + self.get_group_by_gid(gid).map(|g| g.name.clone()) } - fn get_effective_uid(&mut self) -> uid_t { - match self.euid { + fn get_effective_uid(&self) -> uid_t { + match self.euid.get() { Some(uid) => uid, None => { let uid = super::get_effective_uid(); - self.euid = Some(uid); + self.euid.set(Some(uid)); uid } } } - fn get_effective_username(&mut self) -> Option { + fn get_effective_username(&self) -> Option> { let uid = self.get_effective_uid(); - self.get_user_by_uid(uid).map(|u| u.name) + self.get_user_by_uid(uid).map(|u| u.name.clone()) } }