1#! /usr/bin/env perl 2# Copyright 1999-2024 The OpenSSL Project Authors. All Rights Reserved. 3# 4# Licensed under the Apache License 2.0 (the "License"). You may not use 5# this file except in compliance with the License. You can obtain a copy 6# in the file LICENSE in the source distribution or at 7# https://www.openssl.org/source/license.html 8 9use strict; 10use warnings; 11 12use File::Basename; 13use File::Spec::Functions qw(abs2rel rel2abs); 14 15use lib "."; 16use configdata; 17 18my $config = "crypto/err/openssl.ec"; 19my $debug = 0; 20my $internal = 0; 21my $nowrite = 0; 22my $rebuild = 0; 23my $reindex = 0; 24my $static = 0; 25my $unref = 0; 26my %modules = (); 27 28my $errors = 0; 29my @t = localtime(); 30my $YEAR = $t[5] + 1900; 31 32sub phase 33{ 34 my $text = uc(shift); 35 print STDERR "\n---\n$text\n" if $debug; 36} 37 38sub help 39{ 40 print STDERR <<"EOF"; 41mkerr.pl [options] [files...] 42 43Options: 44 45 -conf FILE Use the named config file FILE instead of the default. 46 47 -debug Verbose output debugging on stderr. 48 49 -internal Generate code that is to be built as part of OpenSSL itself. 50 Also scans internal list of files. 51 52 -module M Only useful with -internal! 53 Only write files for library module M. Whether files are 54 actually written or not depends on other options, such as 55 -rebuild. 56 Note: this option is cumulative. If not given at all, all 57 internal modules will be considered. 58 59 -nowrite Do not write the header/source files, even if changed. 60 61 -rebuild Rebuild all header and C source files, even if there 62 were no changes. 63 64 -reindex Ignore previously assigned values (except for R records in 65 the config file) and renumber everything starting at 100. 66 67 -static Make the load/unload functions static. 68 69 -unref List all unreferenced function and reason codes on stderr; 70 implies -nowrite. 71 72 -help Show this help text. 73 74 ... Additional arguments are added to the file list to scan, 75 if '-internal' was NOT specified on the command line. 76 77EOF 78} 79 80while ( @ARGV ) { 81 my $arg = $ARGV[0]; 82 last unless $arg =~ /-.*/; 83 $arg = $1 if $arg =~ /-(-.*)/; 84 if ( $arg eq "-conf" ) { 85 $config = $ARGV[1]; 86 shift @ARGV; 87 } elsif ( $arg eq "-debug" ) { 88 $debug = 1; 89 $unref = 1; 90 } elsif ( $arg eq "-internal" ) { 91 $internal = 1; 92 } elsif ( $arg eq "-nowrite" ) { 93 $nowrite = 1; 94 } elsif ( $arg eq "-rebuild" ) { 95 $rebuild = 1; 96 } elsif ( $arg eq "-reindex" ) { 97 $reindex = 1; 98 } elsif ( $arg eq "-static" ) { 99 $static = 1; 100 } elsif ( $arg eq "-unref" ) { 101 $unref = 1; 102 $nowrite = 1; 103 } elsif ( $arg eq "-module" ) { 104 shift @ARGV; 105 $modules{uc $ARGV[0]} = 1; 106 } elsif ( $arg =~ /-*h(elp)?/ ) { 107 &help(); 108 exit; 109 } elsif ( $arg =~ /-.*/ ) { 110 die "Unknown option $arg; use -h for help.\n"; 111 } 112 shift @ARGV; 113} 114 115my @source; 116if ( $internal ) { 117 die "Cannot mix -internal and -static\n" if $static; 118 die "Extra parameters given.\n" if @ARGV; 119 @source = ( glob('crypto/*.c'), glob('crypto/*/*.c'), 120 glob('ssl/*.c'), glob('ssl/*/*.c'), glob('ssl/*/*/*.c'), 121 glob('providers/*.c'), glob('providers/*/*.c'), 122 glob('providers/*/*/*.c') ); 123} else { 124 die "-module isn't useful without -internal\n" if scalar keys %modules > 0; 125 @source = @ARGV; 126} 127 128# Data parsed out of the config and state files. 129my %hpubinc; # lib -> public header 130my %libpubinc; # public header -> lib 131my %hprivinc; # lib -> private header 132my %libprivinc; # private header -> lib 133my %cskip; # error_file -> lib 134my %errorfile; # lib -> error file name 135my %rmax; # lib -> max assigned reason code 136my %rassigned; # lib -> colon-separated list of assigned reason codes 137my %rnew; # lib -> count of new reason codes 138my %rextra; # "extra" reason code -> lib 139my %rcodes; # reason-name -> value 140my $statefile; # state file with assigned reason and function codes 141my %strings; # define -> text 142 143# Read and parse the config file 144open(IN, "$config") || die "Can't open config file $config, $!,"; 145while ( <IN> ) { 146 next if /^#/ || /^$/; 147 if ( /^L\s+(\S+)\s+(\S+)\s+(\S+)(?:\s+(\S+))?\s+$/ ) { 148 my $lib = $1; 149 my $pubhdr = $2; 150 my $err = $3; 151 my $privhdr = $4 // 'NONE'; 152 $hpubinc{$lib} = $pubhdr; 153 $libpubinc{$pubhdr} = $lib; 154 $hprivinc{$lib} = $privhdr; 155 $libprivinc{$privhdr} = $lib; 156 $cskip{$err} = $lib; 157 $errorfile{$lib} = $err; 158 next if $err eq 'NONE'; 159 $rmax{$lib} = 100; 160 $rassigned{$lib} = ":"; 161 $rnew{$lib} = 0; 162 die "Public header file must be in include/openssl ($pubhdr is not)\n" 163 if ($internal 164 && $pubhdr ne 'NONE' 165 && $pubhdr !~ m|^include/openssl/|); 166 die "Private header file may only be specified with -internal ($privhdr given)\n" 167 unless ($privhdr eq 'NONE' || $internal); 168 } elsif ( /^R\s+(\S+)\s+(\S+)/ ) { 169 $rextra{$1} = $2; 170 $rcodes{$1} = $2; 171 } elsif ( /^S\s+(\S+)/ ) { 172 $statefile = $1; 173 } else { 174 die "Illegal config line $_\n"; 175 } 176} 177close IN; 178 179if ( ! $statefile ) { 180 $statefile = $config; 181 $statefile =~ s/.ec/.txt/; 182} 183 184# The statefile has all the previous assignments. 185&phase("Reading state"); 186my $skippedstate = 0; 187if ( ! $reindex && $statefile ) { 188 open(STATE, "<$statefile") || die "Can't open $statefile, $!"; 189 190 # Scan function and reason codes and store them: keep a note of the 191 # maximum code used. 192 while ( <STATE> ) { 193 next if /^#/ || /^$/; 194 my $name; 195 my $code; 196 if ( /^(.+):(\d+):\\$/ ) { 197 $name = $1; 198 $code = $2; 199 my $next = <STATE>; 200 $next =~ s/^\s*(.*)\s*$/$1/; 201 die "Duplicate define $name" if exists $strings{$name}; 202 $strings{$name} = $next; 203 } elsif ( /^(\S+):(\d+):(.*)$/ ) { 204 $name = $1; 205 $code = $2; 206 die "Duplicate define $name" if exists $strings{$name}; 207 $strings{$name} = $3; 208 } else { 209 die "Bad line in $statefile:\n$_\n"; 210 } 211 my $lib = $name; 212 $lib =~ s/^((?:OSSL_|OPENSSL_)?[^_]{2,}).*$/$1/; 213 $lib = "SSL" if $lib =~ /TLS/; 214 if ( !defined $errorfile{$lib} ) { 215 print "Skipping $_"; 216 $skippedstate++; 217 next; 218 } 219 next if $errorfile{$lib} eq 'NONE'; 220 if ( $name =~ /^(?:OSSL_|OPENSSL_)?[A-Z0-9]{2,}_R_/ ) { 221 die "$lib reason code $code collision at $name\n" 222 if $rassigned{$lib} =~ /:$code:/; 223 $rassigned{$lib} .= "$code:"; 224 if ( !exists $rextra{$name} ) { 225 $rmax{$lib} = $code if $code > $rmax{$lib}; 226 } 227 $rcodes{$name} = $code; 228 } elsif ( $name =~ /^(?:OSSL_|OPENSSL_)?[A-Z0-9]{2,}_F_/ ) { 229 # We do nothing with the function codes, just let them go away 230 } else { 231 die "Bad line in $statefile:\n$_\n"; 232 } 233 } 234 close(STATE); 235 236 if ( $debug ) { 237 foreach my $lib ( sort keys %rmax ) { 238 print STDERR "Reason codes for ${lib}:\n"; 239 if ( $rassigned{$lib} =~ m/^:(.*):$/ ) { 240 my @rassigned = sort { $a <=> $b } split( ":", $1 ); 241 print STDERR " ", join(' ', @rassigned), "\n"; 242 } else { 243 print STDERR " --none--\n"; 244 } 245 } 246 } 247} 248 249# Scan each C source file and look for reason codes. This is done by 250# looking for strings that "look like" reason codes: basically anything 251# consisting of all uppercase and numerics which _R_ in it and which has 252# the name of an error library at the start. Should there be anything else, 253# such as a type name, we add exceptions here. 254# If a code doesn't exist in list compiled from headers then mark it 255# with the value "X" as a place holder to give it a value later. 256# Store all reason codes found in and %usedreasons so all those unreferenced 257# can be printed out. 258&phase("Scanning source"); 259my %usedreasons; 260foreach my $file ( @source ) { 261 # Don't parse the error source file. 262 next if exists $cskip{$file}; 263 open( IN, "<$file" ) || die "Can't open $file, $!,"; 264 my $func; 265 my $linenr = 0; 266 print STDERR "$file:\n" if $debug; 267 while ( <IN> ) { 268 269 # skip obsoleted source files entirely! 270 last if /^#error\s+obsolete/; 271 $linenr++; 272 273 if ( /(((?:OSSL_|OPENSSL_)?[A-Z0-9]{2,})_R_[A-Z0-9_]+)/ ) { 274 next unless exists $errorfile{$2}; 275 next if $errorfile{$2} eq 'NONE'; 276 $usedreasons{$1} = 1; 277 if ( !exists $rcodes{$1} ) { 278 print STDERR " New reason $1\n" if $debug; 279 $rcodes{$1} = "X"; 280 $rnew{$2}++; 281 } 282 print STDERR " Reason $1 = $rcodes{$1}\n" if $debug; 283 } 284 } 285 close IN; 286} 287print STDERR "\n" if $debug; 288 289# Now process each library in turn. 290&phase("Writing files"); 291my $newstate = 0; 292foreach my $lib ( keys %errorfile ) { 293 next if ! $rnew{$lib} && ! $rebuild; 294 next if scalar keys %modules > 0 && !$modules{$lib}; 295 next if $nowrite; 296 print STDERR "$lib: $rnew{$lib} new reasons\n" if $rnew{$lib}; 297 $newstate = 1; 298 299 # If we get here then we have some new error codes so we 300 # need to rebuild the header file and C file. 301 302 # Make a sorted list of error and reason codes for later use. 303 my @reasons = sort grep( /^${lib}_/, keys %rcodes ); 304 305 # indent level for innermost preprocessor lines 306 my $indent = " "; 307 308 # Flag if the sub-library is disablable 309 # There are a few exceptions, where disabling the sub-library 310 # doesn't actually remove the whole sub-library, but rather implements 311 # it with a NULL backend. 312 my $disablable = 313 ($lib ne "SSL" && $lib ne "ASYNC" && $lib ne "DSO" 314 && (grep { $lib eq uc $_ } @disablables, @disablables_int)); 315 316 # Rewrite the internal header file if there is one ($internal only!) 317 318 if ($hprivinc{$lib} ne 'NONE') { 319 my $hfile = $hprivinc{$lib}; 320 my $guard = $hfile; 321 322 if ($guard =~ m|^include/|) { 323 $guard = $'; 324 } else { 325 $guard = basename($guard); 326 } 327 $guard = "OSSL_" . join('_', split(m|[./]|, uc $guard)); 328 329 open( OUT, ">$hfile" ) || die "Can't write to $hfile, $!,"; 330 print OUT <<"EOF"; 331/* 332 * Generated by util/mkerr.pl DO NOT EDIT 333 * Copyright 2020-$YEAR The OpenSSL Project Authors. All Rights Reserved. 334 * 335 * Licensed under the Apache License 2.0 (the \"License\"). You may not use 336 * this file except in compliance with the License. You can obtain a copy 337 * in the file LICENSE in the source distribution or at 338 * https://www.openssl.org/source/license.html 339 */ 340 341#ifndef $guard 342# define $guard 343# pragma once 344 345# include <openssl/opensslconf.h> 346# include <openssl/symhacks.h> 347 348# ifdef __cplusplus 349extern \"C\" { 350# endif 351 352EOF 353 $indent = ' '; 354 if ($disablable) { 355 print OUT <<"EOF"; 356# ifndef OPENSSL_NO_${lib} 357 358EOF 359 $indent = " "; 360 } 361 print OUT <<"EOF"; 362int ossl_err_load_${lib}_strings(void); 363EOF 364 365 # If this library doesn't have a public header file, we write all 366 # definitions that would end up there here instead 367 if ($hpubinc{$lib} eq 'NONE') { 368 print OUT "\n/*\n * $lib reason codes.\n */\n"; 369 foreach my $i ( @reasons ) { 370 my $z = 48 - length($i); 371 $z = 0 if $z < 0; 372 if ( $rcodes{$i} eq "X" ) { 373 $rassigned{$lib} =~ m/^:([^:]*):/; 374 my $findcode = $1; 375 $findcode = $rmax{$lib} if !defined $findcode; 376 while ( $rassigned{$lib} =~ m/:$findcode:/ ) { 377 $findcode++; 378 } 379 $rcodes{$i} = $findcode; 380 $rassigned{$lib} .= "$findcode:"; 381 print STDERR "New Reason code $i\n" if $debug; 382 } 383 printf OUT "#${indent}define $i%s $rcodes{$i}\n", " " x $z; 384 } 385 print OUT "\n"; 386 } 387 388 # This doesn't go all the way down to zero, to allow for the ending 389 # brace for 'extern "C" {'. 390 while (length($indent) > 1) { 391 $indent = substr $indent, 0, -1; 392 print OUT "#${indent}endif\n"; 393 } 394 395 print OUT <<"EOF"; 396 397# ifdef __cplusplus 398} 399# endif 400#endif 401EOF 402 close OUT; 403 } 404 405 # Rewrite the public header file 406 407 if ($hpubinc{$lib} ne 'NONE') { 408 my $extra_include = 409 $internal 410 ? ($lib ne 'SSL' 411 ? "# include <openssl/cryptoerr_legacy.h>\n" 412 : "# include <openssl/sslerr_legacy.h>\n") 413 : ''; 414 my $hfile = $hpubinc{$lib}; 415 my $guard = $hfile; 416 $guard =~ s|^include/||; 417 $guard = join('_', split(m|[./]|, uc $guard)); 418 $guard = "OSSL_" . $guard unless $internal; 419 420 open( OUT, ">$hfile" ) || die "Can't write to $hfile, $!,"; 421 print OUT <<"EOF"; 422/* 423 * Generated by util/mkerr.pl DO NOT EDIT 424 * Copyright 1995-$YEAR The OpenSSL Project Authors. All Rights Reserved. 425 * 426 * Licensed under the Apache License 2.0 (the \"License\"). You may not use 427 * this file except in compliance with the License. You can obtain a copy 428 * in the file LICENSE in the source distribution or at 429 * https://www.openssl.org/source/license.html 430 */ 431 432#ifndef $guard 433# define $guard 434# pragma once 435 436# include <openssl/opensslconf.h> 437# include <openssl/symhacks.h> 438$extra_include 439 440EOF 441 $indent = ' '; 442 if ( $internal ) { 443 if ($disablable) { 444 print OUT <<"EOF"; 445# ifndef OPENSSL_NO_${lib} 446 447EOF 448 $indent .= ' '; 449 } 450 } else { 451 print OUT <<"EOF"; 452# define ${lib}err(f, r) ERR_${lib}_error(0, (r), OPENSSL_FILE, OPENSSL_LINE) 453# define ERR_R_${lib}_LIB ERR_${lib}_lib() 454 455EOF 456 if ( ! $static ) { 457 print OUT <<"EOF"; 458 459# ifdef __cplusplus 460extern \"C\" { 461# endif 462int ERR_load_${lib}_strings(void); 463void ERR_unload_${lib}_strings(void); 464void ERR_${lib}_error(int function, int reason, const char *file, int line); 465# ifdef __cplusplus 466} 467# endif 468EOF 469 } 470 } 471 472 print OUT "\n/*\n * $lib reason codes.\n */\n"; 473 foreach my $i ( @reasons ) { 474 my $z = 48 - length($i); 475 $z = 0 if $z < 0; 476 if ( $rcodes{$i} eq "X" ) { 477 $rassigned{$lib} =~ m/^:([^:]*):/; 478 my $findcode = $1; 479 $findcode = $rmax{$lib} if !defined $findcode; 480 while ( $rassigned{$lib} =~ m/:$findcode:/ ) { 481 $findcode++; 482 } 483 $rcodes{$i} = $findcode; 484 $rassigned{$lib} .= "$findcode:"; 485 print STDERR "New Reason code $i\n" if $debug; 486 } 487 printf OUT "#${indent}define $i%s $rcodes{$i}\n", " " x $z; 488 } 489 print OUT "\n"; 490 491 while (length($indent) > 0) { 492 $indent = substr $indent, 0, -1; 493 print OUT "#${indent}endif\n"; 494 } 495 close OUT; 496 } 497 498 # Rewrite the C source file containing the error details. 499 500 if ($errorfile{$lib} ne 'NONE') { 501 # First, read any existing reason string definitions: 502 my $cfile = $errorfile{$lib}; 503 my $pack_lib = $internal ? "ERR_LIB_${lib}" : "0"; 504 my $hpubincf = $hpubinc{$lib}; 505 my $hprivincf = $hprivinc{$lib}; 506 my $includes = ''; 507 if ($internal) { 508 if ($hpubincf ne 'NONE') { 509 $hpubincf =~ s|^include/||; 510 $includes .= "#include <${hpubincf}>\n"; 511 } 512 if ($hprivincf =~ m|^include/|) { 513 $hprivincf = $'; 514 } else { 515 $hprivincf = abs2rel(rel2abs($hprivincf), 516 rel2abs(dirname($cfile))); 517 } 518 $includes .= "#include \"${hprivincf}\"\n"; 519 } else { 520 $includes .= "#include \"${hpubincf}\"\n"; 521 } 522 523 open( OUT, ">$cfile" ) 524 || die "Can't open $cfile for writing, $!, stopped"; 525 526 my $const = $internal ? 'const ' : ''; 527 528 print OUT <<"EOF"; 529/* 530 * Generated by util/mkerr.pl DO NOT EDIT 531 * Copyright 1995-$YEAR The OpenSSL Project Authors. All Rights Reserved. 532 * 533 * Licensed under the Apache License 2.0 (the "License"). You may not use 534 * this file except in compliance with the License. You can obtain a copy 535 * in the file LICENSE in the source distribution or at 536 * https://www.openssl.org/source/license.html 537 */ 538 539#include <openssl/err.h> 540$includes 541EOF 542 $indent = ''; 543 if ( $internal ) { 544 if ($disablable) { 545 print OUT <<"EOF"; 546#ifndef OPENSSL_NO_${lib} 547 548EOF 549 $indent .= ' '; 550 } 551 } 552 print OUT <<"EOF"; 553#${indent}ifndef OPENSSL_NO_ERR 554 555static ${const}ERR_STRING_DATA ${lib}_str_reasons[] = { 556EOF 557 558 # Add each reason code. 559 foreach my $i ( @reasons ) { 560 my $rn; 561 if ( exists $strings{$i} ) { 562 $rn = $strings{$i}; 563 $rn = "" if $rn eq '*'; 564 } else { 565 $i =~ /^${lib}_R_(\S+)$/; 566 $rn = $1; 567 $rn =~ tr/_[A-Z]/ [a-z]/; 568 $strings{$i} = $rn; 569 } 570 my $lines; 571 $lines = " {ERR_PACK($pack_lib, 0, $i), \"$rn\"},"; 572 $lines = " {ERR_PACK($pack_lib, 0, $i),\n \"$rn\"}," 573 if length($lines) > 80; 574 print OUT "$lines\n"; 575 } 576 print OUT <<"EOF"; 577 {0, NULL} 578}; 579 580#${indent}endif 581EOF 582 if ( $internal ) { 583 print OUT <<"EOF"; 584 585int ossl_err_load_${lib}_strings(void) 586{ 587#${indent}ifndef OPENSSL_NO_ERR 588 if (ERR_reason_error_string(${lib}_str_reasons[0].error) == NULL) 589 ERR_load_strings_const(${lib}_str_reasons); 590#${indent}endif 591 return 1; 592} 593EOF 594 } else { 595 my $st = $static ? "static " : ""; 596 print OUT <<"EOF"; 597 598static int lib_code = 0; 599static int error_loaded = 0; 600 601${st}int ERR_load_${lib}_strings(void) 602{ 603 if (lib_code == 0) 604 lib_code = ERR_get_next_error_library(); 605 606 if (!error_loaded) { 607#ifndef OPENSSL_NO_ERR 608 ERR_load_strings(lib_code, ${lib}_str_reasons); 609#endif 610 error_loaded = 1; 611 } 612 return 1; 613} 614 615${st}void ERR_unload_${lib}_strings(void) 616{ 617 if (error_loaded) { 618#ifndef OPENSSL_NO_ERR 619 ERR_unload_strings(lib_code, ${lib}_str_reasons); 620#endif 621 error_loaded = 0; 622 } 623} 624 625${st}void ERR_${lib}_error(int function, int reason, const char *file, int line) 626{ 627 if (lib_code == 0) 628 lib_code = ERR_get_next_error_library(); 629 ERR_raise(lib_code, reason); 630 ERR_set_debug(file, line, NULL); 631} 632 633${st}int ERR_${lib}_lib(void) 634{ 635 if (lib_code == 0) 636 lib_code = ERR_get_next_error_library(); 637 return lib_code; 638} 639EOF 640 641 } 642 643 while (length($indent) > 1) { 644 $indent = substr $indent, 0, -1; 645 print OUT "#${indent}endif\n"; 646 } 647 if ($internal && $disablable) { 648 print OUT <<"EOF"; 649#else 650NON_EMPTY_TRANSLATION_UNIT 651#endif 652EOF 653 } 654 close OUT; 655 } 656} 657 658&phase("Ending"); 659# Make a list of unreferenced reason codes 660if ( $unref ) { 661 my @runref; 662 foreach ( keys %rcodes ) { 663 push( @runref, $_ ) unless exists $usedreasons{$_}; 664 } 665 if ( @runref ) { 666 print STDERR "The following reason codes were not referenced:\n"; 667 foreach ( sort @runref ) { 668 print STDERR " $_\n"; 669 } 670 } 671} 672 673die "Found $errors errors, quitting" if $errors; 674 675# Update the state file 676if ( $newstate ) { 677 open(OUT, ">$statefile.new") 678 || die "Can't write $statefile.new, $!"; 679 print OUT <<"EOF"; 680# Copyright 1999-$YEAR The OpenSSL Project Authors. All Rights Reserved. 681# 682# Licensed under the Apache License 2.0 (the "License"). You may not use 683# this file except in compliance with the License. You can obtain a copy 684# in the file LICENSE in the source distribution or at 685# https://www.openssl.org/source/license.html 686EOF 687 print OUT "\n#Reason codes\n"; 688 foreach my $i ( sort keys %rcodes ) { 689 my $short = "$i:$rcodes{$i}:"; 690 my $t = exists $strings{$i} ? "$strings{$i}" : ""; 691 $t = "\\\n\t" . $t if length($short) + length($t) > 80; 692 print OUT "$short$t\n"; 693 } 694 close(OUT); 695 if ( $skippedstate ) { 696 print "Skipped state, leaving update in $statefile.new"; 697 } else { 698 rename "$statefile", "$statefile.old" 699 || die "Can't backup $statefile to $statefile.old, $!"; 700 rename "$statefile.new", "$statefile" 701 || die "Can't rename $statefile to $statefile.new, $!"; 702 } 703} 704 705exit; 706