aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHimbeer <himbeer@disroot.org>2025-03-17 14:45:37 +0100
committerHimbeer <himbeer@disroot.org>2025-03-17 15:58:19 +0100
commit928cc84d24903871b3bb04eaa2ccb8de1c3c3ee5 (patch)
tree55d9163b3b93e0a313afae3ad79d6c6729d51148
parent083e9e9f93db9c8ef488a2aec3ab171b0591c1f3 (diff)
Parse configuration file at /data/wg.peers
-rw-r--r--Cargo.lock409
-rw-r--r--Cargo.toml1
-rw-r--r--src/main.rs307
3 files changed, 716 insertions, 1 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1c5ba2a..2153271 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,5 +3,414 @@
version = 3
[[package]]
+name = "anyhow"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "libc"
+version = "0.2.171"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
+
+[[package]]
+name = "log"
+version = "0.4.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
+
+[[package]]
+name = "memoffset"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "netlink-packet-core"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-generic"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "netlink-packet-core",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-route"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66"
+dependencies = [
+ "anyhow",
+ "bitflags",
+ "byteorder",
+ "libc",
+ "netlink-packet-core",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-utils"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "paste",
+ "thiserror",
+]
+
+[[package]]
+name = "netlink-packet-wireguard"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b25b050ff1f6a1e23c6777b72db22790fe5b6b5ccfd3858672587a79876c8f"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "libc",
+ "log",
+ "netlink-packet-generic",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-request"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "890da3a441eac11fbc621eaf5da8186032dbd047c37e5cf2f49b80319b381a2f"
+dependencies = [
+ "netlink-packet-core",
+ "netlink-packet-generic",
+ "netlink-packet-route",
+ "netlink-packet-utils",
+ "netlink-sys",
+ "nix",
+ "once_cell",
+]
+
+[[package]]
+name = "netlink-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23"
+dependencies = [
+ "bytes",
+ "libc",
+ "log",
+]
+
+[[package]]
+name = "nix"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
+dependencies = [
+ "autocfg",
+ "bitflags",
+ "cfg-if",
+ "libc",
+ "memoffset",
+ "pin-utils",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
name = "rsdsl_wg"
version = "0.1.0"
+dependencies = [
+ "wireguard-control",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wireguard-control"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "248fb4e222045eb93d2a5269bb45472c2dadadce2c093a9e717847c0be72e611"
+dependencies = [
+ "base64",
+ "hex",
+ "libc",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-generic",
+ "netlink-packet-route",
+ "netlink-packet-utils",
+ "netlink-packet-wireguard",
+ "netlink-request",
+ "netlink-sys",
+ "rand_core",
+ "x25519-dalek",
+]
+
+[[package]]
+name = "x25519-dalek"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
+dependencies = [
+ "curve25519-dalek",
+ "rand_core",
+ "serde",
+ "zeroize",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 43aceb2..a39569f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,3 +6,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
+wireguard-control = "1.6.1"
diff --git a/src/main.rs b/src/main.rs
index e7a11a9..4db82e7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,308 @@
+use std::collections::HashMap;
+use std::fmt;
+use std::fs::File;
+use std::io;
+use std::net::IpAddr;
+
+const CONFIG_PATH: &str = "/data/wg.peers";
+
+#[derive(Debug)]
+enum ConfigError {
+ AddrParse(std::net::AddrParseError),
+ DuplicateLink(String),
+ Eof,
+ ExpectedLinkStanza(String),
+ IncompleteLinkHead,
+ InvalidAllowedIp(String),
+ InvalidCidr,
+ InvalidKey,
+ Io(io::Error),
+ NoEndpoint(String),
+ NoPrivateKey(String),
+ NoPublicKey(String),
+ NoPresharedKey(String),
+ NoAddresses(String),
+ NoAllowedIps(String),
+ NoKeepaliveInterval(String),
+ ParseInt(std::num::ParseIntError),
+}
+
+impl fmt::Display for ConfigError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "parse config: ")?;
+
+ match self {
+ Self::AddrParse(e) => write!(f, "parse IP address: {}", e),
+ Self::DuplicateLink(name) => write!(f, "duplicate link {}", name),
+ Self::Eof => write!(f, "EOF"),
+ Self::ExpectedLinkStanza(kw) => {
+ write!(f, "expected \"link\" or \"delete\", got {}", kw)
+ }
+ Self::IncompleteLinkHead => write!(f, "incomplete link head (want \"link <name>\")"),
+ Self::InvalidAllowedIp(allowed_ip) => write!(f, "invalid AllowedIP: {}", allowed_ip),
+ Self::InvalidCidr => write!(f, "invalid CIDR (want exactly one /)"),
+ Self::InvalidKey => write!(f, "invalid WireGuard key"),
+ Self::Io(e) => write!(f, "io: {}", e),
+ Self::NoEndpoint(link) => write!(f, "missing endpoint for link {}", link),
+ Self::NoPrivateKey(link) => write!(f, "missing private key for link {}", link),
+ Self::NoPublicKey(link) => write!(f, "missing public key for link {}", link),
+ Self::NoPresharedKey(link) => write!(f, "missing preshared key for link {}", link),
+ Self::NoAddresses(link) => write!(f, "missing addresses for link {}", link),
+ Self::NoAllowedIps(link) => write!(f, "missing AllowedIPs for link {}", link),
+ Self::NoKeepaliveInterval(link) => {
+ write!(f, "missing keepalive interval for link {}", link)
+ }
+ Self::ParseInt(e) => write!(f, "parse int: {}", e),
+ }
+ }
+}
+
+impl From<std::net::AddrParseError> for ConfigError {
+ fn from(e: std::net::AddrParseError) -> ConfigError {
+ ConfigError::AddrParse(e)
+ }
+}
+
+impl From<wireguard_control::InvalidKey> for ConfigError {
+ fn from(e: wireguard_control::InvalidKey) -> ConfigError {
+ ConfigError::InvalidKey
+ }
+}
+
+impl From<io::Error> for ConfigError {
+ fn from(e: io::Error) -> ConfigError {
+ ConfigError::Io(e)
+ }
+}
+
+impl From<std::num::ParseIntError> for ConfigError {
+ fn from(e: std::num::ParseIntError) -> ConfigError {
+ ConfigError::ParseInt(e)
+ }
+}
+
+impl std::error::Error for ConfigError {}
+
+#[derive(Debug)]
+enum Error {
+ Config(ConfigError),
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Config(e) => e.fmt(f),
+ }
+ }
+}
+
+impl From<ConfigError> for Error {
+ fn from(e: ConfigError) -> Error {
+ Error::Config(e)
+ }
+}
+
+impl std::error::Error for Error {}
+
+#[derive(Debug)]
+struct Link {
+ endpoint: SocketAddr,
+ private_key: wireguard_control::Key,
+ public_key: wireguard_control::Key,
+ preshared_key: wireguard_control::Key,
+ addresses: Vec<(IpAddr, u8)>,
+ allowed_ips: Vec<wireguard_control::AllowedIp>,
+ keepalive_seconds: u16,
+}
+
+#[derive(Debug)]
+enum LinkStanza {
+ Link(Link),
+ Delete,
+}
+
+#[derive(Debug)]
+struct LinkConfig {
+ name: String,
+ link: LinkStanza,
+}
+
+impl LinkConfig {
+ fn parse<R: io::BufRead>(r: &mut R) -> Result<Self, ConfigError> {
+ let mut head = String::new();
+ let mut n = r.read_line(&mut head)?;
+ if n == 0 {
+ return Err(ConfigError::Eof);
+ }
+
+ let head: Vec<&str> = head.split(' ').collect();
+ if head.len() < 2 {
+ return Err(ConfigError::IncompleteLinkHead);
+ }
+
+ let name = head[1].to_string();
+
+ let link_keyword = head[0];
+ if link_keyword == "delete" {
+ return Ok(Self {
+ name,
+ link: LinkStanza::Delete,
+ });
+ } else if link_keyword != "link" {
+ return Err(ConfigError::ExpectedLinkStanza(link_keyword.to_string()));
+ }
+
+ let mut endpoint = String::new();
+ n = r.read_line(&mut endpoint)?;
+ if n == 0 {
+ return Err(ConfigError::NoEndpoint(name));
+ }
+ let endpoint: SocketAddr = endpoint.parse()?;
+
+ let mut private_key = String::new();
+ n = r.read_line(&mut private_key)?;
+ if n == 0 {
+ return Err(ConfigError::NoPrivateKey(name));
+ }
+ let private_key = wireguard_control::Key::from_base64(&private_key)?;
+
+ let mut public_key = String::new();
+ n = r.read_line(&mut public_key)?;
+ if n == 0 {
+ return Err(ConfigError::NoPublicKey(name));
+ }
+ let public_key = wireguard_control::Key::from_base64(&public_key)?;
+
+ let mut preshared_key = String::new();
+ n = r.read_line(&mut preshared_key)?;
+ if n == 0 {
+ return Err(ConfigError::NoPresharedKey(name));
+ }
+ let preshared_key = wireguard_control::Key::from_base64(&preshared_key)?;
+
+ let mut addresses = String::new();
+ n = r.read_line(&mut addresses)?;
+ if n == 0 {
+ return Err(ConfigError::NoAddresses(name));
+ }
+
+ let address_strs = addresses.split(' ');
+ let mut addresses = Vec::new();
+
+ for address_str in address_strs {
+ let parts: Vec<&str> = address_str.split('/').collect();
+ if parts.len() != 2 {
+ return Err(ConfigError::InvalidCidr);
+ }
+
+ let address: IpAddr = parts[0].parse()?;
+ let prefix_length: u8 = parts[1].parse()?;
+
+ addresses.push((address, prefix_length));
+ }
+
+ let mut allowed_ips = String::new();
+ n = r.read_line(&mut allowed_ips)?;
+ if n == 0 {
+ return Err(ConfigError::NoAllowedIps(name));
+ }
+
+ let allowed_ip_strs = allowed_ips.split(' ');
+ let mut allowed_ips = Vec::new();
+
+ for allowed_ip_str in allowed_ip_strs {
+ let allowed_ip: wireguard_control::AllowedIp = allowed_ip_str
+ .parse()
+ .map_err(|_| ConfigError::InvalidAllowedIp(allowed_ip_str.to_string()))?;
+ allowed_ips.push(allowed_ip);
+ }
+
+ let mut keepalive_seconds = String::new();
+ n = r.read_line(&mut keepalive_seconds)?;
+ if n == 0 {
+ return Err(ConfigError::NoKeepaliveInterval(name));
+ }
+
+ let keepalive_seconds: u16 = keepalive_seconds.parse()?;
+
+ Ok(Self {
+ name,
+ link: LinkStanza::Link(Link {
+ endpoint,
+ private_key,
+ public_key,
+ preshared_key,
+ addresses,
+ allowed_ips,
+ keepalive_seconds,
+ }),
+ })
+ }
+}
+
+#[derive(Debug)]
+struct Config {
+ links: HashMap<String, LinkStanza>,
+}
+
+impl Config {
+ fn parse<R: io::BufRead>(r: &mut R) -> Result<Self, ConfigError> {
+ let mut links = HashMap::new();
+ loop {
+ let eof = Self::skip_blank_lines(r)?;
+ if eof {
+ break;
+ }
+
+ let link_config = LinkConfig::parse(r);
+ if let Err(ConfigError::Eof) = link_config {
+ break;
+ }
+ let link_config = link_config?;
+
+ if links
+ .insert(link_config.name.clone(), link_config.link)
+ .is_some()
+ {
+ return Err(ConfigError::DuplicateLink(link_config.name));
+ }
+ }
+
+ Ok(Self { links })
+ }
+
+ fn skip_blank_lines<R: io::BufRead>(r: &mut R) -> Result<bool, ConfigError> {
+ let mut s = String::new();
+ while r.read_line(&mut s)? > 0 {
+ if s != "\n" {
+ return Ok(false);
+ }
+
+ s.clear();
+ }
+
+ Ok(true)
+ }
+}
+
fn main() {
- println!("Hello, world!");
+ println!("[info] init");
+ match run() {
+ Ok(_) => loop {
+ std::thread::park()
+ },
+ Err(e) => {
+ eprintln!("[warn] {}", e);
+ std::process::exit(1);
+ }
+ }
+}
+
+fn run() -> Result<(), Error> {
+ let f = match File::open(CONFIG_PATH) {
+ Ok(f) => f,
+ Err(e) => return Err(ConfigError::Io(e).into()),
+ };
+ let mut br = io::BufReader::new(f);
+ let config = Config::parse(&mut br)?;
}