From 2ba4c3b1c20737da7d0e2a7c99be7222e20cdc15 Mon Sep 17 00:00:00 2001 From: Adrian Heine Date: Sun, 13 Mar 2016 11:15:12 +0100 Subject: [PATCH] Init --- .gitignore | 2 + Cargo.toml | 6 + src/command_runner.rs | 20 +++ src/lib.rs | 22 +++ src/loggers.rs | 41 ++++++ src/schema.rs | 202 ++++++++++++++++++++++++++++ src/symbols/mod.rs | 8 ++ src/symbols/systemd/mod.rs | 1 + src/symbols/systemd/user_session.rs | 67 +++++++++ src/symbols/user.rs | 187 +++++++++++++++++++++++++ 10 files changed, 556 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/command_runner.rs create mode 100644 src/lib.rs create mode 100644 src/loggers.rs create mode 100644 src/schema.rs create mode 100644 src/symbols/mod.rs create mode 100644 src/symbols/systemd/mod.rs create mode 100644 src/symbols/systemd/user_session.rs create mode 100644 src/symbols/user.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e8a7f2a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "schematics" +version = "0.1.0" +authors = ["Adrian Heine "] + +[dependencies] diff --git a/src/command_runner.rs b/src/command_runner.rs new file mode 100644 index 0000000..a5682d7 --- /dev/null +++ b/src/command_runner.rs @@ -0,0 +1,20 @@ +use std::io::Result as IoResult; +use std::process::Command; +use std::process::Output; + +pub trait CommandRunner { + fn run_with_args(&self, program: &str, args: &[&str]) -> IoResult; +} + +#[derive(Debug)] +pub struct StdCommandRunner; + +impl CommandRunner for StdCommandRunner { + fn run_with_args(&self, program: &str, args: &[&str]) -> IoResult { + Command::new(program).args(args).output() + } +} + +#[cfg(test)] +mod test { +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..16d5ac5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,22 @@ +// rustfmt + +#![deny(fat_ptr_transmutes, +trivial_casts, trivial_numeric_casts, unsafe_code, +unstable_features, unused_extern_crates, +unused_import_braces, unused_qualifications, +unused_results, variant_size_differences +)] + +/* +#![warn(missing_docs, +missing_copy_implementations, +missing_debug_implementations +)] +*/ + +#![allow(box_pointers)] + +pub mod command_runner; +pub mod loggers; +pub mod symbols; +pub mod schema; diff --git a/src/loggers.rs b/src/loggers.rs new file mode 100644 index 0000000..4084e23 --- /dev/null +++ b/src/loggers.rs @@ -0,0 +1,41 @@ +use std::io::stderr; +use std::io::Write; + +pub trait Logger { + fn write(&mut self, &str); + fn debug(&mut self, &str); +} + +pub struct StdErrLogger; + +impl Logger for StdErrLogger { + fn debug(&mut self, str: &str) { + writeln!(&mut stderr(), "{}", str).unwrap(); + } + fn write(&mut self, str: &str) { + writeln!(&mut stderr(), "{}", str).unwrap(); + } +} + +pub struct FilteringLogger<'a> { + logger: &'a mut Logger +} + +impl<'a> FilteringLogger<'a> { + pub fn new(logger: &'a mut Logger) -> Self { + FilteringLogger { logger: logger } + } +} + +impl<'a> Logger for FilteringLogger<'a> { + fn debug(&mut self, str: &str) { + return + } + fn write(&mut self, str: &str) { + self.logger.write(str) + } +} + +#[cfg(test)] +mod test { +} diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..8aacf90 --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,202 @@ +use std::error::Error; +use std::fmt; +use std::io::Write; + +use loggers::Logger; +use symbols::Symbol; + +#[derive(Debug,PartialEq)] +pub enum SymbolRunError { + Symbol(E), + ExecuteDidNotReach(()) +} + +impl Error for SymbolRunError { + fn description(&self) -> &str { + match self { + &SymbolRunError::Symbol(_) => "Symbol execution error", + &SymbolRunError::ExecuteDidNotReach(_) => "Target not reached after executing symbol" + } + } + fn cause(&self) -> Option<&Error> { + match self { + &SymbolRunError::Symbol(ref e) => Some(e), + &SymbolRunError::ExecuteDidNotReach(_) => None + } + } +} + +impl fmt::Display for SymbolRunError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &SymbolRunError::Symbol(ref e) => write!(f, "{}", e), + &SymbolRunError::ExecuteDidNotReach(_) => write!(f, "{}", self.description()) + } + } +} + +impl From for SymbolRunError { + fn from(err: E) -> SymbolRunError { + SymbolRunError::Symbol(err) + } +} + +pub trait SymbolRunner { + fn run_symbol(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError> + where S::Error: Error; +} + +pub struct InitializingSymbolRunner; + +impl SymbolRunner for InitializingSymbolRunner { + fn run_symbol(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError> + where S::Error: Error + { + let target_reached = try!(symbol.target_reached()); + if target_reached { + logger.write(format!("{} already reached", symbol).as_str()); + } else { + logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); + logger.write(format!("Executing {}", symbol).as_str()); + try!(symbol.execute()); + let target_reached = try!(symbol.target_reached()); + logger.debug(format!("Symbol reports target_reached: {:?} (should be true)", target_reached).as_str()); + if !target_reached { + return Err(SymbolRunError::ExecuteDidNotReach(())); + } + } + Ok(()) + } +} + +pub struct DrySymbolRunner; + +impl SymbolRunner for DrySymbolRunner { + fn run_symbol(&self, logger: &mut Logger, symbol: &S) -> Result<(), SymbolRunError> + where S::Error: Error + { + let target_reached = try!(symbol.target_reached()); + logger.debug(format!("Symbol reports target_reached: {:?}", target_reached).as_str()); + if !target_reached { + logger.write(format!("Would execute {}", symbol).as_str()); + } + Ok(()) + } +} + +// FIXME: Add ExpectingSymbolRunner + +#[cfg(test)] +mod test { + use std::cell::RefCell; + use std::error::Error; + use std::fmt; + + use loggers::Logger; + use symbols::Symbol; + use schema::SymbolRunner; + use schema::InitializingSymbolRunner; + use schema::SymbolRunError; + + #[derive(Debug, PartialEq, Clone)] + enum DummySymbolError { + Error(()) + } + + impl Error for DummySymbolError { + fn description(&self) -> &str { + return "Description"; + } + } + + impl fmt::Display for DummySymbolError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Dummy symbol error") + } + } + + #[derive(Debug)] + struct DummySymbol { + _target_reacheds: Vec>, + _cur_target_reached: RefCell, + _execute: Result<(), DummySymbolError>, + } + + impl Symbol for DummySymbol { + type Error = DummySymbolError; + fn target_reached(&self) -> Result { + let mut cur = self._cur_target_reached.borrow_mut(); + let ret = self._target_reacheds[*cur].clone(); + *cur = *cur + 1; + ret + } + fn execute(&self) -> Result<(), Self::Error> { self._execute.clone() } + } + + impl fmt::Display for DummySymbol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Dummy symbol") + } + } + + impl DummySymbol { + fn new(target_reached: Vec>, + execute: Result<(), DummySymbolError>) -> DummySymbol { + DummySymbol { _target_reacheds: target_reached, _cur_target_reached: RefCell::new(0), _execute: execute } + } + } + + struct DummyLogger { + log: Vec + } + + impl DummyLogger { + fn new() -> DummyLogger { + DummyLogger { log: Vec::new() } + } + } + + impl Logger for DummyLogger { + fn write(&mut self, line: &str) { + self.log.push(From::from(line)); + } + fn debug(&mut self, line: &str) { + self.log.push(From::from(line)); + } + } + + #[test] + fn nothing_needed_to_be_done() { + let result: Result<(), SymbolRunError> = InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(vec![Ok(true)], Ok(())) ); + assert_eq!( + result, + Ok(()) + ); + } + + #[test] + fn everything_is_ok() { + let result: Result<(), SymbolRunError> = InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(vec![Ok(false), Ok(true)], Ok(())) ); + assert_eq!( + result, + Ok(()) + ); + } + + #[test] + fn executing_did_not_change_state() { + let result: Result<(), SymbolRunError> = InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(vec![Ok(false), Ok(false)], Ok(()))); + assert_eq!( + result, + Err(SymbolRunError::ExecuteDidNotReach(())) + ); + } + + #[test] + fn executing_did_not_work() { + assert_eq!( + InitializingSymbolRunner.run_symbol( &mut DummyLogger::new(), &DummySymbol::new(vec![Ok(false)], Err(DummySymbolError::Error(()))) ), + Err(SymbolRunError::Symbol(DummySymbolError::Error(()))) + ); + } +} diff --git a/src/symbols/mod.rs b/src/symbols/mod.rs new file mode 100644 index 0000000..90e2643 --- /dev/null +++ b/src/symbols/mod.rs @@ -0,0 +1,8 @@ +pub trait Symbol { + type Error; + fn target_reached(&self) -> Result; + fn execute(&self) -> Result<(), Self::Error>; +} + +pub mod user; +pub mod systemd; diff --git a/src/symbols/systemd/mod.rs b/src/symbols/systemd/mod.rs new file mode 100644 index 0000000..c339c2b --- /dev/null +++ b/src/symbols/systemd/mod.rs @@ -0,0 +1 @@ +pub mod user_session; diff --git a/src/symbols/systemd/user_session.rs b/src/symbols/systemd/user_session.rs new file mode 100644 index 0000000..4ab19a2 --- /dev/null +++ b/src/symbols/systemd/user_session.rs @@ -0,0 +1,67 @@ +use std::error::Error; +use std::fmt; +use std::io::Error as IoError; +use std::path::PathBuf; + +use command_runner::CommandRunner; +use symbols::Symbol; + +#[derive(Debug)] +pub enum SystemdUserSessionError { + ExecError(E) +} + +impl Error for SystemdUserSessionError { + fn description(&self) -> &str { + match self { + &SystemdUserSessionError::ExecError(ref e) => e.description() + } + } + fn cause(&self) -> Option<&Error> { + match self { + &SystemdUserSessionError::ExecError(ref e) => Some(e) + } + } +} + +impl fmt::Display for SystemdUserSessionError { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{}", self.description()) + } +} + +pub struct SystemdUserSession<'a> { + user_name: &'a str, + command_runner: &'a CommandRunner +} + +impl<'a> SystemdUserSession<'a> { + pub fn new(user_name: &'a str, command_runner: &'a CommandRunner) -> Self { + SystemdUserSession { + user_name: user_name, + command_runner: command_runner + } + } +} + +impl<'a> Symbol for SystemdUserSession<'a> { + type Error = SystemdUserSessionError; + fn target_reached(&self) -> Result { + let mut path = PathBuf::from("/var/lib/systemd/linger"); + path.push(self.user_name); + Ok(path.exists()) + } + + fn execute(&self) -> Result<(), Self::Error> { + match self.command_runner.run_with_args("loginctl", &["enable-linger", self.user_name]) { + Ok(_) => Ok(()), + Err(e) => Err(SystemdUserSessionError::ExecError(e)) + } + } +} + +impl<'a> fmt::Display for SystemdUserSession<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(),fmt::Error>{ + write!(f, "Systemd user session for {}", self.user_name) + } +} diff --git a/src/symbols/user.rs b/src/symbols/user.rs new file mode 100644 index 0000000..71d221f --- /dev/null +++ b/src/symbols/user.rs @@ -0,0 +1,187 @@ +use std::error::Error; +use std::fmt; +use std::io::Error as IoError; + +use command_runner::CommandRunner; +use symbols::Symbol; + +#[derive(Debug, PartialEq)] +pub enum UserAdderError { + AlreadyExists, + UnknownError, + ImplError(E) +} + +impl Error for UserAdderError { + fn description(&self) -> &str { + match self { + &UserAdderError::AlreadyExists => "User already exists", + &UserAdderError::UnknownError => "Unknown error", + &UserAdderError::ImplError(_) => "User adding error" + } + } + fn cause(&self) -> Option<&Error> { + match self { + &UserAdderError::ImplError(ref e) => Some(e), + _ => None + } + } +} + +impl fmt::Display for UserAdderError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.cause() { + Some(e) => write!(f, "{} (cause: {})", self.description(), e), + None => write!(f, "{}", self.description()) + } + } +} + +pub trait UserAdder { + type SubE: Error; + fn add_user(&self, user_name: &str) -> Result<(), UserAdderError>; +} + +#[derive(Debug, PartialEq)] +pub enum UserError { + GenericError, + ExecError(E) +} + +impl Error for UserError { + fn description(&self) -> &str { + match self { + &UserError::GenericError => "Could not find out if user exists", + &UserError::ExecError(_) => "Error executing symbol" + } + } + fn cause(&self) -> Option<&Error> { + match self { + &UserError::ExecError(ref e) => Some(e), + _ => None + } + } +} + +impl fmt::Display for UserError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.cause() { + Some(e) => write!(f, "{} (cause: {})", self.description(), e), + None => write!(f, "{}", self.description()) + } + } +} + +pub struct User<'a, E, A> where E: Error + Sized, A: 'a + UserAdder { + user_name: &'a str, + command_runner: &'a CommandRunner, + user_adder: &'a A +} + +impl<'a, E: Error + Sized, A: 'a + UserAdder> User<'a, E, A> { + pub fn new(user_name: &'a str, command_runner: &'a CommandRunner, user_adder: &'a A) -> User<'a, E, A> { + User { + user_name: user_name, + command_runner: command_runner, + user_adder: user_adder + } + } +} + +impl<'a, E: Error, A: UserAdder> fmt::Display for User<'a, E, A> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "User {}", self.user_name) + } +} + +impl<'a, E: Error, A: UserAdder> Symbol for User<'a, E, A> { + type Error = UserError>; + fn target_reached(&self) -> Result { + let output = self.command_runner.run_with_args("getent", &["passwd", self.user_name]); + match output { + Ok(output) => match output.status.code() { + Some(2) => Ok(false), + Some(0) => Ok(true), + _ => Err(UserError::GenericError) + }, + Err(_) => Err(UserError::GenericError) + } + } + + fn execute(&self) -> Result<(), Self::Error> { + self.user_adder.add_user(self.user_name).map_err(|e| UserError::ExecError(e)) + } +} + +pub struct SystemUserAdder<'a> { + command_runner: &'a CommandRunner +} + +impl<'a> SystemUserAdder<'a> { + pub fn new(command_runner: &'a CommandRunner) -> SystemUserAdder<'a> { + SystemUserAdder { command_runner: command_runner } + } +} + +impl<'a> UserAdder for SystemUserAdder<'a> { + type SubE = IoError; + fn add_user(&self, user_name: &str) -> Result<(), UserAdderError> { + let output = self.command_runner.run_with_args("adduser", &["--system", "--disabled-login", "--disabled-password", user_name]); + match output { + Ok(output) => match output.status.code() { + Some(0) => Ok(()), + Some(1) => Err(UserAdderError::AlreadyExists), + Some(_) => Err(UserAdderError::UnknownError), + None => Err(UserAdderError::UnknownError), + }, + Err(e) => Err(UserAdderError::ImplError(e)) + } + } +} + +#[cfg(test)] +mod test { + use std::error::Error; + use std::fmt; + + use command_runner::StdCommandRunner; + use symbols::Symbol; + use symbols::user::User; + use symbols::user::UserAdder; + use symbols::user::UserAdderError; + + #[derive(Debug, PartialEq)] + struct DummyError; + impl Error for DummyError { + fn description(&self) -> &str { + "DummyError" + } + } + + impl fmt::Display for DummyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "DummyError") + } + } + + struct DummyUserAdder; + + impl UserAdder for DummyUserAdder { + type SubE = DummyError; + fn add_user(&self, user_name: &str) -> Result<(), UserAdderError> { + Ok(()) + } + } + + #[test] + fn test_target_reached_nonexisting() { + let symbol = User { user_name: "nonexisting", command_runner: &StdCommandRunner, user_adder: &DummyUserAdder }; + assert_eq!(symbol.target_reached(), Ok(false)); + } + + #[test] + fn test_target_reached_root() { + let symbol = User { user_name: "root", command_runner: &StdCommandRunner, user_adder: &DummyUserAdder }; + assert_eq!(symbol.target_reached(), Ok(true)); + } +}