use std::
{
fmt, fs::File, io::Read, marker::PhantomData, path::{Path, PathBuf}, sync::OnceLock
};
use instance_copy_on_write::{ICoW, ICoWError, ICoWRead, cow_mutex::ICoWWeak};
use crate::
{
CDnsErrorType,
CDnsResult,
HostConfig,
ResolveConfig,
common,
internal_error,
internal_error_map
};
pub static GLOBAL_CONFIG: OnceLock<DnsConfigs<DnsConfigGlobal>> = OnceLock::new();
pub trait ConfigGetter
{
fn get_hosts(&self) -> &DnsConfig<HostConfig>;
fn get_resolv(&self) -> &DnsConfig<ResolveConfig>;
}
pub trait DnsConfigReader: fmt::Debug
{
type RetItem;
fn read_config(&self) -> ICoWRead<'_, Self::RetItem>;
fn get_reader(&self) -> ICoWWeak<Self::RetItem>;
}
pub trait DnsConfigUpdater: fmt::Debug
{
type NewItem;
fn get_path_to_file(&self) -> Option<&Path>;
fn update_in_place(&self, new_item: Self::NewItem) -> CDnsResult<()>;
}
#[derive(Debug)]
pub struct DnsConfig<CFG: fmt::Debug>
{
file_path: Option<PathBuf>,
conf: ICoW<CFG>
}
impl<CFG: fmt::Debug> DnsConfig<CFG>
{
fn new(file_path: &Path, conf: CFG) -> Self
{
return
Self
{
file_path: Some(file_path.to_path_buf()),
conf: ICoW::new(conf),
};
}
fn new_local(conf: CFG) -> Self
{
return
Self
{
file_path: None,
conf: ICoW::new(conf),
};
}
}
impl<CFG: fmt::Debug> DnsConfigReader for DnsConfig<CFG>
{
type RetItem = CFG;
fn read_config(&self) -> ICoWRead<'_, Self::RetItem>
{
return self.conf.read();
}
fn get_reader(&self) -> ICoWWeak<Self::RetItem>
{
return self.conf.reader();
}
}
impl<CFG: fmt::Debug> DnsConfigUpdater for DnsConfig<CFG>
{
type NewItem = CFG;
fn get_path_to_file(&self) -> Option<&Path>
{
return self.file_path.as_ref().map(|f| f.as_path());
}
fn update_in_place(&self, new_item: Self::NewItem) -> CDnsResult<()>
{
let mut commit_item = new_item;
loop
{
let Err((err, ret_item)) = self.conf.try_new_inplace(commit_item)
else
{
return Ok(());
};
match err
{
ICoWError::ExclusiveLockPending =>
internal_error!(CDnsErrorType::SoftAssertion,
"update_in_place() exclusive lock pending error, not exclusive lock should be held!"),
ICoWError::WouldBlock =>
{
commit_item = ret_item;
}
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum DnsConfigSource<'s, CFG>
{
SourceFile(&'s Path),
SourceText(&'s str),
SourceInstance(CFG),
}
pub trait DnsConfigDest {}
#[derive(Debug)]
pub struct DnsConfigGlobal;
impl DnsConfigDest for DnsConfigGlobal {}
#[derive(Debug)]
pub struct DnsConfigUser;
impl DnsConfigDest for DnsConfigUser {}
#[derive(Debug)]
pub struct DnsConfigs<DEST: DnsConfigDest>
{
hosts_conf: DnsConfig<HostConfig>,
resolv_conf: DnsConfig<ResolveConfig>,
_p: PhantomData<DEST>
}
impl DnsConfigs<DnsConfigGlobal>
{
pub
fn init_global_config(opt_conf: Option<DnsConfigs<DnsConfigGlobal>>) -> CDnsResult<()>
{
let global_cfg =
opt_conf.unwrap_or(DnsConfigs::new_system()?);
return
GLOBAL_CONFIG
.set(global_cfg)
.map_err(|_c|
internal_error_map!(CDnsErrorType::SoftAssertion, "GLOBAL_CONFIG already inited!")
);
}
pub
fn reload_hosts(&self) -> CDnsResult<()>
{
let hosts_cfg = Self::load_host(self.hosts_conf.get_path_to_file().unwrap())?;
return self.hosts_conf.update_in_place(hosts_cfg);
}
pub
fn reload_resolv(&self) -> CDnsResult<()>
{
let resolv_cfg = Self::load_resolv(self.resolv_conf.get_path_to_file().unwrap())?;
return self.resolv_conf.update_in_place(resolv_cfg);
}
}
impl DnsConfigs<DnsConfigUser>
{
pub
fn reload_hosts(&self, raw_cfg: Option<String>) -> CDnsResult<()>
{
let Some(raw_cfgtext) = raw_cfg
else
{
let Some(file_path) = self.hosts_conf.get_path_to_file()
else
{
internal_error!(CDnsErrorType::ConfigError, "cannot reload hosts, no data provided!")
};
let hosts_cfg = Self::load_host(file_path)?;
return self.hosts_conf.update_in_place(hosts_cfg);
};
let hosts = HostConfig::parse_host_file_internal(&raw_cfgtext)?;
return self.hosts_conf.update_in_place(hosts);
}
pub
fn reload_resolv(&self, raw_cfg: Option<String>) -> CDnsResult<()>
{
let Some(raw_cfgtext) = raw_cfg
else
{
let Some(file_path) = self.resolv_conf.get_path_to_file()
else
{
internal_error!(CDnsErrorType::ConfigError, "cannot reload resolv.conf, no data provided!")
};
let resolv_cfg = Self::load_resolv(file_path)?;
return self.resolv_conf.update_in_place(resolv_cfg);
};
let resolv_conf = ResolveConfig::parser_resolv_internal(&raw_cfgtext)?;
return self.resolv_conf.update_in_place(resolv_conf);
}
}
impl<DEST: DnsConfigDest> DnsConfigs<DEST>
{
fn load_file(file_path: &Path) -> CDnsResult<String>
{
let mut hosts_conf =
File::options()
.read(true)
.open(file_path)
.map_err(|e|
internal_error_map!(CDnsErrorType::IoError,
"cannot read file {}, error: {}", file_path.display(), e)
)?;
let mut contents = String::new();
hosts_conf
.read_to_string(&mut contents)
.map_err(|e|
internal_error_map!(CDnsErrorType::IoError,
"cannot read file {}, error: {}", file_path.display(), e)
)?;
return Ok(contents);
}
pub
fn load_host(file_path: &Path) -> CDnsResult<HostConfig>
{
let contents = Self::load_file(file_path)?;
return HostConfig::parse_host_file_internal(&contents);
}
pub
fn load_resolv(file_path: &Path) -> CDnsResult<ResolveConfig>
{
let contents = Self::load_file(file_path)?;
return ResolveConfig::parser_resolv_internal(&contents);
}
pub
fn new_system() -> CDnsResult<Self>
{
let hosts_path = Path::new(common::HOST_CFG_PATH);
let hosts =
DnsConfig::<HostConfig>::new(hosts_path, Self::load_host(hosts_path)? );
let resolv_path = Path::new(common::RESOLV_CFG_PATH);
let resolv =
DnsConfig::<ResolveConfig>::new(resolv_path, Self::load_resolv(resolv_path)?);
return Ok(
Self
{
hosts_conf: hosts,
resolv_conf: resolv,
_p: PhantomData,
}
);
}
pub
fn new_custom(hosts: DnsConfigSource<'_, HostConfig>, resolv: DnsConfigSource<'_, ResolveConfig>) -> CDnsResult<Self>
{
let hosts_conf =
match hosts
{
DnsConfigSource::SourceFile(path) =>
DnsConfig::<HostConfig>::new(path, Self::load_host(path)? ),
DnsConfigSource::SourceText(cfg_text) =>
DnsConfig::<HostConfig>::new_local(HostConfig::parse_host_file_internal(cfg_text)? ),
DnsConfigSource::SourceInstance(inst) =>
DnsConfig::<HostConfig>::new_local(inst),
};
let resolv_conf =
match resolv
{
DnsConfigSource::SourceFile(path) =>
DnsConfig::<ResolveConfig>::new(path, Self::load_resolv(path)? ),
DnsConfigSource::SourceText(cfg_text) =>
DnsConfig::<ResolveConfig>::new_local(ResolveConfig::parser_resolv_internal(cfg_text)? ),
DnsConfigSource::SourceInstance(inst) =>
DnsConfig::<ResolveConfig>::new_local(inst),
};
return Ok(
Self
{
hosts_conf: hosts_conf,
resolv_conf: resolv_conf,
_p: PhantomData,
}
);
}
pub
fn get_hosts_l(&self) -> &DnsConfig<HostConfig>
{
return &self.hosts_conf;
}
pub
fn get_resolv_l(&self) -> &DnsConfig<ResolveConfig>
{
return &self.resolv_conf;
}
}
impl<DEST: DnsConfigDest> ConfigGetter for DnsConfigs<DEST>
{
fn get_hosts(&self) -> &DnsConfig<HostConfig>
{
return &self.hosts_conf;
}
fn get_resolv(&self) -> &DnsConfig<ResolveConfig>
{
return &self.resolv_conf;
}
}
impl<DEST: DnsConfigDest> ConfigGetter for &DnsConfigs<DEST>
{
fn get_hosts(&self) -> &DnsConfig<HostConfig>
{
return &self.hosts_conf;
}
fn get_resolv(&self) -> &DnsConfig<ResolveConfig>
{
return &self.resolv_conf;
}
}
#[cfg(test)]
mod test_config
{
use std::net::IpAddr;
use crate::QType;
use super::*;
#[test]
fn test_0()
{
assert_eq!(GLOBAL_CONFIG.get().is_none(), true);
DnsConfigs::<DnsConfigGlobal>::init_global_config(None).unwrap();
assert_eq!(GLOBAL_CONFIG.get().is_some(), true);
let read0 = GLOBAL_CONFIG.get().unwrap().get_hosts().read_config();
let localhost = read0.search_by_ip(&"127.0.0.1".parse::<IpAddr>().unwrap());
assert_eq!(localhost.is_some(), true);
drop(read0);
let reader = GLOBAL_CONFIG.get().unwrap().get_hosts().get_reader();
let rr = reader.aquire().unwrap();
let fqdn = rr.search_by_fqdn(QType::A, "localhost");
assert_eq!(fqdn.is_some(), true);
}
}