Commit Diff


commit - 9919a59522a2e5d6fff449a0db34a65ea3739fa0
commit + 8e4d062a47b7003064cb9ff22232e4e9b804571b
blob - df99c69198f5813df5fc3eaa007a2af0e60a7bbd
blob + ffaf97ed9dd4c2c3e5388126b167e66e9e946c6a
--- .rustfmt.toml
+++ .rustfmt.toml
@@ -1 +1,2 @@
 max_width = 80
+imports_granularity = "Crate"
blob - b1c6e1b68dee3acc61a1caa530b96c67464d8796
blob + ef5baa7e0c6d3d1cbb5673ba4f4a8e308cb42714
--- asp.rs
+++ asp.rs
@@ -15,20 +15,19 @@
 // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 //
 
-use std::env;
-use std::ffi::{CStr, CString};
-use std::fs;
-use std::io::{self, Read, Write};
-use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
-use std::os::raw::{c_char, c_int};
-use std::os::unix::io::AsRawFd;
-use std::process::Command;
-use std::thread;
-use std::time::Duration;
+use std::{
+    env,
+    ffi::{CStr, CString},
+    fs,
+    io::{self, Read, Write},
+    net::{SocketAddr, TcpStream, ToSocketAddrs},
+    os::unix::io::AsRawFd,
+    process::Command,
+    thread,
+    time::Duration,
+};
 
 const VERSION: &str = env!("ASP_VERSION");
-const TLS_WANT_POLLIN: isize = -2;
-const TLS_WANT_POLLOUT: isize = -3;
 
 const HEAD: &str = r#"<!DOCTYPE html>
 <html lang="en">
@@ -62,38 +61,45 @@ li { list-style: none; margin-bottom: 2px; padding: 5p
 <div class="container">
 "#;
 
-// libtls(3) FFI
+use ffi::*;
 
-enum Tls {}
-enum TlsCfg {}
+mod ffi {
+    use std::os::raw::{c_char, c_int};
 
-unsafe extern "C" {
-    fn tls_init() -> c_int;
-    fn tls_config_new() -> *mut TlsCfg;
-    fn tls_config_free(cfg: *mut TlsCfg);
-    fn tls_client() -> *mut Tls;
-    fn tls_configure(
-        ctx: *mut Tls,
-        cfg: *mut TlsCfg,
-    ) -> c_int;
-    fn tls_connect_socket(
-        ctx: *mut Tls,
-        fd: c_int,
-        name: *const c_char,
-    ) -> c_int;
-    fn tls_read(
-        ctx: *mut Tls,
-        buf: *mut u8,
-        len: usize,
-    ) -> isize;
-    fn tls_write(
-        ctx: *mut Tls,
-        buf: *const u8,
-        len: usize,
-    ) -> isize;
-    fn tls_close(ctx: *mut Tls) -> c_int;
-    fn tls_free(ctx: *mut Tls);
-    fn tls_error(ctx: *mut Tls) -> *const c_char;
+    pub enum Tls {}
+    pub enum TlsCfg {}
+
+    pub const TLS_WANT_POLLIN: isize = -2;
+    pub const TLS_WANT_POLLOUT: isize = -3;
+
+    unsafe extern "C" {
+        pub fn tls_init() -> c_int;
+        pub fn tls_config_new() -> *mut TlsCfg;
+        pub fn tls_config_free(cfg: *mut TlsCfg);
+        pub fn tls_client() -> *mut Tls;
+        pub fn tls_configure(
+            ctx: *mut Tls,
+            cfg: *mut TlsCfg,
+        ) -> c_int;
+        pub fn tls_connect_socket(
+            ctx: *mut Tls,
+            fd: c_int,
+            name: *const c_char,
+        ) -> c_int;
+        pub fn tls_read(
+            ctx: *mut Tls,
+            buf: *mut u8,
+            len: usize,
+        ) -> isize;
+        pub fn tls_write(
+            ctx: *mut Tls,
+            buf: *const u8,
+            len: usize,
+        ) -> isize;
+        pub fn tls_close(ctx: *mut Tls) -> c_int;
+        pub fn tls_free(ctx: *mut Tls);
+        pub fn tls_error(ctx: *mut Tls) -> *const c_char;
+    }
 }
 
 struct Check {
@@ -162,8 +168,7 @@ fn parse_checks(path: &str) -> Vec<Check> {
             !t.is_empty() && !t.starts_with('#')
         })
         .filter_map(|line| {
-            let c: Vec<&str> =
-                line.splitn(4, ',').map(|s| s.trim()).collect();
+            let c: Vec<&str> = line.splitn(4, ',').map(|s| s.trim()).collect();
             if c.len() < 4 {
                 return None;
             }
@@ -187,23 +192,15 @@ fn ipv(kind: &str) -> &str {
     }
 }
 
-fn resolve(
-    host: &str,
-    port: u16,
-    v: &str,
-) -> io::Result<SocketAddr> {
-    let addrs: Vec<SocketAddr> =
-        (host, port).to_socket_addrs()?.collect();
+fn resolve(host: &str, port: u16, v: &str) -> io::Result<SocketAddr> {
+    let addrs: Vec<SocketAddr> = (host, port).to_socket_addrs()?.collect();
     let addr = match v {
         "4" => addrs.iter().find(|a| a.is_ipv4()),
         "6" => addrs.iter().find(|a| a.is_ipv6()),
         _ => addrs.first(),
     };
     addr.copied().ok_or_else(|| {
-        io::Error::new(
-            io::ErrorKind::AddrNotAvailable,
-            "no address",
-        )
+        io::Error::new(io::ErrorKind::AddrNotAvailable, "no address")
     })
 }
 
@@ -216,22 +213,19 @@ fn parse_url(url: &str) -> Option<(&str, &str, u16, &s
     let (host, port) = if hp.starts_with('[') {
         let end = hp.find(']')?;
         let h = &hp[1..end];
-        let p =
-            if hp.len() > end + 1 && hp.as_bytes()[end + 1] == b':'
-            {
-                hp[end + 2..].parse().ok()?
-            } else if scheme == "https" {
-                443
-            } else {
-                80
-            };
+        let p = if hp.len() > end + 1 && hp.as_bytes()[end + 1] == b':' {
+            hp[end + 2..].parse().ok()?
+        } else if scheme == "https" {
+            443
+        } else {
+            80
+        };
         (h, p)
     } else {
         match hp.rsplit_once(':') {
             Some((h, p)) => (h, p.parse().ok()?),
             None => {
-                let p =
-                    if scheme == "https" { 443 } else { 80 };
+                let p = if scheme == "https" { 443 } else { 80 };
                 (hp, p)
             }
         }
@@ -251,9 +245,7 @@ unsafe fn tls_err(ctx: *mut Tls) -> String {
     if p.is_null() {
         "TLS error".into()
     } else {
-        unsafe {
-            CStr::from_ptr(p).to_string_lossy().into_owned()
-        }
+        unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() }
     }
 }
 
@@ -261,11 +253,7 @@ fn now() -> String {
     Command::new("date")
         .arg("+%FT%T%z")
         .output()
-        .map(|o| {
-            String::from_utf8_lossy(&o.stdout)
-                .trim()
-                .to_string()
-        })
+        .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
         .unwrap_or_default()
 }
 
@@ -284,8 +272,7 @@ fn parse_headers(raw: &str) -> (i32, Option<String>) {
         if line.len() > 9 {
             let lower = line[..9].to_ascii_lowercase();
             if lower == "location:" {
-                location =
-                    Some(line[9..].trim().to_string());
+                location = Some(line[9..].trim().to_string());
             }
         }
     }
@@ -293,14 +280,11 @@ fn parse_headers(raw: &str) -> (i32, Option<String>) {
 }
 
 fn resolve_redirect(base: &str, loc: &str) -> String {
-    if loc.starts_with("http://")
-        || loc.starts_with("https://")
-    {
+    if loc.starts_with("http://") || loc.starts_with("https://") {
         return loc.to_string();
     }
     if loc.starts_with("//") {
-        let scheme =
-            base.split("://").next().unwrap_or("https");
+        let scheme = base.split("://").next().unwrap_or("https");
         return format!("{}:{}", scheme, loc);
     }
     if let Some((scheme, host, port, _)) = parse_url(base) {
@@ -321,10 +305,8 @@ fn http_get(
     v: &str,
     timeout: Duration,
 ) -> Result<(i32, Option<String>), String> {
-    let (scheme, host, port, path) =
-        parse_url(url).ok_or("invalid URL")?;
-    let addr =
-        resolve(host, port, v).map_err(|e| e.to_string())?;
+    let (scheme, host, port, path) = parse_url(url).ok_or("invalid URL")?;
+    let addr = resolve(host, port, v).map_err(|e| e.to_string())?;
     let stream = TcpStream::connect_timeout(&addr, timeout)
         .map_err(|e| e.to_string())?;
     let _ = stream.set_read_timeout(Some(timeout));
@@ -353,8 +335,7 @@ fn check_http(
 ) -> (bool, String) {
     let mut url = url.to_string();
     for _ in 0..5 {
-        let (code, loc) = match http_get(&url, v, timeout)
-        {
+        let (code, loc) = match http_get(&url, v, timeout) {
             Ok(r) => r,
             Err(e) => return (false, e),
         };
@@ -363,10 +344,7 @@ fn check_http(
                 url = resolve_redirect(&url, &l);
             }
             _ => {
-                let info = format!(
-                    "status {}, expected {}",
-                    code, expected
-                );
+                let info = format!("status {}, expected {}", code, expected);
                 return (code == expected, info);
             }
         }
@@ -374,28 +352,18 @@ fn check_http(
     (false, "too many redirects".into())
 }
 
-fn http_req(
-    stream: &TcpStream,
-    req: &str,
-) -> Result<String, String> {
+fn http_req(stream: &TcpStream, req: &str) -> Result<String, String> {
     let mut s = stream;
-    s.write_all(req.as_bytes())
-        .map_err(|e| e.to_string())?;
+    s.write_all(req.as_bytes()).map_err(|e| e.to_string())?;
     let mut buf = [0u8; 4096];
     let mut total = 0;
     loop {
-        let n = s
-            .read(&mut buf[total..])
-            .map_err(|e| e.to_string())?;
+        let n = s.read(&mut buf[total..]).map_err(|e| e.to_string())?;
         if n == 0 {
             break;
         }
         total += n;
-        if total >= 4
-            && buf[..total]
-                .windows(4)
-                .any(|w| w == b"\r\n\r\n")
-        {
+        if total >= 4 && buf[..total].windows(4).any(|w| w == b"\r\n\r\n") {
             break;
         }
         if total >= buf.len() {
@@ -431,14 +399,8 @@ unsafe fn https_req(
         }
         tls_config_free(cfg);
 
-        let name = CString::new(host)
-            .map_err(|_| "invalid host")?;
-        if tls_connect_socket(
-            ctx,
-            stream.as_raw_fd(),
-            name.as_ptr(),
-        ) != 0
-        {
+        let name = CString::new(host).map_err(|_| "invalid host")?;
+        if tls_connect_socket(ctx, stream.as_raw_fd(), name.as_ptr()) != 0 {
             let e = tls_err(ctx);
             tls_close(ctx);
             tls_free(ctx);
@@ -448,11 +410,7 @@ unsafe fn https_req(
         let bytes = req.as_bytes();
         let mut off = 0;
         while off < bytes.len() {
-            let n = tls_write(
-                ctx,
-                bytes[off..].as_ptr(),
-                bytes.len() - off,
-            );
+            let n = tls_write(ctx, bytes[off..].as_ptr(), bytes.len() - off);
             if n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT {
                 continue;
             }
@@ -468,11 +426,7 @@ unsafe fn https_req(
         let mut buf = [0u8; 4096];
         let mut total = 0;
         loop {
-            let n = tls_read(
-                ctx,
-                buf[total..].as_mut_ptr(),
-                buf.len() - total,
-            );
+            let n = tls_read(ctx, buf[total..].as_mut_ptr(), buf.len() - total);
             if n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT {
                 continue;
             }
@@ -480,11 +434,7 @@ unsafe fn https_req(
                 break;
             }
             total += n as usize;
-            if total >= 4
-                && buf[..total]
-                    .windows(4)
-                    .any(|w| w == b"\r\n\r\n")
-            {
+            if total >= 4 && buf[..total].windows(4).any(|w| w == b"\r\n\r\n") {
                 break;
             }
             if total >= buf.len() {
@@ -498,8 +448,7 @@ unsafe fn https_req(
         if total == 0 {
             return Err("empty response".into());
         }
-        Ok(String::from_utf8_lossy(&buf[..total])
-            .into_owned())
+        Ok(String::from_utf8_lossy(&buf[..total]).into_owned())
     }
 }
 
@@ -556,10 +505,7 @@ fn check_port(
     }
 }
 
-fn run_checks(
-    checks: &[Check],
-    timeout: u64,
-) -> Vec<CheckResult> {
+fn run_checks(checks: &[Check], timeout: u64) -> Vec<CheckResult> {
     let dur = Duration::from_secs(timeout);
     thread::scope(|s| {
         let handles: Vec<_> = checks
@@ -575,29 +521,15 @@ fn run_checks(
                         };
                     }
                     let v = ipv(&c.kind);
-                    let (ok, info) =
-                        if c.kind.starts_with("http") {
-                            check_http(
-                                &c.host, c.expected, v, dur,
-                            )
-                        } else if c.kind.starts_with("ping") {
-                            check_ping(
-                                &c.host, c.expected, v,
-                                timeout,
-                            )
-                        } else if c.kind.starts_with("port") {
-                            check_port(
-                                &c.host, c.expected, v, dur,
-                            )
-                        } else {
-                            (
-                                false,
-                                format!(
-                                    "unknown: {}",
-                                    c.kind
-                                ),
-                            )
-                        };
+                    let (ok, info) = if c.kind.starts_with("http") {
+                        check_http(&c.host, c.expected, v, dur)
+                    } else if c.kind.starts_with("ping") {
+                        check_ping(&c.host, c.expected, v, timeout)
+                    } else if c.kind.starts_with("port") {
+                        check_port(&c.host, c.expected, v, dur)
+                    } else {
+                        (false, format!("unknown: {}", c.kind))
+                    };
                     CheckResult {
                         name: c.name.clone(),
                         ok,
@@ -607,18 +539,12 @@ fn run_checks(
                 })
             })
             .collect();
-        handles
-            .into_iter()
-            .map(|h| h.join().unwrap())
-            .collect()
+        handles.into_iter().map(|h| h.join().unwrap()).collect()
     })
 }
 
 fn render_html(results: &[CheckResult], incidents: &str) {
-    let outages = results
-        .iter()
-        .filter(|r| !r.group && !r.ok)
-        .count();
+    let outages = results.iter().filter(|r| !r.group && !r.ok).count();
 
     print!("{}", HEAD);
     println!("<h1>Status</h1>");
@@ -639,10 +565,7 @@ fn render_html(results: &[CheckResult], incidents: &st
     println!("<h1>Services</h1>\n<ul>");
     for r in results {
         if r.group {
-            println!(
-                "<li class=\"panel group\">{}</li>",
-                esc(&r.name)
-            );
+            println!("<li class=\"panel group\">{}</li>", esc(&r.name));
         } else if r.ok {
             println!(
                 "<li>{} <span class=\"status success\">\
@@ -671,11 +594,8 @@ fn render_html(results: &[CheckResult], incidents: &st
         }
     }
 
+    println!("<p class=\"small info\">Last check: {}</p>", now());
     println!(
-        "<p class=\"small info\">Last check: {}</p>",
-        now()
-    );
-    println!(
         "<p class=\"small info\">Powered by \
          <a href=\"https://git.ijanc.org/asp\">asp</a></p>"
     );
@@ -683,14 +603,13 @@ fn render_html(results: &[CheckResult], incidents: &st
 }
 
 fn main() {
-    let (checks_path, incidents_path, timeout) =
-        match parse_args() {
-            Some(a) => a,
-            None => {
-                usage();
-                std::process::exit(1);
-            }
-        };
+    let (checks_path, incidents_path, timeout) = match parse_args() {
+        Some(a) => a,
+        None => {
+            usage();
+            std::process::exit(1);
+        }
+    };
 
     unsafe {
         if tls_init() != 0 {
@@ -701,8 +620,7 @@ fn main() {
 
     let checks = parse_checks(&checks_path);
     let results = run_checks(&checks, timeout);
-    let incidents =
-        fs::read_to_string(&incidents_path).unwrap_or_default();
+    let incidents = fs::read_to_string(&incidents_path).unwrap_or_default();
 
     render_html(&results, &incidents);
 }