command.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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. io::{BufReader, Write},
  7. path::PathBuf,
  8. process::{Command as StdCommand, Stdio},
  9. sync::{Arc, Mutex, RwLock},
  10. thread::spawn,
  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::{block_on as block_on_task, channel, Receiver, Sender};
  19. pub use encoding_rs::Encoding;
  20. use os_pipe::{pipe, PipeReader, PipeWriter};
  21. use serde::Serialize;
  22. use shared_child::SharedChild;
  23. use tauri_utils::platform;
  24. type ChildStore = Arc<Mutex<HashMap<u32, Arc<SharedChild>>>>;
  25. fn commands() -> &'static ChildStore {
  26. use once_cell::sync::Lazy;
  27. static STORE: Lazy<ChildStore> = Lazy::new(Default::default);
  28. &STORE
  29. }
  30. /// Kills all child processes created with [`Command`].
  31. /// By default it's called before the [`crate::App`] exits.
  32. pub fn kill_children() {
  33. let commands = commands().lock().unwrap();
  34. let children = commands.values();
  35. for child in children {
  36. let _ = child.kill();
  37. }
  38. }
  39. /// Payload for the [`CommandEvent::Terminated`] command event.
  40. #[derive(Debug, Clone, Serialize)]
  41. pub struct TerminatedPayload {
  42. /// Exit code of the process.
  43. pub code: Option<i32>,
  44. /// If the process was terminated by a signal, represents that signal.
  45. pub signal: Option<i32>,
  46. }
  47. /// A event sent to the command callback.
  48. #[derive(Debug, Clone, Serialize)]
  49. #[serde(tag = "event", content = "payload")]
  50. #[non_exhaustive]
  51. pub enum CommandEvent {
  52. /// Stderr bytes until a newline (\n) or carriage return (\r) is found.
  53. Stderr(String),
  54. /// Stdout bytes until a newline (\n) or carriage return (\r) is found.
  55. Stdout(String),
  56. /// An error happened waiting for the command to finish or converting the stdout/stderr bytes to an UTF-8 string.
  57. Error(String),
  58. /// Command process terminated.
  59. Terminated(TerminatedPayload),
  60. }
  61. /// The type to spawn commands.
  62. #[derive(Debug)]
  63. pub struct Command {
  64. program: String,
  65. args: Vec<String>,
  66. env_clear: bool,
  67. env: HashMap<String, String>,
  68. current_dir: Option<PathBuf>,
  69. encoding: Option<&'static Encoding>,
  70. }
  71. /// Spawned child process.
  72. #[derive(Debug)]
  73. pub struct CommandChild {
  74. inner: Arc<SharedChild>,
  75. stdin_writer: PipeWriter,
  76. }
  77. impl CommandChild {
  78. /// Writes to process stdin.
  79. pub fn write(&mut self, buf: &[u8]) -> crate::api::Result<()> {
  80. self.stdin_writer.write_all(buf)?;
  81. Ok(())
  82. }
  83. /// Sends a kill signal to the child.
  84. pub fn kill(self) -> crate::api::Result<()> {
  85. self.inner.kill()?;
  86. Ok(())
  87. }
  88. /// Returns the process pid.
  89. pub fn pid(&self) -> u32 {
  90. self.inner.id()
  91. }
  92. }
  93. /// Describes the result of a process after it has terminated.
  94. #[derive(Debug)]
  95. pub struct ExitStatus {
  96. code: Option<i32>,
  97. }
  98. impl ExitStatus {
  99. /// Returns the exit code of the process, if any.
  100. pub fn code(&self) -> Option<i32> {
  101. self.code
  102. }
  103. /// Returns true if exit status is zero. Signal termination is not considered a success, and success is defined as a zero exit status.
  104. pub fn success(&self) -> bool {
  105. self.code == Some(0)
  106. }
  107. }
  108. /// The output of a finished process.
  109. #[derive(Debug)]
  110. pub struct Output {
  111. /// The status (exit code) of the process.
  112. pub status: ExitStatus,
  113. /// The data that the process wrote to stdout.
  114. pub stdout: String,
  115. /// The data that the process wrote to stderr.
  116. pub stderr: String,
  117. }
  118. fn relative_command_path(command: String) -> crate::Result<String> {
  119. match platform::current_exe()?.parent() {
  120. #[cfg(windows)]
  121. Some(exe_dir) => Ok(format!("{}\\{}.exe", exe_dir.display(), command)),
  122. #[cfg(not(windows))]
  123. Some(exe_dir) => Ok(format!("{}/{}", exe_dir.display(), command)),
  124. None => Err(crate::api::Error::Command("Could not evaluate executable dir".to_string()).into()),
  125. }
  126. }
  127. impl From<Command> for StdCommand {
  128. fn from(cmd: Command) -> StdCommand {
  129. let mut command = StdCommand::new(cmd.program);
  130. command.args(cmd.args);
  131. command.stdout(Stdio::piped());
  132. command.stdin(Stdio::piped());
  133. command.stderr(Stdio::piped());
  134. if cmd.env_clear {
  135. command.env_clear();
  136. }
  137. command.envs(cmd.env);
  138. if let Some(current_dir) = cmd.current_dir {
  139. command.current_dir(current_dir);
  140. }
  141. #[cfg(windows)]
  142. command.creation_flags(CREATE_NO_WINDOW);
  143. command
  144. }
  145. }
  146. impl Command {
  147. /// Creates a new Command for launching the given program.
  148. pub fn new<S: Into<String>>(program: S) -> Self {
  149. Self {
  150. program: program.into(),
  151. args: Default::default(),
  152. env_clear: false,
  153. env: Default::default(),
  154. current_dir: None,
  155. encoding: None,
  156. }
  157. }
  158. /// Creates a new Command for launching the given sidecar program.
  159. ///
  160. /// A sidecar program is a embedded external binary in order to make your application work
  161. /// or to prevent users having to install additional dependencies (e.g. Node.js, Python, etc).
  162. pub fn new_sidecar<S: Into<String>>(program: S) -> crate::Result<Self> {
  163. Ok(Self::new(relative_command_path(program.into())?))
  164. }
  165. /// Appends arguments to the command.
  166. #[must_use]
  167. pub fn args<I, S>(mut self, args: I) -> Self
  168. where
  169. I: IntoIterator<Item = S>,
  170. S: AsRef<str>,
  171. {
  172. for arg in args {
  173. self.args.push(arg.as_ref().to_string());
  174. }
  175. self
  176. }
  177. /// Clears the entire environment map for the child process.
  178. #[must_use]
  179. pub fn env_clear(mut self) -> Self {
  180. self.env_clear = true;
  181. self
  182. }
  183. /// Adds or updates multiple environment variable mappings.
  184. #[must_use]
  185. pub fn envs(mut self, env: HashMap<String, String>) -> Self {
  186. self.env = env;
  187. self
  188. }
  189. /// Sets the working directory for the child process.
  190. #[must_use]
  191. pub fn current_dir(mut self, current_dir: PathBuf) -> Self {
  192. self.current_dir.replace(current_dir);
  193. self
  194. }
  195. /// Sets the character encoding for stdout/stderr.
  196. #[must_use]
  197. pub fn encoding(mut self, encoding: &'static Encoding) -> Self {
  198. self.encoding.replace(encoding);
  199. self
  200. }
  201. /// Spawns the command.
  202. ///
  203. /// # Examples
  204. ///
  205. /// ```rust,no_run
  206. /// use tauri::api::process::{Command, CommandEvent};
  207. /// tauri::async_runtime::spawn(async move {
  208. /// let (mut rx, mut child) = Command::new("cargo")
  209. /// .args(["tauri", "dev"])
  210. /// .spawn()
  211. /// .expect("Failed to spawn cargo");
  212. ///
  213. /// let mut i = 0;
  214. /// while let Some(event) = rx.recv().await {
  215. /// if let CommandEvent::Stdout(line) = event {
  216. /// println!("got: {}", line);
  217. /// i += 1;
  218. /// if i == 4 {
  219. /// child.write("message from Rust\n".as_bytes()).unwrap();
  220. /// i = 0;
  221. /// }
  222. /// }
  223. /// }
  224. /// });
  225. /// ```
  226. pub fn spawn(self) -> crate::api::Result<(Receiver<CommandEvent>, CommandChild)> {
  227. let encoding = self.encoding;
  228. let mut command: StdCommand = self.into();
  229. let (stdout_reader, stdout_writer) = pipe()?;
  230. let (stderr_reader, stderr_writer) = pipe()?;
  231. let (stdin_reader, stdin_writer) = pipe()?;
  232. command.stdout(stdout_writer);
  233. command.stderr(stderr_writer);
  234. command.stdin(stdin_reader);
  235. let shared_child = SharedChild::spawn(&mut command)?;
  236. let child = Arc::new(shared_child);
  237. let child_ = child.clone();
  238. let guard = Arc::new(RwLock::new(()));
  239. commands().lock().unwrap().insert(child.id(), child.clone());
  240. let (tx, rx) = channel(1);
  241. spawn_pipe_reader(
  242. tx.clone(),
  243. guard.clone(),
  244. stdout_reader,
  245. CommandEvent::Stdout,
  246. encoding,
  247. );
  248. spawn_pipe_reader(
  249. tx.clone(),
  250. guard.clone(),
  251. stderr_reader,
  252. CommandEvent::Stderr,
  253. encoding,
  254. );
  255. spawn(move || {
  256. let _ = match child_.wait() {
  257. Ok(status) => {
  258. let _l = guard.write().unwrap();
  259. commands().lock().unwrap().remove(&child_.id());
  260. block_on_task(async move {
  261. tx.send(CommandEvent::Terminated(TerminatedPayload {
  262. code: status.code(),
  263. #[cfg(windows)]
  264. signal: None,
  265. #[cfg(unix)]
  266. signal: status.signal(),
  267. }))
  268. .await
  269. })
  270. }
  271. Err(e) => {
  272. let _l = guard.write().unwrap();
  273. block_on_task(async move { tx.send(CommandEvent::Error(e.to_string())).await })
  274. }
  275. };
  276. });
  277. Ok((
  278. rx,
  279. CommandChild {
  280. inner: child,
  281. stdin_writer,
  282. },
  283. ))
  284. }
  285. /// Executes a command as a child process, waiting for it to finish and collecting its exit status.
  286. /// Stdin, stdout and stderr are ignored.
  287. ///
  288. /// # Examples
  289. /// ```rust,no_run
  290. /// use tauri::api::process::Command;
  291. /// let status = Command::new("which").args(["ls"]).status().unwrap();
  292. /// println!("`which` finished with status: {:?}", status.code());
  293. /// ```
  294. pub fn status(self) -> crate::api::Result<ExitStatus> {
  295. let (mut rx, _child) = self.spawn()?;
  296. let code = crate::async_runtime::safe_block_on(async move {
  297. let mut code = None;
  298. #[allow(clippy::collapsible_match)]
  299. while let Some(event) = rx.recv().await {
  300. if let CommandEvent::Terminated(payload) = event {
  301. code = payload.code;
  302. }
  303. }
  304. code
  305. });
  306. Ok(ExitStatus { code })
  307. }
  308. /// Executes the command as a child process, waiting for it to finish and collecting all of its output.
  309. /// Stdin is ignored.
  310. ///
  311. /// # Examples
  312. ///
  313. /// ```rust,no_run
  314. /// use tauri::api::process::Command;
  315. /// let output = Command::new("echo").args(["TAURI"]).output().unwrap();
  316. /// assert!(output.status.success());
  317. /// assert_eq!(output.stdout, "TAURI");
  318. /// ```
  319. pub fn output(self) -> crate::api::Result<Output> {
  320. let (mut rx, _child) = self.spawn()?;
  321. let output = crate::async_runtime::safe_block_on(async move {
  322. let mut code = None;
  323. let mut stdout = String::new();
  324. let mut stderr = String::new();
  325. while let Some(event) = rx.recv().await {
  326. match event {
  327. CommandEvent::Terminated(payload) => {
  328. code = payload.code;
  329. }
  330. CommandEvent::Stdout(line) => {
  331. stdout.push_str(line.as_str());
  332. stdout.push('\n');
  333. }
  334. CommandEvent::Stderr(line) => {
  335. stderr.push_str(line.as_str());
  336. stderr.push('\n');
  337. }
  338. CommandEvent::Error(_) => {}
  339. }
  340. }
  341. Output {
  342. status: ExitStatus { code },
  343. stdout,
  344. stderr,
  345. }
  346. });
  347. Ok(output)
  348. }
  349. }
  350. fn spawn_pipe_reader<F: Fn(String) -> CommandEvent + Send + Copy + 'static>(
  351. tx: Sender<CommandEvent>,
  352. guard: Arc<RwLock<()>>,
  353. pipe_reader: PipeReader,
  354. wrapper: F,
  355. character_encoding: Option<&'static Encoding>,
  356. ) {
  357. spawn(move || {
  358. let _lock = guard.read().unwrap();
  359. let mut reader = BufReader::new(pipe_reader);
  360. let mut buf = Vec::new();
  361. loop {
  362. buf.clear();
  363. match tauri_utils::io::read_line(&mut reader, &mut buf) {
  364. Ok(n) => {
  365. if n == 0 {
  366. break;
  367. }
  368. let tx_ = tx.clone();
  369. let line = match character_encoding {
  370. Some(encoding) => Ok(encoding.decode_with_bom_removal(&buf).0.into()),
  371. None => String::from_utf8(buf.clone()),
  372. };
  373. block_on_task(async move {
  374. let _ = match line {
  375. Ok(line) => tx_.send(wrapper(line)).await,
  376. Err(e) => tx_.send(CommandEvent::Error(e.to_string())).await,
  377. };
  378. });
  379. }
  380. Err(e) => {
  381. let tx_ = tx.clone();
  382. let _ = block_on_task(async move { tx_.send(CommandEvent::Error(e.to_string())).await });
  383. }
  384. }
  385. }
  386. });
  387. }
  388. // tests for the commands functions.
  389. #[cfg(test)]
  390. mod test {
  391. #[cfg(not(windows))]
  392. use super::*;
  393. #[cfg(not(windows))]
  394. #[test]
  395. fn test_cmd_output() {
  396. // create a command to run cat.
  397. let cmd = Command::new("cat").args(&["test/api/test.txt"]);
  398. let (mut rx, _) = cmd.spawn().unwrap();
  399. crate::async_runtime::block_on(async move {
  400. while let Some(event) = rx.recv().await {
  401. match event {
  402. CommandEvent::Terminated(payload) => {
  403. assert_eq!(payload.code, Some(0));
  404. }
  405. CommandEvent::Stdout(line) => {
  406. assert_eq!(line, "This is a test doc!".to_string());
  407. }
  408. _ => {}
  409. }
  410. }
  411. });
  412. }
  413. #[cfg(not(windows))]
  414. #[test]
  415. // test the failure case
  416. fn test_cmd_fail() {
  417. let cmd = Command::new("cat").args(&["test/api/"]);
  418. let (mut rx, _) = cmd.spawn().unwrap();
  419. crate::async_runtime::block_on(async move {
  420. while let Some(event) = rx.recv().await {
  421. match event {
  422. CommandEvent::Terminated(payload) => {
  423. assert_eq!(payload.code, Some(1));
  424. }
  425. CommandEvent::Stderr(line) => {
  426. assert_eq!(line, "cat: test/api/: Is a directory".to_string());
  427. }
  428. _ => {}
  429. }
  430. }
  431. });
  432. }
  433. }