From a1977e8057d7eddc58435b1ef2613f1d50953081 Mon Sep 17 00:00:00 2001 From: Alejandro Martin Perez Date: Tue, 13 Oct 2026 13:55:21 +0200 Subject: [PATCH] feat(ntp): one-shot mode and Registry-configurable server list (#26) - ntpd: `ntpd once` (or -q/--once) performs a single sync and exits (the `ntpdate` role) instead of only running the periodic daemon. - gen-hiv: default Ntpd\Parameters\Servers = pool.ntp.org;time.cloudflare.com; time.google.com (fallbacks live in configuration, not code) so first sync is reliable behind NAT. - docs: userland.md section 3.6. The SNTP client itself (libntp: request/reply parse, offset/delay, civil time; 19/19 host unit tests) and the periodic daemon (ob_set_datetime, Interval/ Timeout/Enabled) were already implemented. Verified: QEMU 953/953; VirtualBox (NAT, e1000): `ntpd once` -> 'synced: offset=198ms delay=0ms stratum=2', 'one-shot sync complete'; the auto-started daemon also synced (offset=1520ms). --- docs/networking/userland.md | 10 ++++++++++ tools/gen-hiv/src/main.rs | 2 +- userbin/ntpd/src/main.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/networking/userland.md b/docs/networking/userland.md index a31f83b0..2f95297e 100644 --- a/docs/networking/userland.md +++ b/docs/networking/userland.md @@ -770,6 +770,16 @@ daemon solo aporta transporte, configuración y estado. La documentación completa (configuración, ciclo de vida, estado y limitaciones) está en [`docs/services/ntpd.md`](../services/ntpd.md). +**Modo one-shot (`ntpdate`).** Invocado como `ntpd once` (o `-q`/`--once`), +`ntpd` realiza una única sincronización y sale (0 si sincronizó, 1 si no). Es el +papel de `ntpdate`: no hay un binario aparte, se reutiliza el mismo daemon. + +**Servidores.** La lista se configura por Registry en +`Ntpd\Parameters\Servers` (separados por `;`). El hive por defecto trae +`pool.ntp.org;time.cloudflare.com;time.google.com` — los fallbacks van en la +configuración, no en el código, para que el primer sync sea fiable detrás de +NAT. `Interval` (segundos) y `Timeout` (ms) también son valores del Registry. + --- ## 4. NeoInit System Configuration diff --git a/tools/gen-hiv/src/main.rs b/tools/gen-hiv/src/main.rs index 8348f166..85efb136 100644 --- a/tools/gen-hiv/src/main.rs +++ b/tools/gen-hiv/src/main.rs @@ -299,7 +299,7 @@ fn build_default_system_hive(enable_tests: bool, enable_network_test: bool) -> H // subkey; it never writes configuration (single source of truth: Registry). b.add_value(_V_NTP_TO, "Timeout", REG_DWORD, &3000u32.to_le_bytes(), NULL_CELL, _NTPPARAM); b.add_value(_V_NTP_INT, "Interval", REG_DWORD, &3600u32.to_le_bytes(), _V_NTP_TO, _NTPPARAM); - b.add_value(_V_NTP_SRV, "Servers", REG_SZ, b"pool.ntp.org\0", _V_NTP_INT, _NTPPARAM); + b.add_value(_V_NTP_SRV, "Servers", REG_SZ, b"pool.ntp.org;time.cloudflare.com;time.google.com\0", _V_NTP_INT, _NTPPARAM); b.add_value(_V_NTP_EN, "Enabled", REG_DWORD, &1u32.to_le_bytes(), _V_NTP_SRV, _NTPPARAM); // Interfaces\0: DHCPEnabled + DNS servers (default 0.0.0.0 = unset/automatic; diff --git a/userbin/ntpd/src/main.rs b/userbin/ntpd/src/main.rs index c403a1ae..dfa47d10 100644 --- a/userbin/ntpd/src/main.rs +++ b/userbin/ntpd/src/main.rs @@ -406,13 +406,31 @@ fn network_ready() -> bool { libnet::iface_info(0, &mut iface) == 0 && iface.ip != [0u8; 4] } +/// True when invoked as `ntpd once` / `-q` / `--once` (one-shot, `ntpdate` role). +fn arg_once() -> bool { + let a = libneodos::args::read_args(); + for w in a.split(|&b| b == b' ' || b == b'\t' || b == b'\0' || b == b'\r' || b == b'\n') { + if w.eq_ignore_ascii_case(b"once") || w == b"-q" || w == b"--once" { + return true; + } + } + false +} + #[no_mangle] pub extern "C" fn _start() -> ! { write_str(b"\r\n[ntpd] NeoDOS NTP Service v0.1\r\n"); + // `ntpd once` (or `-q`): synchronize a single time and exit — the `ntpdate` + // role. Without it the daemon runs the periodic loop. + let once = arg_once(); + let cfg = load_config(); if !cfg.enabled { write_str(b"[ntpd] NTP disabled by configuration; idling\r\n"); + if once { + syscall::sys_exit(1); + } publish("Disabled", "", 0, 0, 0, 0, "", 0); loop { // #358: observe graceful shutdown even while idling. @@ -429,6 +447,10 @@ pub extern "C" fn _start() -> ! { loop { if !network_ready() { write_str(b"[ntpd] network not ready; waiting\r\n"); + if once { + write_str(b"[ntpd] one-shot: network not ready\r\n"); + syscall::sys_exit(1); + } publish("WaitingNetwork", "", 0, 0, 0, 0, "network unavailable", sync_count); if wait_seconds(backoff) { graceful_exit(); @@ -496,6 +518,10 @@ pub extern "C" fn _start() -> ! { if synced { backoff = INITIAL_BACKOFF_SECS; + if once { + write_str(b"[ntpd] one-shot sync complete\r\n"); + syscall::sys_exit(0); + } if wait_seconds(cfg.interval) { graceful_exit(); } @@ -503,6 +529,10 @@ pub extern "C" fn _start() -> ! { write_str(b"[ntpd] all servers failed; backoff "); write_u32(backoff); write_str(b"s\r\n"); + if once { + write_str(b"[ntpd] one-shot sync failed\r\n"); + syscall::sys_exit(1); + } if wait_seconds(backoff) { graceful_exit(); }