package devices import ( "os" "path/filepath" ) var ( subsystemProperty = "SUBSYSTEM" ) // Matcher is the top-level matcher for devices. Rule sets // are evaluated using OR operator type Matcher struct { matchAll bool rules []*Rule } // MatchAll will search /sys/devices and match all devices in the tree func (m *Matcher) MatchAll() *Matcher { m.matchAll = true return m } // WithRule will add a rule to the matcher func (m *Matcher) WithRules(newRules ...*Rule) *Matcher { m.rules = append(m.rules, newRules...) return m } // MatchDevice will iterate the rules and attempt to evaluate if the device // matches (properties and attributes). func (m *Matcher) MatchDevice(device Device) bool { if m.matchAll { return true } return m.allRulesMatch(device) } // allRulesMatch attempts to evaluate all rules within a rule set against given device func (m *Matcher) allRulesMatch(device Device) bool { for _, r := range m.rules { if r.ruleType == attributeRule { attrMatched := r.isAttributeEqual(device) if !attrMatched { return false } } else { propMatched := r.isPropertyEqual(device) if !propMatched { return false } } } return true } // SearchPaths returns list of /sys search paths based on // the properties and attributes of device i.e. /sys/class/usb is // search path for devices with usb subsystem. func (m *Matcher) SearchPaths() []string { if m.matchAll { return []string{sysDevicePath} } searchPaths := []string{} for _, r := range m.rules { if r.name != subsystemProperty { continue } // check class path classPath := filepath.Join(SysPath, "class", r.subsystem) if _, err := os.ReadDir(classPath); err == nil { searchPaths = append(searchPaths, classPath) } // check bus path busPath := filepath.Join(SysPath, "bus", r.subsystem) if _, err := os.ReadDir(busPath); err == nil { searchPaths = append(searchPaths, busPath) } return searchPaths } return []string{} } // New creates a new matcher func NewMatcher() *Matcher { return &Matcher{} }