# vim: set ts=4 sw=4 sts=4 et:
# --- JSON helper functions ---

# Escape a string for use in a JSON value (handles backslashes, double quotes, newlines, tabs)
# Args: $1=string
# Prints: escaped string (without surrounding quotes)
_json_escape() {
    printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/	/\\t/g' | tr '\n' ' '
}

# Escape a string for use as a Prometheus label value (handles backslashes, double quotes, newlines)
# Args: $1=string
# Prints: escaped string (without surrounding quotes)
_prom_escape() {
    printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' | tr '\n' ' '
}

# Convert a shell capability value to a JSON boolean token
# Args: $1=value (1=true, 0=false, -1/empty=null, any other non-empty string=true)
# Prints: JSON token (true/false/null)
# Note: capability variables can be set to arbitrary strings internally to carry
# detection-path context (e.g. cap_ssbd='Intel SSBD'); for the JSON output those
# are normalized to true so consumers see a clean boolean | null type.
_json_cap() {
    case "${1:-}" in
        0) printf 'false' ;;
        -1 | '') printf 'null' ;;
        *) printf 'true' ;;
    esac
}

# Emit a JSON string value or null
# Args: $1=string (empty=null)
# Prints: JSON token ("escaped string" or null)
_json_str() {
    if [ -n "${1:-}" ]; then
        printf '"%s"' "$(_json_escape "$1")"
    else
        printf 'null'
    fi
}

# Emit a JSON number value or null
# Args: $1=number (empty=null)
# Prints: JSON token
_json_num() {
    if [ -n "${1:-}" ]; then
        printf '%s' "$1"
    else
        printf 'null'
    fi
}

# Emit a JSON boolean value or null
# Args: $1=value (1/0/empty)
# Prints: JSON token
_json_bool() {
    case "${1:-}" in
        1) printf 'true' ;;
        0) printf 'false' ;;
        *) printf 'null' ;;
    esac
}

# --- JSON section builders (comprehensive format) ---

# Build the "meta" section of the comprehensive JSON output
# Sets: g_json_meta
# shellcheck disable=SC2034
_build_json_meta() {
    local timestamp
    timestamp=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo "unknown")
    local run_as_root
    if [ "$(id -u)" -eq 0 ]; then
        run_as_root='true'
    else
        run_as_root='false'
    fi
    g_json_meta=$(printf '{"script_version":%s,"format_version":1,"timestamp":%s,"os":%s,"mode":"%s","run_as_root":%s,"reduced_accuracy":%s,"paranoid":%s,"sysfs_only":%s,"extra":%s}' \
        "$(_json_str "$VERSION")" \
        "$(_json_str "$timestamp")" \
        "$(_json_str "$g_os")" \
        "$g_mode" \
        "$run_as_root" \
        "$(_json_bool "${g_bad_accuracy:-0}")" \
        "$(_json_bool "$opt_paranoid")" \
        "$(_json_bool "$opt_sysfs_only")" \
        "$(_json_bool "$opt_extra")")
}

# Build the "system" section of the comprehensive JSON output
# Sets: g_json_system
# shellcheck disable=SC2034
_build_json_system() {
    local kernel_release kernel_version kernel_arch smt_val
    if [ "$g_mode" = live ]; then
        kernel_release=$(uname -r)
        kernel_version=$(uname -v)
        kernel_arch=$(uname -m)
    else
        kernel_release=''
        kernel_version=''
        kernel_arch=''
    fi
    # SMT detection
    is_cpu_smt_enabled
    smt_val=$?
    case $smt_val in
        0) smt_val='true' ;;
        1) smt_val='false' ;;
        *) smt_val='null' ;;
    esac
    is_running_as_guest || true
    g_json_system=$(printf '{"kernel_release":%s,"kernel_version":%s,"kernel_arch":%s,"kernel_image":%s,"kernel_config":%s,"kernel_version_string":%s,"kernel_cmdline":%s,"cpu_count":%s,"smt_enabled":%s,"hypervisor_host":%s,"hypervisor_host_reason":%s,"guest_vm":%s,"guest_vm_reason":%s}' \
        "$(_json_str "$kernel_release")" \
        "$(_json_str "$kernel_version")" \
        "$(_json_str "$kernel_arch")" \
        "$(_json_str "${opt_kernel:-}")" \
        "$(_json_str "${opt_config:-}")" \
        "$(_json_str "${g_kernel_version:-}")" \
        "$(_json_str "${g_kernel_cmdline:-}")" \
        "$(_json_num "${g_max_core_id:+$((g_max_core_id + 1))}")" \
        "$smt_val" \
        "$(_json_bool "${g_has_vmm:-}")" \
        "$(_json_str "${g_has_vmm_reason:-}")" \
        "$(_json_bool "${g_is_guest_vm:-}")" \
        "$(_json_str "${g_is_guest_vm_reason:-}")")
}

# Build the "cpu" section of the comprehensive JSON output
# Sets: g_json_cpu
# shellcheck disable=SC2034
_build_json_cpu() {
    local cpuid_hex codename caps arch_sub arch_type sbpb_norm
    if [ -n "${cpu_cpuid:-}" ]; then
        cpuid_hex=$(printf '0x%08x' "$cpu_cpuid")
    else
        cpuid_hex=''
    fi
    codename=''
    if is_intel; then
        codename=$(get_intel_codename 2>/dev/null || true)
    fi

    # cap_sbpb uses non-standard encoding (1=YES, 2=NO, 3=UNKNOWN) because the
    # CVE-2023-20569 check distinguishes the unknown case. Normalize for JSON.
    case "${cap_sbpb:-}" in
        1) sbpb_norm=1 ;;
        2) sbpb_norm=0 ;;
        3) sbpb_norm=-1 ;;
        *) sbpb_norm='' ;;
    esac

    # Determine architecture type and build the arch-specific sub-object
    case "${cpu_vendor:-}" in
        GenuineIntel | AuthenticAMD | HygonGenuine)
            arch_type='x86'
            # Build x86 capabilities sub-object
            caps=$(printf '{"spec_ctrl":%s,"ibrs":%s,"ibpb":%s,"ibpb_ret":%s,"stibp":%s,"ssbd":%s,"l1d_flush":%s,"md_clear":%s,"arch_capabilities":%s,"rdcl_no":%s,"ibrs_all":%s,"rsba":%s,"l1dflush_no":%s,"ssb_no":%s,"mds_no":%s,"taa_no":%s,"pschange_msc_no":%s,"tsx_ctrl_msr":%s,"tsx_ctrl_rtm_disable":%s,"tsx_ctrl_cpuid_clear":%s,"gds_ctrl":%s,"gds_no":%s,"gds_mitg_dis":%s,"gds_mitg_lock":%s,"rfds_no":%s,"rfds_clear":%s,"its_no":%s,"sbdr_ssdp_no":%s,"fbsdp_no":%s,"psdp_no":%s,"fb_clear":%s,"rtm":%s,"tsx_force_abort":%s,"tsx_force_abort_rtm_disable":%s,"tsx_force_abort_cpuid_clear":%s,"sgx":%s,"srbds":%s,"srbds_on":%s,"amd_ssb_no":%s,"hygon_ssb_no":%s,"ipred":%s,"rrsba":%s,"bhi":%s,"tsa_sq_no":%s,"tsa_l1_no":%s,"verw_clear":%s,"autoibrs":%s,"sbpb":%s,"avx2":%s,"avx512":%s}' \
                "$(_json_cap "${cap_spec_ctrl:-}")" \
                "$(_json_cap "${cap_ibrs:-}")" \
                "$(_json_cap "${cap_ibpb:-}")" \
                "$(_json_cap "${cap_ibpb_ret:-}")" \
                "$(_json_cap "${cap_stibp:-}")" \
                "$(_json_cap "${cap_ssbd:-}")" \
                "$(_json_cap "${cap_l1df:-}")" \
                "$(_json_cap "${cap_md_clear:-}")" \
                "$(_json_cap "${cap_arch_capabilities:-}")" \
                "$(_json_cap "${cap_rdcl_no:-}")" \
                "$(_json_cap "${cap_ibrs_all:-}")" \
                "$(_json_cap "${cap_rsba:-}")" \
                "$(_json_cap "${cap_l1dflush_no:-}")" \
                "$(_json_cap "${cap_ssb_no:-}")" \
                "$(_json_cap "${cap_mds_no:-}")" \
                "$(_json_cap "${cap_taa_no:-}")" \
                "$(_json_cap "${cap_pschange_msc_no:-}")" \
                "$(_json_cap "${cap_tsx_ctrl_msr:-}")" \
                "$(_json_cap "${cap_tsx_ctrl_rtm_disable:-}")" \
                "$(_json_cap "${cap_tsx_ctrl_cpuid_clear:-}")" \
                "$(_json_cap "${cap_gds_ctrl:-}")" \
                "$(_json_cap "${cap_gds_no:-}")" \
                "$(_json_cap "${cap_gds_mitg_dis:-}")" \
                "$(_json_cap "${cap_gds_mitg_lock:-}")" \
                "$(_json_cap "${cap_rfds_no:-}")" \
                "$(_json_cap "${cap_rfds_clear:-}")" \
                "$(_json_cap "${cap_its_no:-}")" \
                "$(_json_cap "${cap_sbdr_ssdp_no:-}")" \
                "$(_json_cap "${cap_fbsdp_no:-}")" \
                "$(_json_cap "${cap_psdp_no:-}")" \
                "$(_json_cap "${cap_fb_clear:-}")" \
                "$(_json_cap "${cap_rtm:-}")" \
                "$(_json_cap "${cap_tsx_force_abort:-}")" \
                "$(_json_cap "${cap_tsx_force_abort_rtm_disable:-}")" \
                "$(_json_cap "${cap_tsx_force_abort_cpuid_clear:-}")" \
                "$(_json_cap "${cap_sgx:-}")" \
                "$(_json_cap "${cap_srbds:-}")" \
                "$(_json_cap "${cap_srbds_on:-}")" \
                "$(_json_cap "${cap_amd_ssb_no:-}")" \
                "$(_json_cap "${cap_hygon_ssb_no:-}")" \
                "$(_json_cap "${cap_ipred:-}")" \
                "$(_json_cap "${cap_rrsba:-}")" \
                "$(_json_cap "${cap_bhi:-}")" \
                "$(_json_cap "${cap_tsa_sq_no:-}")" \
                "$(_json_cap "${cap_tsa_l1_no:-}")" \
                "$(_json_cap "${cap_verw_clear:-}")" \
                "$(_json_cap "${cap_autoibrs:-}")" \
                "$(_json_cap "$sbpb_norm")" \
                "$(_json_cap "${cap_avx2:-}")" \
                "$(_json_cap "${cap_avx512:-}")")
            arch_sub=$(printf '{"family":%s,"model":%s,"stepping":%s,"cpuid":%s,"platform_id":%s,"hybrid":%s,"codename":%s,"capabilities":%s}' \
                "$(_json_num "${cpu_family:-}")" \
                "$(_json_num "${cpu_model:-}")" \
                "$(_json_num "${cpu_stepping:-}")" \
                "$(_json_str "$cpuid_hex")" \
                "$(_json_num "${cpu_platformid:-}")" \
                "$(_json_bool "${cpu_hybrid:-}")" \
                "$(_json_str "$codename")" \
                "$caps")
            ;;
        ARM | CAVIUM | PHYTIUM)
            arch_type='arm'
            arch_sub=$(printf '{"part_list":%s,"arch_list":%s,"capabilities":{}}' \
                "$(_json_str "${cpu_part_list:-}")" \
                "$(_json_str "${cpu_arch_list:-}")")
            ;;
        *)
            arch_type=''
            arch_sub=''
            ;;
    esac

    if [ -n "$arch_type" ]; then
        g_json_cpu=$(printf '{"arch":"%s","vendor":%s,"friendly_name":%s,"%s":%s}' \
            "$arch_type" \
            "$(_json_str "${cpu_vendor:-}")" \
            "$(_json_str "${cpu_friendly_name:-}")" \
            "$arch_type" \
            "$arch_sub")
    else
        g_json_cpu=$(printf '{"arch":null,"vendor":%s,"friendly_name":%s}' \
            "$(_json_str "${cpu_vendor:-}")" \
            "$(_json_str "${cpu_friendly_name:-}")")
    fi
}

# Build the "cpu_microcode" section of the comprehensive JSON output
# Sets: g_json_cpu_microcode
# shellcheck disable=SC2034
_build_json_cpu_microcode() {
    local ucode_uptodate ucode_hex latest_hex blacklisted
    if [ -n "${cpu_ucode:-}" ]; then
        ucode_hex=$(printf '0x%x' "$cpu_ucode")
    else
        ucode_hex=''
    fi
    is_latest_known_ucode
    case $? in
        0) ucode_uptodate='true' ;;
        1) ucode_uptodate='false' ;;
        *) ucode_uptodate='null' ;;
    esac
    if is_ucode_blacklisted; then
        blacklisted='true'
    else
        blacklisted='false'
    fi
    latest_hex="${ret_is_latest_known_ucode_version:-}"
    g_json_cpu_microcode=$(printf '{"installed_version":%s,"latest_version":%s,"microcode_up_to_date":%s,"is_blacklisted":%s,"message":%s,"db_source":%s,"db_info":%s,"unreliable_in_vm":%s}' \
        "$(_json_str "$ucode_hex")" \
        "$(_json_str "$latest_hex")" \
        "$ucode_uptodate" \
        "$blacklisted" \
        "$(_json_str "${ret_is_latest_known_ucode_latest:-}")" \
        "$(_json_str "${g_mcedb_source:-}")" \
        "$(_json_str "${g_mcedb_info:-}")" \
        "$(_json_bool "${g_is_guest_vm:-}")")
}

# --- Format-specific batch emitters ---

# Emit a single CVE result as plain text
# Args: $1=cve $2=aka $3=status $4=description
# Callers: pvulnstatus
_emit_text() {
    _pr_echo 0 "$1: $3 ($4)"
}

# Append CVE ID to the space-separated short output buffer
# Args: $1=cve $2=aka $3=status $4=description
# Sets: g_short_output
# Callers: pvulnstatus
_emit_short() {
    g_short_output="${g_short_output}$1 "
}

# Append a CVE result as a terse JSON object to the batch output buffer
# Args: $1=cve $2=aka $3=status(UNK|VULN|OK) $4=description
# Sets: g_json_output
# Callers: pvulnstatus
_emit_json_terse() {
    local is_vuln esc_name esc_infos
    case "$3" in
        UNK) is_vuln="null" ;;
        VULN) is_vuln="true" ;;
        OK) is_vuln="false" ;;
        *)
            echo "$0: error: unknown status '$3' passed to _emit_json_terse()" >&2
            exit 255
            ;;
    esac
    esc_name=$(_json_escape "$2")
    esc_infos=$(_json_escape "$4")
    [ -z "$g_json_output" ] && g_json_output='['
    g_json_output="${g_json_output}{\"NAME\":\"$esc_name\",\"CVE\":\"$1\",\"VULNERABLE\":$is_vuln,\"INFOS\":\"$esc_infos\"},"
}

# Append a CVE result as a comprehensive JSON object to the batch output buffer
# Args: $1=cve $2=aka $3=status(UNK|VULN|OK) $4=description
# Sets: g_json_vulns
# Callers: pvulnstatus
_emit_json_full() {
    local is_vuln esc_name esc_infos aliases cpu_affected sysfs_status sysfs_msg
    case "$3" in
        UNK) is_vuln="null" ;;
        VULN) is_vuln="true" ;;
        OK) is_vuln="false" ;;
        *)
            echo "$0: error: unknown status '$3' passed to _emit_json_full()" >&2
            exit 255
            ;;
    esac
    esc_name=$(_json_escape "$2")
    esc_infos=$(_json_escape "$4")
    aliases=$(_cve_registry_field "$1" 4)

    # CPU affection status (cached, cheap)
    if is_cpu_affected "$1" 2>/dev/null; then
        cpu_affected='true'
    else
        cpu_affected='false'
    fi

    # sysfs status: use the value captured by this CVE's check function, then clear it
    # so it doesn't leak into the next CVE that might not call sys_interface_check
    sysfs_status="${g_json_cve_sysfs_status:-}"
    sysfs_msg="${g_json_cve_sysfs_msg:-}"

    : "${g_json_vulns:=}"
    g_json_vulns="${g_json_vulns}{\"cve\":\"$1\",\"name\":\"$esc_name\",\"aliases\":$(_json_str "$aliases"),\"cpu_affected\":$cpu_affected,\"status\":\"$3\",\"vulnerable\":$is_vuln,\"info\":\"$esc_infos\",\"sysfs_status\":$(_json_str "$sysfs_status"),\"sysfs_message\":$(_json_str "$sysfs_msg")},"
}

# Accumulate a CVE result into the NRPE output buffers
# Args: $1=cve $2=aka $3=status $4=description
# Sets: g_nrpe_total, g_nrpe_vuln_count, g_nrpe_unk_count, g_nrpe_vuln_ids, g_nrpe_vuln_details, g_nrpe_unk_details
# Callers: pvulnstatus
_emit_nrpe() {
    g_nrpe_total=$((g_nrpe_total + 1))
    case "$3" in
        VULN)
            g_nrpe_vuln_count=$((g_nrpe_vuln_count + 1))
            g_nrpe_vuln_ids="${g_nrpe_vuln_ids:+$g_nrpe_vuln_ids }$1"
            g_nrpe_vuln_details="${g_nrpe_vuln_details:+$g_nrpe_vuln_details\n}[CRITICAL] $1 ($2): $4"
            ;;
        UNK)
            g_nrpe_unk_count=$((g_nrpe_unk_count + 1))
            g_nrpe_unk_details="${g_nrpe_unk_details:+$g_nrpe_unk_details\n}[UNKNOWN]  $1 ($2): $4"
            ;;
    esac
}

# Append a CVE result as a Prometheus gauge to the batch output buffer
# Status is encoded numerically: 0=not_vulnerable, 1=vulnerable, 2=unknown
# Args: $1=cve $2=aka $3=status(UNK|VULN|OK) $4=description
# Sets: g_smc_vuln_output, g_smc_ok_count, g_smc_vuln_count, g_smc_unk_count
# Callers: pvulnstatus
_emit_prometheus() {
    local numeric_status cpu_affected full_name esc_name
    case "$3" in
        OK)
            numeric_status=0
            g_smc_ok_count=$((g_smc_ok_count + 1))
            ;;
        VULN)
            numeric_status=1
            g_smc_vuln_count=$((g_smc_vuln_count + 1))
            ;;
        UNK)
            numeric_status=2
            g_smc_unk_count=$((g_smc_unk_count + 1))
            ;;
        *)
            echo "$0: error: unknown status '$3' passed to _emit_prometheus()" >&2
            exit 255
            ;;
    esac
    if is_cpu_affected "$1" 2>/dev/null; then
        cpu_affected='true'
    else
        cpu_affected='false'
    fi
    # use the complete CVE name (field 4) rather than the short aka key (field 2)
    full_name=$(_cve_registry_field "$1" 4)
    esc_name=$(_prom_escape "$full_name")
    g_smc_vuln_output="${g_smc_vuln_output:+$g_smc_vuln_output\n}smc_vulnerability_status{cve=\"$1\",name=\"$esc_name\",cpu_affected=\"$cpu_affected\"} $numeric_status"
}

# Build the smc_system_info Prometheus metric line
# Sets: g_smc_system_info_line
# Callers: src/main.sh (after check_cpu / check_cpu_vulnerabilities)
# shellcheck disable=SC2034
_build_prometheus_system_info() {
    local kernel_release kernel_arch hypervisor_host sys_labels
    if [ "$g_mode" = live ]; then
        kernel_release=$(uname -r 2>/dev/null || true)
        kernel_arch=$(uname -m 2>/dev/null || true)
    else
        kernel_release=''
        kernel_arch=''
    fi
    case "${g_has_vmm:-}" in
        1) hypervisor_host='true' ;;
        0) hypervisor_host='false' ;;
        *) hypervisor_host='' ;;
    esac
    sys_labels=''
    [ -n "$kernel_release" ] && sys_labels="${sys_labels:+$sys_labels,}kernel_release=\"$(_prom_escape "$kernel_release")\""
    [ -n "$kernel_arch" ] && sys_labels="${sys_labels:+$sys_labels,}kernel_arch=\"$(_prom_escape "$kernel_arch")\""
    [ -n "$hypervisor_host" ] && sys_labels="${sys_labels:+$sys_labels,}hypervisor_host=\"$hypervisor_host\""
    [ -n "$sys_labels" ] && g_smc_system_info_line="smc_system_info{$sys_labels} 1"
}

# Build the smc_cpu_info Prometheus metric line
# Sets: g_smc_cpu_info_line
# Callers: src/main.sh (after check_cpu / check_cpu_vulnerabilities)
# shellcheck disable=SC2034
_build_prometheus_cpu_info() {
    local cpuid_hex ucode_hex ucode_latest_hex ucode_uptodate ucode_blacklisted codename smt_val cpu_labels
    if [ -n "${cpu_cpuid:-}" ]; then
        cpuid_hex=$(printf '0x%08x' "$cpu_cpuid")
    else
        cpuid_hex=''
    fi
    if [ -n "${cpu_ucode:-}" ]; then
        ucode_hex=$(printf '0x%x' "$cpu_ucode")
    else
        ucode_hex=''
    fi
    is_latest_known_ucode
    case $? in
        0) ucode_uptodate='true' ;;
        1) ucode_uptodate='false' ;;
        *) ucode_uptodate='' ;;
    esac
    ucode_latest_hex="${ret_is_latest_known_ucode_version:-}"
    if is_ucode_blacklisted; then
        ucode_blacklisted='true'
    else
        ucode_blacklisted='false'
    fi
    codename=''
    if is_intel; then
        codename=$(get_intel_codename 2>/dev/null || true)
    fi
    is_cpu_smt_enabled
    case $? in
        0) smt_val='true' ;;
        1) smt_val='false' ;;
        *) smt_val='' ;;
    esac
    cpu_labels=''
    [ -n "${cpu_vendor:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}vendor=\"$(_prom_escape "$cpu_vendor")\""
    [ -n "${cpu_friendly_name:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}model=\"$(_prom_escape "$cpu_friendly_name")\""
    # arch-specific labels
    case "${cpu_vendor:-}" in
        GenuineIntel | AuthenticAMD | HygonGenuine)
            cpu_labels="${cpu_labels:+$cpu_labels,}arch=\"x86\""
            [ -n "${cpu_family:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}family=\"$cpu_family\""
            [ -n "${cpu_model:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}model_id=\"$cpu_model\""
            [ -n "${cpu_stepping:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}stepping=\"$cpu_stepping\""
            [ -n "$cpuid_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}cpuid=\"$cpuid_hex\""
            [ -n "$codename" ] && cpu_labels="${cpu_labels:+$cpu_labels,}codename=\"$(_prom_escape "$codename")\""
            ;;
        ARM | CAVIUM | PHYTIUM)
            cpu_labels="${cpu_labels:+$cpu_labels,}arch=\"arm\""
            [ -n "${cpu_part_list:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}part_list=\"$(_prom_escape "$cpu_part_list")\""
            [ -n "${cpu_arch_list:-}" ] && cpu_labels="${cpu_labels:+$cpu_labels,}arch_list=\"$(_prom_escape "$cpu_arch_list")\""
            ;;
    esac
    [ -n "$smt_val" ] && cpu_labels="${cpu_labels:+$cpu_labels,}smt=\"$smt_val\""
    [ -n "$ucode_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode=\"$ucode_hex\""
    [ -n "$ucode_latest_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode_latest=\"$ucode_latest_hex\""
    [ -n "$ucode_uptodate" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode_up_to_date=\"$ucode_uptodate\""
    # always emit microcode_blacklisted when we have microcode info (it's a boolean, never omit)
    [ -n "$ucode_hex" ] && cpu_labels="${cpu_labels:+$cpu_labels,}microcode_blacklisted=\"$ucode_blacklisted\""
    [ -n "$cpu_labels" ] && g_smc_cpu_info_line="smc_cpu_info{$cpu_labels} 1"
}

# Update global state used to determine the program exit code
# Args: $1=cve $2=status(UNK|VULN|OK)
# Sets: g_unknown, g_critical
# Callers: pvulnstatus
_record_result() {
    case "$2" in
        UNK) g_unknown="1" ;;
        VULN) g_critical="1" ;;
        OK) ;;
        *)
            echo "$0: error: unknown status '$2' passed to _record_result()" >&2
            exit 255
            ;;
    esac
}

# Print the final vulnerability status for a CVE and dispatch to batch emitters
# Args: $1=cve $2=status(UNK|OK|VULN) $3=description
# Sets: g_pvulnstatus_last_cve
pvulnstatus() {
    local aka vulnstatus
    g_pvulnstatus_last_cve="$1"
    if [ "$opt_batch" = 1 ]; then
        aka=$(_cve_registry_field "$1" 2)

        case "$opt_batch_format" in
            text) _emit_text "$1" "$aka" "$2" "$3" ;;
            short) _emit_short "$1" "$aka" "$2" "$3" ;;
            json) _emit_json_full "$1" "$aka" "$2" "$3" ;;
            json-terse) _emit_json_terse "$1" "$aka" "$2" "$3" ;;
            nrpe) _emit_nrpe "$1" "$aka" "$2" "$3" ;;
            prometheus) _emit_prometheus "$1" "$aka" "$2" "$3" ;;
            *)
                echo "$0: error: invalid batch format '$opt_batch_format' specified" >&2
                exit 255
                ;;
        esac
        # reset per-CVE sysfs globals so they don't leak into the next CVE
        g_json_cve_sysfs_status=''
        g_json_cve_sysfs_msg=''
    fi

    _record_result "$1" "$2"

    # display info if we're not in quiet/batch mode
    vulnstatus="$2"
    shift 2
    pr_info_nol "> \033[46m\033[30mSTATUS:\033[0m "
    : "${g_final_summary:=}"
    : "${g_final_summary_count:=0}"
    g_final_summary_count=$((g_final_summary_count + 1))
    # wrap to a new line every 4 entries for readability
    if [ "$g_final_summary_count" -gt 1 ] && [ $((g_final_summary_count % 4)) -eq 1 ]; then
        g_final_summary="$g_final_summary\n          "
    fi
    # pad entry to fixed width so columns align despite varying CVE ID lengths
    case "$vulnstatus" in
        UNK)
            pstatus yellow 'UNKNOWN' "$@"
            _summary_label=$(printf "%-17s" "$g_pvulnstatus_last_cve:??")
            g_final_summary="$g_final_summary \033[43m\033[30m$_summary_label\033[0m"
            ;;
        VULN)
            pstatus red 'VULNERABLE' "$@"
            _summary_label=$(printf "%-17s" "$g_pvulnstatus_last_cve:KO")
            g_final_summary="$g_final_summary \033[41m\033[30m$_summary_label\033[0m"
            ;;
        OK)
            pstatus green 'NOT VULNERABLE' "$@"
            _summary_label=$(printf "%-17s" "$g_pvulnstatus_last_cve:OK")
            g_final_summary="$g_final_summary \033[42m\033[30m$_summary_label\033[0m"
            ;;
        *)
            echo "$0: error: unknown status '$vulnstatus' passed to pvulnstatus()" >&2
            exit 255
            ;;
    esac
}
