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.
This commit is contained in:
Ben S
2016-01-26 20:24:36 +00:00
parent 28ddc0c667
commit 476e31662b
4 changed files with 91 additions and 13 deletions
+50 -2
View File
@@ -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<String>,
}
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()
}
+2
View File
@@ -119,6 +119,8 @@ pub use cache::UsersCache;
pub mod mock;
pub mod os;
pub mod switch;
mod traits;
+16 -11
View File
@@ -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))
}
+23
View File
@@ -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;
}
}