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