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.
This commit is contained in:
Red Bear OS
2026-07-18 00:29:43 +09:00
parent 324172abaf
commit 1769b083ab
9 changed files with 65 additions and 11 deletions
+7 -2
View File
@@ -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"); 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.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"); // 8s (was 30s): a DHCP server that is present answers a DISCOVER in well
try_fmt!(socket.set_write_timeout(Some(Duration::new(30, 0))), "failed to set write timeout"); // 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 subnet_option: Option<[u8; 4]> = None;
let mut router_option: Option<[u8; 4]> = None; let mut router_option: Option<[u8; 4]> = None;
+18 -4
View File
@@ -48,11 +48,25 @@ impl DiskATAPI {
let mut cmd = [0; 16]; let mut cmd = [0; 16];
cmd[0] = SCSI_READ_CAPACITY; 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 // An ATAPI drive with no media inserted (an empty optical drive — the
let blk_count = BigEndian::read_u32(&buf[0..4]) + 1; // default state of QEMU's DVD-ROM and of most real machines that have an
let blk_size = BigEndian::read_u32(&buf[4..8]); // 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 { Ok(DiskATAPI {
id, id,
+8 -3
View File
@@ -448,12 +448,17 @@ impl HbaPort {
self.fbs.read(), self.fbs.read(),
); );
error!("IS {:X} IE {:X} CMD {:X} TFD {:X}", is, ie, cmd, tfd); // Diagnostic register dump, emitted at debug level: a port error is
error!( // 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 {:X} SCTL {:X} SERR {:X} SACT {:X}",
ssts, sctl, serr, sact 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); self.is.write(u32::MAX);
Err(Error::new(EIO)) Err(Error::new(EIO))
+1
View File
@@ -1,4 +1,5 @@
[unit] [unit]
condition_path_exists = ["/usr/bin/evdevd"]
description = "Evdev input daemon" description = "Evdev input daemon"
requires_weak = [ requires_weak = [
"12_boot-late.target", "12_boot-late.target",
+1
View File
@@ -1,4 +1,5 @@
[unit] [unit]
condition_path_exists = ["/usr/bin/dbus-daemon"]
description = "D-Bus system bus" description = "D-Bus system bus"
requires_weak = ["12_boot_late.target", "00_ipcd.service"] requires_weak = ["12_boot_late.target", "00_ipcd.service"]
+1
View File
@@ -1,4 +1,5 @@
[unit] [unit]
condition_path_exists = ["/usr/bin/seatd"]
description = "seatd seat management" description = "seatd seat management"
requires_weak = ["12_dbus.service"] requires_weak = ["12_dbus.service"]
+1
View File
@@ -1,4 +1,5 @@
[unit] [unit]
condition_path_exists = ["/usr/bin/thermald"]
description = "CPU Thermal Monitor" description = "CPU Thermal Monitor"
requires_weak = ["00_base.target"] requires_weak = ["00_base.target"]
+17
View File
@@ -125,6 +125,13 @@ pub struct UnitInfo {
pub condition_architecture: Option<Vec<String>>, pub condition_architecture: Option<Vec<String>>,
// FIXME replace this with hwd reading from the devicetree // FIXME replace this with hwd reading from the devicetree
pub condition_board: Option<Vec<String>>, pub condition_board: Option<Vec<String>>,
/// 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<Vec<String>>,
} }
fn true_bool() -> bool { fn true_bool() -> bool {
@@ -190,6 +197,7 @@ impl Unit {
requires_weak: script.1, requires_weak: script.1,
condition_architecture: None, condition_architecture: None,
condition_board: None, condition_board: None,
condition_path_exists: None,
}, },
kind: UnitKind::LegacyScript { script: script.0 }, 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 true
} }
} }
+11 -2
View File
@@ -2,7 +2,7 @@
extern crate log; extern crate log;
use std::process; use std::process;
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, Context, Result};
use event::{EventFlags, EventQueue}; use event::{EventFlags, EventQueue};
use libredox::flag::{O_NONBLOCK, O_RDWR}; use libredox::flag::{O_NONBLOCK, O_RDWR};
use libredox::Fd; use libredox::Fd;
@@ -36,7 +36,12 @@ fn get_all_network_adapters() -> Result<Vec<String>> {
} }
if adapters.is_empty() { 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); info!("Found {} network adapter(s): {:?}", adapters.len(), adapters);
Ok(adapters) Ok(adapters)
@@ -44,6 +49,10 @@ fn get_all_network_adapters() -> Result<Vec<String>> {
fn run(daemon: daemon::Daemon) -> Result<()> { fn run(daemon: daemon::Daemon) -> Result<()> {
let adapters = get_all_network_adapters()?; 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()); trace!("found {} network adapter(s)", adapters.len());
let mut network_fds = Vec::new(); let mut network_fds = Vec::new();