|  | // Copyright (c) 2014, Google Inc. | 
|  | // | 
|  | // Permission to use, copy, modify, and/or distribute this software for any | 
|  | // purpose with or without fee is hereby granted, provided that the above | 
|  | // copyright notice and this permission notice appear in all copies. | 
|  | // | 
|  | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
|  | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
|  | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | 
|  | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
|  | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | 
|  | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | 
|  | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "bufio" | 
|  | "errors" | 
|  | "flag" | 
|  | "fmt" | 
|  | "io" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "sort" | 
|  | "strconv" | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | // ssl.h reserves values 1000 and above for error codes corresponding to | 
|  | // alerts. If automatically assigned reason codes exceed this value, this script | 
|  | // will error. This must be kept in sync with SSL_AD_REASON_OFFSET in ssl.h. | 
|  | const reservedReasonCode = 1000 | 
|  |  | 
|  | var resetFlag *bool = flag.Bool("reset", false, "If true, ignore current assignments and reassign from scratch") | 
|  |  | 
|  | type libraryInfo struct { | 
|  | sourceDirs []string | 
|  | headerName string | 
|  | } | 
|  |  | 
|  | func getLibraryInfo(lib string) libraryInfo { | 
|  | var info libraryInfo | 
|  | if lib == "ssl" { | 
|  | info.sourceDirs = []string{"ssl"} | 
|  | } else { | 
|  | info.sourceDirs = []string{ | 
|  | filepath.Join("crypto", lib), | 
|  | filepath.Join("crypto", lib+"_extra"), | 
|  | filepath.Join("crypto", "fipsmodule", lib), | 
|  | } | 
|  | } | 
|  | info.headerName = lib + ".h" | 
|  |  | 
|  | if lib == "evp" { | 
|  | info.headerName = "evp_errors.h" | 
|  | info.sourceDirs = append(info.sourceDirs, filepath.Join("crypto", "hpke")) | 
|  | } | 
|  |  | 
|  | return info | 
|  | } | 
|  |  | 
|  | func makeErrors(lib string, reset bool) error { | 
|  | topLevelPath, err := findToplevel() | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | info := getLibraryInfo(lib) | 
|  |  | 
|  | headerPath := filepath.Join(topLevelPath, "include", "openssl", info.headerName) | 
|  | errDir := filepath.Join(topLevelPath, "crypto", "err") | 
|  | dataPath := filepath.Join(errDir, lib+".errordata") | 
|  |  | 
|  | headerFile, err := os.Open(headerPath) | 
|  | if err != nil { | 
|  | if os.IsNotExist(err) { | 
|  | return fmt.Errorf("No header %s. Run in the right directory or touch the file.", headerPath) | 
|  | } | 
|  |  | 
|  | return err | 
|  | } | 
|  |  | 
|  | prefix := strings.ToUpper(lib) | 
|  | reasons, err := parseHeader(prefix, headerFile) | 
|  | headerFile.Close() | 
|  |  | 
|  | if reset { | 
|  | err = nil | 
|  | // Retain any reason codes above reservedReasonCode. | 
|  | newReasons := make(map[string]int) | 
|  | for key, value := range reasons { | 
|  | if value >= reservedReasonCode { | 
|  | newReasons[key] = value | 
|  | } | 
|  | } | 
|  | reasons = newReasons | 
|  | } | 
|  |  | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | for _, sourceDir := range info.sourceDirs { | 
|  | fullPath := filepath.Join(topLevelPath, sourceDir) | 
|  | dir, err := os.Open(fullPath) | 
|  | if err != nil { | 
|  | if os.IsNotExist(err) { | 
|  | // Some directories in the search path may not exist. | 
|  | continue | 
|  | } | 
|  | return err | 
|  | } | 
|  | defer dir.Close() | 
|  | filenames, err := dir.Readdirnames(-1) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | for _, name := range filenames { | 
|  | if !strings.HasSuffix(name, ".c") && !strings.HasSuffix(name, ".cc") { | 
|  | continue | 
|  | } | 
|  |  | 
|  | if err := addReasons(reasons, filepath.Join(fullPath, name), prefix); err != nil { | 
|  | return err | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | assignNewValues(reasons, reservedReasonCode) | 
|  |  | 
|  | headerFile, err = os.Open(headerPath) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | defer headerFile.Close() | 
|  |  | 
|  | newHeaderFile, err := os.OpenFile(headerPath+".tmp", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | defer newHeaderFile.Close() | 
|  |  | 
|  | if err := writeHeaderFile(newHeaderFile, headerFile, prefix, reasons); err != nil { | 
|  | return err | 
|  | } | 
|  | // Windows forbids renaming an open file. | 
|  | headerFile.Close() | 
|  | newHeaderFile.Close() | 
|  | if err := os.Rename(headerPath+".tmp", headerPath); err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | dataFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | outputStrings(dataFile, lib, reasons) | 
|  | dataFile.Close() | 
|  |  | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func findToplevel() (path string, err error) { | 
|  | path = "." | 
|  | buildingPath := filepath.Join(path, "BUILDING.md") | 
|  |  | 
|  | _, err = os.Stat(buildingPath) | 
|  | for i := 0; i < 2 && err != nil && os.IsNotExist(err); i++ { | 
|  | if i == 0 { | 
|  | path = ".." | 
|  | } else { | 
|  | path = filepath.Join("..", path) | 
|  | } | 
|  | buildingPath = filepath.Join(path, "BUILDING.md") | 
|  | _, err = os.Stat(buildingPath) | 
|  | } | 
|  | if err != nil { | 
|  | return "", errors.New("Cannot find BUILDING.md file at the top-level") | 
|  | } | 
|  | return path, nil | 
|  | } | 
|  |  | 
|  | type assignment struct { | 
|  | key   string | 
|  | value int | 
|  | } | 
|  |  | 
|  | type assignmentsSlice []assignment | 
|  |  | 
|  | func (a assignmentsSlice) Len() int { | 
|  | return len(a) | 
|  | } | 
|  |  | 
|  | func (a assignmentsSlice) Less(i, j int) bool { | 
|  | return a[i].value < a[j].value | 
|  | } | 
|  |  | 
|  | func (a assignmentsSlice) Swap(i, j int) { | 
|  | a[i], a[j] = a[j], a[i] | 
|  | } | 
|  |  | 
|  | func outputAssignments(w io.Writer, assignments map[string]int) { | 
|  | var sorted assignmentsSlice | 
|  |  | 
|  | for key, value := range assignments { | 
|  | sorted = append(sorted, assignment{key, value}) | 
|  | } | 
|  |  | 
|  | sort.Sort(sorted) | 
|  |  | 
|  | for _, assignment := range sorted { | 
|  | fmt.Fprintf(w, "#define %s %d\n", assignment.key, assignment.value) | 
|  | } | 
|  | } | 
|  |  | 
|  | func parseDefineLine(line, lib string) (key string, value int, ok bool) { | 
|  | if !strings.HasPrefix(line, "#define ") { | 
|  | return | 
|  | } | 
|  |  | 
|  | fields := strings.Fields(line) | 
|  | if len(fields) != 3 { | 
|  | return | 
|  | } | 
|  |  | 
|  | key = fields[1] | 
|  | if !strings.HasPrefix(key, lib+"_R_") { | 
|  | return | 
|  | } | 
|  |  | 
|  | var err error | 
|  | if value, err = strconv.Atoi(fields[2]); err != nil { | 
|  | return | 
|  | } | 
|  |  | 
|  | ok = true | 
|  | return | 
|  | } | 
|  |  | 
|  | func writeHeaderFile(w io.Writer, headerFile io.Reader, lib string, reasons map[string]int) error { | 
|  | var last []byte | 
|  | var haveLast, sawDefine bool | 
|  | newLine := []byte("\n") | 
|  |  | 
|  | scanner := bufio.NewScanner(headerFile) | 
|  | for scanner.Scan() { | 
|  | line := scanner.Text() | 
|  | _, _, ok := parseDefineLine(line, lib) | 
|  | if ok { | 
|  | sawDefine = true | 
|  | continue | 
|  | } | 
|  |  | 
|  | if haveLast { | 
|  | w.Write(last) | 
|  | w.Write(newLine) | 
|  | } | 
|  |  | 
|  | if len(line) > 0 || !sawDefine { | 
|  | last = []byte(line) | 
|  | haveLast = true | 
|  | } else { | 
|  | haveLast = false | 
|  | } | 
|  | sawDefine = false | 
|  | } | 
|  |  | 
|  | if err := scanner.Err(); err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | outputAssignments(w, reasons) | 
|  | w.Write(newLine) | 
|  |  | 
|  | if haveLast { | 
|  | w.Write(last) | 
|  | w.Write(newLine) | 
|  | } | 
|  |  | 
|  | return nil | 
|  | } | 
|  |  | 
|  | func outputStrings(w io.Writer, lib string, assignments map[string]int) { | 
|  | lib = strings.ToUpper(lib) | 
|  | prefixLen := len(lib + "_R_") | 
|  |  | 
|  | keys := make([]string, 0, len(assignments)) | 
|  | for key := range assignments { | 
|  | keys = append(keys, key) | 
|  | } | 
|  | sort.Strings(keys) | 
|  |  | 
|  | for _, key := range keys { | 
|  | fmt.Fprintf(w, "%s,%d,%s\n", lib, assignments[key], key[prefixLen:]) | 
|  | } | 
|  | } | 
|  |  | 
|  | func assignNewValues(assignments map[string]int, reserved int) { | 
|  | // Needs to be in sync with the reason limit in | 
|  | // |ERR_reason_error_string|. | 
|  | max := 99 | 
|  |  | 
|  | for _, value := range assignments { | 
|  | if reserved >= 0 && value >= reserved { | 
|  | continue | 
|  | } | 
|  | if value > max { | 
|  | max = value | 
|  | } | 
|  | } | 
|  |  | 
|  | max++ | 
|  |  | 
|  | // Sort the keys, so this script is reproducible. | 
|  | keys := make([]string, 0, len(assignments)) | 
|  | for key, value := range assignments { | 
|  | if value == -1 { | 
|  | keys = append(keys, key) | 
|  | } | 
|  | } | 
|  | sort.Strings(keys) | 
|  |  | 
|  | for _, key := range keys { | 
|  | if reserved >= 0 && max >= reserved { | 
|  | // If this happens, try passing -reset. Otherwise bump | 
|  | // up reservedReasonCode. | 
|  | panic("Automatically-assigned values exceeded limit!") | 
|  | } | 
|  | assignments[key] = max | 
|  | max++ | 
|  | } | 
|  | } | 
|  |  | 
|  | func handleDeclareMacro(line, join, macroName string, m map[string]int) { | 
|  | if i := strings.Index(line, macroName); i >= 0 { | 
|  | contents := line[i+len(macroName):] | 
|  | if i := strings.Index(contents, ")"); i >= 0 { | 
|  | contents = contents[:i] | 
|  | args := strings.Split(contents, ",") | 
|  | for i := range args { | 
|  | args[i] = strings.TrimSpace(args[i]) | 
|  | } | 
|  | if len(args) != 2 { | 
|  | panic("Bad macro line: " + line) | 
|  | } | 
|  | token := args[0] + join + args[1] | 
|  | if _, ok := m[token]; !ok { | 
|  | m[token] = -1 | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func addReasons(reasons map[string]int, filename, prefix string) error { | 
|  | file, err := os.Open(filename) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | defer file.Close() | 
|  |  | 
|  | reasonPrefix := prefix + "_R_" | 
|  |  | 
|  | scanner := bufio.NewScanner(file) | 
|  | for scanner.Scan() { | 
|  | line := scanner.Text() | 
|  |  | 
|  | handleDeclareMacro(line, "_R_", "OPENSSL_DECLARE_ERROR_REASON(", reasons) | 
|  |  | 
|  | for len(line) > 0 { | 
|  | i := strings.Index(line, prefix+"_") | 
|  | if i == -1 { | 
|  | break | 
|  | } | 
|  |  | 
|  | line = line[i:] | 
|  | end := strings.IndexFunc(line, func(r rune) bool { | 
|  | return !(r == '_' || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9')) | 
|  | }) | 
|  | if end == -1 { | 
|  | end = len(line) | 
|  | } | 
|  |  | 
|  | var token string | 
|  | token, line = line[:end], line[end:] | 
|  |  | 
|  | switch { | 
|  | case strings.HasPrefix(token, reasonPrefix): | 
|  | if _, ok := reasons[token]; !ok { | 
|  | reasons[token] = -1 | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | return scanner.Err() | 
|  | } | 
|  |  | 
|  | func parseHeader(lib string, file io.Reader) (reasons map[string]int, err error) { | 
|  | reasons = make(map[string]int) | 
|  |  | 
|  | scanner := bufio.NewScanner(file) | 
|  | for scanner.Scan() { | 
|  | key, value, ok := parseDefineLine(scanner.Text(), lib) | 
|  | if !ok { | 
|  | continue | 
|  | } | 
|  |  | 
|  | reasons[key] = value | 
|  | } | 
|  |  | 
|  | err = scanner.Err() | 
|  | return | 
|  | } | 
|  |  | 
|  | func main() { | 
|  | flag.Parse() | 
|  | if flag.NArg() == 0 { | 
|  | fmt.Fprintf(os.Stderr, "Usage: make_errors.go LIB [LIB2...]\n") | 
|  | os.Exit(1) | 
|  | } | 
|  |  | 
|  | for _, lib := range flag.Args() { | 
|  | if err := makeErrors(lib, *resetFlag); err != nil { | 
|  | fmt.Fprintf(os.Stderr, "Error generating errors for %q: %s\n", lib, err) | 
|  | os.Exit(1) | 
|  | } | 
|  | } | 
|  | } |