• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
37func 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
133func 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
149type assignment struct {
150	key   string
151	value int
152}
153
154type assignmentsSlice []assignment
155
156func (a assignmentsSlice) Len() int {
157	return len(a)
158}
159
160func (a assignmentsSlice) Less(i, j int) bool {
161	return a[i].value < a[j].value
162}
163
164func (a assignmentsSlice) Swap(i, j int) {
165	a[i], a[j] = a[j], a[i]
166}
167
168func 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
182func 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
206func 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
249func 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
264func 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
300func 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
320func 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
364func 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
381func 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