Skip to content
mod.rs 7.92 KiB
Newer Older
Luker's avatar
Luker committed
/*
 * Copyright (c) 2021, Luca Fulchir <luker@fenrirproject.org>
 *
 * This file is part of dfim.
 *
 * dfim is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * dfim is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with dfim.  If not, see <https://www.gnu.org/licenses/>.
 */

Luker's avatar
Luker committed
pub mod device;
pub mod errors;
pub mod include;
pub mod trgt;
use crate::config::trgt::TargetCommon;
pub trait CfgVerify {
    fn standardize(&mut self);
    fn check_consistency(
        &self,
        logger: &slog::Logger,
    ) -> Result<(), errors::ConfigError>;
}

#[derive(::serde::Deserialize, ::serde::Serialize, Copy, Clone)]
Luker's avatar
Luker committed
#[serde(deny_unknown_fields)]
pub enum LogTime {
    UTC,
    Local,
}

#[derive(::serde::Deserialize, ::serde::Serialize, Copy, Clone)]
Luker's avatar
Luker committed
#[serde(deny_unknown_fields)]
pub enum LogFacility {
    Kern,
    User,
    Mail,
    Daemon,
    Auth,
    Syslog,
    Lpr,
    News,
    Uucp,
    Cron,
    Authpriv,
    Ftp,
    Local0,
    Local1,
    Local2,
    Local3,
    Local4,
    Local5,
    Local6,
    Local7,
}
#[derive(::serde::Deserialize, ::serde::Serialize, Copy, Clone)]
Luker's avatar
Luker committed
#[serde(deny_unknown_fields)]
pub enum LogLevel {
    Critical,
    Error,
    Warning,
    Info,
    Debug,
    Trace,
}
#[derive(::serde::Deserialize, ::serde::Serialize, Copy, Clone)]
Luker's avatar
Luker committed
#[serde(deny_unknown_fields)]
pub enum LogTermStream {
    StdErr,
    StdOut,
}
#[derive(::serde::Deserialize, ::serde::Serialize, Clone)]
Luker's avatar
Luker committed
#[serde(deny_unknown_fields)]
#[serde(rename_all = "lowercase")]
pub enum LogSyslog {
    Socket(::std::path::PathBuf),
    Udp {
        from: ::std::net::SocketAddr,
        to: ::std::net::SocketAddr,
        hostname_id: String,
    },
    Tcp {
        to: ::std::net::SocketAddr,
        hostname_id: String,
    },
}
#[derive(::serde::Deserialize, ::serde::Serialize, Clone)]
Luker's avatar
Luker committed
#[serde(rename_all = "lowercase")]
#[serde(deny_unknown_fields)]
pub enum LogDest {
    Terminal(Option<LogTermStream>),
    Syslog {
        dest: LogSyslog,
        facility: LogFacility,
        level: LogLevel,
    },
}
#[derive(::serde::Deserialize, ::serde::Serialize, Clone)]
Luker's avatar
Luker committed
#[serde(deny_unknown_fields)]
pub struct Logging {
    pub level: LogLevel,
    pub time: LogTime,
    #[serde(flatten)]
    pub dest: LogDest,
}
#[derive(::serde::Deserialize, ::serde::Serialize, Clone)]
Luker's avatar
Luker committed
#[serde(deny_unknown_fields)]
Luker's avatar
Luker committed
pub struct Main {
Luker's avatar
Luker committed
    // don't provide a default, be explicit
    pub dry_run: bool,
Luker's avatar
Luker committed
    pub logging: Logging,
    pub include: ::std::path::PathBuf,
    /* Todo: only for notifications
    pub user: String,
    pub group: String,
    */
}

Luker's avatar
Luker committed
pub struct FullConfig {
    pub main: Main,
Luker's avatar
Luker committed
    pub devices: Vec<device::Device>,
    pub targets: Vec<trgt::Target>,
Luker's avatar
Luker committed
}

impl CfgVerify for FullConfig {
    fn standardize(&mut self) {
        self.uninline();
        for d in self.devices.iter_mut() {
            d.standardize();
        }
        for t in self.targets.iter_mut() {
            t.standardize();
        }
    }
    fn check_consistency(
        &self,
        logger: &slog::Logger,
    ) -> Result<(), errors::ConfigError> {
        for d in self.devices.iter() {
Luker's avatar
Luker committed
            let id_re = ::regex::Regex::new(r"^[\w\d_-]+$").unwrap();
            if !id_re.is_match(&d.id) {
                ::slog::error!(
                    logger,
                    "device name \"{}\" is invalid: must not contain \
                     whitespace, only letters,numbers, and -_",
                    &d.id
                );
                return Err(errors::ConfigError::Consistency(
                    "Invalid device name".to_owned(),
                ));
            }

            d.check_consistency(logger)?;
        }
        for t in self.targets.iter() {
            t.check_consistency(logger)?;
        }
        Ok(())
    }
}

Luker's avatar
Luker committed
impl FullConfig {
    pub fn parse(
        logger: &::slog::Logger,
Luker's avatar
Luker committed
        conf_path: &::std::path::Path,
    ) -> std::result::Result<FullConfig, errors::ConfigError> {
        let main: Main = {
            // parse file in this block so that the fopen is
            // closed as soon as possible
            let conf_device = ::std::fs::File::open(conf_path)?;
            let mut extensions = ::ron::extensions::Extensions::default();
            extensions.insert(::ron::extensions::Extensions::IMPLICIT_SOME);
            extensions.insert(::ron::extensions::Extensions::UNWRAP_NEWTYPES);
            // TODO: wait for new RON release
            //match ::ron::Options::default().
            // with_default_extensions(extensions).from_reader(&conf_device) {
            match ::ron::de::from_reader(&conf_device) {
                Ok(cfg) => cfg,
                Err(repr) => {
                    ::slog::error!(logger, "on file: {:?}", &conf_path);
                    ::slog::error!(logger, "{}", repr);
                    return Err(errors::ConfigError::Parsing(repr));
                }
Luker's avatar
Luker committed
            }
Luker's avatar
Luker committed
        };

        let mut cfg = FullConfig {
            main: main,
            devices: Vec::new(),
            targets: Vec::new(),
        };

        // now parse all the included device confs

        if !cfg.main.include.is_dir() {
            ::std::eprintln!("Error: include conf directory not found");
            return Err(errors::ConfigError::IO(::std::io::Error::new(
                ::std::io::ErrorKind::NotFound,
                "include conf directory not found",
            )));
Luker's avatar
Luker committed
        }
Luker's avatar
Luker committed
        let re = ::regex::Regex::new(r"^.*\.ron$").unwrap();
        for filename in ::std::fs::read_dir(&cfg.main.include)? {
            let filename = filename?.path();
            if !re.is_match(filename.to_str().unwrap()) {
                continue;
            }
            let mut include = match include::parse_include(logger, &filename) {
                Ok(include) => include,
                Err(e) => {
                    return Err(e);
                }
            };
Luker's avatar
Luker committed
            cfg.targets.append(&mut include.targets);
            cfg.devices.append(&mut include.devices);
Luker's avatar
Luker committed
        }
Luker's avatar
Luker committed
        return Ok(cfg);
Luker's avatar
Luker committed
    }
    fn uninline(&mut self) {
Luker's avatar
Luker committed
        // We have a few places in the config where we let the user inline the
        // configuration for ease of use
        // Uninline them so that we have a standard way of accessing stuff

        let mut queue = Vec::<trgt::Target>::new();
        let mut next_queue = Vec::<trgt::Target>::new();
Luker's avatar
Luker committed

        for dev in &mut self.devices {
            match &mut dev.target {
                trgt::TargetId::Id(_) => {}
                trgt::TargetId::Inline(t) => {
Luker's avatar
Luker committed
                    let tmp_id = t.id().to_string();
                    queue.push(t.clone());
                    dev.target = trgt::TargetId::Id(tmp_id);
Luker's avatar
Luker committed
                }
            }
        }
        queue.append(&mut self.targets);
        while queue.len() > 0 {
            for target in &mut queue {
                match target {
                    trgt::Target::GPT(g) => {
Luker's avatar
Luker committed
                        for p in &mut g.partitions {
                            match &mut p.target {
                                trgt::TargetId::Id(_) => {}
                                trgt::TargetId::Inline(target) => {
Luker's avatar
Luker committed
                                    let tmp_id = target.id().to_string();
                                    next_queue.push(target.clone());
                                    p.target = trgt::TargetId::Id(tmp_id);
Luker's avatar
Luker committed
                                }
                            }
                        }
                    }
                    _ => {}
                }
            }
            self.targets.append(&mut queue);
            ::std::mem::swap(&mut queue, &mut next_queue);
        }
    }
}