github.com/jaypipes/ghw@v0.21.1/pkg/snapshot/clonetree_pci_linux.go (about) 1 // 2 // Use and distribution licensed under the Apache license version 2. 3 // 4 // See the COPYING file in the root project directory for full text. 5 // 6 7 package snapshot 8 9 import ( 10 "fmt" 11 "os" 12 "path/filepath" 13 14 pciaddr "github.com/jaypipes/ghw/pkg/pci/address" 15 ) 16 17 const ( 18 // root directory: entry point to start scanning the PCI forest 19 // warning: don't use the context package here, this means not even the linuxpath package. 20 // TODO(fromani) remove the path duplication 21 sysBusPCIDir = "/sys/bus/pci/devices" 22 ) 23 24 // ExpectedClonePCIContent return a slice of glob patterns which represent the pseudofiles 25 // ghw cares about, pertaining to PCI devices only. 26 // Beware: the content is host-specific, because the PCI topology is host-dependent and unpredictable. 27 func ExpectedClonePCIContent() []string { 28 fileSpecs := []string{ 29 "/sys/bus/pci/drivers/*", 30 } 31 pciRoots := []string{ 32 sysBusPCIDir, 33 } 34 for { 35 if len(pciRoots) == 0 { 36 break 37 } 38 pciRoot := pciRoots[0] 39 pciRoots = pciRoots[1:] 40 specs, roots := scanPCIDeviceRoot(pciRoot) 41 pciRoots = append(pciRoots, roots...) 42 fileSpecs = append(fileSpecs, specs...) 43 } 44 return fileSpecs 45 } 46 47 // scanPCIDeviceRoot reports a slice of glob patterns which represent the pseudofiles 48 // ghw cares about pertaining to all the PCI devices connected to the bus connected from the 49 // given root; usually (but not always) a CPU packages has 1+ PCI(e) roots, forming the first 50 // level; more PCI bridges are (usually) attached to this level, creating deep nested trees. 51 // hence we need to scan all possible roots, to make sure not to miss important devices. 52 // 53 // note about notifying errors. This function and its helper functions do use trace() everywhere 54 // to report recoverable errors, even though it would have been appropriate to use Warn(). 55 // This is unfortunate, and again a byproduct of the fact we cannot use context.Context to avoid 56 // circular dependencies. 57 // TODO(fromani): switch to Warn() as soon as we figure out how to break this circular dep. 58 func scanPCIDeviceRoot(root string) (fileSpecs []string, pciRoots []string) { 59 trace("scanning PCI device root %q\n", root) 60 61 perDevEntries := []string{ 62 "class", 63 "device", 64 "driver", 65 "iommu_group", 66 "irq", 67 "local_cpulist", 68 "modalias", 69 "numa_node", 70 "revision", 71 "vendor", 72 } 73 entries, err := os.ReadDir(root) 74 if err != nil { 75 return []string{}, []string{} 76 } 77 for _, entry := range entries { 78 entryName := entry.Name() 79 if addr := pciaddr.FromString(entryName); addr == nil { 80 // doesn't look like a entry we care about 81 // This is by far and large the most likely path 82 // hence we should NOT trace/warn here. 83 continue 84 } 85 86 entryPath := filepath.Join(root, entryName) 87 pciEntry, err := findPCIEntryFromPath(root, entryName) 88 if err != nil { 89 trace("error scanning %q: %v", entryName, err) 90 continue 91 } 92 93 trace("PCI entry is %q\n", pciEntry) 94 fileSpecs = append(fileSpecs, entryPath) 95 for _, perNetEntry := range perDevEntries { 96 fileSpecs = append(fileSpecs, filepath.Join(pciEntry, perNetEntry)) 97 } 98 99 if isPCIBridge(entryPath) { 100 trace("adding new PCI root %q\n", entryName) 101 pciRoots = append(pciRoots, pciEntry) 102 } 103 } 104 return fileSpecs, pciRoots 105 } 106 107 func findPCIEntryFromPath(root, entryName string) (string, error) { 108 entryPath := filepath.Join(root, entryName) 109 fi, err := os.Lstat(entryPath) 110 if err != nil { 111 return "", fmt.Errorf("stat(%s) failed: %v\n", entryPath, err) 112 } 113 if fi.Mode()&os.ModeSymlink == 0 { 114 // regular file, nothing to resolve 115 return entryPath, nil 116 } 117 // resolve symlink 118 target, err := os.Readlink(entryPath) 119 trace("entry %q is symlink resolved to %q\n", entryPath, target) 120 if err != nil { 121 return "", fmt.Errorf("readlink(%s) failed: %v - skipped\n", entryPath, err) 122 } 123 return filepath.Clean(filepath.Join(root, target)), nil 124 } 125 126 func isPCIBridge(entryPath string) bool { 127 subNodes, err := os.ReadDir(entryPath) 128 if err != nil { 129 // this is so unlikely we don't even return error. But we trace just in case. 130 trace("error scanning device entry path %q: %v", entryPath, err) 131 return false 132 } 133 for _, subNode := range subNodes { 134 if !subNode.IsDir() { 135 continue 136 } 137 if addr := pciaddr.FromString(subNode.Name()); addr != nil { 138 // we got an entry in the directory pertaining to this device 139 // which is a directory itself and it is named like a PCI address. 140 // Hence we infer the device we are considering is a PCI bridge of sorts. 141 // This is is indeed a bit brutal, but the only possible alternative 142 // (besides blindly copying everything in /sys/bus/pci/devices) is 143 // to detect the type of the device and pick only the bridges. 144 // This approach duplicates the logic within the `pci` subkpg 145 // - or forces us into awkward dep cycles, and has poorer forward 146 // compatibility. 147 return true 148 } 149 } 150 return false 151 }