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
}