//
// Syd: rock-solid application kernel
// src/utils/syd-dns.rs: Resolve hostname into IPs using system DNS resolver
//
// Copyright (c) 2024, 2025, 2026 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

use std::{net::IpAddr, process::ExitCode};

use libc::{res_init, AF_INET, AF_INET6};
use nix::errno::Errno;
use syd::{
    dns::{lookup_addr, resolve_host, resolve_rand},
    path::XPathBuf,
};

// Set global allocator to GrapheneOS allocator.
#[cfg(all(
    not(coverage),
    not(feature = "prof"),
    not(target_os = "android"),
    not(target_arch = "riscv64"),
    target_page_size_4k,
    target_pointer_width = "64"
))]
#[global_allocator]
static GLOBAL: hardened_malloc::HardenedMalloc = hardened_malloc::HardenedMalloc;

// Set global allocator to tcmalloc if profiling is enabled.
#[cfg(feature = "prof")]
#[global_allocator]
static GLOBAL: tcmalloc::TCMalloc = tcmalloc::TCMalloc;

syd::main! {
    use lexopt::prelude::*;

    syd::set_sigpipe_dfl()?;

    // Parse CLI options.
    let mut opt_ipv4 = false;
    let mut opt_ipv6 = false;
    let mut opt_rand = false;
    let mut opt_rdns = false;
    let mut hostname = None;

    let mut parser = lexopt::Parser::from_env();
    while let Some(arg) = parser.next()? {
        match arg {
            Short('h') => {
                help();
                return Ok(ExitCode::SUCCESS);
            }
            Short('r') => opt_rand = true,
            Short('R') => opt_rdns = true,
            Short('4') => opt_ipv4 = true,
            Short('6') => opt_ipv6 = true,
            Value(val) if hostname.is_none() => {
                hostname = Some(val.string()?);
            }
            _ => return Err(arg.unexpected().into()),
        }
    }

    if opt_rdns && (opt_ipv4 || opt_ipv6 || opt_rand) {
        eprintln!("Options -4, -6 and -r must not be given with -R!");
        return Err(Errno::EINVAL.into());
    }

    let family = if opt_rdns {
        None
    } else if opt_ipv4 && opt_ipv6 {
        eprintln!("At most one of -4, -6 must be given!");
        return Err(Errno::EINVAL.into());
    } else if opt_ipv4 {
        Some(AF_INET)
    } else if opt_ipv6 {
        Some(AF_INET6)
    } else {
        None
    };

    let hostname = if let Some(hostname) = hostname {
        hostname
    } else {
        help();
        return Err(Errno::ENOENT.into());
    };

    if opt_rdns {
        // SAFETY: Initialize system DNS resolver.
        // Note: resolve_{rand,host} use to_socket_addrs,
        // so do not require the libc call.
        if unsafe { res_init() } != 0 {
            return Err(Errno::EFAULT.into());
        }

        let addr = match hostname.parse::<IpAddr>() {
            Ok(addr) => addr,
            Err(err) => {
                eprintln!("Failed to parse IP address `{hostname}': {err}");
                return Err(Errno::EINVAL.into());
            }
        };
        let name: XPathBuf = lookup_addr(addr)?.into();
        println!("{name}");
    } else if opt_rand {
        let addr = resolve_rand(&hostname, family)?;
        println!("{addr}");
    } else {
        for addr in resolve_host(&hostname, family)? {
            println!("{addr}");
        }
    }

    Ok(ExitCode::SUCCESS)
}

fn help() {
    println!("Usage: syd-dns [-hr46] hostname");
    println!("       syd-dns [-R] IPv4/6 address");
    println!("Resolve hostname into IPs using system DNS resolver.");
    println!("Given -R, perform a reverse-DNS lookup using system DNS resolver.");
    println!("If -4 is given, print only IPv4 addresses.");
    println!("If -6 is given, print only IPv6 addresses.");
    println!("If -r is given, print a random IP.");
}
