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