diff --git a/pkg/snapshot/clonetree.go b/pkg/snapshot/clonetree.go index ba9f8f10..d2f75116 100644 --- a/pkg/snapshot/clonetree.go +++ b/pkg/snapshot/clonetree.go @@ -52,6 +52,7 @@ func ExpectedCloneContent() []string { fileSpecs := ExpectedCloneStaticContent() fileSpecs = append(fileSpecs, ExpectedCloneNetContent()...) fileSpecs = append(fileSpecs, ExpectedClonePCIContent()...) + fileSpecs = append(fileSpecs, ExpectedCloneGPUContent()...) return fileSpecs } @@ -202,3 +203,61 @@ func copyLink(path, targetPath string) error { return nil } + +type filterFunc func(string) bool + +// cloneContentByClass copies all the content related to a given device class +// (devClass), possibly filtering out devices whose name does NOT pass a +// filter (filterName). Each entry in `/sys/class/$CLASS` is actually a +// symbolic link. We can filter out entries depending on the link target. +// Each filter is a simple function which takes the entry name or the link +// target and must return true if the entry should be collected, false +// otherwise. Last, explicitely collect a list of attributes for each entry, +// given as list of glob patterns as `subEntries`. +// Return the final list of glob patterns to be collected. +func cloneContentByClass(devClass string, subEntries []string, filterName filterFunc, filterLink filterFunc) []string { + var fileSpecs []string + + // warning: don't use the context package here, this means not even the linuxpath package. + // TODO(fromani) remove the path duplication + sysClass := filepath.Join("sys", "class", devClass) + entries, err := ioutil.ReadDir(sysClass) + if err != nil { + // we should not import context, hence we can't Warn() + return fileSpecs + } + for _, entry := range entries { + devName := entry.Name() + + if !filterName(devName) { + continue + } + + devPath := filepath.Join(sysClass, devName) + dest, err := os.Readlink(devPath) + if err != nil { + continue + } + + if !filterLink(dest) { + continue + } + + // so, first copy the symlink itself + fileSpecs = append(fileSpecs, devPath) + // now we have to clone the content of the actual entry + // related (and found into a subdir of) the backing hardware + // device + devData := filepath.Clean(filepath.Join(sysClass, dest)) + for _, subEntry := range subEntries { + fileSpecs = append(fileSpecs, filepath.Join(devData, subEntry)) + } + } + + return fileSpecs +} + +// filterNone allows all content, filtering out none of it +func filterNone(_ string) bool { + return true +} diff --git a/pkg/snapshot/clonetree_gpu.go b/pkg/snapshot/clonetree_gpu.go new file mode 100644 index 00000000..a26d6b01 --- /dev/null +++ b/pkg/snapshot/clonetree_gpu.go @@ -0,0 +1,33 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package snapshot + +import ( + "strings" +) + +// ExpectedCloneGPUContent returns a slice of strings pertaining to the GPU devices ghw +// cares about. We cannot use a static list because we want to grab only the first cardX data +// (see comment in pkg/gpu/gpu_linux.go) +// Additionally, we want to make sure to clone the backing device data. +func ExpectedCloneGPUContent() []string { + cardEntries := []string{ + "device", + } + + filterName := func(cardName string) bool { + if !strings.HasPrefix(cardName, "card") { + return false + } + if strings.ContainsRune(cardName, '-') { + return false + } + return true + } + + return cloneContentByClass("drm", cardEntries, filterName, filterNone) +} diff --git a/pkg/snapshot/clonetree_net.go b/pkg/snapshot/clonetree_net.go index 0a67e41d..6c89a6cc 100644 --- a/pkg/snapshot/clonetree_net.go +++ b/pkg/snapshot/clonetree_net.go @@ -7,58 +7,25 @@ package snapshot import ( - "io/ioutil" - "os" - "path/filepath" "strings" ) -const ( - // warning: don't use the context package here, this means not even the linuxpath package. - // TODO(fromani) remove the path duplication - sysClassNet = "/sys/class/net" -) - // ExpectedCloneNetContent returns a slice of strings pertaning to the network interfaces ghw // cares about. We cannot use a static list because we want to filter away the virtual devices, // which ghw doesn't concern itself about. So we need to do some runtime discovery. // Additionally, we want to make sure to clone the backing device data. func ExpectedCloneNetContent() []string { - var fileSpecs []string ifaceEntries := []string{ "addr_assign_type", // intentionally avoid to clone "address" to avoid to leak any host-idenfifiable data. } - entries, err := ioutil.ReadDir(sysClassNet) - if err != nil { - // we should not import context, hence we can't Warn() - return fileSpecs - } - for _, entry := range entries { - netName := entry.Name() - netPath := filepath.Join(sysClassNet, netName) - dest, err := os.Readlink(netPath) - if err != nil { - continue - } - if strings.Contains(dest, "devices/virtual/net") { - // there is no point in cloning data for virtual devices, - // because ghw concerns itself with HardWare. - continue - } - - // so, first copy the symlink itself - fileSpecs = append(fileSpecs, netPath) - // now we have to clone the content of the actual network interface - // data related (and found into a subdir of) the backing hardware - // device - netIface := filepath.Clean(filepath.Join(sysClassNet, dest)) - for _, ifaceEntry := range ifaceEntries { - fileSpecs = append(fileSpecs, filepath.Join(netIface, ifaceEntry)) + filterLink := func(linkDest string) bool { + if strings.Contains(linkDest, "devices/virtual/net") { + return false } - + return true } - return fileSpecs + return cloneContentByClass("net", ifaceEntries, filterNone, filterLink) }