1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
use std::io::{BufRead, BufReader}; use std::ops::{Deref, DerefMut}; use std::process::{Child, Command, Stdio}; /// AutoKillChild is kind of bag which contains `Child`. /// It makes it automatically commit suicide after it gets dropped. /// /// It's designed to be used with tor running in rust application. AKC guarantees killing tor application on exit. /// Note: It ignores process killing error in Drop. pub struct AutoKillChild { child: Option<Child>, } impl From<Child> for AutoKillChild { fn from(c: Child) -> Self{ Self::new(c) } } impl AutoKillChild { pub fn new(c: Child) -> Self{ Self{ child: Some(c) } } /// into_inner takes child from AutoKillChild. /// It prevents child from dying automatically after it's dropped. pub fn into_inner(mut self) -> Child { self.child.take().unwrap() } } impl Drop for AutoKillChild { fn drop(&mut self) { if let Some(c) = &mut self.child { // do not unwrap. Process might have died already. let _ = c.kill(); } } } impl Deref for AutoKillChild { type Target = Child; #[inline] fn deref(&self) -> &Self::Target { self.child.as_ref().unwrap() } } impl DerefMut for AutoKillChild { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { self.child.as_mut().unwrap() } } // TODO(teawithsand): add bootstrapping runner here /// run_tor runs new tor from specified path with specified args. /// It should not be used when control port is disabled. /// /// # Parameters /// * `path` - path to run tor binary. Note: if not found rust will query $PATH see docs for `std::process::Command::new` /// * `args` - cli args provided to tor binary in raw form - array of strings. Format should be like: ["--DisableNetwork", "1"] /// /// For arguments reference take a look at: https://www.torproject.org/docs/tor-manual.html.en /// /// # Common parameters /// 1. CookieAuthentication 1 - enables cookie authentication, since null may not be safe in some contexts /// 2. ControlPort PORT - sets control port which should be used by tor controller, like torut, to controll this instance of tor. /// /// # Result detection note /// It exists after finding "Opened Control listener" in the stdout. /// Tor may *not* print such text to stdout. In that case this function will never exit(unless tor process dies). /// /// # Stdout note /// This function uses `std::io::BufReader` to read data from stdout in order to decide if tor is running or not. /// Dropping buf_reader drops it's internal buffer with data, which may cause partial data loss. /// /// For most cases it's fine, so it probably won't be fixed. /// Alternative to this is char-by-char reading which is slower but should be also fine here. pub fn run_tor<A, T, P>(path: P, args: A) -> Result<Child, std::io::Error> where A: AsRef<[T]>, T: AsRef<str>, P: AsRef<str>, { let path = path.as_ref(); let mut c = Command::new(path) .args(args.as_ref().iter().map(|t| t.as_ref())) // .env_clear() .stdout(Stdio::piped()) .stderr(Stdio::piped()) .stdin(Stdio::piped()) .spawn()?; { // Stdio is piped so this works { let mut stdout = BufReader::new(c.stdout.as_mut().unwrap()); loop { // wait until tor starts // hacky but works // stem does something simmilar internally but they use regexes to match bootstrapping messages from tor // // https://stem.torproject.org/_modules/stem/process.html#launch_tor let mut l = String::new(); match stdout.read_line(&mut l) { Ok(v) => v, Err(e) => { // kill if tor process hasn't died already // this should make sure that tor process is not alive *almost* always let _ = c.kill(); return Err(e); } }; if l.contains("Opened Control listener") { break; } } // buffered stdout is dropped here. // It may cause partial data loss but it's better than dropping child. } } Ok(c) } // TODO(teaiwthsand): async run_tor // tests for these are in testing.rs