A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
5.4 KiB

7 years ago
8 years ago
5 years ago
8 years ago
8 years ago
5 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
8 years ago
5 years ago
5 years ago
4 years ago
7 years ago
7 years ago
2 years ago
7 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
2 years ago
5 years ago
5 years ago
4 years ago
8 years ago
2 years ago
2 years ago
  1. use async_trait::async_trait;
  2. #[cfg(test)]
  3. use mockall::mock;
  4. use std::error::Error;
  5. use std::ffi::OsStr;
  6. use std::io::Result as IoResult;
  7. use std::process::Output;
  8. use std::process::Stdio;
  9. use tokio::io::AsyncWriteExt;
  10. use tokio::process::Command;
  11. #[macro_export]
  12. macro_rules! args {
  13. ($($x:expr),*) => (
  14. &[$($x.as_ref()),*]
  15. );
  16. ($($x:expr,)*) => (args![$($x),*]) // handle trailing commas
  17. }
  18. fn check_success(output: Output) -> Result<Output, Box<dyn Error>> {
  19. if output.status.success() {
  20. Ok(output)
  21. } else {
  22. Err(std::str::from_utf8(&output.stderr)?.into())
  23. }
  24. }
  25. pub fn is_success(res: Result<Output, impl Error + 'static>) -> Result<Output, Box<dyn Error>> {
  26. check_success(res?)
  27. }
  28. pub fn get_output(output: Output) -> Result<Vec<u8>, Box<dyn Error>> {
  29. Ok(check_success(output)?.stdout)
  30. }
  31. #[async_trait(?Send)]
  32. pub trait CommandRunner {
  33. async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output>;
  34. async fn run_with_args<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> IoResult<Output> {
  35. self.run(program, args, b"").await
  36. }
  37. async fn get_output<'a>(
  38. &self,
  39. program: &str,
  40. args: &'a [&'a OsStr],
  41. ) -> Result<Vec<u8>, Box<dyn Error>> {
  42. let output = self.run_with_args(program, args).await?;
  43. get_output(output)
  44. }
  45. async fn run_successfully<'a>(
  46. &self,
  47. program: &str,
  48. args: &'a [&'a OsStr],
  49. ) -> Result<(), Box<dyn Error>> {
  50. is_success(self.run(program, args, b"").await)?;
  51. Ok(())
  52. }
  53. async fn get_stderr<'a>(
  54. &self,
  55. program: &str,
  56. args: &'a [&'a OsStr],
  57. ) -> Result<Vec<u8>, Box<dyn Error>> {
  58. Ok(is_success(self.run_with_args(program, args).await)?.stderr)
  59. }
  60. }
  61. #[cfg(test)]
  62. mock! {
  63. pub CommandRunner {
  64. }
  65. #[async_trait(?Send)]
  66. impl CommandRunner for CommandRunner {
  67. async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output>;
  68. async fn run_with_args<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> IoResult<Output>;
  69. async fn get_output<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result<Vec<u8>, Box<dyn Error>>;
  70. async fn run_successfully<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result<(), Box<dyn Error>>;
  71. async fn get_stderr<'a>(&self, program: &str, args: &'a [&'a OsStr]) -> Result<Vec<u8>, Box<dyn Error>>;
  72. }
  73. }
  74. #[derive(Debug)]
  75. pub struct StdCommandRunner;
  76. #[async_trait(?Send)]
  77. impl CommandRunner for StdCommandRunner {
  78. async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output> {
  79. //println!("{} {:?}", program, args);
  80. let mut child = Command::new(program)
  81. .args(args)
  82. .stdin(Stdio::piped())
  83. .stdout(Stdio::piped())
  84. .stderr(Stdio::piped())
  85. .spawn()?;
  86. let stdin = child.stdin.as_mut().expect("Failed to open stdin");
  87. stdin.write_all(input).await?;
  88. let res = child.wait_with_output().await;
  89. //println!("{:?}", res);
  90. #[allow(clippy::let_and_return)]
  91. res
  92. }
  93. }
  94. #[derive(Debug)]
  95. pub struct SetuidCommandRunner<U: AsRef<str>> {
  96. user_name: U,
  97. }
  98. impl<U: AsRef<str>> SetuidCommandRunner<U> {
  99. pub const fn new(user_name: U) -> Self {
  100. Self { user_name }
  101. }
  102. }
  103. use std::env;
  104. use users::get_user_by_name;
  105. struct TempSetEnv<'a> {
  106. name: &'a str,
  107. old_value: Option<String>,
  108. }
  109. impl<'a> TempSetEnv<'a> {
  110. fn new(name: &'a str, new_value: impl AsRef<OsStr>) -> TempSetEnv<'a> {
  111. let old_value = env::var(name);
  112. env::set_var(name, new_value);
  113. TempSetEnv {
  114. name,
  115. old_value: old_value.ok(),
  116. }
  117. }
  118. }
  119. impl Drop for TempSetEnv<'_> {
  120. fn drop(&mut self) {
  121. match self.old_value {
  122. Some(ref val) => env::set_var(self.name, val),
  123. None => env::remove_var(self.name),
  124. }
  125. }
  126. }
  127. #[async_trait(?Send)]
  128. impl<U: AsRef<str>> CommandRunner for SetuidCommandRunner<U> {
  129. async fn run<'a>(&self, program: &str, args: &'a [&'a OsStr], input: &[u8]) -> IoResult<Output> {
  130. let uid = get_user_by_name(self.user_name.as_ref())
  131. .expect("User does not exist")
  132. .uid();
  133. let set_home = TempSetEnv::new("HOME", format!("/home/{}", self.user_name.as_ref()));
  134. let set_dbus = TempSetEnv::new("XDG_RUNTIME_DIR", format!("/run/user/{uid}"));
  135. //println!("{} {:?}", program, args);
  136. let mut child = Command::new(program)
  137. .args(args)
  138. .stdin(Stdio::piped())
  139. .uid(uid)
  140. .gid(uid)
  141. .stdout(Stdio::piped())
  142. .stderr(Stdio::piped())
  143. .spawn()
  144. .expect("Failed to spawn child process");
  145. let stdin = child.stdin.as_mut().expect("Failed to open stdin");
  146. stdin
  147. .write_all(input)
  148. .await
  149. .expect("Failed to write to stdin");
  150. let res = child.wait_with_output().await;
  151. drop(set_home);
  152. drop(set_dbus);
  153. //println!("{:?}", res);
  154. res
  155. }
  156. }
  157. #[cfg(test)]
  158. mod test {
  159. use crate::args;
  160. use crate::async_utils::run;
  161. use crate::command_runner::{CommandRunner, StdCommandRunner};
  162. use futures_util::future::FutureExt;
  163. use std::time::Instant;
  164. #[test]
  165. fn test() {
  166. let c = StdCommandRunner;
  167. run(async {
  168. let args = args!["1"];
  169. let start = Instant::now();
  170. let res = c.run("sleep", args, b"").fuse();
  171. let ps = c.run("ps", args![], b"").fuse();
  172. futures_util::pin_mut!(res, ps);
  173. loop {
  174. futures_util::select! {
  175. _ = res => {},
  176. _ = ps => assert!(start.elapsed().as_millis() < 1000),
  177. complete => break,
  178. }
  179. }
  180. });
  181. }
  182. }