aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/subsystem/linux
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2023-01-17 19:20:37 +0100
committerAleksandr Nogikh <wp32pw@gmail.com>2023-02-10 14:34:44 +0100
commitba1c7407eaa0c09e93d8f319c9e7e65bdf0187d3 (patch)
treef7348e12a2c86a7e1858412c4b206882d15b2003 /pkg/subsystem/linux
parent4c1f201b6fc2cc30625d3c706b1f45cc68ef0223 (diff)
pkg/subsystem/linux: extract names for subsystems
Extract the short subsystem name from the mailing list email. Stip the common prefixes and suffixes and make sure there are no duplicates. As a fallback, assign the whole list email address as a subsystem name.
Diffstat (limited to 'pkg/subsystem/linux')
-rw-r--r--pkg/subsystem/linux/names.go103
-rw-r--r--pkg/subsystem/linux/names_test.go115
-rw-r--r--pkg/subsystem/linux/subsystems.go4
-rw-r--r--pkg/subsystem/linux/subsystems_test.go4
4 files changed, 226 insertions, 0 deletions
diff --git a/pkg/subsystem/linux/names.go b/pkg/subsystem/linux/names.go
new file mode 100644
index 000000000..0f422ea05
--- /dev/null
+++ b/pkg/subsystem/linux/names.go
@@ -0,0 +1,103 @@
+// Copyright 2023 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package linux
+
+import (
+ "fmt"
+ "regexp"
+
+ "github.com/google/syzkaller/pkg/subsystem/entity"
+)
+
+// setSubsystemNames assigns unique names to the presented subsystems.
+// If it failed to assign a name to a subsystem, the Name field remains empty.
+func setSubsystemNames(list []*entity.Subsystem) error {
+ dups := map[string]bool{}
+ for _, item := range list {
+ // For now, we can only infer name from the list email.
+ if len(item.Lists) == 0 {
+ return fmt.Errorf("no lists for %#v", item)
+ }
+ email := item.Lists[0]
+ name := emailToName(email)
+ if !validateName(name) {
+ return fmt.Errorf("failed to extract a name from %s", email)
+ }
+ if dups[name] {
+ return fmt.Errorf("duplicate subsystem name: %s", item.Name)
+ }
+ item.Name = name
+ dups[name] = true
+ }
+ return nil
+}
+
+func validateName(name string) bool {
+ const (
+ minLen = 2
+ maxLen = 16 // otherwise the email subject can get too big
+ )
+ return len(name) >= minLen && len(name) <= maxLen
+}
+
+func emailToName(email string) string {
+ if name := emailExceptions[email]; name != "" {
+ return name
+ }
+ ret := emailStripRe.FindStringSubmatch(email)
+ if ret == nil {
+ return ""
+ }
+ return ret[1]
+}
+
+func buildEmailStripRe() *regexp.Regexp {
+ raw := `^(?:`
+ for i := 0; i < len(stripPrefixes); i++ {
+ if i > 0 {
+ raw += "|"
+ }
+ raw += regexp.QuoteMeta(stripPrefixes[i])
+ }
+ raw += ")*(.*?)(?:"
+ for i := 0; i < len(stripSuffixes); i++ {
+ if i > 0 {
+ raw += "|"
+ }
+ raw += regexp.QuoteMeta(stripSuffixes[i])
+ }
+ raw += ")*@.*$"
+ return regexp.MustCompile(raw)
+}
+
+var (
+ emailExceptions = map[string]string{
+ "patches@opensource.cirrus.com": "cirrus",
+ "virtualization@lists.linux-foundation.org": "virt", // the name is too long
+ "dev@openvswitch.org": "openvswitch",
+ "devel@acpica.org": "acpica",
+ "kernel@dh-electronics.com": "dh-electr",
+ "devel@lists.orangefs.org": "orangefs",
+ "linux-arm-kernel@axis.com": "axis",
+ "Dell.Client.Kernel@dell.com": "dell",
+ "sound-open-firmware@alsa-project.org": "sof",
+ "platform-driver-x86@vger.kernel.org": "x86-drivers",
+ "linux-trace-devel@vger.kernel.org": "rt-tools",
+ "aws-nitro-enclaves-devel@amazon.com": "nitro",
+ "brcm80211-dev-list.pdl@broadcom.com": "brcm80211",
+ "osmocom-net-gprs@lists.osmocom.org": "osmocom",
+ "netdev@vger.kernel.org": "net",
+ "megaraidlinux.pdl@broadcom.com": "megaraid",
+ "mpi3mr-linuxdrv.pdl@broadcom.com": "mpi3",
+ "MPT-FusionLinux.pdl@broadcom.com": "mpt-fusion",
+ }
+ stripPrefixes = []string{"linux-"}
+ stripSuffixes = []string{
+ "-devel", "-dev", "-devs", "-developer", "devel",
+ "-user", "-users",
+ "-discussion", "-discuss", "-list", "-en",
+ "-kernel", "-linux", "-general",
+ }
+ emailStripRe = buildEmailStripRe()
+)
diff --git a/pkg/subsystem/linux/names_test.go b/pkg/subsystem/linux/names_test.go
new file mode 100644
index 000000000..0b2352411
--- /dev/null
+++ b/pkg/subsystem/linux/names_test.go
@@ -0,0 +1,115 @@
+// Copyright 2023 syzkaller project authors. All rights reserved.
+// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
+
+package linux
+
+import (
+ "testing"
+
+ "github.com/google/syzkaller/pkg/subsystem/entity"
+)
+
+func TestEmailToName(t *testing.T) {
+ tests := map[string]string{
+ // These are following the general rules.
+ "linux-nilfs@vger.kernel.org": "nilfs",
+ "tomoyo-dev-en@lists.osdn.me": "tomoyo",
+ "tipc-discussion@lists.sourceforge.net": "tipc",
+ "v9fs-developer@lists.sourceforge.net": "v9fs",
+ "zd1211-devs@lists.sourceforge.net": "zd1211",
+ // Test that we can handle exceptions.
+ "virtualization@lists.linux-foundation.org": "virt",
+ }
+ for email, name := range tests {
+ result := emailToName(email)
+ if result != name {
+ t.Fatalf("%#v: expected %#v, got %#v", email, name, result)
+ }
+ }
+}
+
+type subsystemTestInput struct {
+ email string
+ outName string
+}
+
+func (sti subsystemTestInput) ToSubsystem() *entity.Subsystem {
+ s := &entity.Subsystem{}
+ if sti.email != "" {
+ s.Lists = append(s.Lists, sti.email)
+ }
+ return s
+}
+
+func TestSetSubsystemNames(t *testing.T) {
+ tests := []struct {
+ name string
+ inputs []subsystemTestInput
+ mustFail bool
+ }{
+ {
+ name: "plan test",
+ inputs: []subsystemTestInput{
+ {
+ email: "linux-ntfs-dev@lists.sourceforge.net",
+ outName: "ntfs",
+ },
+ {
+ email: "llvm@lists.linux.dev",
+ outName: "llvm",
+ },
+ },
+ },
+ {
+ name: "has dup name",
+ inputs: []subsystemTestInput{
+ {
+ email: "linux-ntfs-dev@lists.sourceforge.net",
+ outName: "ntfs",
+ },
+ {
+ email: "ntfs@lists.sourceforge.net",
+ outName: "ntfs",
+ },
+ },
+ mustFail: true,
+ },
+ {
+ name: "has empty list",
+ inputs: []subsystemTestInput{
+ {
+ email: "linux-ntfs-dev@lists.sourceforge.net",
+ outName: "ntfs",
+ },
+ {
+ email: "",
+ outName: "",
+ },
+ },
+ mustFail: true,
+ },
+ }
+ for _, test := range tests {
+ curr := test
+ t.Run(curr.name, func(t *testing.T) {
+ list := []*entity.Subsystem{}
+ for _, i := range curr.inputs {
+ list = append(list, i.ToSubsystem())
+ }
+ err := setSubsystemNames(list)
+ if curr.mustFail != (err != nil) {
+ t.Fatalf("expected failure: %v, got: %v", curr.mustFail, err)
+ }
+ if curr.mustFail {
+ return
+ }
+ for i, item := range list {
+ if item.Name != curr.inputs[i].outName {
+ t.Fatalf("invalid name for #%d: expected %#v, got %#v",
+ i+1, curr.inputs[i].outName, item.Name,
+ )
+ }
+ }
+ })
+ }
+}
diff --git a/pkg/subsystem/linux/subsystems.go b/pkg/subsystem/linux/subsystems.go
index b5ffa924c..468ba5fbc 100644
--- a/pkg/subsystem/linux/subsystems.go
+++ b/pkg/subsystem/linux/subsystems.go
@@ -82,6 +82,9 @@ func (ctx *linuxCtx) getSubsystems() ([]*entity.Subsystem, error) {
mergeRawRecords(s, raw.records)
ret = append(ret, s)
}
+ if err := setSubsystemNames(ret); err != nil {
+ return nil, fmt.Errorf("failed to set names: %w", err)
+ }
return ret, nil
}
@@ -95,6 +98,7 @@ func mergeRawRecords(subsystem *entity.Subsystem, records []*maintainersRecord)
for s := range m {
ret = append(ret, s)
}
+ sort.Strings(ret)
return ret
}
var lists, maintainers []string
diff --git a/pkg/subsystem/linux/subsystems_test.go b/pkg/subsystem/linux/subsystems_test.go
index f5699f26e..caf675244 100644
--- a/pkg/subsystem/linux/subsystems_test.go
+++ b/pkg/subsystem/linux/subsystems_test.go
@@ -25,17 +25,21 @@ func TestGroupLinuxSubsystems(t *testing.T) {
}
expected := []*entity.Subsystem{
{
+ Name: "fs",
Lists: []string{"linux-fsdevel@vger.kernel.org"},
Maintainers: []string{"email_vfs@email.com"},
},
{
+ Name: "ext4",
Lists: []string{"linux-ext4@vger.kernel.org"},
Maintainers: []string{"email_ext4@email.com", "email_ext4_2@email.com"},
},
{
+ Name: "mm",
Lists: []string{"linux-mm@kvack.org"},
},
{
+ Name: "kernel",
Lists: []string{"linux-kernel@vger.kernel.org"},
Maintainers: []string{"email_rest@email.com"},
},