From 476e31662bd81be57f679aa5e86e28543e4ba9aa Mon Sep 17 00:00:00 2001 From: Ben S Date: Tue, 26 Jan 2016 20:24:36 +0000 Subject: [PATCH] Begin to move OS routines to a new module This tries to mimic the Metadata struct in libc by providing a struct with hidden fields, then an OS-dependent trait that gives you accessors to those fields. Its goal is to remove any OS-specific things about users and groups -- which there will be a lot of -- from the default, publicly-accessible struct. This may not seem like a big problem, but you'll see it a lot in mocking code: "unimportant" fields, such as the user's home directory and shell, can now be mocked to include a "sensible" value even when they're not used. --- src/base.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 2 ++ src/mock.rs | 27 ++++++++++++++++----------- src/os.rs | 23 +++++++++++++++++++++++ 4 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 src/os.rs diff --git a/src/base.rs b/src/base.rs index 8a038d2..57f9546 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1,4 +1,5 @@ use std::ffi::{CStr, CString}; +use std::path::Path; use std::ptr::read; use std::str::from_utf8_unchecked; use std::sync::Arc; @@ -11,6 +12,8 @@ use libc::{c_char, time_t}; #[cfg(target_os = "linux")] use libc::c_char; +use os::*; + #[cfg(any(target_os = "macos", target_os = "freebsd", target_os = "dragonfly"))] #[repr(C)] @@ -75,10 +78,10 @@ pub struct User { pub primary_group: gid_t, /// This user's home directory - pub home_dir: String, + home_dir: String, /// This user's shell - pub shell: String, + shell: String, } /// Information about a particular group. @@ -95,6 +98,51 @@ pub struct Group { pub members: Vec, } +impl unix::UserExt for User { + fn home_dir(&self) -> &Path { + Path::new(&self.home_dir) + } + + fn with_home_dir(mut self, home_dir: &str) -> User { + self.home_dir = home_dir.to_owned(); + self + } + + fn shell(&self) -> &Path { + Path::new(&self.shell) + } + + fn with_shell(mut self, shell: &str) -> User { + self.shell = shell.to_owned(); + self + } + + fn new(uid: uid_t, name: &str, primary_group: gid_t) -> User { + User { + uid: uid, + name: Arc::new(name.to_owned()), + primary_group: primary_group, + home_dir: "/var/empty".to_owned(), + shell: "/bin/false".to_owned(), + } + } +} + +impl unix::GroupExt for Group { + fn members(&self) -> &[String] { + &*self.members + } + + fn new(gid: gid_t, name: &str) -> Group { + Group { + gid: gid, + name: Arc::new(name.to_owned()), + members: Vec::new(), + } + } +} + + unsafe fn from_raw_buf(p: *const i8) -> String { from_utf8_unchecked(CStr::from_ptr(p).to_bytes()).to_string() } diff --git a/src/lib.rs b/src/lib.rs index a3f185e..0a387dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,6 +119,8 @@ pub use cache::UsersCache; pub mod mock; +pub mod os; + pub mod switch; mod traits; diff --git a/src/mock.rs b/src/mock.rs index 65c7c0d..9a42fa6 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -18,11 +18,13 @@ //! //! ```rust //! use users::mock::{MockUsers, User, Group}; +//! use users::os::unix::{UserExt, GroupExt}; //! use std::sync::Arc; //! //! let mut users = MockUsers::with_current_uid(1000); -//! 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() ] }); +//! let bobbins = User::new(1000, "Bobbins", 1000).with_home_dir("/home/bobbins"); +//! users.add_user(bobbins); +//! users.add_group(Group::new(100, "funkyppl")); //! ``` //! //! The exports get re-exported into the mock module, for simpler `use` lines. @@ -38,6 +40,7 @@ //! //! ```rust //! use users::{Users, UsersCache, User}; +//! use users::os::unix::UserExt; //! use users::mock::MockUsers; //! use std::sync::Arc; //! @@ -46,7 +49,7 @@ //! } //! //! let mut users = MockUsers::with_current_uid(1001); -//! 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()}); +//! users.add_user(User::new(1001, "fred", 101)); //! print_current_username(&mut users); //! //! let mut actual_users = UsersCache::new(); @@ -144,15 +147,17 @@ impl Groups for MockUsers { #[cfg(test)] mod test { - use super::{MockUsers}; + use super::MockUsers; use base::{User, Group}; use traits::{Users, Groups}; use std::sync::Arc; + use os::unix::{UserExt, GroupExt}; + #[test] fn current_username() { let mut users = MockUsers::with_current_uid(1337); - 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() }); + users.add_user(User::new(1337, "fred", 101)); assert_eq!(Some(Arc::new("fred".into())), users.get_current_username()) } @@ -165,21 +170,21 @@ mod test { #[test] fn uid() { let mut users = MockUsers::with_current_uid(0); - 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() }); + users.add_user(User::new(1337, "fred", 101)); 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: Arc::new("fred".to_string()), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); + users.add_user(User::new(1440, "fred", 101)); 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: Arc::new("fred".to_string()), primary_group: 101, home_dir: "/home/fred".to_string(), shell: "/bin/bash".to_string() }); + users.add_user(User::new(1337, "fred", 101)); assert_eq!(None, users.get_user_by_name("criminy").map(|u| u.uid)) } @@ -192,21 +197,21 @@ mod test { #[test] fn gid() { let mut users = MockUsers::with_current_uid(0); - users.add_group(Group { gid: 1337, name: Arc::new("fred".to_string()), members: vec![], }); + users.add_group(Group::new(1337, "fred")); 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: Arc::new("fred".to_string()), members: vec![], }); + users.add_group(Group::new(1337, "fred")); 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: Arc::new("fred".to_string()), members: vec![], }); + users.add_group(Group::new(1337, "fred")); assert_eq!(None, users.get_group_by_name("santa").map(|g| g.gid)) } diff --git a/src/os.rs b/src/os.rs new file mode 100644 index 0000000..f50b035 --- /dev/null +++ b/src/os.rs @@ -0,0 +1,23 @@ + +pub mod unix { + use std::path::Path; + use libc::{uid_t, gid_t}; + + pub trait UserExt { + fn home_dir(&self) -> &Path; + fn with_home_dir(mut self, home_dir: &str) -> Self; + + fn shell(&self) -> &Path; + fn with_shell(mut self, shell: &str) -> Self; + + // TODO(ogham): Isn't it weird that the setters take a string slice, but + // the getters return a Path? + + fn new(uid: uid_t, name: &str, primary_group: gid_t) -> Self; + } + + pub trait GroupExt { + fn members(&self) -> &[String]; + fn new(gid: gid_t, name: &str) -> Self; + } +} \ No newline at end of file