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  }