diff options
| -rw-r--r-- | Cargo.lock | 2 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | src/lib.rs | 3 | ||||
| -rw-r--r-- | src/net.rs | 74 | ||||
| -rw-r--r-- | src/process/monitor.rs | 77 | ||||
| -rw-r--r-- | talpid_cli/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid_cli/src/main.rs | 54 | ||||
| -rw-r--r-- | talpid_openvpn_plugin/src/ffi/parse.rs | 2 |
8 files changed, 102 insertions, 112 deletions
diff --git a/Cargo.lock b/Cargo.lock index a13f3df512..4fb8fb2009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,7 @@ name = "talpid_cli" version = "0.0.0" dependencies = [ "clap 2.20.3 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "talpid_core 0.0.0", ] @@ -138,6 +139,7 @@ version = "0.0.0" dependencies = [ "assert_matches 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "clonablechild 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 902b80ead6..f0bba952ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Linus Färnstrand <linus@mullvad.net>"] [dependencies] clonablechild = "0.1" +error-chain = "0.8" [dev-dependencies] assert_matches = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 81225be473..f8e5e96a46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,9 @@ extern crate assert_matches; extern crate clonablechild; +#[macro_use] +extern crate error_chain; + /// Working with processes. pub mod process; diff --git a/src/net.rs b/src/net.rs index 2f5d98e29a..fb119ea8af 100644 --- a/src/net.rs +++ b/src/net.rs @@ -1,14 +1,24 @@ -use std::error::Error; use std::fmt; use std::io; use std::iter; use std::net::SocketAddr; -use std::num::ParseIntError; use std::option; use std::slice; use std::str::FromStr; use std::vec; + +error_chain! { + errors { + /// Error indicating parsing the address failed + AddrParse(s: String) { + description("Invalid address format") + display("Unable to parse address. {}", s) + } + } +} + + /// Representation of a TCP or UDP endpoint. The IP level address is represented by either an IP /// directly or a hostname/domain. The IP level address together with a port becomes a socket /// address. @@ -44,19 +54,23 @@ impl RemoteAddr { } } - fn from_domain_str(s: &str) -> Result<Self, AddrParseError> { + fn from_domain_str(s: &str) -> Result<Self> { let (address, port_str) = Self::split_at_last_colon(s)?; - let port = u16::from_str(port_str)?; + let port = u16::from_str(port_str).chain_err(|| { + ErrorKind::AddrParse(format!("Invalid port: \"{}\"", port_str)) + })?; if address.is_empty() || address.contains(':') { - return Err(AddrParseError(())); + let msg = format!("Invalid IP or domain: \"{}\"", address); + return Err(ErrorKind::AddrParse(msg).into()); } Ok(RemoteAddr::Domain(address.to_owned(), port)) } - fn split_at_last_colon(s: &str) -> Result<(&str, &str), AddrParseError> { + fn split_at_last_colon(s: &str) -> Result<(&str, &str)> { let mut iter = s.rsplitn(2, ':'); let port = iter.next().unwrap(); - let address = iter.next().ok_or(AddrParseError(()))?; + let address = iter.next() + .ok_or_else(|| Error::from(ErrorKind::AddrParse("No colon".to_owned())))?; Ok((address, port)) } } @@ -68,8 +82,8 @@ impl From<SocketAddr> for RemoteAddr { } impl FromStr for RemoteAddr { - type Err = AddrParseError; - fn from_str(s: &str) -> Result<Self, Self::Err> { + type Err = Error; + fn from_str(s: &str) -> Result<Self> { if let Ok(addr) = SocketAddr::from_str(s) { Ok(RemoteAddr::from(addr)) } else { @@ -78,7 +92,6 @@ impl FromStr for RemoteAddr { } } - impl fmt::Display for RemoteAddr { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match *self { @@ -88,29 +101,6 @@ impl fmt::Display for RemoteAddr { } } -/// Representation of the errors that can happen when parsing a string into a `RemoteAddr`. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct AddrParseError(()); - -impl From<ParseIntError> for AddrParseError { - fn from(_: ParseIntError) -> Self { - AddrParseError(()) - } -} - -impl fmt::Display for AddrParseError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str(self.description()) - } -} - -impl Error for AddrParseError { - fn description(&self) -> &str { - "Invalid remote address format" - } -} - - /// A trait for objects which can be converted to one or more `RemoteAddr` values. pub trait ToRemoteAddrs { /// Returned iterator over remote addresses which this type may correspond @@ -200,7 +190,8 @@ mod remote_addr_tests { #[test] fn from_ipv6_str_without_brackets() { - assert!(RemoteAddr::from_str("fe80::1:1337").is_err()); + let result = RemoteAddr::from_str("fe80::1:1337"); + assert_matches!(result, Err(Error(ErrorKind::AddrParse(_), _))); } #[test] @@ -212,27 +203,32 @@ mod remote_addr_tests { #[test] fn from_ipv6_str_without_port() { - assert!(RemoteAddr::from_str("fe80::1").is_err()); + let result = RemoteAddr::from_str("fe80::1"); + assert_matches!(result, Err(Error(ErrorKind::AddrParse(_), _))); } #[test] fn from_str_no_colon() { - assert!(RemoteAddr::from_str("example.com").is_err()); + let result = RemoteAddr::from_str("example.com"); + assert_matches!(result, Err(Error(ErrorKind::AddrParse(_), _))); } #[test] fn from_str_invalid_port_large() { - assert!(RemoteAddr::from_str("example.com:99999").is_err()); + let result = RemoteAddr::from_str("example.com:99999"); + assert_matches!(result, Err(Error(ErrorKind::AddrParse(_), _))); } #[test] fn from_str_empty_address() { - assert!(RemoteAddr::from_str(":100").is_err()); + let result = RemoteAddr::from_str(":100"); + assert_matches!(result, Err(Error(ErrorKind::AddrParse(_), _))); } #[test] fn from_str_empty_port() { - assert!(RemoteAddr::from_str("example.com:").is_err()); + let result = RemoteAddr::from_str("example.com:"); + assert_matches!(result, Err(Error(ErrorKind::AddrParse(_), _))); } #[test] diff --git a/src/process/monitor.rs b/src/process/monitor.rs index e8d32a7973..8caac5cc79 100644 --- a/src/process/monitor.rs +++ b/src/process/monitor.rs @@ -1,11 +1,27 @@ -use std::error::Error; -use std::fmt; use std::io; use std::process::{ChildStdout, ChildStderr}; use std::sync::{Arc, Mutex}; use std::thread; +error_chain! { + errors { + /// The transition could not be made because the state machine was not in a state that + /// could transition to the desired state. + InvalidState { + description("Invalid state for desired transition") + } + /// Error representing a failure in spawning the child process + Spawn { + description("Unable to spawn child process") + } + /// Error representing a failure in sending a kill signal to the child process + Kill { + description("Unable to send kill signal to process") + } + } +} + /// Trait for objects that represent child processes that `ChildMonitor` can monitor pub trait MonitoredChild: Clone + Send + 'static { /// Waits for the child to exit completely, returning if the child exited cleanly or not. @@ -31,49 +47,6 @@ pub trait ChildSpawner: Send + 'static { } -/// Type alias for results of transitions in the `ChildMonitor` state machine. -pub type TransitionResult<T> = Result<T, TransitionError>; - -/// Error type for transitions in the `ChildMonitor` state machine. -#[derive(Debug)] -pub enum TransitionError { - /// The transition could not be made because the state machine was not in a state that could - /// transition to the desired state. - InvalidState, - - /// The transition failed because of an `io::Error`. - IoError(io::Error), -} - -impl From<io::Error> for TransitionError { - fn from(error: io::Error) -> Self { - TransitionError::IoError(error) - } -} - -impl fmt::Display for TransitionError { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.write_str(self.description()) - } -} - -impl Error for TransitionError { - fn description(&self) -> &str { - match *self { - TransitionError::InvalidState => "Invalid state for desired transition", - TransitionError::IoError(..) => "Transition failed due to IO error", - } - } - - fn cause(&self) -> Option<&Error> { - match *self { - TransitionError::IoError(ref e) => Some(e), - _ => None, - } - } -} - - enum State<C: MonitoredChild> { Stopped, Running(RunningState<C>), @@ -104,14 +77,12 @@ impl<S: ChildSpawner> ChildMonitor<S> { /// Starts the child process and begins to monitor it. `listener` will be called as soon as the /// child process exits. - pub fn start<L>(&mut self, - listener: L) - -> TransitionResult<(Option<ChildStdout>, Option<ChildStderr>)> + pub fn start<L>(&mut self, listener: L) -> Result<(Option<ChildStdout>, Option<ChildStderr>)> where L: FnMut(bool) + Send + 'static { let mut state_lock = self.state.lock().unwrap(); if let State::Stopped = *state_lock { - let mut child = self.spawner.spawn()?; + let mut child = self.spawner.spawn().chain_err(|| ErrorKind::Spawn)?; let io = (child.stdout(), child.stderr()); let thread_handle = self.spawn_monitor(child.clone(), listener); *state_lock = State::Running(RunningState { @@ -120,7 +91,7 @@ impl<S: ChildSpawner> ChildMonitor<S> { }); Ok(io) } else { - Err(TransitionError::InvalidState) + Err(ErrorKind::InvalidState.into()) } } @@ -139,13 +110,13 @@ impl<S: ChildSpawner> ChildMonitor<S> { } /// Sends a kill signal to the child process. - pub fn stop(&self) -> TransitionResult<()> { + pub fn stop(&self) -> Result<()> { let state_lock = self.state.lock().unwrap(); if let State::Running(ref running_state) = *state_lock { - running_state.child.kill()?; + running_state.child.kill().chain_err(|| ErrorKind::Kill)?; Ok(()) } else { - Err(TransitionError::InvalidState) + Err(ErrorKind::InvalidState.into()) } } } diff --git a/talpid_cli/Cargo.toml b/talpid_cli/Cargo.toml index 5abed624a4..637b1f6699 100644 --- a/talpid_cli/Cargo.toml +++ b/talpid_cli/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Linus Färnstrand <linus@mullvad.net>"] [dependencies] clap = "2.20" +error-chain = "0.8" [dependencies.talpid_core] path = "../" diff --git a/talpid_cli/src/main.rs b/talpid_cli/src/main.rs index a174d7ed37..4a4239f622 100644 --- a/talpid_cli/src/main.rs +++ b/talpid_cli/src/main.rs @@ -1,34 +1,49 @@ +// `error_chain!` can recurse deeply +#![recursion_limit = "1024"] + extern crate talpid_core; #[macro_use] extern crate clap; +#[macro_use] +extern crate error_chain; use std::io::{self, Read, Write}; use std::sync::mpsc::{self, Receiver}; use std::thread; use talpid_core::process::OpenVpnCommand; -use talpid_core::process::monitor::{ChildMonitor, TransitionResult, ChildSpawner}; +use talpid_core::process::monitor::{ChildMonitor, ChildSpawner}; mod cli; use cli::Args; -/// Macro for printing to stderr. Will simply do nothing if the printing fails for some reason. -macro_rules! eprintln { - ($($arg:tt)*) => ( - use std::io::Write; - let _ = writeln!(&mut ::std::io::stderr(), $($arg)* ); - ) + +error_chain! { + links { + Monitor(talpid_core::process::monitor::Error, talpid_core::process::monitor::ErrorKind); + } } + fn main() { - let args = cli::parse_args_or_exit(); + if let Err(ref e) = run() { + println!("error: {}", e); + for e in e.iter().skip(1) { + println!("caused by: {}", e); + } + if let Some(backtrace) = e.backtrace() { + println!("backtrace: {:?}", backtrace); + } + ::std::process::exit(1); + } +} +fn run() -> Result<()> { + let args = cli::parse_args_or_exit(); let command = create_openvpn_command(&args); let monitor = ChildMonitor::new(command); - if let Err(e) = main_loop(monitor) { - eprintln!("OpenVPN failed: {}", e); - } + main_loop(monitor) } fn create_openvpn_command(args: &Args) -> OpenVpnCommand { @@ -41,27 +56,28 @@ fn create_openvpn_command(args: &Args) -> OpenVpnCommand { command } -fn main_loop<S>(mut monitor: ChildMonitor<S>) -> TransitionResult<()> +fn main_loop<S>(mut monitor: ChildMonitor<S>) -> Result<()> where S: ChildSpawner { loop { - let rx = start_monitor(&mut monitor)?; + let rx = start_monitor(&mut monitor).chain_err(|| "Unable to start OpenVPN")?; let clean_exit = rx.recv().unwrap(); println!("Monitored process exited. clean: {}", clean_exit); std::thread::sleep(std::time::Duration::from_millis(500)); } } -fn start_monitor<S>(monitor: &mut ChildMonitor<S>) -> TransitionResult<Receiver<bool>> +fn start_monitor<S>(monitor: &mut ChildMonitor<S>) -> Result<Receiver<bool>> where S: ChildSpawner { let (tx, rx) = mpsc::channel(); let callback = move |clean| tx.send(clean).unwrap(); - monitor.start(callback).map(|(stdout, stderr)| { - stdout.map(|stream| pass_io(stream, io::stdout())); - stderr.map(|stream| pass_io(stream, io::stderr())); - rx - }) + Ok(monitor.start(callback) + .map(|(stdout, stderr)| { + stdout.map(|stream| pass_io(stream, io::stdout())); + stderr.map(|stream| pass_io(stream, io::stderr())); + rx + })?) } fn pass_io<I, O>(mut input: I, mut output: O) diff --git a/talpid_openvpn_plugin/src/ffi/parse.rs b/talpid_openvpn_plugin/src/ffi/parse.rs index db26335d27..d694f1efb7 100644 --- a/talpid_openvpn_plugin/src/ffi/parse.rs +++ b/talpid_openvpn_plugin/src/ffi/parse.rs @@ -28,7 +28,7 @@ error_chain!{ /// terminated. Likewise, if any string pointed to is not properly null-terminated it may crash. pub unsafe fn string_array(mut ptr: *const *const c_char) -> Result<Vec<String>> { if ptr.is_null() { - Err(Error::from(ErrorKind::Null)) + Err(ErrorKind::Null.into()) } else { let mut strings = Vec::new(); while !(*ptr).is_null() { |
