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