Files
hiperiso/host/initramfs/hw_collect.sh
T
vasilito 4325590686 Add host kernel config and initramfs scripts with P0-P3 hardware inventory
Includes: init (PID 1), hiperiso-lib.sh, qemu_launch.sh, hw_collect.sh,

kvm_check.sh, fallback_boot.sh, log_flush.sh, conf_replace.sh, make_floppy.sh.

13-phase boot timing, 18 QEMU HMP commands, network pcap capture.
2026-06-30 14:30:39 +03:00

507 lines
22 KiB
Bash
Executable File

#!/bin/sh
# ============================================================
# hw_collect.sh -- Hardware inventory collector for hiperiso
#
# Collects comprehensive hardware data across three phases:
#
# hw_collect.sh pre Before QEMU: host /proc, /sys, ACPI,
# SMBIOS, KVM params, USB, EFI
# hw_collect.sh monitor Background: send HMP commands to QEMU
# via FIFO pipe chardev (18 commands)
# hw_collect.sh post After QEMU: parse raw dump, generate
# JSON summaries, extract crash data
#
# Output directory: ${LOG_DIR}/hw/
#
# Host files (always present if pre-phase ran):
# host_cpuinfo.txt /proc/cpuinfo
# host_meminfo.txt /proc/meminfo
# host_interrupts.txt /proc/interrupts
# host_iomem.txt /proc/iomem (physical memory map)
# host_ioports.txt /proc/ioports
# host_cmdline.txt /proc/cmdline
# host_pci_devices.txt /proc/bus/pci/devices
# host_cpu_cache.txt CPU cache topology from sysfs
# host_numa.txt NUMA node topology
# host_iommu.txt IOMMU groups (VT-d/AMD-Vi)
# host_kvm.txt /dev/kvm presence
# host_block.txt Block device info
# host_dmesg.txt Host kernel boot messages
# host_version.txt /proc/version
# host_diskstats.txt /proc/diskstats
# host_usb.txt USB device tree from sysfs
# host_dmi.txt DMI/SMBIOS identifiers from sysfs
# acpi/ Host ACPI tables (binary .aml files)
# smbios/ Host DMI/SMBIOS raw tables
# kvm_caps.json KVM capabilities (module params)
#
# QEMU files (present if QEMU launched and ran >= 3 seconds):
# qemu_version.txt QEMU version string
# qemu_qtree.txt Full QEMU device tree
# qemu_pci.txt PCI device list as seen by guest
# qemu_memmap.txt Guest memory map (mtree)
# qemu_memmap_flat.txt Flat memory map (mtree -f)
# qemu_cpuid.txt CPUID leaves exposed to guest
# qemu_chardev.txt QEMU character devices
# qemu_block.txt QEMU block devices
# qemu_net.txt QEMU network devices
# qemu_ioapic.txt IO-APIC interrupt routing
# qemu_lapic.txt Local APIC state
# qemu_registers.txt CPU register snapshot
# qemu_tlb.txt TLB state (TCG only)
# qemu_numa.txt Guest NUMA topology
# qemu_hpet.txt HPET timer state
# qemu_irq.txt IRQ statistics
# qemu_qomtree.txt QOM object hierarchy
# qemu_smbios.txt SMBIOS table (if QEMU supports it)
# pci_summary.json Structured PCI device list
# ============================================================
. /hiperiso-lib.sh
LOG_DIR="${LOG_DIR:?hw_collect: LOG_DIR not set}"
HW_DIR="${LOG_DIR}/hw"
MON_IN="/tmp/hw_mon.in"
MON_OUT="/tmp/hw_mon.out"
mkdir -p "$HW_DIR"
# ── Mode: pre ────────────────────────────────────────────────
hw_collect_pre() {
hiperiso_log "hw_collect: collecting host hardware inventory..."
# ── /proc snapshots ──────────────────────────────────────
cat /proc/cpuinfo > "$HW_DIR/host_cpuinfo.txt" 2>/dev/null
cat /proc/meminfo > "$HW_DIR/host_meminfo.txt" 2>/dev/null
cat /proc/version > "$HW_DIR/host_version.txt" 2>/dev/null
cat /proc/diskstats > "$HW_DIR/host_diskstats.txt" 2>/dev/null
[ -f /proc/interrupts ] && cat /proc/interrupts > "$HW_DIR/host_interrupts.txt" 2>/dev/null
[ -f /proc/iomem ] && cat /proc/iomem > "$HW_DIR/host_iomem.txt" 2>/dev/null
[ -f /proc/ioports ] && cat /proc/ioports > "$HW_DIR/host_ioports.txt" 2>/dev/null
[ -f /proc/cmdline ] && cat /proc/cmdline > "$HW_DIR/host_cmdline.txt" 2>/dev/null
[ -f /proc/bus/pci/devices ] && cat /proc/bus/pci/devices > "$HW_DIR/host_pci_devices.txt" 2>/dev/null
# ── CPU cache topology ───────────────────────────────────
{
printf '=== CPU Cache Topology ===\n'
for _idx in /sys/devices/system/cpu/cpu*/cache/index*/; do
[ -d "$_idx" ] || continue
_level=$(cat "${_idx}level" 2>/dev/null)
_type=$(cat "${_idx}type" 2>/dev/null)
_size=$(cat "${_idx}size" 2>/dev/null)
_shared=$(cat "${_idx}shared_cpu_list" 2>/dev/null)
printf 'cpu=%s level=%s type=%s size=%s shared=%s\n' \
"$(printf '%s' "$_idx" | sed 's|.*/cpu\(cpu[0-9]*\)/.*|\1|')" \
"$_level" "$_type" "$_size" "$_shared"
done
} > "$HW_DIR/host_cpu_cache.txt" 2>/dev/null
# ── CPU topology (thread/core/socket IDs) ────────────────
{
printf '=== CPU Topology ===\n'
for _cpu in /sys/devices/system/cpu/cpu[0-9]*; do
[ -d "$_cpu" ] || continue
_name=$(basename "$_cpu")
_core=$(cat "${_cpu}/topology/core_id" 2>/dev/null)
_sock=$(cat "${_cpu}/topology/physical_package_id" 2>/dev/null)
_threads=$(cat "${_cpu}/topology/thread_siblings_list" 2>/dev/null)
printf '%s: core=%s socket=%s siblings=%s\n' \
"$_name" "${_core:-?}" "${_sock:-?}" "${_threads:-?}"
done
} > "$HW_DIR/host_cpu_topology.txt" 2>/dev/null
# ── NUMA topology ────────────────────────────────────────
if [ -d /sys/devices/system/node ]; then
{
printf '=== NUMA Topology ===\n'
for _node in /sys/devices/system/node/node[0-9]*; do
[ -d "$_node" ] || continue
_name=$(basename "$_node")
_cpus=$(cat "${_node}/cpulist" 2>/dev/null)
printf '%s: cpus=%s\n' "$_name" "$_cpus"
done
} > "$HW_DIR/host_numa.txt" 2>/dev/null
fi
# ── IOMMU groups (VT-d / AMD-Vi) ─────────────────────────
if [ -d /sys/kernel/iommu_groups ]; then
{
printf '=== IOMMU Groups ===\n'
for _grp in /sys/kernel/iommu_groups/[0-9]*; do
[ -d "$_grp" ] || continue
_gid=$(basename "$_grp")
printf 'group %s:\n' "$_gid"
for _dev in "$_grp"/devices/*; do
[ -e "$_dev" ] || continue
printf ' %s\n' "$(basename "$_dev")"
done
done
} > "$HW_DIR/host_iommu.txt" 2>/dev/null
fi
# ── KVM presence ─────────────────────────────────────────
if [ -e /dev/kvm ]; then
{
printf '=== KVM Device ===\n'
ls -la /dev/kvm 2>/dev/null
} > "$HW_DIR/host_kvm.txt" 2>/dev/null
fi
# ── KVM capabilities (module parameters) ─────────────────
_kvm_json="$HW_DIR/kvm_caps.json"
{
printf '{\n'
printf ' "kvm_present": %s,\n' $([ -e /dev/kvm ] && echo true || echo false)
# Core KVM parameters
_first=1
printf ' "kvm_core": {'
if [ -d /sys/module/kvm/parameters ]; then
for _p in /sys/module/kvm/parameters/*; do
[ -f "$_p" ] || continue
_pname=$(basename "$_p")
_pval=$(cat "$_p" 2>/dev/null)
if [ "$_first" -eq 1 ]; then _first=0; else printf ','; fi
printf '\n "%s": "%s"' "$_pname" "$_pval"
done
fi
[ "$_first" -eq 0 ] && printf '\n '
printf '},\n'
# Intel VT-x parameters
_first=1
printf ' "kvm_intel": {'
if [ -d /sys/module/kvm_intel/parameters ]; then
for _p in /sys/module/kvm_intel/parameters/*; do
[ -f "$_p" ] || continue
_pname=$(basename "$_p")
_pval=$(cat "$_p" 2>/dev/null)
if [ "$_first" -eq 1 ]; then _first=0; else printf ','; fi
printf '\n "%s": "%s"' "$_pname" "$_pval"
done
fi
[ "$_first" -eq 0 ] && printf '\n '
printf '},\n'
# AMD-V parameters
_first=1
printf ' "kvm_amd": {'
if [ -d /sys/module/kvm_amd/parameters ]; then
for _p in /sys/module/kvm_amd/parameters/*; do
[ -f "$_p" ] || continue
_pname=$(basename "$_p")
_pval=$(cat "$_p" 2>/dev/null)
if [ "$_first" -eq 1 ]; then _first=0; else printf ','; fi
printf '\n "%s": "%s"' "$_pname" "$_pval"
done
fi
[ "$_first" -eq 0 ] && printf '\n '
printf '}\n'
printf '}\n'
} > "$_kvm_json" 2>/dev/null
# ── Block devices ────────────────────────────────────────
{
printf '=== Block Devices ===\n'
for _blk in /sys/block/sd* /sys/block/nvme* /sys/block/sr* /sys/block/mmcblk*; do
[ -d "$_blk" ] || continue
_name=$(basename "$_blk")
_size=$(cat "${_blk}/size" 2>/dev/null)
_rm=$(cat "${_blk}/removable" 2>/dev/null)
_ro=$(cat "${_blk}/readonly" 2>/dev/null)
_model=$(cat "${_blk}/device/model" 2>/dev/null)
_vendor=$(cat "${_blk}/device/vendor" 2>/dev/null)
printf '%s: sectors=%s removable=%s readonly=%s model=%s vendor=%s\n' \
"$_name" "${_size:-?}" "${_rm:-?}" "${_ro:-?}" \
"$(printf '%s' "$_model" | tr -d ' ')" \
"$(printf '%s' "$_vendor" | tr -d ' ')"
done
} > "$HW_DIR/host_block.txt" 2>/dev/null
# ── USB device tree ──────────────────────────────────────
{
printf '=== USB Devices ===\n'
for _dev in /sys/bus/usb/devices/*; do
[ -d "$_dev" ] || continue
_name=$(basename "$_dev")
_vid=$(cat "${_dev}/idVendor" 2>/dev/null)
_pid=$(cat "${_dev}/idProduct" 2>/dev/null)
_mfr=$(cat "${_dev}/manufacturer" 2>/dev/null)
_prod=$(cat "${_dev}/product" 2>/dev/null)
_speed=$(cat "${_dev}/speed" 2>/dev/null)
[ -n "$_vid" ] || continue
printf '%s: %s:%s speed=%s mfr=%s product=%s\n' \
"$_name" "$_vid" "$_pid" "${_speed:-?}" \
"$(printf '%s' "$_mfr" | tr -d '\n')" \
"$(printf '%s' "$_prod" | tr -d '\n')"
done
} > "$HW_DIR/host_usb.txt" 2>/dev/null
# ── ACPI tables (host firmware) ──────────────────────────
if [ -d /sys/firmware/acpi/tables ]; then
mkdir -p "$HW_DIR/acpi"
cp -r /sys/firmware/acpi/tables/* "$HW_DIR/acpi/" 2>/dev/null
{
printf '=== ACPI Tables ===\n'
for _t in /sys/firmware/acpi/tables/[A-Z]*; do
[ -f "$_t" ] || continue
_sig=$(basename "$_t")
_size=$(wc -c < "$_t" 2>/dev/null)
printf '%s: %s bytes\n' "$_sig" "${_size:-?}"
done
} > "$HW_DIR/acpi/_summary.txt" 2>/dev/null
fi
# ── DMI / SMBIOS (host firmware) ─────────────────────────
if [ -d /sys/firmware/dmi/tables ]; then
mkdir -p "$HW_DIR/smbios"
cp /sys/firmware/dmi/tables/* "$HW_DIR/smbios/" 2>/dev/null
fi
if [ -d /sys/class/dmi/id ]; then
{
printf '=== DMI Identifiers ===\n'
for _f in /sys/class/dmi/id/*; do
[ -f "$_f" ] || continue
_key=$(basename "$_f")
_val=$(cat "$_f" 2>/dev/null)
[ -n "$_val" ] && printf '%s: %s\n' "$_key" "$_val"
done
} > "$HW_DIR/host_dmi.txt" 2>/dev/null
fi
# ── EFI firmware info (if UEFI boot) ─────────────────────
if [ -d /sys/firmware/efi ]; then
{
printf '=== EFI Firmware ===\n'
cat /sys/firmware/efi/fw_platform_size 2>/dev/null
printf 'Runtime services: '
ls /sys/firmware/efi/runtime 2>/dev/null || printf '(not exported)\n'
printf 'EFI variables:\n'
ls /sys/firmware/efi/efivars/ 2>/dev/null | head -20
} > "$HW_DIR/host_efi.txt" 2>/dev/null
fi
# ── Kernel config (if available) ─────────────────────────
if [ -f /proc/config.gz ]; then
cp /proc/config.gz "$HW_DIR/host_kernel_config.gz" 2>/dev/null
fi
# ── DMESG (host kernel messages) ─────────────────────────
if command -v dmesg >/dev/null 2>&1; then
dmesg > "$HW_DIR/host_dmesg.txt" 2>/dev/null
fi
_count=$(ls "$HW_DIR"/host_*.txt "$HW_DIR"/*.json 2>/dev/null | wc -l)
hiperiso_log "hw_collect: host inventory complete ($_count files)"
}
# ── Mode: monitor ────────────────────────────────────────────
hw_setup_monitor_fifo() {
mkfifo "$MON_IN" "$MON_OUT" 2>/dev/null
cat "$MON_OUT" > "${HW_DIR}/qemu_monitor_raw.txt" 2>/dev/null &
MON_READER_PID=$!
printf '%s\n' "$MON_READER_PID" > /tmp/hw_mon_reader.pid
# Writer: sends HMP commands via echo-delimited markers.
# QEMU pipe mode does not echo input, so we inject markers
# using QEMU's HMP 'echo' command to enable section splitting.
(
sleep 3
# ── P0: Core inventory ───────────────────────────────
printf 'echo ===SECTION===version\n'; sleep 0.1
printf 'info version\n'; sleep 0.3
printf 'echo ===SECTION===qtree\n'; sleep 0.1
printf 'info qtree\n'; sleep 0.5
printf 'echo ===SECTION===pci\n'; sleep 0.1
printf 'info pci\n'; sleep 0.3
# ── P1: CPU + memory map ─────────────────────────────
printf 'echo ===SECTION===cpuid\n'; sleep 0.1
printf 'info cpuid\n'; sleep 0.3
printf 'echo ===SECTION===memmap\n'; sleep 0.1
printf 'info mtree\n'; sleep 0.3
printf 'echo ===SECTION===memmap_flat\n'; sleep 0.1
printf 'info mtree -f\n'; sleep 0.3
# ── P2: SMBIOS + registers + TLB ─────────────────────
printf 'echo ===SECTION===smbios\n'; sleep 0.1
printf 'info smbios\n'; sleep 0.3
printf 'echo ===SECTION===registers\n'; sleep 0.1
printf 'info registers\n'; sleep 0.3
printf 'echo ===SECTION===tlb\n'; sleep 0.1
printf 'info tlb\n'; sleep 0.3
# ── P3: Interrupt routing + KVM caps ─────────────────
printf 'echo ===SECTION===ioapic\n'; sleep 0.1
printf 'info ioapic\n'; sleep 0.3
printf 'echo ===SECTION===lapic\n'; sleep 0.1
printf 'info lapic\n'; sleep 0.3
printf 'echo ===SECTION===irq\n'; sleep 0.1
printf 'info irq\n'; sleep 0.3
# ── Additional: topology + timers ────────────────────
printf 'echo ===SECTION===numa\n'; sleep 0.1
printf 'info numa\n'; sleep 0.3
printf 'echo ===SECTION===hpet\n'; sleep 0.1
printf 'info hpet\n'; sleep 0.3
printf 'echo ===SECTION===qomtree\n'; sleep 0.1
printf 'info qom-tree\n'; sleep 0.3
# ── Device summaries ─────────────────────────────────
printf 'echo ===SECTION===chardev\n'; sleep 0.1
printf 'info chardev\n'; sleep 0.3
printf 'echo ===SECTION===block\n'; sleep 0.1
printf 'info block\n'; sleep 0.3
printf 'echo ===SECTION===net\n'; sleep 0.1
printf 'info networking\n'; sleep 0.3
) > "$MON_IN" 2>/dev/null &
MON_WRITER_PID=$!
printf '%s\n' "$MON_WRITER_PID" > /tmp/hw_mon_writer.pid
hiperiso_log "hw_collect: monitor FIFO reader (PID $MON_READER_PID)"
hiperiso_log "hw_collect: monitor FIFO writer (PID $MON_WRITER_PID)"
}
# ── Mode: post ───────────────────────────────────────────────
hw_collect_post() {
_raw="${HW_DIR}/qemu_monitor_raw.txt"
if [ ! -f "$_raw" ] || [ ! -s "$_raw" ]; then
hiperiso_log "hw_collect: no QEMU monitor data (raw file missing or empty)"
hw_cleanup_procs
return 1
fi
hiperiso_log "hw_collect: parsing QEMU monitor output..."
# Split raw dump into per-section files using echo markers
# injected by the writer. Each ===SECTION===xxx line triggers
# a section change; subsequent output goes to that file.
_awk_script='
BEGIN { section = "header"; }
/===SECTION===version/ { section = "version"; next; }
/===SECTION===qtree/ { section = "qtree"; next; }
/===SECTION===pci/ { section = "pci"; next; }
/===SECTION===cpuid/ { section = "cpuid"; next; }
/===SECTION===memmap_flat/ { section = "memmap_flat"; next; }
/===SECTION===memmap/ { section = "memmap"; next; }
/===SECTION===smbios/ { section = "smbios"; next; }
/===SECTION===registers/ { section = "registers"; next; }
/===SECTION===tlb/ { section = "tlb"; next; }
/===SECTION===ioapic/ { section = "ioapic"; next; }
/===SECTION===lapic/ { section = "lapic"; next; }
/===SECTION===irq/ { section = "irq"; next; }
/===SECTION===numa/ { section = "numa"; next; }
/===SECTION===hpet/ { section = "hpet"; next; }
/===SECTION===qomtree/ { section = "qomtree"; next; }
/===SECTION===chardev/ { section = "chardev"; next; }
/===SECTION===block/ { section = "block"; next; }
/===SECTION===net/ { section = "net"; next; }
{
if (section != "header")
print > "'"$HW_DIR"'/qemu_" section ".txt";
}
'
awk "$_awk_script" "$_raw" 2>/dev/null
for _f in \
qemu_version qemu_qtree qemu_pci qemu_cpuid \
qemu_memmap qemu_memmap_flat qemu_smbios \
qemu_registers qemu_tlb qemu_ioapic qemu_lapic \
qemu_irq qemu_numa qemu_hpet qemu_qomtree \
qemu_chardev qemu_block qemu_net; do
_fp="${HW_DIR}/${_f}.txt"
if [ -f "$_fp" ] && [ ! -s "$_fp" ]; then
rm -f "$_fp"
fi
done
# Generate PCI summary JSON from qemu_pci.txt
hw_gen_pci_json
_count=$(ls "$HW_DIR"/qemu_*.txt "$HW_DIR"/*.json 2>/dev/null | wc -l)
hiperiso_log "hw_collect: parsed $_count QEMU + JSON files"
hw_cleanup_procs
hiperiso_log "hw_collect: inventory files:"
ls -la "$HW_DIR"/ 2>/dev/null >> "${LOG_DIR}/env.txt"
}
# ── PCI summary JSON ─────────────────────────────────────────
# Parse qemu_pci.txt to extract structured PCI device data.
hw_gen_pci_json() {
_pci="${HW_DIR}/qemu_pci.txt"
_out="${HW_DIR}/pci_summary.json"
[ -f "$_pci" ] || return 0
{
printf '{\n'
printf ' "devices": [\n'
awk '
/^Bus / {
if (found) printf " },\n"
found = 1
bus = $2; gsub(/,/, "", bus)
dev = $4; gsub(/,/, "", dev)
fn = $6; gsub(/:/, "", fn)
printf " {\"bus\": %s, \"device\": %s, \"function\": %s", bus, dev, fn
}
/vendor_id = / {
gsub(/.*= /, "", $0)
printf ", \"vendor_id\": \"%s\"", $NF
}
/device_id = / {
gsub(/.*= /, "", $0)
printf ", \"device_id\": \"%s\"", $NF
}
/class = / {
gsub(/.*= /, "", $0)
printf ", \"class\": \"%s\"", $NF
}
/IRQ / {
_irq = $2; gsub(/\./, "", _irq)
printf ", \"irq\": \"%s\"", _irq
}
END { if (found) printf " }"; printf "\n" }
' "$_pci" 2>/dev/null
printf ' ]\n'
printf '}\n'
} > "$_out" 2>/dev/null
if [ ! -s "$_out" ]; then
rm -f "$_out"
fi
}
# ── Process cleanup ──────────────────────────────────────────
hw_cleanup_procs() {
rm -f "$MON_IN" "$MON_OUT" 2>/dev/null
for _pidfile in /tmp/hw_mon_reader.pid /tmp/hw_mon_writer.pid; do
if [ -f "$_pidfile" ]; then
_pid=$(cat "$_pidfile" 2>/dev/null)
if [ -n "$_pid" ] && kill -0 "$_pid" 2>/dev/null; then
kill "$_pid" 2>/dev/null
fi
rm -f "$_pidfile"
fi
done
}
# ── Main ─────────────────────────────────────────────────────
case "${1:-}" in
pre) hw_collect_pre ;;
monitor) hw_setup_monitor_fifo ;;
post) hw_collect_post ;;
*)
printf 'Usage: hw_collect.sh {pre|monitor|post}\n' >&2
exit 1
;;
esac