...

Source file src/edge-infra.dev/pkg/lib/kernel/devices/crawler.go

Documentation: edge-infra.dev/pkg/lib/kernel/devices

     1  package devices
     2  
     3  import (
     4  	"io/fs"
     5  	"os"
     6  	"path/filepath"
     7  	"slices"
     8  	"strings"
     9  )
    10  
    11  // mock file mode for symlinks
    12  var isSymlinkFn = func(fileInfo fs.FileInfo) bool {
    13  	return fileInfo.Mode()&fs.ModeSymlink != 0
    14  }
    15  
    16  type opts struct {
    17  	matchAll bool
    18  	matchers []*Matcher
    19  }
    20  
    21  type Option func(*opts)
    22  
    23  // Match all devices
    24  func WithMatchAll(matchAll bool) Option {
    25  	return func(o *opts) {
    26  		o.matchAll = matchAll
    27  	}
    28  }
    29  
    30  // Matchers to search with
    31  func WithMatchers(matchers ...*Matcher) Option {
    32  	return func(o *opts) {
    33  		o.matchers = matchers
    34  	}
    35  }
    36  
    37  // Find walks rootPath and returns a mapped list of devices that
    38  // match to the provided matcher. Find will
    39  // search /sys/devices or /sys/class if matchers specify a subsystem.
    40  func Find(options ...Option) (map[string]Device, error) {
    41  	var (
    42  		devices = map[string]Device{}
    43  		err     error
    44  	)
    45  
    46  	renderedOpts := &opts{}
    47  	for _, o := range options {
    48  		o(renderedOpts)
    49  	}
    50  
    51  	if renderedOpts.matchAll {
    52  		renderedOpts.matchers = []*Matcher{
    53  			NewMatcher().MatchAll(),
    54  		}
    55  	}
    56  
    57  	// set search path from subsystem specified in matchers
    58  	searchPaths := []string{}
    59  	for _, m := range renderedOpts.matchers {
    60  		searchPaths = append(searchPaths, m.SearchPaths()...)
    61  	}
    62  	slices.Sort(searchPaths)
    63  	searchPaths = slices.Compact(searchPaths)
    64  	if slices.Contains(searchPaths, sysDevicePath) {
    65  		searchPaths = []string{sysDevicePath}
    66  	}
    67  
    68  	for _, sPath := range searchPaths {
    69  		devices, err = searchPath(sPath, devices, renderedOpts.matchers...)
    70  		if err != nil {
    71  			return devices, err
    72  		}
    73  	}
    74  	return devices, nil
    75  }
    76  
    77  func searchPath(searchPath string, devices map[string]Device, matchers ...*Matcher) (map[string]Device, error) {
    78  	if err := filepath.Walk(searchPath, func(path string, _ os.FileInfo, err error) error {
    79  		if err != nil {
    80  			return nil
    81  		}
    82  
    83  		realPath, err := fetchRealPath(path)
    84  		if err != nil {
    85  			return err
    86  		}
    87  
    88  		realPathInfo, err := os.Stat(realPath)
    89  		if err != nil {
    90  			if os.IsNotExist(err) {
    91  				return nil
    92  			}
    93  			return err
    94  		}
    95  
    96  		if !realPathInfo.IsDir() {
    97  			return nil
    98  		}
    99  
   100  		if realPath == searchPath || path == searchPath {
   101  			return nil
   102  		}
   103  
   104  		if _, err := os.Stat(filepath.Join(realPath, "uevent")); err != nil || os.IsNotExist(err) {
   105  			return nil
   106  		}
   107  
   108  		newDevice, err := New(realPath)
   109  		if err != nil {
   110  			return nil
   111  		}
   112  
   113  		if _, exists := devices[realPath]; exists {
   114  			return nil // already added
   115  		}
   116  
   117  		if hasAscendant(newDevice.Path(), devices) {
   118  			devices[newDevice.Path()] = newDevice
   119  			return nil
   120  		}
   121  
   122  		if len(matchers) == 0 {
   123  			devices[newDevice.Path()] = newDevice
   124  			return nil
   125  		}
   126  
   127  		for _, m := range matchers {
   128  			if m.MatchDevice(newDevice) {
   129  				devices[newDevice.Path()] = newDevice
   130  				return nil
   131  			}
   132  		}
   133  		return nil
   134  	}); err != nil {
   135  		return devices, err
   136  	}
   137  	return devices, nil
   138  }
   139  
   140  // fetch real path returns the true path, handling symlinks
   141  func fetchRealPath(path string) (string, error) {
   142  	fileInfo, err := os.Lstat(path)
   143  	if err != nil {
   144  		return "", err
   145  	}
   146  
   147  	if isSymlinkFn(fileInfo) {
   148  		newPath, err := filepath.EvalSymlinks(path)
   149  		if err != nil {
   150  			return "", err
   151  		}
   152  		return filepath.Abs(newPath)
   153  	}
   154  	return path, nil
   155  }
   156  
   157  // hasAscendant will search devices for an ascendant device given a path
   158  func hasAscendant(path string, devices map[string]Device) bool {
   159  	pathSplit := strings.Split(path, "/")
   160  	for idx := len(pathSplit) - 1; idx >= 0; idx-- {
   161  		searchPath := strings.Join(pathSplit[:idx], "/")
   162  		if slices.Contains([]string{sysDevicePath, sysBusPath, sysClassPath}, searchPath) {
   163  			return false
   164  		}
   165  		if _, ok := devices[searchPath]; ok {
   166  			return true
   167  		}
   168  	}
   169  	return false
   170  }
   171  

View as plain text