aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/subsystem/linux
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2023-01-11 19:36:30 +0100
committerAleksandr Nogikh <wp32pw@gmail.com>2023-02-10 14:34:44 +0100
commit2246b3b0ea578783224118ca3af660ecb0ebd2b6 (patch)
treed38c9eb6ca8053b61b2fecc654e5c2df2de10be6 /pkg/subsystem/linux
parent81d09d26268f7cb9a371b05c1abe07d581eb952a (diff)
pkg/subsystem/linux: convert MAINTAINERS patters to regexps
Take care of the corner cases and add extensive tests.
Diffstat (limited to 'pkg/subsystem/linux')
-rw-r--r--pkg/subsystem/linux/maintainers.go68
-rw-r--r--pkg/subsystem/linux/maintainers_test.go134
2 files changed, 202 insertions, 0 deletions
diff --git a/pkg/subsystem/linux/maintainers.go b/pkg/subsystem/linux/maintainers.go
index ce692bfd4..6a188b261 100644
--- a/pkg/subsystem/linux/maintainers.go
+++ b/pkg/subsystem/linux/maintainers.go
@@ -8,9 +8,12 @@ import (
"fmt"
"io"
"net/mail"
+ "path/filepath"
"regexp"
"strings"
"unicode"
+
+ "github.com/google/syzkaller/pkg/subsystem/entity"
)
// maintainersRecord represents a single raw record in the MAINTAINERS file.
@@ -142,3 +145,68 @@ func parseEmail(value string) (string, error) {
}
return addr.Address, nil
}
+
+func (r maintainersRecord) ToPathRule() entity.PathRule {
+ inclRe := strings.Builder{}
+ for i, wildcard := range r.includePatterns {
+ if i > 0 {
+ inclRe.WriteByte('|')
+ }
+ wildcardToRegexp(wildcard, &inclRe)
+ }
+ for _, rg := range r.regexps {
+ if inclRe.Len() > 0 {
+ inclRe.WriteByte('|')
+ }
+ inclRe.WriteString(rg)
+ }
+ exclRe := strings.Builder{}
+ for i, wildcard := range r.excludePatterns {
+ if i > 0 {
+ exclRe.WriteByte('|')
+ }
+ wildcardToRegexp(wildcard, &exclRe)
+ }
+ return entity.PathRule{
+ IncludeRegexp: inclRe.String(),
+ ExcludeRegexp: exclRe.String(),
+ }
+}
+
+var (
+ escapedSeparator = regexp.QuoteMeta(fmt.Sprintf("%c", filepath.Separator))
+ wildcardReplace = map[byte]string{
+ '*': `[^` + escapedSeparator + `]*`,
+ '?': `.`,
+ '/': escapedSeparator,
+ }
+)
+
+func wildcardToRegexp(wildcard string, store *strings.Builder) {
+ store.WriteByte('^')
+
+ // We diverge a bit from the standard MAINTAINERS rule semantics.
+ // path/* corresponds to the files belonging to the `path` folder,
+ // but, since we also infer the parent-child relationship, it's
+ // easier to make it cover the whole subtree.
+ if len(wildcard) >= 2 && wildcard[len(wildcard)-2:] == "/*" {
+ wildcard = wildcard[:len(wildcard)-1]
+ }
+
+ tokenStart := 0
+ for i, c := range wildcard {
+ replace, exists := wildcardReplace[byte(c)]
+ if !exists {
+ continue
+ }
+ store.WriteString(regexp.QuoteMeta(wildcard[tokenStart:i]))
+ store.WriteString(replace)
+ tokenStart = i + 1
+ }
+ if tokenStart < len(wildcard) {
+ store.WriteString(regexp.QuoteMeta(wildcard[tokenStart:]))
+ }
+ if wildcard == "" || wildcard[len(wildcard)-1] != '/' {
+ store.WriteByte('$')
+ }
+}
diff --git a/pkg/subsystem/linux/maintainers_test.go b/pkg/subsystem/linux/maintainers_test.go
index 874bdcaa7..dc3299dbf 100644
--- a/pkg/subsystem/linux/maintainers_test.go
+++ b/pkg/subsystem/linux/maintainers_test.go
@@ -8,8 +8,142 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
+ "github.com/google/syzkaller/pkg/subsystem/entity"
+ "github.com/google/syzkaller/pkg/subsystem/match"
)
+func TestRecordToPathRule(t *testing.T) {
+ tests := []struct {
+ name string
+ record maintainersRecord
+ match []string
+ noMatch []string
+ }{
+ {
+ name: `general test`,
+ record: maintainersRecord{
+ includePatterns: []string{
+ `drivers/gpio/gpio-*wm*.c`,
+ `drivers/hwmon/wm83??-hwmon.c`,
+ `include/linux/mfd/arizona/`,
+ `include/linux/wm97xx.h`,
+ },
+ },
+ match: []string{
+ `drivers/gpio/gpio-wm831x.c`,
+ `drivers/gpio/gpio-abcdwm831x.c`,
+ `drivers/hwmon/wm8355-hwmon.c`,
+ `include/linux/mfd/arizona/file.c`,
+ `include/linux/mfd/arizona/subfolder/file.c`,
+ `include/linux/wm97xx.h`,
+ },
+ noMatch: []string{
+ `drivers/gpio/gpio-w831x.c`,
+ `drivers/hwmon/wm83556-hwmon.c`,
+ `drivers/hwmon/wm831-hwmon.c`,
+ `include/linux/mfd`,
+ `include`,
+ `random-file`,
+ },
+ },
+ {
+ name: `include patterns and regexp`,
+ record: maintainersRecord{
+ includePatterns: []string{`drivers/rtc/rtc-opal.c`},
+ regexps: []string{`[^a-z0-9]ps3`},
+ },
+ match: []string{
+ `drivers/rtc/rtc-opal.c`,
+ `drivers/ps3/a.c`,
+ `drivers/sub/ps3/a.c`,
+ `drivers/sub/sub/ps3.c`,
+ },
+ noMatch: []string{
+ `drivers/aps3/a.c`,
+ `drivers/abc/aps3.c`,
+ },
+ },
+ {
+ name: `exclude patterns`,
+ record: maintainersRecord{
+ includePatterns: []string{`security/`},
+ excludePatterns: []string{`security/selinux/`},
+ },
+ match: []string{
+ `security/apparmor/abcd.c`,
+ `security/abcd.c`,
+ },
+ noMatch: []string{
+ `security/selinux/abcd.c`,
+ },
+ },
+ {
+ name: `handle / at the end`,
+ record: maintainersRecord{
+ includePatterns: []string{
+ `with-subfolders/`,
+ `dir/only-one`,
+ `also-with-subfolders/*`,
+ },
+ },
+ match: []string{
+ `with-subfolders/a`,
+ `with-subfolders/a/b`,
+ `dir/only-one`,
+ `also-with-subfolders/a.c`,
+ `also-with-subfolders/b/a.c`,
+ },
+ noMatch: []string{
+ `dir/only-one/a.c`,
+ `dir/only-one/a/b.c`,
+ },
+ },
+ {
+ name: `wildcards are well escaped`,
+ record: maintainersRecord{
+ includePatterns: []string{`drivers/net/ethernet/smsc/smc91x.*`},
+ },
+ match: []string{
+ `drivers/net/ethernet/smsc/smc91x.c`,
+ `drivers/net/ethernet/smsc/smc91x.h`,
+ },
+ noMatch: []string{
+ `drivers/net/ethernet/smsc/smc91xAh`,
+ },
+ },
+ {
+ name: `match everything`,
+ record: maintainersRecord{
+ includePatterns: []string{`*`, `*/`},
+ },
+ match: []string{
+ `a`,
+ `a/b`,
+ `a/b/c`,
+ },
+ },
+ }
+
+ for _, loopTest := range tests {
+ test := loopTest
+ t.Run(test.name, func(t *testing.T) {
+ pm := match.MakePathMatcher([]*entity.Subsystem{
+ {PathRules: []entity.PathRule{test.record.ToPathRule()}},
+ })
+ for _, path := range test.match {
+ if len(pm.Match(path)) != 1 {
+ t.Fatalf("did not match %#v", path)
+ }
+ }
+ for _, path := range test.noMatch {
+ if len(pm.Match(path)) > 0 {
+ t.Fatalf("matched %#v", path)
+ }
+ }
+ })
+ }
+}
+
func TestLinuxMaintainers(t *testing.T) {
result, err := parseLinuxMaintainers(
strings.NewReader(maintainersSample),