process.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. use std::{
  5. collections::HashMap,
  6. env,
  7. io::{BufRead, BufReader, Write},
  8. path::PathBuf,
  9. process::{exit, Command as StdCommand, Stdio},
  10. sync::Arc,
  11. };
  12. #[cfg(unix)]
  13. use std::os::unix::process::ExitStatusExt;
  14. #[cfg(windows)]
  15. use std::os::windows::process::CommandExt;
  16. #[cfg(windows)]
  17. const CREATE_NO_WINDOW: u32 = 0x0800_0000;
  18. use crate::async_runtime::{channel, spawn, Receiver, RwLock};
  19. use os_pipe::{pipe, PipeWriter};
  20. use serde::Serialize;
  21. use shared_child::SharedChild;
  22. use tauri_utils::platform;
  23. /// Payload for the `Terminated` command event.
  24. #[derive(Debug, Clone, Serialize)]
  25. pub struct TerminatedPayload {
  26. /// Exit code of the process.
  27. pub code: Option<i32>,
  28. /// If the process was terminated by a signal, represents that signal.
  29. pub signal: Option<i32>,
  30. }
  31. /// A event sent to the command callback.
  32. #[derive(Debug, Clone, Serialize)]
  33. #[serde(tag = "event", content = "payload")]
  34. pub enum CommandEvent {
  35. /// Stderr line.
  36. Stderr(String),
  37. /// Stdout line.
  38. Stdout(String),
  39. /// An error happened.
  40. Error(String),
  41. /// Command process terminated.
  42. Terminated(TerminatedPayload),
  43. }
  44. macro_rules! get_std_command {
  45. ($self: ident) => {{
  46. let mut command = StdCommand::new($self.program);
  47. command.args(&$self.args);
  48. command.stdout(Stdio::piped());
  49. command.stdin(Stdio::piped());
  50. command.stderr(Stdio::piped());
  51. if $self.env_clear {
  52. command.env_clear();
  53. }
  54. command.envs($self.env);
  55. if let Some(current_dir) = $self.current_dir {
  56. command.current_dir(current_dir);
  57. }
  58. #[cfg(windows)]
  59. command.creation_flags(CREATE_NO_WINDOW);
  60. command
  61. }};
  62. }
  63. /// Get the current binary
  64. pub fn current_binary() -> Option<PathBuf> {
  65. let mut current_binary = None;
  66. // if we are running with an APP Image, we should return the app image path
  67. #[cfg(target_os = "linux")]
  68. if let Some(app_image_path) = env::var_os("APPIMAGE") {
  69. current_binary = Some(PathBuf::from(app_image_path));
  70. }
  71. // if we didn't extracted binary in previous step,
  72. // let use the current_exe from current environment
  73. if current_binary.is_none() {
  74. if let Ok(current_process) = env::current_exe() {
  75. current_binary = Some(current_process);
  76. }
  77. }
  78. current_binary
  79. }
  80. /// Restart the process.
  81. pub fn restart() {
  82. if let Some(path) = current_binary() {
  83. StdCommand::new(path)
  84. .spawn()
  85. .expect("application failed to start");
  86. }
  87. exit(0);
  88. }
  89. /// API to spawn commands.
  90. pub struct Command {
  91. program: String,
  92. args: Vec<String>,
  93. env_clear: bool,
  94. env: HashMap<String, String>,
  95. current_dir: Option<PathBuf>,
  96. }
  97. /// Child spawned.
  98. pub struct CommandChild {
  99. inner: Arc<SharedChild>,
  100. stdin_writer: PipeWriter,
  101. }
  102. impl CommandChild {
  103. /// Write to process stdin.
  104. pub fn write(&mut self, buf: &[u8]) -> crate::api::Result<()> {
  105. self.stdin_writer.write_all(buf)?;
  106. Ok(())
  107. }
  108. /// Send a kill signal to the child.
  109. pub fn kill(self) -> crate::api::Result<()> {
  110. self.inner.kill()?;
  111. Ok(())
  112. }
  113. /// Returns the process pid.
  114. pub fn pid(&self) -> u32 {
  115. self.inner.id()
  116. }
  117. }
  118. /// Describes the result of a process after it has terminated.
  119. pub struct ExitStatus {
  120. code: Option<i32>,
  121. }
  122. impl ExitStatus {
  123. /// Returns the exit code of the process, if any.
  124. pub fn code(&self) -> Option<i32> {
  125. self.code
  126. }
  127. /// Was termination successful? Signal termination is not considered a success, and success is defined as a zero exit status.
  128. pub fn success(&self) -> bool {
  129. self.code == Some(0)
  130. }
  131. }
  132. /// The output of a finished process.
  133. pub struct Output {
  134. /// The status (exit code) of the process.
  135. pub status: ExitStatus,
  136. /// The data that the process wrote to stdout.
  137. pub stdout: String,
  138. /// The data that the process wrote to stderr.
  139. pub stderr: String,
  140. }
  141. #[cfg(not(windows))]
  142. fn relative_command_path(command: String) -> crate::Result<String> {
  143. match std::env::current_exe()?.parent() {
  144. Some(exe_dir) => Ok(format!(
  145. "{}/{}",
  146. exe_dir.to_string_lossy().to_string(),
  147. command
  148. )),
  149. None => Err(super::Error::Command("Could not evaluate executable dir".to_string()).into()),
  150. }
  151. }
  152. #[cfg(windows)]
  153. fn relative_command_path(command: String) -> crate::Result<String> {
  154. match std::env::current_exe()?.parent() {
  155. Some(exe_dir) => Ok(format!(
  156. "{}/{}.exe",
  157. exe_dir.to_string_lossy().to_string(),
  158. command
  159. )),
  160. None => Err(super::Error::Command("Could not evaluate executable dir".to_string()).into()),
  161. }
  162. }
  163. impl Command {
  164. /// Creates a new Command for launching the given program.
  165. pub fn new<S: Into<String>>(program: S) -> Self {
  166. Self {
  167. program: program.into(),
  168. args: Default::default(),
  169. env_clear: false,
  170. env: Default::default(),
  171. current_dir: None,
  172. }
  173. }
  174. /// Creates a new Command for launching the given sidecar program.
  175. pub fn new_sidecar<S: Into<String>>(program: S) -> crate::Result<Self> {
  176. let program = format!(
  177. "{}-{}",
  178. program.into(),
  179. platform::target_triple().expect("unsupported platform")
  180. );
  181. Ok(Self::new(relative_command_path(program)?))
  182. }
  183. /// Append args to the command.
  184. pub fn args<I, S>(mut self, args: I) -> Self
  185. where
  186. I: IntoIterator<Item = S>,
  187. S: AsRef<str>,
  188. {
  189. for arg in args {
  190. self.args.push(arg.as_ref().to_string());
  191. }
  192. self
  193. }
  194. /// Clears the entire environment map for the child process.
  195. pub fn env_clear(mut self) -> Self {
  196. self.env_clear = true;
  197. self
  198. }
  199. /// Adds or updates multiple environment variable mappings.
  200. pub fn envs(mut self, env: HashMap<String, String>) -> Self {
  201. self.env = env;
  202. self
  203. }
  204. /// Sets the working directory for the child process.
  205. pub fn current_dir(mut self, current_dir: PathBuf) -> Self {
  206. self.current_dir.replace(current_dir);
  207. self
  208. }
  209. /// Spawns the command.
  210. pub fn spawn(self) -> crate::api::Result<(Receiver<CommandEvent>, CommandChild)> {
  211. let mut command = get_std_command!(self);
  212. let (stdout_reader, stdout_writer) = pipe()?;
  213. let (stderr_reader, stderr_writer) = pipe()?;
  214. let (stdin_reader, stdin_writer) = pipe()?;
  215. command.stdout(stdout_writer);
  216. command.stderr(stderr_writer);
  217. command.stdin(stdin_reader);
  218. let shared_child = SharedChild::spawn(&mut command)?;
  219. let child = Arc::new(shared_child);
  220. let child_ = child.clone();
  221. let guard = Arc::new(RwLock::new(()));
  222. let (tx, rx) = channel(1);
  223. let tx_ = tx.clone();
  224. let guard_ = guard.clone();
  225. spawn(async move {
  226. let _lock = guard_.read().await;
  227. let reader = BufReader::new(stdout_reader);
  228. for line in reader.lines() {
  229. let _ = match line {
  230. Ok(line) => tx_.send(CommandEvent::Stdout(line)).await,
  231. Err(e) => tx_.send(CommandEvent::Error(e.to_string())).await,
  232. };
  233. }
  234. });
  235. let tx_ = tx.clone();
  236. let guard_ = guard.clone();
  237. spawn(async move {
  238. let _lock = guard_.read().await;
  239. let reader = BufReader::new(stderr_reader);
  240. for line in reader.lines() {
  241. let _ = match line {
  242. Ok(line) => tx_.send(CommandEvent::Stderr(line)).await,
  243. Err(e) => tx_.send(CommandEvent::Error(e.to_string())).await,
  244. };
  245. }
  246. });
  247. spawn(async move {
  248. let _ = match child_.wait() {
  249. Ok(status) => {
  250. guard.write().await;
  251. tx.send(CommandEvent::Terminated(TerminatedPayload {
  252. code: status.code(),
  253. #[cfg(windows)]
  254. signal: None,
  255. #[cfg(unix)]
  256. signal: status.signal(),
  257. }))
  258. .await
  259. }
  260. Err(e) => {
  261. guard.write().await;
  262. tx.send(CommandEvent::Error(e.to_string())).await
  263. }
  264. };
  265. });
  266. Ok((
  267. rx,
  268. CommandChild {
  269. inner: child,
  270. stdin_writer,
  271. },
  272. ))
  273. }
  274. /// Executes a command as a child process, waiting for it to finish and collecting its exit status.
  275. /// Stdin, stdout and stderr are ignored.
  276. pub fn status(self) -> crate::api::Result<ExitStatus> {
  277. let (mut rx, _child) = self.spawn()?;
  278. let code = crate::async_runtime::block_on(async move {
  279. let mut code = None;
  280. while let Some(event) = rx.recv().await {
  281. if let CommandEvent::Terminated(payload) = event {
  282. code = payload.code;
  283. }
  284. }
  285. code
  286. });
  287. Ok(ExitStatus { code })
  288. }
  289. /// Executes the command as a child process, waiting for it to finish and collecting all of its output.
  290. /// Stdin is ignored.
  291. pub fn output(self) -> crate::api::Result<Output> {
  292. let (mut rx, _child) = self.spawn()?;
  293. let output = crate::async_runtime::block_on(async move {
  294. let mut code = None;
  295. let mut stdout = String::new();
  296. let mut stderr = String::new();
  297. while let Some(event) = rx.recv().await {
  298. match event {
  299. CommandEvent::Terminated(payload) => {
  300. code = payload.code;
  301. }
  302. CommandEvent::Stdout(line) => {
  303. stdout.push_str(line.as_str());
  304. stdout.push('\n');
  305. }
  306. CommandEvent::Stderr(line) => {
  307. stderr.push_str(line.as_str());
  308. stderr.push('\n');
  309. }
  310. CommandEvent::Error(_) => {}
  311. }
  312. }
  313. Output {
  314. status: ExitStatus { code },
  315. stdout,
  316. stderr,
  317. }
  318. });
  319. Ok(output)
  320. }
  321. }
  322. // tests for the commands functions.
  323. #[cfg(test)]
  324. mod test {
  325. use super::*;
  326. #[cfg(not(windows))]
  327. #[test]
  328. fn test_cmd_output() {
  329. // create a command to run cat.
  330. let cmd = Command::new("cat").args(&["test/api/test.txt"]);
  331. let (mut rx, _) = cmd.spawn().unwrap();
  332. crate::async_runtime::block_on(async move {
  333. while let Some(event) = rx.recv().await {
  334. match event {
  335. CommandEvent::Terminated(payload) => {
  336. assert_eq!(payload.code, Some(0));
  337. }
  338. CommandEvent::Stdout(line) => {
  339. assert_eq!(line, "This is a test doc!".to_string());
  340. }
  341. _ => {}
  342. }
  343. }
  344. });
  345. }
  346. #[cfg(not(windows))]
  347. #[test]
  348. // test the failure case
  349. fn test_cmd_fail() {
  350. let cmd = Command::new("cat").args(&["test/api/"]);
  351. let (mut rx, _) = cmd.spawn().unwrap();
  352. crate::async_runtime::block_on(async move {
  353. while let Some(event) = rx.recv().await {
  354. match event {
  355. CommandEvent::Terminated(payload) => {
  356. assert_eq!(payload.code, Some(1));
  357. }
  358. CommandEvent::Stderr(line) => {
  359. assert_eq!(line, "cat: test/api/: Is a directory".to_string());
  360. }
  361. _ => {}
  362. }
  363. }
  364. });
  365. }
  366. }