From 1769b083ab6f1d84609c1b73e815f463a555d526 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Sat, 18 Jul 2026 00:29:43 +0900 Subject: [PATCH] fix(boot): clean up bare/mini boot warnings and errors - init: add condition_path_exists so optional-daemon units (dbus, seatd, thermald, evdevd) are silently skipped when their binary is absent in a minimal image, instead of emitting [FAILED] 'No such file or directory' on the bare target. Boot-critical units omit the field and still hard-fail. - ahcid: an empty ATAPI/optical drive (QEMU's default DVD-ROM, most bare-metal optical bays) failed READ CAPACITY and logged 4 ERROR lines every boot; register it with a zero block count as a normal no-media state and drop the HBA register dump to debug level. - netstack: a machine with no NIC (bare, or an unsupported NIC) logged an ERROR and exited non-zero; treat 'no network adapter' as a normal idle state (info + clean exit). - dhcpd: cut the socket timeout 30s -> 8s so the network stage fails fast when no DHCP server answers instead of stalling boot. --- dhcpd/src/main.rs | 9 ++++++-- drivers/storage/ahcid/src/ahci/disk_atapi.rs | 22 ++++++++++++++++---- drivers/storage/ahcid/src/ahci/hba.rs | 11 +++++++--- init.d/10_evdevd.service | 1 + init.d/12_dbus.service | 1 + init.d/13_seatd.service | 1 + init.d/30_thermald.service | 1 + init/src/unit.rs | 17 +++++++++++++++ netstack/src/main.rs | 13 ++++++++++-- 9 files changed, 65 insertions(+), 11 deletions(-) diff --git a/dhcpd/src/main.rs b/dhcpd/src/main.rs index 064b33ddfd..e8b1707acf 100644 --- a/dhcpd/src/main.rs +++ b/dhcpd/src/main.rs @@ -122,8 +122,13 @@ fn dhcp(iface: &str, verbose: bool) -> Result<(), String> { let socket = try_fmt!(UdpSocket::bind(("0.0.0.0", 68)), "failed to bind udp"); try_fmt!(socket.connect(SocketAddr::from(([255, 255, 255, 255], 67))), "failed to connect"); - try_fmt!(socket.set_read_timeout(Some(Duration::new(30, 0))), "failed to set read timeout"); - try_fmt!(socket.set_write_timeout(Some(Duration::new(30, 0))), "failed to set write timeout"); + // 8s (was 30s): a DHCP server that is present answers a DISCOVER in well + // under a second, so a long read timeout only serves to stall boot when no + // server responds (e.g. QEMU user-net where the limited broadcast is not + // routed, or a link with no DHCP). Failing fast keeps the network stage off + // the critical path to login instead of hanging the boot for ~30s+. + try_fmt!(socket.set_read_timeout(Some(Duration::new(8, 0))), "failed to set read timeout"); + try_fmt!(socket.set_write_timeout(Some(Duration::new(8, 0))), "failed to set write timeout"); let mut subnet_option: Option<[u8; 4]> = None; let mut router_option: Option<[u8; 4]> = None; diff --git a/drivers/storage/ahcid/src/ahci/disk_atapi.rs b/drivers/storage/ahcid/src/ahci/disk_atapi.rs index a0e75c0964..e366e2e1a6 100644 --- a/drivers/storage/ahcid/src/ahci/disk_atapi.rs +++ b/drivers/storage/ahcid/src/ahci/disk_atapi.rs @@ -48,11 +48,25 @@ impl DiskATAPI { let mut cmd = [0; 16]; cmd[0] = SCSI_READ_CAPACITY; - port.atapi_dma(&cmd, 8, &mut clb, &mut ctbas, &mut buf)?; - // Instead of a count, contains number of last LBA, so add 1 - let blk_count = BigEndian::read_u32(&buf[0..4]) + 1; - let blk_size = BigEndian::read_u32(&buf[4..8]); + // An ATAPI drive with no media inserted (an empty optical drive — the + // default state of QEMU's DVD-ROM and of most real machines that have an + // optical bay at all) fails READ CAPACITY with a check-condition / I/O + // error. That is a normal, expected condition, NOT a driver fault, so + // register the drive with a zero block count instead of failing + // enumeration and logging a scary ERROR on every boot. It becomes + // usable once media is present and the capacity is re-read. + let (blk_count, blk_size) = + match port.atapi_dma(&cmd, 8, &mut clb, &mut ctbas, &mut buf) { + Ok(_) => { + // Instead of a count, contains number of last LBA, so add 1 + (BigEndian::read_u32(&buf[0..4]) + 1, BigEndian::read_u32(&buf[4..8])) + } + Err(_) => { + log::info!("ahcid: ATAPI port {}: no media present (empty drive)", id); + (0, 2048) + } + }; Ok(DiskATAPI { id, diff --git a/drivers/storage/ahcid/src/ahci/hba.rs b/drivers/storage/ahcid/src/ahci/hba.rs index bea8792c80..5d3af5be9c 100644 --- a/drivers/storage/ahcid/src/ahci/hba.rs +++ b/drivers/storage/ahcid/src/ahci/hba.rs @@ -448,12 +448,17 @@ impl HbaPort { self.fbs.read(), ); - error!("IS {:X} IE {:X} CMD {:X} TFD {:X}", is, ie, cmd, tfd); - error!( + // Diagnostic register dump, emitted at debug level: a port error is + // an expected, recoverable condition (e.g. READ CAPACITY on an empty + // ATAPI/optical drive) and the caller decides whether it is fatal and + // logs an appropriate user-facing message. Keeping this at `error!` + // spammed three scary lines on every boot for an empty CD-ROM. + debug!("IS {:X} IE {:X} CMD {:X} TFD {:X}", is, ie, cmd, tfd); + debug!( "SSTS {:X} SCTL {:X} SERR {:X} SACT {:X}", ssts, sctl, serr, sact ); - error!("CI {:X} SNTF {:X} FBS {:X}", ci, sntf, fbs); + debug!("CI {:X} SNTF {:X} FBS {:X}", ci, sntf, fbs); self.is.write(u32::MAX); Err(Error::new(EIO)) diff --git a/init.d/10_evdevd.service b/init.d/10_evdevd.service index 3ef3c577ac..ae69a19a14 100644 --- a/init.d/10_evdevd.service +++ b/init.d/10_evdevd.service @@ -1,4 +1,5 @@ [unit] +condition_path_exists = ["/usr/bin/evdevd"] description = "Evdev input daemon" requires_weak = [ "12_boot-late.target", diff --git a/init.d/12_dbus.service b/init.d/12_dbus.service index 0c64eb1c31..42deac3a7f 100644 --- a/init.d/12_dbus.service +++ b/init.d/12_dbus.service @@ -1,4 +1,5 @@ [unit] +condition_path_exists = ["/usr/bin/dbus-daemon"] description = "D-Bus system bus" requires_weak = ["12_boot_late.target", "00_ipcd.service"] diff --git a/init.d/13_seatd.service b/init.d/13_seatd.service index d985278c29..619bc7d8a6 100644 --- a/init.d/13_seatd.service +++ b/init.d/13_seatd.service @@ -1,4 +1,5 @@ [unit] +condition_path_exists = ["/usr/bin/seatd"] description = "seatd seat management" requires_weak = ["12_dbus.service"] diff --git a/init.d/30_thermald.service b/init.d/30_thermald.service index 4402500bb5..b3219d7038 100644 --- a/init.d/30_thermald.service +++ b/init.d/30_thermald.service @@ -1,4 +1,5 @@ [unit] +condition_path_exists = ["/usr/bin/thermald"] description = "CPU Thermal Monitor" requires_weak = ["00_base.target"] diff --git a/init/src/unit.rs b/init/src/unit.rs index 98053cb2d7..8e5fa8709e 100644 --- a/init/src/unit.rs +++ b/init/src/unit.rs @@ -125,6 +125,13 @@ pub struct UnitInfo { pub condition_architecture: Option>, // FIXME replace this with hwd reading from the devicetree pub condition_board: Option>, + /// Only start this unit if every listed path exists. Used for optional + /// daemons whose binary may be absent in a minimal image (e.g. dbus-daemon, + /// seatd, thermald, evdevd on the bare target). When the binary is not + /// installed the unit is silently skipped instead of producing a spurious + /// `[ FAILED ]` "No such file or directory" line — real boot-critical units + /// omit this field and still hard-fail if their binary is missing. + pub condition_path_exists: Option>, } fn true_bool() -> bool { @@ -190,6 +197,7 @@ impl Unit { requires_weak: script.1, condition_architecture: None, condition_board: None, + condition_path_exists: None, }, kind: UnitKind::LegacyScript { script: script.0 }, }); @@ -246,6 +254,15 @@ impl Unit { } } + if let Some(condition_path_exists) = &self.info.condition_path_exists { + if !condition_path_exists + .iter() + .all(|path| std::path::Path::new(path).exists()) + { + return false; + } + } + true } } diff --git a/netstack/src/main.rs b/netstack/src/main.rs index 49e24684a6..905e679432 100644 --- a/netstack/src/main.rs +++ b/netstack/src/main.rs @@ -2,7 +2,7 @@ extern crate log; use std::process; -use anyhow::{anyhow, bail, Context, Result}; +use anyhow::{anyhow, Context, Result}; use event::{EventFlags, EventQueue}; use libredox::flag::{O_NONBLOCK, O_RDWR}; use libredox::Fd; @@ -36,7 +36,12 @@ fn get_all_network_adapters() -> Result> { } if adapters.is_empty() { - bail!("no network adapter found"); + // No NIC present is a normal state on a diskless/headless/bare machine, + // or simply before an unsupported NIC's driver attaches. Report it as + // info and let the caller exit cleanly instead of emitting a spurious + // ERROR + non-zero exit on every boot of a NIC-less system. + info!("no network adapter found; network stack idle"); + return Ok(adapters); } info!("Found {} network adapter(s): {:?}", adapters.len(), adapters); Ok(adapters) @@ -44,6 +49,10 @@ fn get_all_network_adapters() -> Result> { fn run(daemon: daemon::Daemon) -> Result<()> { let adapters = get_all_network_adapters()?; + if adapters.is_empty() { + // Nothing to service; exit cleanly (see get_all_network_adapters). + return Ok(()); + } trace!("found {} network adapter(s)", adapters.len()); let mut network_fds = Vec::new();