package inspur import ( "fmt" "regexp" "strconv" "strings" ) // auditSNChangedNVMeRegex matches: // "Front Back Plane N NVMe DiskM SN changed from X to Y" // Captures: disk_num, new_serial var auditSNChangedNVMeRegex = regexp.MustCompile(`NVMe Disk(\d+)\s+SN changed from \S+\s+to\s+(\S+)`) // auditSNChangedRAIDRegex matches: // "Raid(Pcie Slot:N) HDD(enclosure id:E slot:S) SN changed from X to Y" // Captures: pcie_slot, enclosure_id, slot_num, new_serial var auditSNChangedRAIDRegex = regexp.MustCompile(`Raid\(Pcie Slot:(\d+)\) HDD\(enclosure id:(\d+) slot:(\d+)\)\s+SN changed from \S+\s+to\s+(\S+)`) // ParseAuditLogNVMeSerials parses audit.log and returns the final (latest) serial number // per NVMe disk number. The disk number matches the numeric suffix in PCIe location // strings like "#NVME0", "#NVME2", etc. from devicefrusdr.log. // Entries where the serial changed to "NULL" are excluded. func ParseAuditLogNVMeSerials(content []byte) map[int]string { serials := make(map[int]string) for _, line := range strings.Split(string(content), "\n") { m := auditSNChangedNVMeRegex.FindStringSubmatch(line) if m == nil { continue } diskNum, err := strconv.Atoi(m[1]) if err != nil { continue } serial := strings.TrimSpace(m[2]) if strings.EqualFold(serial, "NULL") || serial == "" { delete(serials, diskNum) } else { serials[diskNum] = serial } } if len(serials) == 0 { return nil } return serials } // ParseAuditLogRAIDSerials parses audit.log and returns the final (latest) serial number // per RAID backplane disk. Key format is "BP{enclosure_id-1}:{slot_num}" (e.g. "BP0:0"). // // Each disk slot is claimed by a specific RAID controller (Pcie Slot:N). NULL events from // an old controller do not clear serials assigned by a newer controller, preventing stale // deletions when disks are migrated between RAID arrays. func ParseAuditLogRAIDSerials(content []byte) map[string]string { // owner tracks which PCIe RAID controller slot last assigned a serial to a disk key. serials := make(map[string]string) owner := make(map[string]int) for _, line := range strings.Split(string(content), "\n") { m := auditSNChangedRAIDRegex.FindStringSubmatch(line) if m == nil { continue } pcieSlot, err := strconv.Atoi(m[1]) if err != nil { continue } enclosureID, err := strconv.Atoi(m[2]) if err != nil { continue } slotNum, err := strconv.Atoi(m[3]) if err != nil { continue } serial := strings.TrimSpace(m[4]) key := fmt.Sprintf("BP%d:%d", enclosureID-1, slotNum) if strings.EqualFold(serial, "NULL") || serial == "" { // Only clear if this controller was the last to set the serial. if owner[key] == pcieSlot { delete(serials, key) delete(owner, key) } } else { serials[key] = serial owner[key] = pcieSlot } } if len(serials) == 0 { return nil } return serials }