diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2019-04-04 17:19:17 +0200 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2019-04-04 17:33:17 +0200 |
| commit | 77277422e52cfad4341900bbab2d6c611608292a (patch) | |
| tree | d0e5e1f7c6b54a6d76d249f6d33a1df6c766d58a | |
| parent | 0ff9b0d3321647367fdde3d501641f6e87975919 (diff) | |
| download | mullvadvpn-77277422e52cfad4341900bbab2d6c611608292a.tar.xz mullvadvpn-77277422e52cfad4341900bbab2d6c611608292a.zip | |
Get rid of error-chain in mullvad-types and talpid-types
| -rw-r--r-- | Cargo.lock | 3 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 26 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 6 | ||||
| -rw-r--r-- | mullvad-types/Cargo.toml | 8 | ||||
| -rw-r--r-- | mullvad-types/src/custom_tunnel.rs | 30 | ||||
| -rw-r--r-- | mullvad-types/src/lib.rs | 3 | ||||
| -rw-r--r-- | mullvad-types/src/settings.rs | 74 | ||||
| -rw-r--r-- | talpid-core/src/dns/linux/systemd_resolved.rs | 3 | ||||
| -rw-r--r-- | talpid-core/src/lib.rs | 31 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/blocked_state.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 3 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnecting_state.rs | 7 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 3 | ||||
| -rw-r--r-- | talpid-types/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-types/src/lib.rs | 31 |
17 files changed, 121 insertions, 116 deletions
diff --git a/Cargo.lock b/Cargo.lock index ae84577039..a447c280d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1214,7 +1214,7 @@ name = "mullvad-types" version = "0.1.0" dependencies = [ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "err-derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "ipnetwork 0.14.0 (git+https://github.com/mullvad/ipnetwork?branch=fix-deserialization)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2065,7 +2065,6 @@ name = "talpid-types" version = "0.1.0" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", - "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "ipnetwork 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 82baca2db0..90db136bcb 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -50,6 +50,7 @@ use talpid_core::{ use talpid_types::{ net::{openvpn, wireguard, TransportProtocol, TunnelParameters}, tunnel::{BlockReason, TunnelStateTransition}, + ErrorExt, }; @@ -387,8 +388,8 @@ impl Daemon { .map_err(|_| Error::from("Tunnel parameters receiver stopped listening")) }) }); - if let Err(error) = result { - error!("{}", error.display_chain()); + if let Err(e) = result { + error!("{}", ErrorExt::display_chain(&e)); } } @@ -550,7 +551,10 @@ impl Daemon { let https_handle = self.https_handle.clone(); geoip::send_location_request(https_handle).map_err(|e| { - warn!("Unable to fetch GeoIP location: {}", e.display_chain()); + warn!( + "Unable to fetch GeoIP location: {}", + ChainedError::display_chain(&e) + ); }) } @@ -615,7 +619,7 @@ impl Daemon { } } } - Err(e) => error!("{}", e.display_chain()), + Err(e) => error!("{}", ErrorExt::display_chain(&e)), } } @@ -672,7 +676,7 @@ impl Daemon { self.reconnect_tunnel(); } } - Err(e) => error!("{}", e.display_chain()), + Err(e) => error!("{}", ErrorExt::display_chain(&e)), } } @@ -687,7 +691,7 @@ impl Daemon { self.send_tunnel_command(TunnelCommand::AllowLan(allow_lan)); } } - Err(e) => error!("{}", e.display_chain()), + Err(e) => error!("{}", ErrorExt::display_chain(&e)), } } @@ -710,7 +714,7 @@ impl Daemon { )); } } - Err(e) => error!("{}", e.display_chain()), + Err(e) => error!("{}", ErrorExt::display_chain(&e)), } } @@ -724,7 +728,7 @@ impl Daemon { .notify_settings(self.settings.clone()); } } - Err(e) => error!("{}", e.display_chain()), + Err(e) => error!("{}", ErrorExt::display_chain(&e)), } } @@ -740,7 +744,7 @@ impl Daemon { self.reconnect_tunnel(); } } - Err(e) => error!("{}", e.display_chain()), + Err(e) => error!("{}", ErrorExt::display_chain(&e)), } } @@ -807,7 +811,7 @@ impl Daemon { self.reconnect_tunnel(); } } - Err(e) => error!("{}", e.display_chain()), + Err(e) => error!("{}", ErrorExt::display_chain(&e)), } } @@ -823,7 +827,7 @@ impl Daemon { self.reconnect_tunnel(); } } - Err(e) => error!("{}", e.display_chain()), + Err(e) => error!("{}", ErrorExt::display_chain(&e)), } } diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 1966e0a7bf..24bf2df391 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -544,9 +544,9 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi .send_command_to_daemon(ManagementCommand::SetOpenVpnProxy(tx, proxy)) .and_then(|_| rx.map_err(|_| Error::internal_error())) .and_then(|settings_result| { - settings_result.map_err(|err| match err.kind() { - settings::ErrorKind::InvalidProxyData(msg) => { - Error::invalid_params(msg.to_owned()) + settings_result.map_err(|error| match error { + settings::Error::InvalidProxyData(reason) => { + Error::invalid_params(reason.to_owned()) } _ => Error::internal_error(), }) diff --git a/mullvad-types/Cargo.toml b/mullvad-types/Cargo.toml index 27c59acc0f..d3933508ad 100644 --- a/mullvad-types/Cargo.toml +++ b/mullvad-types/Cargo.toml @@ -8,13 +8,13 @@ edition = "2018" [dependencies] chrono = { version = "0.4", features = ["serde"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -error-chain = "0.12" +err-derive = "0.1.5" ipnetwork = { git = "https://github.com/mullvad/ipnetwork", branch = "fix-deserialization" } +lazy_static = "1.1.0" log = "0.4" regex = "1" -lazy_static = "1.1.0" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" talpid-types = { path = "../talpid-types" } mullvad-paths = { path = "../mullvad-paths" } diff --git a/mullvad-types/src/custom_tunnel.rs b/mullvad-types/src/custom_tunnel.rs index a9171a5478..ac46b609a3 100644 --- a/mullvad-types/src/custom_tunnel.rs +++ b/mullvad-types/src/custom_tunnel.rs @@ -1,20 +1,19 @@ use crate::settings::TunnelOptions; use serde::{Deserialize, Serialize}; use std::{ - fmt, + fmt, io, net::{IpAddr, SocketAddr, ToSocketAddrs}, }; use talpid_types::net::{openvpn, wireguard, TunnelParameters}; -error_chain! { - errors { - InvalidHost(host: String) { - display("Invalid host: {}", host) - } - Unsupported { - description("Tunnel type not supported") - } - } + +#[derive(err_derive::Error, Debug)] +pub enum Error { + #[error(display = "Invalid host/domain: {}", _0)] + InvalidHost(String, #[error(cause)] io::Error), + + #[error(display = "Host has no IPv4 address: {}", _0)] + HostHasNoIpv4(String), } @@ -29,7 +28,10 @@ impl CustomTunnelEndpoint { Self { host, config } } - pub fn to_tunnel_parameters(&self, tunnel_options: TunnelOptions) -> Result<TunnelParameters> { + pub fn to_tunnel_parameters( + &self, + tunnel_options: TunnelOptions, + ) -> Result<TunnelParameters, Error> { let ip = resolve_to_ip(&self.host)?; let mut config = self.config.clone(); config.set_ip(ip); @@ -76,10 +78,10 @@ impl fmt::Display for CustomTunnelEndpoint { /// Returns the first IPv4 address if one exists, otherwise the first IPv6 address. /// Rust only provides means to resolve a socket addr, not just a host, for some reason. So /// because of this we do the resolving with port zero and then pick out the IPs. -fn resolve_to_ip(host: &str) -> Result<IpAddr> { +fn resolve_to_ip(host: &str) -> Result<IpAddr, Error> { let (mut ipv4, mut ipv6): (Vec<IpAddr>, Vec<IpAddr>) = (host, 0) .to_socket_addrs() - .chain_err(|| ErrorKind::InvalidHost(host.to_owned()))? + .map_err(|e| Error::InvalidHost(host.to_owned(), e))? .map(|addr| addr.ip()) .partition(|addr| addr.is_ipv4()); @@ -88,7 +90,7 @@ fn resolve_to_ip(host: &str) -> Result<IpAddr> { log::info!("No IPv4 for host {}", host); ipv6.pop() }) - .ok_or_else(|| ErrorKind::InvalidHost(host.to_owned()).into()) + .ok_or_else(|| Error::HostHasNoIpv4(host.to_owned())) } #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs index c49f9e904d..6947c4ee9c 100644 --- a/mullvad-types/src/lib.rs +++ b/mullvad-types/src/lib.rs @@ -8,9 +8,6 @@ #![deny(rust_2018_idioms)] -#[macro_use] -extern crate error_chain; - pub mod account; pub mod auth_failed; pub mod endpoint; diff --git a/mullvad-types/src/settings.rs b/mullvad-types/src/settings.rs index 5609960883..92b656bfa5 100644 --- a/mullvad-types/src/settings.rs +++ b/mullvad-types/src/settings.rs @@ -7,27 +7,28 @@ use serde_json; use std::{fs::File, io, path::PathBuf}; use talpid_types::net::{openvpn, wireguard, GenericTunnelOptions}; -error_chain! { - errors { - DirectoryError { - description("Unable to create settings directory for program") - } - ReadError(path: PathBuf) { - description("Unable to read settings file") - display("Unable to read settings from {}", path.display()) - } - WriteError(path: PathBuf) { - description("Unable to write settings file") - display("Unable to write settings to {}", path.display()) - } - ParseError { - description("Malformed settings") - } - InvalidProxyData(reason: String) { - description("Invalid proxy configuration was rejected") - display("Invalid proxy configuration was rejected: {}", reason) - } - } + +pub type Result<T> = std::result::Result<T, Error>; + +#[derive(err_derive::Error, Debug)] +pub enum Error { + #[error(display = "Unable to create settings directory")] + DirectoryError(#[error(cause)] mullvad_paths::Error), + + #[error(display = "Unable to read settings from {}", _0)] + ReadError(String, #[error(cause)] io::Error), + + #[error(display = "Malformed settings")] + ParseError(#[error(cause)] serde_json::Error), + + #[error(display = "Unable to serialize settings to JSON")] + SerializeError(#[error(cause)] serde_json::Error), + + #[error(display = "Unable to write settings to {}", _0)] + WriteError(String, #[error(cause)] io::Error), + + #[error(display = "Invalid OpenVPN proxy configuration: {}", _0)] + InvalidProxyData(String), } static SETTINGS_FILE: &str = "settings.json"; @@ -70,20 +71,17 @@ impl Default for Settings { impl Settings { /// Loads user settings from file. If no file is present it returns the defaults. pub fn load() -> Result<Settings> { - let settings_path = Self::get_settings_path()?; - match File::open(&settings_path) { + let path = Self::get_settings_path()?; + match File::open(&path) { Ok(file) => { - info!("Loading settings from {}", settings_path.display()); + info!("Loading settings from {}", path.display()); Self::read_settings(&mut io::BufReader::new(file)) } Err(ref e) if e.kind() == io::ErrorKind::NotFound => { - info!( - "No settings file at {}, using defaults", - settings_path.display() - ); + info!("No settings file at {}, using defaults", path.display()); Ok(Settings::default()) } - Err(e) => Err(e).chain_err(|| ErrorKind::ReadError(settings_path)), + Err(e) => Err(Error::ReadError(path.display().to_string(), e)), } } @@ -92,20 +90,21 @@ impl Settings { let path = Self::get_settings_path()?; debug!("Writing settings to {}", path.display()); - let mut file = File::create(&path).chain_err(|| ErrorKind::WriteError(path.clone()))?; + let mut file = + File::create(&path).map_err(|e| Error::WriteError(path.display().to_string(), e))?; - serde_json::to_writer_pretty(&mut file, self) - .chain_err(|| ErrorKind::WriteError(path.clone()))?; - file.sync_all().chain_err(|| ErrorKind::WriteError(path)) + serde_json::to_writer_pretty(&mut file, self).map_err(Error::SerializeError)?; + file.sync_all() + .map_err(|e| Error::WriteError(path.display().to_string(), e)) } fn get_settings_path() -> Result<PathBuf> { - let dir = ::mullvad_paths::settings_dir().chain_err(|| ErrorKind::DirectoryError)?; + let dir = ::mullvad_paths::settings_dir().map_err(Error::DirectoryError)?; Ok(dir.join(SETTINGS_FILE)) } fn read_settings<T: io::Read>(file: &mut T) -> Result<Settings> { - serde_json::from_reader(file).chain_err(|| ErrorKind::ParseError) + serde_json::from_reader(file).map_err(Error::ParseError) } pub fn get_account_token(&self) -> Option<String> { @@ -203,9 +202,8 @@ impl Settings { pub fn set_openvpn_proxy(&mut self, proxy: Option<openvpn::ProxySettings>) -> Result<bool> { if let Some(ref settings) = proxy { - if let Err(validation_error) = openvpn::ProxySettingsValidation::validate(settings) { - bail!(ErrorKind::InvalidProxyData(validation_error)); - } + openvpn::ProxySettingsValidation::validate(settings) + .map_err(Error::InvalidProxyData)?; } if self.tunnel_options.openvpn.proxy != proxy { diff --git a/talpid-core/src/dns/linux/systemd_resolved.rs b/talpid-core/src/dns/linux/systemd_resolved.rs index 6ed023d9cd..a633cea91e 100644 --- a/talpid-core/src/dns/linux/systemd_resolved.rs +++ b/talpid-core/src/dns/linux/systemd_resolved.rs @@ -1,5 +1,5 @@ use super::RESOLV_CONF_PATH; -use crate::{linux::iface_index, ErrorExt as _}; +use crate::linux::iface_index; use dbus::{ arg::RefArg, stdintf::*, BusType, Interface, Member, Message, MessageItem, MessageItemArray, Signature, @@ -11,6 +11,7 @@ use std::{ net::{IpAddr, Ipv4Addr}, path::Path, }; +use talpid_types::ErrorExt as _; pub type Result<T> = std::result::Result<T, Error>; diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs index 1b75276561..1499557439 100644 --- a/talpid-core/src/lib.rs +++ b/talpid-core/src/lib.rs @@ -60,34 +60,3 @@ mod mktemp; /// Misc utilities for the Linux platform. #[cfg(target_os = "linux")] mod linux; - - -trait ErrorExt { - /// Creates a string representation of the entire error chain. - fn display_chain(&self) -> String; - - /// Like [`display_chain`] but with an extra message at the start of the chain - fn display_chain_with_msg(&self, msg: &str) -> String; -} - -impl<E: std::error::Error> ErrorExt for E { - fn display_chain(&self) -> String { - let mut s = format!("Error: {}", self); - let mut source = self.source(); - while let Some(error) = source { - s.push_str(&format!("\nCaused by: {}", error)); - source = error.source(); - } - s - } - - fn display_chain_with_msg(&self, msg: &str) -> String { - let mut s = format!("Error: {}\nCaused by: {}", msg, self); - let mut source = self.source(); - while let Some(error) = source { - s.push_str(&format!("\nCaused by: {}", error)); - source = error.source(); - } - s - } -} diff --git a/talpid-core/src/tunnel_state_machine/blocked_state.rs b/talpid-core/src/tunnel_state_machine/blocked_state.rs index a080e3ab67..2387d4e922 100644 --- a/talpid-core/src/tunnel_state_machine/blocked_state.rs +++ b/talpid-core/src/tunnel_state_machine/blocked_state.rs @@ -2,9 +2,9 @@ use super::{ ConnectingState, DisconnectedState, EventConsequence, SharedTunnelStateValues, TunnelCommand, TunnelState, TunnelStateTransition, TunnelStateWrapper, }; -use crate::{firewall::FirewallPolicy, ErrorExt}; +use crate::firewall::FirewallPolicy; use futures::{sync::mpsc, Stream}; -use talpid_types::tunnel::BlockReason; +use talpid_types::{tunnel::BlockReason, ErrorExt}; /// No tunnel is running and all network connections are blocked. pub struct BlockedState { diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index fc0c070c59..f0dad2b581 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -5,7 +5,6 @@ use super::{ use crate::{ firewall::FirewallPolicy, tunnel::{CloseHandle, TunnelEvent, TunnelMetadata}, - ErrorExt, }; use futures::{ sync::{mpsc, oneshot}, @@ -14,6 +13,7 @@ use futures::{ use talpid_types::{ net::{Endpoint, TunnelParameters}, tunnel::BlockReason, + ErrorExt, }; pub struct ConnectedStateBootstrap { diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index faa8f77c78..a843fa1fdd 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -6,7 +6,6 @@ use super::{ use crate::{ firewall::FirewallPolicy, tunnel::{self, CloseHandle, TunnelEvent, TunnelMetadata, TunnelMonitor}, - ErrorExt, }; use futures::{ sync::{mpsc, oneshot}, @@ -23,6 +22,7 @@ use std::{ use talpid_types::{ net::{openvpn, TunnelParameters}, tunnel::BlockReason, + ErrorExt, }; diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 437ef865e7..619efb2c10 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -2,8 +2,9 @@ use super::{ BlockedState, ConnectingState, EventConsequence, SharedTunnelStateValues, TunnelCommand, TunnelState, TunnelStateTransition, TunnelStateWrapper, }; -use crate::{firewall::FirewallPolicy, ErrorExt}; +use crate::firewall::FirewallPolicy; use futures::{sync::mpsc, Stream}; +use talpid_types::ErrorExt; /// No tunnel is running. pub struct DisconnectedState; diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 42b2b230ba..3dd1951806 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -2,13 +2,16 @@ use super::{ BlockedState, ConnectingState, DisconnectedState, EventConsequence, SharedTunnelStateValues, TunnelCommand, TunnelState, TunnelStateTransition, TunnelStateWrapper, }; -use crate::{tunnel::CloseHandle, ErrorExt}; +use crate::tunnel::CloseHandle; use futures::{ sync::{mpsc, oneshot}, Async, Future, Stream, }; use std::thread; -use talpid_types::tunnel::{ActionAfterDisconnect, BlockReason}; +use talpid_types::{ + tunnel::{ActionAfterDisconnect, BlockReason}, + ErrorExt, +}; /// This state is active from when we manually trigger a tunnel kill until the tunnel wait /// operation (TunnelExit) returned. diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 74874ab39a..370aad57bc 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -14,7 +14,7 @@ use self::{ disconnected_state::DisconnectedState, disconnecting_state::{AfterDisconnect, DisconnectingState}, }; -use crate::{dns::DnsMonitor, firewall::Firewall, mpsc::IntoSender, offline, ErrorExt}; +use crate::{dns::DnsMonitor, firewall::Firewall, mpsc::IntoSender, offline}; use futures::{sync::mpsc, Async, Future, Poll, Stream}; use std::{ io, @@ -25,6 +25,7 @@ use std::{ use talpid_types::{ net::TunnelParameters, tunnel::{BlockReason, TunnelStateTransition}, + ErrorExt, }; use tokio_core::reactor::Core; diff --git a/talpid-types/Cargo.toml b/talpid-types/Cargo.toml index 678a7832fc..d18b212fe2 100644 --- a/talpid-types/Cargo.toml +++ b/talpid-types/Cargo.toml @@ -11,6 +11,5 @@ serde = { version = "1.0", features = ["derive"] } ipnetwork = "0.14" base64 = "0.10" hex = "0.3" -error-chain = "0.12" x25519-dalek = { version = "0.4.5", features = [ "std", "u64_backend" ], default-features = false } rand = "0.6" diff --git a/talpid-types/src/lib.rs b/talpid-types/src/lib.rs index dfa0e09580..42a2dc4cf7 100644 --- a/talpid-types/src/lib.rs +++ b/talpid-types/src/lib.rs @@ -10,3 +10,34 @@ pub mod net; pub mod tunnel; + + +pub trait ErrorExt { + /// Creates a string representation of the entire error chain. + fn display_chain(&self) -> String; + + /// Like [`display_chain`] but with an extra message at the start of the chain + fn display_chain_with_msg(&self, msg: &str) -> String; +} + +impl<E: std::error::Error> ErrorExt for E { + fn display_chain(&self) -> String { + let mut s = format!("Error: {}", self); + let mut source = self.source(); + while let Some(error) = source { + s.push_str(&format!("\nCaused by: {}", error)); + source = error.source(); + } + s + } + + fn display_chain_with_msg(&self, msg: &str) -> String { + let mut s = format!("Error: {}\nCaused by: {}", msg, self); + let mut source = self.source(); + while let Some(error) = source { + s.push_str(&format!("\nCaused by: {}", error)); + source = error.source(); + } + s + } +} |
