1// Copyright (c) 2014, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15package main 16 17import ( 18 "bufio" 19 "errors" 20 "flag" 21 "fmt" 22 "io" 23 "os" 24 "path/filepath" 25 "sort" 26 "strconv" 27 "strings" 28) 29 30// ssl.h reserves values 1000 and above for error codes corresponding to 31// alerts. If automatically assigned reason codes exceed this value, this script 32// will error. This must be kept in sync with SSL_AD_REASON_OFFSET in ssl.h. 33const reservedReasonCode = 1000 34 35var resetFlag *bool = flag.Bool("reset", false, "If true, ignore current assignments and reassign from scratch") 36 37type libraryInfo struct { 38 sourceDirs []string 39 headerName string 40} 41 42func getLibraryInfo(lib string) libraryInfo { 43 var info libraryInfo 44 if lib == "ssl" { 45 info.sourceDirs = []string{"ssl"} 46 } else { 47 info.sourceDirs = []string{ 48 filepath.Join("crypto", lib), 49 filepath.Join("crypto", lib+"_extra"), 50 filepath.Join("crypto", "fipsmodule", lib), 51 } 52 } 53 info.headerName = lib + ".h" 54 55 if lib == "evp" { 56 info.headerName = "evp_errors.h" 57 info.sourceDirs = append(info.sourceDirs, filepath.Join("crypto", "hpke")) 58 } 59 60 return info 61} 62 63func makeErrors(lib string, reset bool) error { 64 topLevelPath, err := findToplevel() 65 if err != nil { 66 return err 67 } 68 69 info := getLibraryInfo(lib) 70 71 headerPath := filepath.Join(topLevelPath, "include", "openssl", info.headerName) 72 errDir := filepath.Join(topLevelPath, "crypto", "err") 73 dataPath := filepath.Join(errDir, lib+".errordata") 74 75 headerFile, err := os.Open(headerPath) 76 if err != nil { 77 if os.IsNotExist(err) { 78 return fmt.Errorf("No header %s. Run in the right directory or touch the file.", headerPath) 79 } 80 81 return err 82 } 83 84 prefix := strings.ToUpper(lib) 85 reasons, err := parseHeader(prefix, headerFile) 86 headerFile.Close() 87 88 if reset { 89 err = nil 90 // Retain any reason codes above reservedReasonCode. 91 newReasons := make(map[string]int) 92 for key, value := range reasons { 93 if value >= reservedReasonCode { 94 newReasons[key] = value 95 } 96 } 97 reasons = newReasons 98 } 99 100 if err != nil { 101 return err 102 } 103 104 for _, sourceDir := range info.sourceDirs { 105 fullPath := filepath.Join(topLevelPath, sourceDir) 106 dir, err := os.Open(fullPath) 107 if err != nil { 108 if os.IsNotExist(err) { 109 // Some directories in the search path may not exist. 110 continue 111 } 112 return err 113 } 114 defer dir.Close() 115 filenames, err := dir.Readdirnames(-1) 116 if err != nil { 117 return err 118 } 119 120 for _, name := range filenames { 121 if !strings.HasSuffix(name, ".c") && !strings.HasSuffix(name, ".cc") { 122 continue 123 } 124 125 if err := addReasons(reasons, filepath.Join(fullPath, name), prefix); err != nil { 126 return err 127 } 128 } 129 } 130 131 assignNewValues(reasons, reservedReasonCode) 132 133 headerFile, err = os.Open(headerPath) 134 if err != nil { 135 return err 136 } 137 defer headerFile.Close() 138 139 newHeaderFile, err := os.OpenFile(headerPath+".tmp", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) 140 if err != nil { 141 return err 142 } 143 defer newHeaderFile.Close() 144 145 if err := writeHeaderFile(newHeaderFile, headerFile, prefix, reasons); err != nil { 146 return err 147 } 148 // Windows forbids renaming an open file. 149 headerFile.Close() 150 newHeaderFile.Close() 151 if err := os.Rename(headerPath+".tmp", headerPath); err != nil { 152 return err 153 } 154 155 dataFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 156 if err != nil { 157 return err 158 } 159 160 outputStrings(dataFile, lib, reasons) 161 dataFile.Close() 162 163 return nil 164} 165 166func findToplevel() (path string, err error) { 167 path = "." 168 buildingPath := filepath.Join(path, "BUILDING.md") 169 170 _, err = os.Stat(buildingPath) 171 for i := 0; i < 2 && err != nil && os.IsNotExist(err); i++ { 172 if i == 0 { 173 path = ".." 174 } else { 175 path = filepath.Join("..", path) 176 } 177 buildingPath = filepath.Join(path, "BUILDING.md") 178 _, err = os.Stat(buildingPath) 179 } 180 if err != nil { 181 return "", errors.New("Cannot find BUILDING.md file at the top-level") 182 } 183 return path, nil 184} 185 186type assignment struct { 187 key string 188 value int 189} 190 191type assignmentsSlice []assignment 192 193func (a assignmentsSlice) Len() int { 194 return len(a) 195} 196 197func (a assignmentsSlice) Less(i, j int) bool { 198 return a[i].value < a[j].value 199} 200 201func (a assignmentsSlice) Swap(i, j int) { 202 a[i], a[j] = a[j], a[i] 203} 204 205func outputAssignments(w io.Writer, assignments map[string]int) { 206 var sorted assignmentsSlice 207 208 for key, value := range assignments { 209 sorted = append(sorted, assignment{key, value}) 210 } 211 212 sort.Sort(sorted) 213 214 for _, assignment := range sorted { 215 fmt.Fprintf(w, "#define %s %d\n", assignment.key, assignment.value) 216 } 217} 218 219func parseDefineLine(line, lib string) (key string, value int, ok bool) { 220 if !strings.HasPrefix(line, "#define ") { 221 return 222 } 223 224 fields := strings.Fields(line) 225 if len(fields) != 3 { 226 return 227 } 228 229 key = fields[1] 230 if !strings.HasPrefix(key, lib+"_R_") { 231 return 232 } 233 234 var err error 235 if value, err = strconv.Atoi(fields[2]); err != nil { 236 return 237 } 238 239 ok = true 240 return 241} 242 243func writeHeaderFile(w io.Writer, headerFile io.Reader, lib string, reasons map[string]int) error { 244 var last []byte 245 var haveLast, sawDefine bool 246 newLine := []byte("\n") 247 248 scanner := bufio.NewScanner(headerFile) 249 for scanner.Scan() { 250 line := scanner.Text() 251 _, _, ok := parseDefineLine(line, lib) 252 if ok { 253 sawDefine = true 254 continue 255 } 256 257 if haveLast { 258 w.Write(last) 259 w.Write(newLine) 260 } 261 262 if len(line) > 0 || !sawDefine { 263 last = []byte(line) 264 haveLast = true 265 } else { 266 haveLast = false 267 } 268 sawDefine = false 269 } 270 271 if err := scanner.Err(); err != nil { 272 return err 273 } 274 275 outputAssignments(w, reasons) 276 w.Write(newLine) 277 278 if haveLast { 279 w.Write(last) 280 w.Write(newLine) 281 } 282 283 return nil 284} 285 286func outputStrings(w io.Writer, lib string, assignments map[string]int) { 287 lib = strings.ToUpper(lib) 288 prefixLen := len(lib + "_R_") 289 290 keys := make([]string, 0, len(assignments)) 291 for key := range assignments { 292 keys = append(keys, key) 293 } 294 sort.Strings(keys) 295 296 for _, key := range keys { 297 fmt.Fprintf(w, "%s,%d,%s\n", lib, assignments[key], key[prefixLen:]) 298 } 299} 300 301func assignNewValues(assignments map[string]int, reserved int) { 302 // Needs to be in sync with the reason limit in 303 // |ERR_reason_error_string|. 304 max := 99 305 306 for _, value := range assignments { 307 if reserved >= 0 && value >= reserved { 308 continue 309 } 310 if value > max { 311 max = value 312 } 313 } 314 315 max++ 316 317 // Sort the keys, so this script is reproducible. 318 keys := make([]string, 0, len(assignments)) 319 for key, value := range assignments { 320 if value == -1 { 321 keys = append(keys, key) 322 } 323 } 324 sort.Strings(keys) 325 326 for _, key := range keys { 327 if reserved >= 0 && max >= reserved { 328 // If this happens, try passing -reset. Otherwise bump 329 // up reservedReasonCode. 330 panic("Automatically-assigned values exceeded limit!") 331 } 332 assignments[key] = max 333 max++ 334 } 335} 336 337func handleDeclareMacro(line, join, macroName string, m map[string]int) { 338 if i := strings.Index(line, macroName); i >= 0 { 339 contents := line[i+len(macroName):] 340 if i := strings.Index(contents, ")"); i >= 0 { 341 contents = contents[:i] 342 args := strings.Split(contents, ",") 343 for i := range args { 344 args[i] = strings.TrimSpace(args[i]) 345 } 346 if len(args) != 2 { 347 panic("Bad macro line: " + line) 348 } 349 token := args[0] + join + args[1] 350 if _, ok := m[token]; !ok { 351 m[token] = -1 352 } 353 } 354 } 355} 356 357func addReasons(reasons map[string]int, filename, prefix string) error { 358 file, err := os.Open(filename) 359 if err != nil { 360 return err 361 } 362 defer file.Close() 363 364 reasonPrefix := prefix + "_R_" 365 366 scanner := bufio.NewScanner(file) 367 for scanner.Scan() { 368 line := scanner.Text() 369 370 handleDeclareMacro(line, "_R_", "OPENSSL_DECLARE_ERROR_REASON(", reasons) 371 372 for len(line) > 0 { 373 i := strings.Index(line, prefix+"_") 374 if i == -1 { 375 break 376 } 377 378 line = line[i:] 379 end := strings.IndexFunc(line, func(r rune) bool { 380 return !(r == '_' || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')) 381 }) 382 if end == -1 { 383 end = len(line) 384 } 385 386 var token string 387 token, line = line[:end], line[end:] 388 389 switch { 390 case strings.HasPrefix(token, reasonPrefix): 391 if _, ok := reasons[token]; !ok { 392 reasons[token] = -1 393 } 394 } 395 } 396 } 397 398 return scanner.Err() 399} 400 401func parseHeader(lib string, file io.Reader) (reasons map[string]int, err error) { 402 reasons = make(map[string]int) 403 404 scanner := bufio.NewScanner(file) 405 for scanner.Scan() { 406 key, value, ok := parseDefineLine(scanner.Text(), lib) 407 if !ok { 408 continue 409 } 410 411 reasons[key] = value 412 } 413 414 err = scanner.Err() 415 return 416} 417 418func main() { 419 flag.Parse() 420 if flag.NArg() == 0 { 421 fmt.Fprintf(os.Stderr, "Usage: make_errors.go LIB [LIB2...]\n") 422 os.Exit(1) 423 } 424 425 for _, lib := range flag.Args() { 426 if err := makeErrors(lib, *resetFlag); err != nil { 427 fmt.Fprintf(os.Stderr, "Error generating errors for %q: %s\n", lib, err) 428 os.Exit(1) 429 } 430 } 431} 432