package hwids import ( "bufio" "fmt" "os" "strings" ) var pciPath = []string{ "/usr/share/hwdata/pci.ids", "/usr/share/misc/pci.ids", } type PCIType int const ( PCIVendor PCIType = iota PCIDevice PCISubsystem ) type PciDevices map[uint16][]PciDevice // PciDevice represents a PCI device type PciDevice struct { Type PCIType Vendor, Device uint16 SubVendor, SubDevice uint16 VendorName, DeviceName string SubName string } // Load load the hardware database for PCI devices and return a map of // vendor -> list of devices. func Load() (PciDevices, error) { // if the environment variable HWDATAPATH is set, we add it to the // list of paths we check for the hardware database. extraPath := os.Getenv("HWDATA") if extraPath != "" { pciPath = append(pciPath, extraPath) } for _, f := range pciPath { fh, err := os.Open(f) if err != nil { continue } defer fh.Close() return parse(fh) } return PciDevices{}, fmt.Errorf("hwids: could not find a pci.ids file") } func parse(f *os.File) (PciDevices, error) { devices := make(PciDevices) s := bufio.NewScanner(f) // this is to keep track of the current device. The format of the // file is as follow: // vendor vendor_name // device device_name <-- single tab // subvendor subdevice subsystem_name <-- two tabs // the variable is to keep track of the current vendor / device cur := PciDevice{} for s.Scan() { l := s.Text() // skip empty lines or lines that are a comment if len(l) == 0 || l[0] == '#' { continue } // lines starting with a C are the classes definitions, and // they are at the end of the file, which means we're done // parsing the devices if l[0] == 'C' { break } parts := strings.SplitN(l, " ", 2) if len(parts) != 2 { return devices, fmt.Errorf("hwids: malformed PCI ID line (missing ID separator): %s", l) } ids, name := parts[0], parts[1] if len(ids) < 2 || len(name) == 0 { return devices, fmt.Errorf("hwids: malformed PCI ID line (empty ID or name): %s", l) } cur.Type = PCIVendor if ids[0] == '\t' { if ids[1] == '\t' { cur.Type = PCISubsystem } else { cur.Type = PCIDevice } } var err error switch cur.Type { case PCIVendor: _, err = fmt.Sscanf(ids, "%x", &cur.Vendor) cur.VendorName = name case PCIDevice: _, err = fmt.Sscanf(ids, "%x", &cur.Device) cur.DeviceName = name case PCISubsystem: _, err = fmt.Sscanf(ids, "%x %x", &cur.SubVendor, &cur.SubDevice) cur.SubName = name } if err != nil { return devices, fmt.Errorf("hwids: malformed PCI ID line: %s: %v", l, err) } // This is to reset the state when we are moving to a // different vendor or device switch cur.Type { case PCIVendor: cur.Device = 0 cur.DeviceName = "" fallthrough case PCIDevice: cur.SubVendor = 0 cur.SubDevice = 0 cur.SubName = "" } _, ok := devices[cur.Vendor] if ok { _devices := devices[cur.Vendor] _devices = append(_devices, cur) devices[cur.Vendor] = _devices } else { _devices := []PciDevice{cur} devices[cur.Vendor] = _devices } } if err := s.Err(); err != nil { return devices, fmt.Errorf("hwids: failed to read PCI ID line: %v", err) } return devices, nil }