1 use mozprofile::prefreader::PrefReaderError; 2 use mozprofile::profile::Profile; 3 use std::collections::HashMap; 4 use std::convert::From; 5 use std::error::Error; 6 use std::ffi::{OsStr, OsString}; 7 use std::fmt; 8 use std::io; 9 use std::io::ErrorKind; 10 use std::path::{Path, PathBuf}; 11 use std::process; 12 use std::process::{Child, Command, Stdio}; 13 use std::thread; 14 use std::time; 15 16 use crate::firefox_args::Arg; 17 18 pub trait Runner { 19 type Process; 20 arg<'a, S>(&'a mut self, arg: S) -> &'a mut Self where S: AsRef<OsStr>21 fn arg<'a, S>(&'a mut self, arg: S) -> &'a mut Self 22 where 23 S: AsRef<OsStr>; 24 args<'a, I, S>(&'a mut self, args: I) -> &'a mut Self where I: IntoIterator<Item = S>, S: AsRef<OsStr>25 fn args<'a, I, S>(&'a mut self, args: I) -> &'a mut Self 26 where 27 I: IntoIterator<Item = S>, 28 S: AsRef<OsStr>; 29 env<'a, K, V>(&'a mut self, key: K, value: V) -> &'a mut Self where K: AsRef<OsStr>, V: AsRef<OsStr>30 fn env<'a, K, V>(&'a mut self, key: K, value: V) -> &'a mut Self 31 where 32 K: AsRef<OsStr>, 33 V: AsRef<OsStr>; 34 envs<'a, I, K, V>(&'a mut self, envs: I) -> &'a mut Self where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>35 fn envs<'a, I, K, V>(&'a mut self, envs: I) -> &'a mut Self 36 where 37 I: IntoIterator<Item = (K, V)>, 38 K: AsRef<OsStr>, 39 V: AsRef<OsStr>; 40 stdout<'a, T>(&'a mut self, stdout: T) -> &'a mut Self where T: Into<Stdio>41 fn stdout<'a, T>(&'a mut self, stdout: T) -> &'a mut Self 42 where 43 T: Into<Stdio>; 44 stderr<'a, T>(&'a mut self, stderr: T) -> &'a mut Self where T: Into<Stdio>45 fn stderr<'a, T>(&'a mut self, stderr: T) -> &'a mut Self 46 where 47 T: Into<Stdio>; 48 start(self) -> Result<Self::Process, RunnerError>49 fn start(self) -> Result<Self::Process, RunnerError>; 50 } 51 52 pub trait RunnerProcess { 53 /// Attempts to collect the exit status of the process if it has already exited. 54 /// 55 /// This function will not block the calling thread and will only advisorily check to see if 56 /// the child process has exited or not. If the process has exited then on Unix the process ID 57 /// is reaped. This function is guaranteed to repeatedly return a successful exit status so 58 /// long as the child has already exited. 59 /// 60 /// If the process has exited, then `Ok(Some(status))` is returned. If the exit status is not 61 /// available at this time then `Ok(None)` is returned. If an error occurs, then that error is 62 /// returned. try_wait(&mut self) -> io::Result<Option<process::ExitStatus>>63 fn try_wait(&mut self) -> io::Result<Option<process::ExitStatus>>; 64 65 /// Waits for the process to exit completely, killing it if it does not stop within `timeout`, 66 /// and returns the status that it exited with. 67 /// 68 /// Firefox' integrated background monitor observes long running threads during shutdown and 69 /// kills these after 63 seconds. If the process fails to exit within the duration of 70 /// `timeout`, it is forcefully killed. 71 /// 72 /// This function will continue to have the same return value after it has been called at least 73 /// once. wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus>74 fn wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus>; 75 76 /// Determine if the process is still running. running(&mut self) -> bool77 fn running(&mut self) -> bool; 78 79 /// Forces the process to exit and returns the exit status. This is 80 /// equivalent to sending a SIGKILL on Unix platforms. kill(&mut self) -> io::Result<process::ExitStatus>81 fn kill(&mut self) -> io::Result<process::ExitStatus>; 82 } 83 84 #[derive(Debug)] 85 pub enum RunnerError { 86 Io(io::Error), 87 PrefReader(PrefReaderError), 88 } 89 90 impl fmt::Display for RunnerError { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result91 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 92 self.description().fmt(f) 93 } 94 } 95 96 impl Error for RunnerError { description(&self) -> &str97 fn description(&self) -> &str { 98 match *self { 99 RunnerError::Io(ref err) => match err.kind() { 100 ErrorKind::NotFound => "no such file or directory", 101 _ => err.description(), 102 }, 103 RunnerError::PrefReader(ref err) => err.description(), 104 } 105 } 106 cause(&self) -> Option<&dyn Error>107 fn cause(&self) -> Option<&dyn Error> { 108 Some(match *self { 109 RunnerError::Io(ref err) => err as &dyn Error, 110 RunnerError::PrefReader(ref err) => err as &dyn Error, 111 }) 112 } 113 } 114 115 impl From<io::Error> for RunnerError { from(value: io::Error) -> RunnerError116 fn from(value: io::Error) -> RunnerError { 117 RunnerError::Io(value) 118 } 119 } 120 121 impl From<PrefReaderError> for RunnerError { from(value: PrefReaderError) -> RunnerError122 fn from(value: PrefReaderError) -> RunnerError { 123 RunnerError::PrefReader(value) 124 } 125 } 126 127 #[derive(Debug)] 128 pub struct FirefoxProcess { 129 process: Child, 130 profile: Profile, 131 } 132 133 impl RunnerProcess for FirefoxProcess { try_wait(&mut self) -> io::Result<Option<process::ExitStatus>>134 fn try_wait(&mut self) -> io::Result<Option<process::ExitStatus>> { 135 self.process.try_wait() 136 } 137 wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus>138 fn wait(&mut self, timeout: time::Duration) -> io::Result<process::ExitStatus> { 139 let start = time::Instant::now(); 140 loop { 141 match self.try_wait() { 142 // child has already exited, reap its exit code 143 Ok(Some(status)) => return Ok(status), 144 145 // child still running and timeout elapsed, kill it 146 Ok(None) if start.elapsed() >= timeout => return self.kill(), 147 148 // child still running, let's give it more time 149 Ok(None) => thread::sleep(time::Duration::from_millis(100)), 150 151 Err(e) => return Err(e), 152 } 153 } 154 } 155 running(&mut self) -> bool156 fn running(&mut self) -> bool { 157 self.try_wait().unwrap().is_none() 158 } 159 kill(&mut self) -> io::Result<process::ExitStatus>160 fn kill(&mut self) -> io::Result<process::ExitStatus> { 161 debug!("Killing process {}", self.process.id()); 162 self.process.kill()?; 163 self.process.wait() 164 } 165 } 166 167 #[derive(Debug)] 168 pub struct FirefoxRunner { 169 path: PathBuf, 170 profile: Profile, 171 args: Vec<OsString>, 172 envs: HashMap<OsString, OsString>, 173 stdout: Option<Stdio>, 174 stderr: Option<Stdio>, 175 } 176 177 impl FirefoxRunner { 178 /// Initialise Firefox process runner. 179 /// 180 /// On macOS, `path` can optionally point to an application bundle, 181 /// i.e. _/Applications/Firefox.app_, as well as to an executable program 182 /// such as _/Applications/Firefox.app/Content/MacOS/firefox-bin_. new(path: &Path, profile: Profile) -> FirefoxRunner183 pub fn new(path: &Path, profile: Profile) -> FirefoxRunner { 184 let mut envs: HashMap<OsString, OsString> = HashMap::new(); 185 envs.insert("MOZ_NO_REMOTE".into(), "1".into()); 186 187 FirefoxRunner { 188 path: path.to_path_buf(), 189 envs: envs, 190 profile: profile, 191 args: vec![], 192 stdout: None, 193 stderr: None, 194 } 195 } 196 } 197 198 impl Runner for FirefoxRunner { 199 type Process = FirefoxProcess; 200 arg<'a, S>(&'a mut self, arg: S) -> &'a mut FirefoxRunner where S: AsRef<OsStr>,201 fn arg<'a, S>(&'a mut self, arg: S) -> &'a mut FirefoxRunner 202 where 203 S: AsRef<OsStr>, 204 { 205 self.args.push((&arg).into()); 206 self 207 } 208 args<'a, I, S>(&'a mut self, args: I) -> &'a mut FirefoxRunner where I: IntoIterator<Item = S>, S: AsRef<OsStr>,209 fn args<'a, I, S>(&'a mut self, args: I) -> &'a mut FirefoxRunner 210 where 211 I: IntoIterator<Item = S>, 212 S: AsRef<OsStr>, 213 { 214 for arg in args { 215 self.args.push((&arg).into()); 216 } 217 self 218 } 219 env<'a, K, V>(&'a mut self, key: K, value: V) -> &'a mut FirefoxRunner where K: AsRef<OsStr>, V: AsRef<OsStr>,220 fn env<'a, K, V>(&'a mut self, key: K, value: V) -> &'a mut FirefoxRunner 221 where 222 K: AsRef<OsStr>, 223 V: AsRef<OsStr>, 224 { 225 self.envs.insert((&key).into(), (&value).into()); 226 self 227 } 228 envs<'a, I, K, V>(&'a mut self, envs: I) -> &'a mut FirefoxRunner where I: IntoIterator<Item = (K, V)>, K: AsRef<OsStr>, V: AsRef<OsStr>,229 fn envs<'a, I, K, V>(&'a mut self, envs: I) -> &'a mut FirefoxRunner 230 where 231 I: IntoIterator<Item = (K, V)>, 232 K: AsRef<OsStr>, 233 V: AsRef<OsStr>, 234 { 235 for (key, value) in envs { 236 self.envs.insert((&key).into(), (&value).into()); 237 } 238 self 239 } 240 stdout<'a, T>(&'a mut self, stdout: T) -> &'a mut Self where T: Into<Stdio>,241 fn stdout<'a, T>(&'a mut self, stdout: T) -> &'a mut Self 242 where 243 T: Into<Stdio>, 244 { 245 self.stdout = Some(stdout.into()); 246 self 247 } 248 stderr<'a, T>(&'a mut self, stderr: T) -> &'a mut Self where T: Into<Stdio>,249 fn stderr<'a, T>(&'a mut self, stderr: T) -> &'a mut Self 250 where 251 T: Into<Stdio>, 252 { 253 self.stderr = Some(stderr.into()); 254 self 255 } 256 start(mut self) -> Result<FirefoxProcess, RunnerError>257 fn start(mut self) -> Result<FirefoxProcess, RunnerError> { 258 self.profile.user_prefs()?.write()?; 259 260 let stdout = self.stdout.unwrap_or_else(|| Stdio::inherit()); 261 let stderr = self.stderr.unwrap_or_else(|| Stdio::inherit()); 262 263 let binary_path = platform::resolve_binary_path(&mut self.path); 264 let mut cmd = Command::new(binary_path); 265 cmd.args(&self.args[..]) 266 .envs(&self.envs) 267 .stdout(stdout) 268 .stderr(stderr); 269 270 let mut seen_foreground = false; 271 let mut seen_no_remote = false; 272 let mut seen_profile = false; 273 for arg in self.args.iter() { 274 match arg.into() { 275 Arg::Foreground => seen_foreground = true, 276 Arg::NoRemote => seen_no_remote = true, 277 Arg::Profile | Arg::NamedProfile | Arg::ProfileManager => seen_profile = true, 278 Arg::Other(_) | Arg::None => {} 279 } 280 } 281 if !seen_foreground { 282 cmd.arg("-foreground"); 283 } 284 if !seen_no_remote { 285 cmd.arg("-no-remote"); 286 } 287 if !seen_profile { 288 cmd.arg("-profile").arg(&self.profile.path); 289 } 290 291 info!("Running command: {:?}", cmd); 292 let process = cmd.spawn()?; 293 Ok(FirefoxProcess { 294 process, 295 profile: self.profile, 296 }) 297 } 298 } 299 300 #[cfg(all(not(target_os = "macos"), unix))] 301 pub mod platform { 302 use path::find_binary; 303 use std::path::PathBuf; 304 resolve_binary_path(path: &mut PathBuf) -> &PathBuf305 pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf { 306 path 307 } 308 309 /// Searches the system path for `firefox`. firefox_default_path() -> Option<PathBuf>310 pub fn firefox_default_path() -> Option<PathBuf> { 311 find_binary("firefox") 312 } 313 arg_prefix_char(c: char) -> bool314 pub fn arg_prefix_char(c: char) -> bool { 315 c == '-' 316 } 317 } 318 319 #[cfg(target_os = "macos")] 320 pub mod platform { 321 use crate::path::{find_binary, is_binary}; 322 use dirs; 323 use plist::Value; 324 use std::path::PathBuf; 325 326 /// Searches for the binary file inside the path passed as parameter. 327 /// If the binary is not found, the path remains unaltered. 328 /// Else, it gets updated by the new binary path. resolve_binary_path(path: &mut PathBuf) -> &PathBuf329 pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf { 330 if path.as_path().is_dir() { 331 let mut info_plist = path.clone(); 332 info_plist.push("Contents"); 333 info_plist.push("Info.plist"); 334 if let Ok(plist) = Value::from_file(&info_plist) { 335 if let Some(dict) = plist.as_dictionary() { 336 if let Some(binary_file) = dict.get("CFBundleExecutable") { 337 match binary_file { 338 Value::String(s) => { 339 path.push("Contents"); 340 path.push("MacOS"); 341 path.push(s); 342 } 343 _ => {} 344 } 345 } 346 } 347 } 348 } 349 path 350 } 351 352 /// Searches the system path for `firefox-bin`, then looks for 353 /// `Applications/Firefox.app/Contents/MacOS/firefox-bin` as well 354 /// as `Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin` 355 /// under both `/` (system root) and the user home directory. firefox_default_path() -> Option<PathBuf>356 pub fn firefox_default_path() -> Option<PathBuf> { 357 if let Some(path) = find_binary("firefox-bin") { 358 return Some(path); 359 } 360 361 let home = dirs::home_dir(); 362 for &(prefix_home, trial_path) in [ 363 ( 364 false, 365 "/Applications/Firefox.app/Contents/MacOS/firefox-bin", 366 ), 367 (true, "Applications/Firefox.app/Contents/MacOS/firefox-bin"), 368 ( 369 false, 370 "/Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin", 371 ), 372 ( 373 true, 374 "Applications/Firefox Nightly.app/Contents/MacOS/firefox-bin", 375 ), 376 ] 377 .iter() 378 { 379 let path = match (home.as_ref(), prefix_home) { 380 (Some(ref home_dir), true) => home_dir.join(trial_path), 381 (None, true) => continue, 382 (_, false) => PathBuf::from(trial_path), 383 }; 384 if is_binary(&path) { 385 return Some(path); 386 } 387 } 388 389 None 390 } 391 arg_prefix_char(c: char) -> bool392 pub fn arg_prefix_char(c: char) -> bool { 393 c == '-' 394 } 395 } 396 397 #[cfg(target_os = "windows")] 398 pub mod platform { 399 use path::{find_binary, is_binary}; 400 use std::io::Error; 401 use std::path::PathBuf; 402 use winreg::enums::*; 403 use winreg::RegKey; 404 resolve_binary_path(path: &mut PathBuf) -> &PathBuf405 pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf { 406 path 407 } 408 409 /// Searches the Windows registry, then the system path for `firefox.exe`. 410 /// 411 /// It _does not_ currently check the `HKEY_CURRENT_USER` tree. firefox_default_path() -> Option<PathBuf>412 pub fn firefox_default_path() -> Option<PathBuf> { 413 if let Ok(Some(path)) = firefox_registry_path() { 414 if is_binary(&path) { 415 return Some(path); 416 } 417 }; 418 find_binary("firefox.exe") 419 } 420 firefox_registry_path() -> Result<Option<PathBuf>, Error>421 fn firefox_registry_path() -> Result<Option<PathBuf>, Error> { 422 let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); 423 for subtree_key in ["SOFTWARE", "SOFTWARE\\WOW6432Node"].iter() { 424 let subtree = hklm.open_subkey_with_flags(subtree_key, KEY_READ)?; 425 let mozilla_org = match subtree.open_subkey_with_flags("mozilla.org\\Mozilla", KEY_READ) 426 { 427 Ok(val) => val, 428 Err(_) => continue, 429 }; 430 let current_version: String = mozilla_org.get_value("CurrentVersion")?; 431 let mozilla = subtree.open_subkey_with_flags("Mozilla", KEY_READ)?; 432 for key_res in mozilla.enum_keys() { 433 let key = key_res?; 434 let section_data = mozilla.open_subkey_with_flags(&key, KEY_READ)?; 435 let version: Result<String, _> = section_data.get_value("GeckoVer"); 436 if let Ok(ver) = version { 437 if ver == current_version { 438 let mut bin_key = key.to_owned(); 439 bin_key.push_str("\\bin"); 440 if let Ok(bin_subtree) = mozilla.open_subkey_with_flags(bin_key, KEY_READ) { 441 let path_to_exe: Result<String, _> = bin_subtree.get_value("PathToExe"); 442 if let Ok(path_to_exe) = path_to_exe { 443 let path = PathBuf::from(path_to_exe); 444 if is_binary(&path) { 445 return Ok(Some(path)); 446 } 447 } 448 } 449 } 450 } 451 } 452 } 453 Ok(None) 454 } 455 arg_prefix_char(c: char) -> bool456 pub fn arg_prefix_char(c: char) -> bool { 457 c == '/' || c == '-' 458 } 459 } 460 461 #[cfg(not(any(unix, target_os = "windows")))] 462 pub mod platform { 463 use std::path::PathBuf; 464 465 /// Returns an unaltered path for all operating systems other than macOS. resolve_binary_path(path: &mut PathBuf) -> &PathBuf466 pub fn resolve_binary_path(path: &mut PathBuf) -> &PathBuf { 467 path 468 } 469 470 /// Returns `None` for all other operating systems than Linux, macOS, and 471 /// Windows. firefox_default_path() -> Option<PathBuf>472 pub fn firefox_default_path() -> Option<PathBuf> { 473 None 474 } 475 arg_prefix_char(c: char) -> bool476 pub fn arg_prefix_char(c: char) -> bool { 477 c == '-' 478 } 479 } 480