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:
+7
-2
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,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,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,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,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"]
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user