resolved/
fs.rs

1use std::io;
2use std::path::{Path, PathBuf};
3use tokio::fs::{read_dir, read_to_string};
4
5use dns_types::hosts::types::Hosts;
6use dns_types::zones::types::{Zone, Zones};
7
8/// Load the hosts and zones from the configuration, generating the
9/// `Zones` parameter for the resolver.
10pub async fn load_zone_configuration(
11    hosts_files: &[PathBuf],
12    hosts_dirs: &[PathBuf],
13    zone_files: &[PathBuf],
14    zone_dirs: &[PathBuf],
15) -> Option<Zones> {
16    let mut is_error = false;
17    let mut hosts_file_paths = Vec::from(hosts_files);
18    let mut zone_file_paths = Vec::from(zone_files);
19
20    for path in zone_dirs {
21        match get_files_from_dir(path).await {
22            Ok(mut paths) => zone_file_paths.append(&mut paths),
23            Err(error) => {
24                tracing::warn!(?path, ?error, "could not read zone directory");
25                is_error = true;
26            }
27        }
28    }
29    for path in hosts_dirs {
30        match get_files_from_dir(path).await {
31            Ok(mut paths) => hosts_file_paths.append(&mut paths),
32            Err(error) => {
33                tracing::warn!(?path, ?error, "could not read hosts directory");
34                is_error = true;
35            }
36        }
37    }
38
39    let mut combined_zones = Zones::new();
40    for path in &zone_file_paths {
41        match zone_from_file(Path::new(path)).await {
42            Ok(Ok(zone)) => combined_zones.insert_merge(zone),
43            Ok(Err(error)) => {
44                tracing::warn!(?path, ?error, "could not parse zone file");
45                is_error = true;
46            }
47            Err(error) => {
48                tracing::warn!(?path, ?error, "could not read zone file");
49                is_error = true;
50            }
51        }
52    }
53
54    let mut combined_hosts = Hosts::default();
55    for path in &hosts_file_paths {
56        match hosts_from_file(Path::new(path)).await {
57            Ok(Ok(hosts)) => combined_hosts.merge(hosts),
58            Ok(Err(error)) => {
59                tracing::warn!(?path, ?error, "could not parse hosts file");
60                is_error = true;
61            }
62            Err(error) => {
63                tracing::warn!(?path, ?error, "could not read hosts file");
64                is_error = true;
65            }
66        }
67    }
68
69    if is_error {
70        None
71    } else {
72        combined_zones.insert_merge(combined_hosts.into());
73        Some(combined_zones)
74    }
75}
76
77/// Read a hosts file, for example /etc/hosts.
78async fn hosts_from_file<P: AsRef<Path>>(
79    path: P,
80) -> io::Result<Result<Hosts, dns_types::hosts::deserialise::Error>> {
81    let data = read_to_string(path).await?;
82    Ok(Hosts::deserialise(&data))
83}
84
85/// Read a zone file.
86///
87/// If it has a SOA record, it is an authoritative zone: it may
88/// only have *one* SOA record, and all RRs must be subdomains of
89/// the SOA domain.
90///
91/// If it does not have a SOA record, it is a non-authoritative
92/// zone, and the root domain will be used for its apex.
93async fn zone_from_file<P: AsRef<Path>>(
94    path: P,
95) -> io::Result<Result<Zone, dns_types::zones::deserialise::Error>> {
96    let data = read_to_string(path).await?;
97    Ok(Zone::deserialise(&data))
98}
99
100/// Get files from a directory, sorted.
101async fn get_files_from_dir(dir: &Path) -> io::Result<Vec<PathBuf>> {
102    let mut out = Vec::new();
103
104    let mut reader = read_dir(dir).await?;
105    while let Some(entry) = reader.next_entry().await? {
106        let path = entry.path();
107        if !path.is_dir() {
108            out.push(path);
109        }
110    }
111
112    out.sort();
113    Ok(out)
114}