Files
NLnetLabs-dnst/src/lib.rs
T
2025-08-08 12:41:00 +02:00

123 lines
3.6 KiB
Rust

use std::ffi::OsString;
use std::path::Path;
use clap::Parser;
use commands::key2ds::Key2ds;
use commands::keygen::Keygen;
use commands::notify::Notify;
use commands::nsec3hash::Nsec3Hash;
use commands::signzone::SignZone;
use commands::update::Update;
use commands::LdnsCommand;
use domain::base::zonefile_fmt::DisplayKind;
use env::Env;
use error::Error;
use log::LogFormatter;
use tracing::level_filters::LevelFilter;
pub use self::args::Args;
pub mod args;
pub mod commands;
pub mod env;
pub mod error;
pub mod log;
pub mod parse;
pub mod util;
/// Define the way that we output zonefile records once for consistent use
/// everywhere.
pub const DISPLAY_KIND: DisplayKind = DisplayKind::Tabbed;
pub fn try_ldns_compatibility<I: IntoIterator<Item = OsString>>(
args: I,
) -> Result<Option<Args>, Error> {
let mut args_iter = args.into_iter();
let binary_path = args_iter.next().ok_or("Missing binary name")?;
let binary_name = extract_binary_name(Path::new(&binary_path))?;
// We only branch on the binary name for the ldns utilities. The rest we
// just handle as regular dnst.
let Some(binary_name) = binary_name.strip_prefix("ldns-") else {
return Ok(None);
};
let res = match binary_name {
"key2ds" => Key2ds::parse_ldns_args(args_iter),
"notify" => Notify::parse_ldns_args(args_iter),
"keygen" => Keygen::parse_ldns_args(args_iter),
"nsec3-hash" => Nsec3Hash::parse_ldns_args(args_iter),
"signzone" => SignZone::parse_ldns_args(args_iter),
"update" => Update::parse_ldns_args(args_iter),
_ => Err(format!("Unrecognized ldns command 'ldns-{binary_name}'").into()),
}?;
Ok(Some(res))
}
/// Get the binary name from a [`Path`].
///
/// The binary name is the file name without any extensions. It is similar
/// to the unstable `Path::file_stem`.
///
/// ```rust
/// use dnst::extract_binary_name;
/// use std::path::Path;
///
/// let bin = extract_binary_name(Path::new("foo/ldns-xxx")).unwrap();
/// assert_eq!(bin, "ldns-xxx");
///
/// let bin = extract_binary_name(Path::new("foo/ldns-xxx.real")).unwrap();
/// assert_eq!(bin, "ldns-xxx");
///
/// let bin = extract_binary_name(Path::new("./ldns-xxx.exe")).unwrap();
/// assert_eq!(bin, "ldns-xxx");
///
/// let bin = extract_binary_name(Path::new("ldns-xxx")).unwrap();
/// assert_eq!(bin, "ldns-xxx");
/// ```
pub fn extract_binary_name(path: &Path) -> Result<&str, Error> {
let filename = path
.file_name()
.ok_or("Missing binary file name")?
.to_str()
.ok_or("Binary file name is not valid unicode")?;
match filename.split_once('.') {
Some((binary, _)) => Ok(binary),
None => Ok(filename),
}
}
fn parse_args(env: impl Env) -> Result<Args, Error> {
if let Some(args) = try_ldns_compatibility(env.args_os())? {
return Ok(args);
}
let args = Args::try_parse_from(env.args_os())?;
Ok(args)
}
pub fn run(env: impl Env) -> u8 {
let stderr = env.stderr();
let mut subscriber = tracing_subscriber::FmtSubscriber::builder()
.with_ansi(stderr.is_terminal())
.with_writer(stderr)
.with_max_level(LevelFilter::WARN)
.event_format(LogFormatter {
program: env.args_os().next().unwrap().to_string_lossy().to_string(),
});
let res = parse_args(&env);
if let Ok(args) = &res {
subscriber = subscriber.with_max_level(args.verbosity);
}
tracing::subscriber::with_default(subscriber.finish(), || {
res.and_then(|args| args.execute(&env))
.map(|()| 0)
.unwrap_or_else(|err| {
err.pretty_print(&env);
err.exit_code()
})
})
}