Home | History | Annotate | Download | only in util
      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 
     15 package main
     16 
     17 import (
     18 	"bufio"
     19 	"errors"
     20 	"flag"
     21 	"fmt"
     22 	"io"
     23 	"os"
     24 	"path/filepath"
     25 	"sort"
     26 	"strconv"
     27 	"strings"
     28 	"unicode"
     29 )
     30 
     31 // ssl.h reserves values 1000 and above for error codes corresponding to
     32 // alerts. If automatically assigned reason codes exceed this value, this script
     33 // will error. This must be kept in sync with SSL_AD_REASON_OFFSET in ssl.h.
     34 const reservedReasonCode = 1000
     35 
     36 var resetFlag *bool = flag.Bool("reset", false, "If true, ignore current assignments and reassign from scratch")
     37 
     38 func makeErrors(reset bool) error {
     39 	topLevelPath, err := findToplevel()
     40 	if err != nil {
     41 		return err
     42 	}
     43 
     44 	dirName, err := os.Getwd()
     45 	if err != nil {
     46 		return err
     47 	}
     48 
     49 	lib := filepath.Base(dirName)
     50 	headerPath := filepath.Join(topLevelPath, "include", "openssl", lib+".h")
     51 	errDir := filepath.Join(topLevelPath, "crypto", "err")
     52 	dataPath := filepath.Join(errDir, lib+".errordata")
     53 
     54 	headerFile, err := os.Open(headerPath)
     55 	if err != nil {
     56 		if os.IsNotExist(err) {
     57 			return fmt.Errorf("No header %s. Run in the right directory or touch the file.", headerPath)
     58 		}
     59 
     60 		return err
     61 	}
     62 
     63 	prefix := strings.ToUpper(lib)
     64 	functions, reasons, err := parseHeader(prefix, headerFile)
     65 	headerFile.Close()
     66 
     67 	if reset {
     68 		err = nil
     69 		functions = make(map[string]int)
     70 		// Retain any reason codes above reservedReasonCode.
     71 		newReasons := make(map[string]int)
     72 		for key, value := range reasons {
     73 			if value >= reservedReasonCode {
     74 				newReasons[key] = value
     75 			}
     76 		}
     77 		reasons = newReasons
     78 	}
     79 
     80 	if err != nil {
     81 		return err
     82 	}
     83 
     84 	dir, err := os.Open(".")
     85 	if err != nil {
     86 		return err
     87 	}
     88 	defer dir.Close()
     89 
     90 	filenames, err := dir.Readdirnames(-1)
     91 	if err != nil {
     92 		return err
     93 	}
     94 
     95 	for _, name := range filenames {
     96 		if !strings.HasSuffix(name, ".c") {
     97 			continue
     98 		}
     99 
    100 		if err := addFunctionsAndReasons(functions, reasons, name, prefix); err != nil {
    101 			return err
    102 		}
    103 	}
    104 
    105 	assignNewValues(functions, -1)
    106 	assignNewValues(reasons, reservedReasonCode)
    107 
    108 	headerFile, err = os.Open(headerPath)
    109 	if err != nil {
    110 		return err
    111 	}
    112 	defer headerFile.Close()
    113 
    114 	newHeaderFile, err := os.OpenFile(headerPath+".tmp", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
    115 	if err != nil {
    116 		return err
    117 	}
    118 	defer newHeaderFile.Close()
    119 
    120 	if err := writeHeaderFile(newHeaderFile, headerFile, prefix, functions, reasons); err != nil {
    121 		return err
    122 	}
    123 	os.Rename(headerPath+".tmp", headerPath)
    124 
    125 	dataFile, err := os.OpenFile(dataPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
    126 	if err != nil {
    127 		return err
    128 	}
    129 
    130 	outputStrings(dataFile, lib, typeFunctions, functions)
    131 	outputStrings(dataFile, lib, typeReasons, reasons)
    132 	dataFile.Close()
    133 
    134 	return nil
    135 }
    136 
    137 func findToplevel() (path string, err error) {
    138 	path = ".."
    139 	buildingPath := filepath.Join(path, "BUILDING")
    140 
    141 	_, err = os.Stat(buildingPath)
    142 	if err != nil && os.IsNotExist(err) {
    143 		path = filepath.Join("..", path)
    144 		buildingPath = filepath.Join(path, "BUILDING")
    145 		_, err = os.Stat(buildingPath)
    146 	}
    147 	if err != nil {
    148 		return "", errors.New("Cannot find BUILDING file at the top-level")
    149 	}
    150 	return path, nil
    151 }
    152 
    153 type assignment struct {
    154 	key   string
    155 	value int
    156 }
    157 
    158 type assignmentsSlice []assignment
    159 
    160 func (a assignmentsSlice) Len() int {
    161 	return len(a)
    162 }
    163 
    164 func (a assignmentsSlice) Less(i, j int) bool {
    165 	return a[i].value < a[j].value
    166 }
    167 
    168 func (a assignmentsSlice) Swap(i, j int) {
    169 	a[i], a[j] = a[j], a[i]
    170 }
    171 
    172 func outputAssignments(w io.Writer, assignments map[string]int) {
    173 	var sorted assignmentsSlice
    174 
    175 	for key, value := range assignments {
    176 		sorted = append(sorted, assignment{key, value})
    177 	}
    178 
    179 	sort.Sort(sorted)
    180 
    181 	for _, assignment := range sorted {
    182 		fmt.Fprintf(w, "#define %s %d\n", assignment.key, assignment.value)
    183 	}
    184 }
    185 
    186 func parseDefineLine(line, lib string) (typ int, key string, value int, ok bool) {
    187 	if !strings.HasPrefix(line, "#define ") {
    188 		return
    189 	}
    190 
    191 	fields := strings.Fields(line)
    192 	if len(fields) != 3 {
    193 		return
    194 	}
    195 
    196 	funcPrefix := lib + "_F_"
    197 	reasonPrefix := lib + "_R_"
    198 
    199 	key = fields[1]
    200 	switch {
    201 	case strings.HasPrefix(key, funcPrefix):
    202 		typ = typeFunctions
    203 	case strings.HasPrefix(key, reasonPrefix):
    204 		typ = typeReasons
    205 	default:
    206 		return
    207 	}
    208 
    209 	var err error
    210 	if value, err = strconv.Atoi(fields[2]); err != nil {
    211 		return
    212 	}
    213 
    214 	ok = true
    215 	return
    216 }
    217 
    218 func writeHeaderFile(w io.Writer, headerFile io.Reader, lib string, functions, reasons map[string]int) error {
    219 	var last []byte
    220 	var haveLast, sawDefine bool
    221 	newLine := []byte("\n")
    222 
    223 	scanner := bufio.NewScanner(headerFile)
    224 	for scanner.Scan() {
    225 		line := scanner.Text()
    226 		_, _, _, ok := parseDefineLine(line, lib)
    227 		if ok {
    228 			sawDefine = true
    229 			continue
    230 		}
    231 
    232 		if haveLast {
    233 			w.Write(last)
    234 			w.Write(newLine)
    235 		}
    236 
    237 		if len(line) > 0 || !sawDefine {
    238 			last = []byte(line)
    239 			haveLast = true
    240 		} else {
    241 			haveLast = false
    242 		}
    243 		sawDefine = false
    244 	}
    245 
    246 	if err := scanner.Err(); err != nil {
    247 		return err
    248 	}
    249 
    250 	outputAssignments(w, functions)
    251 	outputAssignments(w, reasons)
    252 	w.Write(newLine)
    253 
    254 	if haveLast {
    255 		w.Write(last)
    256 		w.Write(newLine)
    257 	}
    258 
    259 	return nil
    260 }
    261 
    262 const (
    263 	typeFunctions = iota
    264 	typeReasons
    265 )
    266 
    267 func outputStrings(w io.Writer, lib string, ty int, assignments map[string]int) {
    268 	lib = strings.ToUpper(lib)
    269 	prefixLen := len(lib + "_F_")
    270 
    271 	keys := make([]string, 0, len(assignments))
    272 	for key := range assignments {
    273 		keys = append(keys, key)
    274 	}
    275 	sort.Strings(keys)
    276 
    277 	for _, key := range keys {
    278 		typeString := "function"
    279 		if ty == typeReasons {
    280 			typeString = "reason"
    281 		}
    282 		fmt.Fprintf(w, "%s,%s,%d,%s\n", lib, typeString, assignments[key], key[prefixLen:])
    283 	}
    284 }
    285 
    286 func assignNewValues(assignments map[string]int, reserved int) {
    287 	// Needs to be in sync with the reason limit in
    288 	// |ERR_reason_error_string|.
    289 	max := 99
    290 
    291 	for _, value := range assignments {
    292 		if reserved >= 0 && value >= reserved {
    293 			continue
    294 		}
    295 		if value > max {
    296 			max = value
    297 		}
    298 	}
    299 
    300 	max++
    301 
    302 	// Sort the keys, so this script is reproducible.
    303 	keys := make([]string, 0, len(assignments))
    304 	for key, value := range assignments {
    305 		if value == -1 {
    306 			keys = append(keys, key)
    307 		}
    308 	}
    309 	sort.Strings(keys)
    310 
    311 	for _, key := range keys {
    312 		if reserved >= 0 && max >= reserved {
    313 			// If this happens, try passing -reset. Otherwise bump
    314 			// up reservedReasonCode.
    315 			panic("Automatically-assigned values exceeded limit!")
    316 		}
    317 		assignments[key] = max
    318 		max++
    319 	}
    320 }
    321 
    322 func handleDeclareMacro(line, join, macroName string, m map[string]int) {
    323 	if i := strings.Index(line, macroName); i >= 0 {
    324 		contents := line[i+len(macroName):]
    325 		if i := strings.Index(contents, ")"); i >= 0 {
    326 			contents = contents[:i]
    327 			args := strings.Split(contents, ",")
    328 			for i := range args {
    329 				args[i] = strings.TrimSpace(args[i])
    330 			}
    331 			if len(args) != 2 {
    332 				panic("Bad macro line: " + line)
    333 			}
    334 			token := args[0] + join + args[1]
    335 			if _, ok := m[token]; !ok {
    336 				m[token] = -1
    337 			}
    338 		}
    339 	}
    340 }
    341 
    342 func addFunctionsAndReasons(functions, reasons map[string]int, filename, prefix string) error {
    343 	file, err := os.Open(filename)
    344 	if err != nil {
    345 		return err
    346 	}
    347 	defer file.Close()
    348 
    349 	reasonPrefix := prefix + "_R_"
    350 	var currentFunction string
    351 
    352 	scanner := bufio.NewScanner(file)
    353 	for scanner.Scan() {
    354 		line := scanner.Text()
    355 
    356 		if len(line) > 0 && unicode.IsLetter(rune(line[0])) {
    357 			/* Function start */
    358 			fields := strings.Fields(line)
    359 			for _, field := range fields {
    360 				if i := strings.Index(field, "("); i != -1 {
    361 					f := field[:i]
    362 					// The return type of some functions is
    363 					// a macro that contains a "(".
    364 					if f == "STACK_OF" {
    365 						continue
    366 					}
    367 					currentFunction = f
    368 					for len(currentFunction) > 0 && currentFunction[0] == '*' {
    369 						currentFunction = currentFunction[1:]
    370 					}
    371 					break
    372 				}
    373 			}
    374 		}
    375 
    376 		// Do not include cross-module error lines.
    377 		if strings.Contains(line, "OPENSSL_PUT_ERROR(" + prefix + ",") {
    378 			functionToken := prefix + "_F_" + currentFunction
    379 			if _, ok := functions[functionToken]; !ok {
    380 				functions[functionToken] = -1
    381 			}
    382 		}
    383 
    384 		handleDeclareMacro(line, "_R_", "OPENSSL_DECLARE_ERROR_REASON(", reasons)
    385 		handleDeclareMacro(line, "_F_", "OPENSSL_DECLARE_ERROR_FUNCTION(", functions)
    386 
    387 		for len(line) > 0 {
    388 			i := strings.Index(line, prefix + "_")
    389 			if i == -1 {
    390 				break
    391 			}
    392 
    393 			line = line[i:]
    394 			end := strings.IndexFunc(line, func(r rune) bool {
    395 				return !(r == '_' || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9'))
    396 			})
    397 			if end == -1 {
    398 				end = len(line)
    399 			}
    400 
    401 			var token string
    402 			token, line = line[:end], line[end:]
    403 
    404 			switch {
    405 			case strings.HasPrefix(token, reasonPrefix):
    406 				if _, ok := reasons[token]; !ok {
    407 					reasons[token] = -1
    408 				}
    409 			}
    410 		}
    411 	}
    412 
    413 	return scanner.Err()
    414 }
    415 
    416 func parseHeader(lib string, file io.Reader) (functions, reasons map[string]int, err error) {
    417 	functions = make(map[string]int)
    418 	reasons = make(map[string]int)
    419 
    420 	scanner := bufio.NewScanner(file)
    421 	for scanner.Scan() {
    422 		typ, key, value, ok := parseDefineLine(scanner.Text(), lib)
    423 		if !ok {
    424 			continue
    425 		}
    426 
    427 		switch typ {
    428 		case typeFunctions:
    429 			functions[key] = value
    430 		case typeReasons:
    431 			reasons[key] = value
    432 		default:
    433 			panic("internal error")
    434 		}
    435 	}
    436 
    437 	err = scanner.Err()
    438 	return
    439 }
    440 
    441 func main() {
    442 	flag.Parse()
    443 
    444 	if err := makeErrors(*resetFlag); err != nil {
    445 		fmt.Fprintf(os.Stderr, "%s\n", err)
    446 		os.Exit(1)
    447 	}
    448 }
    449