package pciids import ( "bufio" _ "embed" "fmt" "os" "strconv" "strings" "sync" ) var ( //go:embed pci.ids embeddedPCIIDs string loadOnce sync.Once vendors map[int]string devices map[string]string ) // VendorName returns vendor name by PCI Vendor ID func VendorName(vendorID int) string { loadPCIIDs() if name, ok := vendors[vendorID]; ok { return name } return "" } // DeviceName returns device name by Vendor ID and Device ID func DeviceName(vendorID, deviceID int) string { loadPCIIDs() key := fmt.Sprintf("%04x:%04x", vendorID, deviceID) if name, ok := devices[key]; ok { return name } return "" } // DeviceInfo returns both vendor and device name func DeviceInfo(vendorID, deviceID int) (vendor, device string) { vendor = VendorName(vendorID) device = DeviceName(vendorID, deviceID) return } // VendorNameFromString tries to parse vendor ID from string (hex) and return name func VendorNameFromString(s string) string { s = strings.TrimSpace(s) if s == "" { return "" } // Try to parse as hex (with or without 0x prefix) s = strings.TrimPrefix(strings.ToLower(s), "0x") var id int for _, c := range s { if c >= '0' && c <= '9' { id = id*16 + int(c-'0') } else if c >= 'a' && c <= 'f' { id = id*16 + int(c-'a'+10) } else { return "" } } return VendorName(id) } func loadPCIIDs() { loadOnce.Do(func() { vendors = make(map[int]string) devices = make(map[string]string) parsePCIIDs(strings.NewReader(embeddedPCIIDs), vendors, devices) for _, path := range candidatePCIIDsPaths() { f, err := os.Open(path) if err != nil { continue } parsePCIIDs(f, vendors, devices) _ = f.Close() } }) } func candidatePCIIDsPaths() []string { paths := []string{ "pci.ids", "/usr/share/hwdata/pci.ids", "/usr/share/misc/pci.ids", "/opt/homebrew/share/pciids/pci.ids", } // Env paths have highest priority, so they are applied last. if env := strings.TrimSpace(os.Getenv("LOGPILE_PCI_IDS_PATH")); env != "" { for _, p := range strings.Split(env, string(os.PathListSeparator)) { p = strings.TrimSpace(p) if p != "" { paths = append(paths, p) } } } return paths } func parsePCIIDs(r interface{ Read([]byte) (int, error) }, outVendors map[int]string, outDevices map[string]string) { scanner := bufio.NewScanner(r) currentVendor := -1 for scanner.Scan() { line := scanner.Text() if line == "" || strings.HasPrefix(line, "#") { continue } // Subdevice line (tab-tab) - ignored for now if strings.HasPrefix(line, "\t\t") { continue } // Device line if strings.HasPrefix(line, "\t") { if currentVendor < 0 { continue } trimmed := strings.TrimLeft(line, "\t") fields := strings.Fields(trimmed) if len(fields) < 2 { continue } deviceID, err := strconv.ParseInt(fields[0], 16, 32) if err != nil { continue } name := strings.TrimSpace(trimmed[len(fields[0]):]) if name == "" { continue } key := fmt.Sprintf("%04x:%04x", currentVendor, int(deviceID)) outDevices[key] = name continue } // Vendor line fields := strings.Fields(line) if len(fields) < 2 { currentVendor = -1 continue } vendorID, err := strconv.ParseInt(fields[0], 16, 32) if err != nil { currentVendor = -1 continue } name := strings.TrimSpace(line[len(fields[0]):]) if name == "" { currentVendor = -1 continue } currentVendor = int(vendorID) outVendors[currentVendor] = name } }