package collector import ( "bee/audit/internal/schema" "log/slog" "os/exec" "regexp" "strings" "time" ) // dmesg -T output: [Thu Jun 18 14:23:45 2026] message // dmesg without -T: [ 123.456789] message var dmesgTimestampRE = regexp.MustCompile(`^\[([^\]]+)\]\s*(.*)$`) // Keywords that indicate an error or hardware problem worth capturing. var dmesgErrorPatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)\berr(or)?\b`), regexp.MustCompile(`(?i)\bfail(ed|ure)?\b`), regexp.MustCompile(`(?i)\bfault\b`), regexp.MustCompile(`(?i)\bwarn(ing)?\b`), regexp.MustCompile(`(?i)\bAER\b`), regexp.MustCompile(`(?i)\bXid\b`), regexp.MustCompile(`(?i)\bNVRM\b`), regexp.MustCompile(`(?i)\bpanic\b`), regexp.MustCompile(`(?i)\bcorrected\b`), regexp.MustCompile(`(?i)\buncorrect`), regexp.MustCompile(`(?i)\bECC\b`), regexp.MustCompile(`(?i)\btimeout\b`), regexp.MustCompile(`(?i)\breset\b`), regexp.MustCompile(`(?i)\bdead\b`), regexp.MustCompile(`(?i)\bhang\b`), regexp.MustCompile(`(?i)\bstall\b`), regexp.MustCompile(`(?i)\bdisabled\b`), } // Boot-time messages below are noisy configuration/driver diagnostics, not // hardware incidents. Raw dmesg remains in the bundle, but presenting each // of them as Critical makes the event log unusable. var dmesgIgnorePatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)bridge window .* failed to assign`), regexp.MustCompile(`(?i)^NVRM: loading NVIDIA .* Kernel Module`), regexp.MustCompile(`(?i)^NVRM: Persistence mode is deprecated`), regexp.MustCompile(`(?i)^nvidia: module verification failed:.*tainting kernel`), regexp.MustCompile(`(?i)^Yama: disabled by default`), regexp.MustCompile(`(?i)^ERST: .*initialized`), regexp.MustCompile(`(?i)iommu sva bind failed: -95`), regexp.MustCompile(`(?i)gpuClearFbhubPoisonIntrForBug`), } // collectDmesgErrors runs `dmesg -T` (or `dmesg` without -T on failure) and // returns only lines that match known error/warning patterns. func collectDmesgErrors() []schema.HardwareEventLog { out, err := exec.Command("dmesg", "-T").Output() if err != nil || len(out) == 0 { // Fallback: dmesg without human-readable timestamps out, err = exec.Command("dmesg").Output() if err != nil || len(out) == 0 { return nil } } entries := parseDmesgErrors(string(out)) if len(entries) == 0 { return nil } slog.Info("dmesg: collected error entries", "count", len(entries)) return entries } func parseDmesgErrors(output string) []schema.HardwareEventLog { var entries []schema.HardwareEventLog collectedAt := time.Now().UTC().Format(time.RFC3339) for _, line := range strings.Split(output, "\n") { line = strings.TrimSpace(line) if line == "" { continue } var timestamp, message string if m := dmesgTimestampRE.FindStringSubmatch(line); m != nil { timestamp = strings.TrimSpace(m[1]) message = strings.TrimSpace(m[2]) } else { message = line } if message == "" { continue } if !matchesAny(message, dmesgErrorPatterns) { continue } if matchesAny(message, dmesgIgnorePatterns) { continue } severity := dmesgSeverity(message) source := "dmesg" var eventTime *string if timestamp != "" { t := timestamp eventTime = &t } else { eventTime = &collectedAt } entries = append(entries, schema.HardwareEventLog{ Source: source, EventTime: eventTime, Severity: &severity, Message: message, }) } return entries } func matchesAny(s string, patterns []*regexp.Regexp) bool { for _, p := range patterns { if p.MatchString(s) { return true } } return false } // dmesgSeverityCriticalPatterns use \b word boundaries, unlike a plain // strings.Contains check, so common words that merely contain a keyword as a // substring don't false-positive — e.g. "disabled by default" contains // "fault" (de-fault), and "undead" would contain "dead". var dmesgSeverityCriticalPatterns = []*regexp.Regexp{ regexp.MustCompile(`(?i)\bpanic\b`), regexp.MustCompile(`(?i)\buncorrect`), regexp.MustCompile(`(?i)\bxid\b`), regexp.MustCompile(`(?i)\berror\b`), regexp.MustCompile(`(?i)\bfault\b`), regexp.MustCompile(`(?i)\bfail(ed|ure)?\b`), regexp.MustCompile(`(?i)\bdead\b`), regexp.MustCompile(`(?i)\bhang\b`), } func dmesgSeverity(msg string) string { if sev, ok := XidSeverity(msg); ok { if sev == "critical" { return statusCritical } return statusWarning } if matchesAny(msg, dmesgSeverityCriticalPatterns) { return statusCritical } return statusWarning }