use crate::command_runner::CommandRunner; use crate::symbols::Symbol; use async_trait::async_trait; use once_cell::sync::Lazy; use std::error::Error; use tokio::sync::Semaphore; pub type Wait = Lazy; static WAIT: Wait = Lazy::new(|| Semaphore::new(1)); #[derive(Debug)] pub struct User { user_name: U, command_runner: C, } impl User { pub const fn new(user_name: U, command_runner: C) -> Self { Self { user_name, command_runner, } } } #[async_trait(?Send)] impl, C: CommandRunner> Symbol for User { async fn target_reached(&self) -> Result> { let output = self .command_runner .run_with_args("getent", args!["passwd", self.user_name.as_ref()]) .await?; match output.status.code() { Some(2) => Ok(false), Some(0) => Ok(true), _ => Err("Unknown error".into()), } } async fn execute(&self) -> Result<(), Box> { // adduser is not reentrant because finding the next uid // and creating the account is not an atomic operation let wait = WAIT.acquire().await; let res = self .command_runner .run_successfully( "adduser", args![ // "-m", // Necessary for Fedora, not accepted in Debian "--system", self.user_name.as_ref(), ], ) .await; drop(wait); res } } #[cfg(test)] mod test { use crate::async_utils::run; use crate::command_runner::StdCommandRunner; use crate::symbols::user::User; use crate::symbols::Symbol; #[test] fn test_target_reached_nonexisting() { let symbol = User { user_name: "nonexisting", command_runner: StdCommandRunner, }; assert_eq!(run(symbol.target_reached()).unwrap(), false); } #[test] fn test_target_reached_root() { let symbol = User { user_name: "root", command_runner: StdCommandRunner, }; assert_eq!(run(symbol.target_reached()).unwrap(), true); } }