• 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
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