github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/subsystem/linux/maintainers.go (about) 1 // Copyright 2023 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package linux 5 6 import ( 7 "bufio" 8 "fmt" 9 "io" 10 "net/mail" 11 "path/filepath" 12 "regexp" 13 "strings" 14 "unicode" 15 16 "github.com/google/syzkaller/pkg/subsystem" 17 ) 18 19 // maintainersRecord represents a single raw record in the MAINTAINERS file. 20 type maintainersRecord struct { 21 name string 22 includePatterns []string 23 excludePatterns []string 24 regexps []string 25 lists []string 26 maintainers []string 27 trees []string 28 } 29 30 func parseLinuxMaintainers(content io.Reader) ([]*maintainersRecord, error) { 31 scanner := bufio.NewScanner(content) 32 // First skip the headers. 33 var skippedLines int 34 for scanner.Scan() { 35 skippedLines++ 36 line := scanner.Text() 37 if line == "Maintainers List" { 38 // Also skip ------. 39 scanner.Scan() 40 break 41 } 42 } 43 ml := &maintainersLexer{scanner: scanner, currentLine: skippedLines + 1} 44 ret := []*maintainersRecord{} 45 loop: 46 for { 47 item := ml.next() 48 switch v := item.(type) { 49 case recordTitle: 50 // The new subsystem begins. 51 ret = append(ret, &maintainersRecord{name: string(v)}) 52 case recordProperty: 53 if len(ret) == 0 { 54 return nil, fmt.Errorf("line %d: property without subsystem", ml.currentLine) 55 } 56 err := applyProperty(ret[len(ret)-1], &v) 57 if err != nil { 58 return nil, fmt.Errorf("line %d: failed to apply the property %#v: %w", 59 ml.currentLine, v, err) 60 } 61 case endOfFile: 62 break loop 63 } 64 } 65 if err := scanner.Err(); err != nil { 66 return nil, err 67 } 68 return ret, nil 69 } 70 71 type maintainersLexer struct { 72 scanner *bufio.Scanner 73 currentLine int 74 inComment bool 75 } 76 77 type recordTitle string 78 type recordProperty struct { 79 key string 80 value string 81 } 82 type endOfFile struct{} 83 84 var propertyRe = regexp.MustCompile(`^([[:alpha:]]):\s+(.*).*$`) 85 86 func (ml *maintainersLexer) next() interface{} { 87 for ml.scanner.Scan() { 88 ml.currentLine++ 89 rawLine := ml.scanner.Text() 90 line := strings.TrimSpace(rawLine) 91 if strings.HasPrefix(line, ".") { 92 ml.inComment = true 93 continue 94 } 95 // A comment continues to the next line(s) if they begin with a space character. 96 if ml.inComment && (line == "" || !unicode.IsSpace(rune(rawLine[0]))) { 97 ml.inComment = false 98 } 99 if ml.inComment || line == "" { 100 continue 101 } 102 // Now let's consider the possible line types. 103 if matches := propertyRe.FindStringSubmatch(line); matches != nil { 104 return recordProperty{key: matches[1], value: matches[2]} 105 } 106 return recordTitle(line) 107 } 108 return endOfFile{} 109 } 110 111 func applyProperty(record *maintainersRecord, property *recordProperty) error { 112 switch property.key { 113 case "F": 114 record.includePatterns = append(record.includePatterns, property.value) 115 case "X": 116 record.excludePatterns = append(record.excludePatterns, property.value) 117 case "N": 118 if _, err := regexp.Compile(property.value); err != nil { 119 return fmt.Errorf("invalid regexp: %s", property.value) 120 } 121 record.regexps = append(record.regexps, property.value) 122 case "M": 123 value, err := parseEmail(property.value) 124 if err != nil { 125 return err 126 } 127 record.maintainers = append(record.maintainers, value) 128 case "L": 129 value, err := parseEmail(property.value) 130 if err != nil { 131 return err 132 } 133 record.lists = append(record.lists, value) 134 case "T": 135 record.trees = append(record.trees, property.value) 136 } 137 return nil 138 } 139 140 func parseEmail(value string) (string, error) { 141 // Sometimes there happen extra symbols at the end of the line, 142 // let's make this parser more error tolerant. 143 if pos := strings.LastIndexAny(value, ">)"); pos >= 0 { 144 value = value[:pos+1] 145 } 146 // Let's also make the parser more robust by skipping everything before the first <, 147 // if it exists. 148 if pos := strings.LastIndexAny(value, "<"); pos >= 0 { 149 value = value[pos:] 150 } 151 addr, err := mail.ParseAddress(value) 152 if err != nil { 153 return "", err 154 } 155 return addr.Address, nil 156 } 157 158 func (r maintainersRecord) ToPathRule() subsystem.PathRule { 159 inclRe := strings.Builder{} 160 for i, wildcard := range r.includePatterns { 161 if i > 0 { 162 inclRe.WriteByte('|') 163 } 164 wildcardToRegexp(wildcard, &inclRe) 165 } 166 for _, rg := range r.regexps { 167 if inclRe.Len() > 0 { 168 inclRe.WriteByte('|') 169 } 170 inclRe.WriteString(rg) 171 } 172 exclRe := strings.Builder{} 173 for i, wildcard := range r.excludePatterns { 174 if i > 0 { 175 exclRe.WriteByte('|') 176 } 177 wildcardToRegexp(wildcard, &exclRe) 178 } 179 return subsystem.PathRule{ 180 IncludeRegexp: inclRe.String(), 181 ExcludeRegexp: exclRe.String(), 182 } 183 } 184 185 func removeMatchingPatterns(records []*maintainersRecord, rule *regexp.Regexp) { 186 filter := func(list []string) []string { 187 ret := []string{} 188 for _, item := range list { 189 if !rule.MatchString(item) { 190 ret = append(ret, item) 191 } 192 } 193 return ret 194 } 195 for _, record := range records { 196 record.includePatterns = filter(record.includePatterns) 197 record.excludePatterns = filter(record.excludePatterns) 198 } 199 } 200 201 var ( 202 escapedSeparator = regexp.QuoteMeta(fmt.Sprintf("%c", filepath.Separator)) 203 wildcardReplace = map[byte]string{ 204 '*': `[^` + escapedSeparator + `]*`, 205 '?': `.`, 206 '/': escapedSeparator, 207 } 208 ) 209 210 func wildcardToRegexp(wildcard string, store *strings.Builder) { 211 store.WriteByte('^') 212 213 // We diverge a bit from the standard MAINTAINERS rule semantics. 214 // path/* corresponds to the files belonging to the `path` folder, 215 // but, since we also infer the parent-child relationship, it's 216 // easier to make it cover the whole subtree. 217 if len(wildcard) >= 2 && wildcard[len(wildcard)-2:] == "/*" { 218 wildcard = wildcard[:len(wildcard)-1] 219 } 220 221 tokenStart := 0 222 for i, c := range wildcard { 223 replace, exists := wildcardReplace[byte(c)] 224 if !exists { 225 continue 226 } 227 store.WriteString(regexp.QuoteMeta(wildcard[tokenStart:i])) 228 store.WriteString(replace) 229 tokenStart = i + 1 230 } 231 if tokenStart < len(wildcard) { 232 store.WriteString(regexp.QuoteMeta(wildcard[tokenStart:])) 233 } 234 if wildcard == "" || wildcard[len(wildcard)-1] != '/' { 235 store.WriteByte('$') 236 } 237 }