You've already forked rust-users
mirror of
https://github.com/uutils/rust-users.git
synced 2026-06-10 15:48:37 -07:00
Return Arcs instead of clones, and take &self
The primary goal of this commit was to avoid having to clone the users and groups it produces. The old implementation was written by me when I hadn't *fully* internalised how lifetimes worked, and just used `clone` to get around the error messages. It was only later that I realised how inefficient this actually is -- the values stored in the cache are never used, only their clones are! Several attempt at this later, I settled on using `Arc` for everything and cloning *those* values instead. All the gory details of what happened are written in a comment in the OS module. The short version is that it's now possible to change the `Users` trait's methods from taking `&mut self` to just taking `&self`. This is within the allowed uses of `Cell`: implementation details of a method that does actually return the same thing each time, which this is. The main casualty is that the user now has to know about `Arc` and initialise a user with a username in such a container, which I hope to solve in a later commit.
This commit is contained in:
+4
-4
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-23
@@ -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<User>;
|
||||
fn get_user_by_uid(&self, uid: uid_t) -> Option<Arc<User>>;
|
||||
|
||||
/// Return a User object if one exists for the given username; otherwise, return None.
|
||||
fn get_user_by_name(&mut self, username: &str) -> Option<User>;
|
||||
fn get_user_by_name(&self, username: &str) -> Option<Arc<User>>;
|
||||
|
||||
/// 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<Group>;
|
||||
fn get_group_by_gid(&self, gid: gid_t) -> Option<Arc<Group>>;
|
||||
|
||||
/// 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<Group>;
|
||||
fn get_group_by_name(&self, group_name: &str) -> Option<Arc<Group>>;
|
||||
|
||||
/// 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<String>;
|
||||
fn get_current_username(&self) -> Option<Arc<String>>;
|
||||
|
||||
/// 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<String>;
|
||||
fn get_current_groupname(&self) -> Option<Arc<String>>;
|
||||
|
||||
/// 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<String>;
|
||||
fn get_effective_username(&self) -> Option<Arc<String>>;
|
||||
|
||||
/// Return the effective group name.
|
||||
fn get_effective_groupname(&mut self) -> Option<String>;
|
||||
fn get_effective_groupname(&self) -> Option<Arc<String>>;
|
||||
}
|
||||
|
||||
#[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<String>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// Vector of the names of the users who belong to this group as a non-primary member
|
||||
pub members: Vec<String>,
|
||||
@@ -267,7 +268,7 @@ unsafe fn passwd_to_user(pointer: *const c_passwd) -> Option<User> {
|
||||
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<Group> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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<String> {
|
||||
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");
|
||||
|
||||
+43
-38
@@ -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<U: Users>(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<uid_t, User>,
|
||||
groups: HashMap<gid_t, Group>,
|
||||
users: HashMap<uid_t, Arc<User>>,
|
||||
groups: HashMap<gid_t, Arc<Group>>,
|
||||
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<User> {
|
||||
self.users.insert(user.uid, user)
|
||||
pub fn add_user(&mut self, user: User) -> Option<Arc<User>> {
|
||||
self.users.insert(user.uid, Arc::new(user))
|
||||
}
|
||||
|
||||
/// Add a group to the groups table.
|
||||
pub fn add_group(&mut self, group: Group) -> Option<Group> {
|
||||
self.groups.insert(group.gid, group)
|
||||
pub fn add_group(&mut self, group: Group) -> Option<Arc<Group>> {
|
||||
self.groups.insert(group.gid, Arc::new(group))
|
||||
}
|
||||
}
|
||||
|
||||
impl Users for MockUsers {
|
||||
fn get_user_by_uid(&mut self, uid: uid_t) -> Option<User> {
|
||||
fn get_user_by_uid(&self, uid: uid_t) -> Option<Arc<User>> {
|
||||
self.users.get(&uid).cloned()
|
||||
}
|
||||
|
||||
fn get_user_by_name(&mut self, username: &str) -> Option<User> {
|
||||
self.users.values().find(|u| u.name == username).cloned()
|
||||
fn get_user_by_name(&self, username: &str) -> Option<Arc<User>> {
|
||||
self.users.values().find(|u| &*u.name == username).cloned()
|
||||
}
|
||||
|
||||
fn get_group_by_gid(&mut self, gid: gid_t) -> Option<Group> {
|
||||
fn get_group_by_gid(&self, gid: gid_t) -> Option<Arc<Group>> {
|
||||
self.groups.get(&gid).cloned()
|
||||
}
|
||||
|
||||
fn get_group_by_name(&mut self, group_name: &str) -> Option<Group> {
|
||||
self.groups.values().find(|g| g.name == group_name).cloned()
|
||||
fn get_group_by_name(&self, group_name: &str) -> Option<Arc<Group>> {
|
||||
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<String> {
|
||||
fn get_current_username(&self) -> Option<Arc<String>> {
|
||||
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<String> {
|
||||
fn get_current_groupname(&self) -> Option<Arc<String>> {
|
||||
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<String> {
|
||||
fn get_effective_username(&self) -> Option<Arc<String>> {
|
||||
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<String> {
|
||||
fn get_effective_groupname(&self) -> Option<Arc<String>> {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<uid_t, User>,
|
||||
users: BiMap<uid_t, User>,
|
||||
groups: BiMap<gid_t, Group>,
|
||||
|
||||
uid: Option<uid_t>,
|
||||
gid: Option<gid_t>,
|
||||
euid: Option<uid_t>,
|
||||
egid: Option<gid_t>,
|
||||
uid: Cell<Option<uid_t>>,
|
||||
gid: Cell<Option<gid_t>>,
|
||||
euid: Cell<Option<uid_t>>,
|
||||
egid: Cell<Option<gid_t>>,
|
||||
}
|
||||
|
||||
/// 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<K, V> {
|
||||
forward: HashMap<K, Option<V>>,
|
||||
backward: HashMap<String, Option<K>>,
|
||||
forward: RefCell< HashMap<K, Option<Arc<V>>> >,
|
||||
backward: RefCell< HashMap<Arc<String>, Option<K>> >,
|
||||
}
|
||||
|
||||
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<User> {
|
||||
match self.users.forward.entry(uid) {
|
||||
fn get_user_by_uid(&self, uid: uid_t) -> Option<Arc<User>> {
|
||||
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<User> {
|
||||
fn get_user_by_name(&self, username: &str) -> Option<Arc<User>> {
|
||||
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<Group> {
|
||||
match self.groups.forward.entry(gid) {
|
||||
fn get_group_by_gid(&self, gid: gid_t) -> Option<Arc<Group>> {
|
||||
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<Group> {
|
||||
fn get_group_by_name(&self, group_name: &str) -> Option<Arc<Group>> {
|
||||
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<String> {
|
||||
fn get_current_username(&self) -> Option<Arc<String>> {
|
||||
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<String> {
|
||||
fn get_current_groupname(&self) -> Option<Arc<String>> {
|
||||
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<String> {
|
||||
fn get_effective_groupname(&self) -> Option<Arc<String>> {
|
||||
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<String> {
|
||||
fn get_effective_username(&self) -> Option<Arc<String>> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user