1#! /usr/bin/env perl 2# Copyright 2018-2023 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 9package OpenSSL::Ordinals; 10 11use strict; 12use warnings; 13use Carp; 14use Scalar::Util qw(blessed); 15use OpenSSL::Util; 16 17use constant { 18 # "magic" filters, see the filters at the end of the file 19 F_NAME => 1, 20 F_NUMBER => 2, 21}; 22 23=head1 NAME 24 25OpenSSL::Ordinals - a private module to read and walk through ordinals 26 27=head1 SYNOPSIS 28 29 use OpenSSL::Ordinals; 30 31 my $ordinals = OpenSSL::Ordinals->new(from => "foo.num"); 32 # or alternatively 33 my $ordinals = OpenSSL::Ordinals->new(); 34 $ordinals->load("foo.num"); 35 36 foreach ($ordinals->items(comparator => by_name()) { 37 print $_->name(), "\n"; 38 } 39 40=head1 DESCRIPTION 41 42This is a OpenSSL private module to load an ordinals (F<.num>) file and 43write out the data you want, sorted and filtered according to your rules. 44 45An ordinals file is a file that enumerates all the symbols that a shared 46library or loadable module must export. Each of them have a unique 47assigned number as well as other attributes to indicate if they only exist 48on a subset of the supported platforms, or if they are specific to certain 49features. 50 51The unique numbers each symbol gets assigned needs to be maintained for a 52shared library or module to stay compatible with previous versions on 53platforms that maintain a transfer vector indexed by position rather than 54by name. They also help keep information on certain symbols that are 55aliases for others for certain platforms, or that have different forms 56on different platforms. 57 58=head2 Main methods 59 60=over 4 61 62=cut 63 64=item B<new> I<%options> 65 66Creates a new instance of the C<OpenSSL::Ordinals> class. It takes options 67in keyed pair form, i.e. a series of C<< key => value >> pairs. Available 68options are: 69 70=over 4 71 72=item B<< from => FILENAME >> 73 74Not only create a new instance, but immediately load it with data from the 75ordinals file FILENAME. 76 77=back 78 79=cut 80 81sub new { 82 my $class = shift; 83 my %opts = @_; 84 85 my $instance = { 86 filename => undef, # File name registered when loading 87 loaded_maxnum => 0, # Highest allocated item number when loading 88 loaded_contents => [], # Loaded items, if loading there was 89 maxassigned => 0, # Current highest assigned item number 90 maxnum => 0, # Current highest allocated item number 91 contents => [], # Items, indexed by number 92 name2num => {}, # Name to number dictionary 93 aliases => {}, # Aliases cache. 94 stats => {}, # Statistics, see 'sub validate' 95 debug => $opts{debug}, 96 }; 97 bless $instance, $class; 98 99 $instance->set_version($opts{version}); 100 $instance->load($opts{from}) if defined($opts{from}); 101 102 return $instance; 103} 104 105=item B<< $ordinals->load FILENAME >> 106 107Loads the data from FILENAME into the instance. Any previously loaded data 108is dropped. 109 110Two internal databases are created. One database is simply a copy of the file 111contents and is treated as read-only. The other database is an exact copy of 112the first, but is treated as a work database, i.e. it can be modified and added 113to. 114 115=cut 116 117sub load { 118 my $self = shift; 119 my $filename = shift; 120 121 croak "Undefined filename" unless defined($filename); 122 123 my @tmp_contents = (); 124 my %tmp_name2num = (); 125 my $max_assigned = 0; 126 my $max_num = 0; 127 open F, '<', $filename or croak "Unable to open $filename"; 128 while (<F>) { 129 s|\R$||; # Better chomp 130 s|#.*||; 131 next if /^\s*$/; 132 133 my $item = OpenSSL::Ordinals::Item->new(source => $filename, from => $_); 134 135 my $num = $item->number(); 136 if ($num eq '?') { 137 $num = ++$max_num; 138 } elsif ($num eq '?+') { 139 $num = $max_num; 140 } else { 141 croak "Disordered ordinals, number sequence restarted" 142 if $max_num > $max_assigned && $num < $max_num; 143 croak "Disordered ordinals, $num < $max_num" 144 if $num < $max_num; 145 $max_assigned = $max_num = $num; 146 } 147 148 $item->intnum($num); 149 push @{$tmp_contents[$num]}, $item; 150 $tmp_name2num{$item->name()} = $num; 151 } 152 close F; 153 154 $self->{contents} = [ @tmp_contents ]; 155 $self->{name2num} = { %tmp_name2num }; 156 $self->{maxassigned} = $max_assigned; 157 $self->{maxnum} = $max_num; 158 $self->{filename} = $filename; 159 160 # Make a deep copy, allowing {contents} to be an independent work array 161 foreach my $i (1..$max_num) { 162 if ($tmp_contents[$i]) { 163 $self->{loaded_contents}->[$i] = 164 [ map { OpenSSL::Ordinals::Item->new($_) } 165 @{$tmp_contents[$i]} ]; 166 } 167 } 168 $self->{loaded_maxnum} = $max_num; 169 return 1; 170} 171 172=item B<< $ordinals->renumber >> 173 174Renumber any item that doesn't have an assigned number yet. 175 176=cut 177 178sub renumber { 179 my $self = shift; 180 181 my $max_assigned = 0; 182 foreach ($self->items(sort => by_number())) { 183 $_->number($_->intnum()) if $_->number() =~ m|^\?|; 184 if ($max_assigned < $_->number()) { 185 $max_assigned = $_->number(); 186 } 187 } 188 $self->{maxassigned} = $max_assigned; 189} 190 191=item B<< $ordinals->rewrite >> 192 193=item B<< $ordinals->rewrite >>, I<%options> 194 195If an ordinals file has been loaded, it gets rewritten with the data from 196the current work database. 197 198If there are more arguments, they are used as I<%options> with the 199same semantics as for B<< $ordinals->items >> described below, apart 200from B<sort>, which is forbidden here. 201 202=cut 203 204sub rewrite { 205 my $self = shift; 206 my %opts = @_; 207 208 $self->write($self->{filename}, %opts); 209} 210 211=item B<< $ordinals->write FILENAME >> 212 213=item B<< $ordinals->write FILENAME >>, I<%options> 214 215Writes the current work database data to the ordinals file FILENAME. 216This also validates the data, see B<< $ordinals->validate >> below. 217 218If there are more arguments, they are used as I<%options> with the 219same semantics as for B<< $ordinals->items >> described next, apart 220from B<sort>, which is forbidden here. 221 222=cut 223 224sub write { 225 my $self = shift; 226 my $filename = shift; 227 my %opts = @_; 228 229 croak "Undefined filename" unless defined($filename); 230 croak "The 'sort' option is not allowed" if $opts{sort}; 231 232 $self->validate(); 233 234 open F, '>', $filename or croak "Unable to open $filename"; 235 foreach ($self->items(%opts, sort => by_number())) { 236 print F $_->to_string(),"\n"; 237 } 238 close F; 239 $self->{filename} = $filename; 240 $self->{loaded_maxnum} = $self->{maxnum}; 241 return 1; 242} 243 244=item B<< $ordinals->items >> I<%options> 245 246Returns a list of items according to a set of criteria. The criteria is 247given in form keyed pair form, i.e. a series of C<< key => value >> pairs. 248Available options are: 249 250=over 4 251 252=item B<< sort => SORTFUNCTION >> 253 254SORTFUNCTION is a reference to a function that takes two arguments, which 255correspond to the classic C<$a> and C<$b> that are available in a C<sort> 256block. 257 258=item B<< filter => FILTERFUNCTION >> 259 260FILTERFUNCTION is a reference to a function that takes one argument, which 261is every OpenSSL::Ordinals::Item element available. 262 263=back 264 265=cut 266 267sub items { 268 my $self = shift; 269 my %opts = @_; 270 271 my $comparator = $opts{sort}; 272 my $filter = $opts{filter} // sub { 1; }; 273 274 my @l = undef; 275 if (ref($filter) eq 'ARRAY') { 276 # run a "magic" filter 277 if ($filter->[0] == F_NUMBER) { 278 my $index = $filter->[1]; 279 @l = $index ? @{$self->{contents}->[$index] // []} : (); 280 } elsif ($filter->[0] == F_NAME) { 281 my $index = $self->{name2num}->{$filter->[1]}; 282 @l = $index ? @{$self->{contents}->[$index] // []} : (); 283 } else { 284 croak __PACKAGE__."->items called with invalid filter"; 285 } 286 } elsif (ref($filter) eq 'CODE') { 287 @l = grep { $filter->($_) } 288 map { @{$_ // []} } 289 @{$self->{contents}}; 290 } else { 291 croak __PACKAGE__."->items called with invalid filter"; 292 } 293 294 return sort { $comparator->($a, $b); } @l 295 if (defined $comparator); 296 return @l; 297} 298 299# Put an array of items back into the object after having checked consistency 300# If there are exactly two items: 301# - They MUST have the same number 302# - They MUST have the same version 303# - For platforms, both MUST hold the same ones, but with opposite values 304# - For features, both MUST hold the same ones. 305# - They MUST NOT have identical name, type, numeral, version, platforms, and features 306# If there's just one item, just put it in the slot of its number 307# In all other cases, something is wrong 308sub _putback { 309 my $self = shift; 310 my @items = @_; 311 312 if (scalar @items < 1 || scalar @items > 2) { 313 croak "Wrong number of items: ", scalar @items, "\n ", 314 join("\n ", map { $_->{source}.": ".$_->name() } @items), "\n"; 315 } 316 if (scalar @items == 2) { 317 # Collect some data 318 my %numbers = (); 319 my %versions = (); 320 my %features = (); 321 foreach (@items) { 322 $numbers{$_->intnum()} = 1; 323 $versions{$_->version()} = 1; 324 foreach ($_->features()) { 325 $features{$_}++; 326 } 327 } 328 329 # Check that all items we're trying to put back have the same number 330 croak "Items don't have the same numeral: ", 331 join(", ", map { $_->name()." => ".$_->intnum() } @items), "\n" 332 if (scalar keys %numbers > 1); 333 croak "Items don't have the same version: ", 334 join(", ", map { $_->name()." => ".$_->version() } @items), "\n" 335 if (scalar keys %versions > 1); 336 337 # Check that both items run with the same features 338 foreach (@items) { 339 } 340 foreach (keys %features) { 341 delete $features{$_} if $features{$_} == 2; 342 } 343 croak "Features not in common between ", 344 $items[0]->name(), " and ", $items[1]->name(), ":", 345 join(", ", sort keys %features), "\n" 346 if %features; 347 348 # Check for in addition identical name, type, and platforms 349 croak "Duplicate entries for ".$items[0]->name()." from ". 350 $items[0]->source()." and ".$items[1]->source()."\n" 351 if $items[0]->name() eq $items[1]->name() 352 && $items[0]->type() eq $items[1]->type() 353 && $items[0]->platforms() eq $items[1]->platforms(); 354 355 # Check that all platforms exist in both items, and have opposite values 356 my @platforms = ( { $items[0]->platforms() }, 357 { $items[1]->platforms() } ); 358 foreach my $platform (keys %{$platforms[0]}) { 359 if (exists $platforms[1]->{$platform}) { 360 if ($platforms[0]->{$platform} != !$platforms[1]->{$platform}) { 361 croak "Platforms aren't opposite: ", 362 join(", ", 363 map { my %tmp_h = $_->platforms(); 364 $_->name().":".$platform 365 ." => " 366 .$tmp_h{$platform} } @items), 367 "\n"; 368 } 369 370 # We're done with these 371 delete $platforms[0]->{$platform}; 372 delete $platforms[1]->{$platform}; 373 } 374 } 375 # If there are any remaining platforms, something's wrong 376 if (%{$platforms[0]} || %{$platforms[0]}) { 377 croak "There are platforms not in common between ", 378 $items[0]->name(), " and ", $items[1]->name(), "\n"; 379 } 380 } 381 $self->{contents}->[$items[0]->intnum()] = [ @items ]; 382} 383 384sub _parse_platforms { 385 my $self = shift; 386 my @defs = @_; 387 388 my %platforms = (); 389 foreach (@defs) { 390 m{^(!)?}; 391 my $op = !(defined $1 && $1 eq '!'); 392 my $def = $'; 393 394 if ($def =~ m{^_?WIN32$}) { $platforms{$&} = $op; } 395 if ($def =~ m{^__FreeBSD__$}) { $platforms{$&} = $op; } 396# For future support 397# if ($def =~ m{^__DragonFly__$}) { $platforms{$&} = $op; } 398# if ($def =~ m{^__OpenBSD__$}) { $platforms{$&} = $op; } 399# if ($def =~ m{^__NetBSD__$}) { $platforms{$&} = $op; } 400 if ($def =~ m{^OPENSSL_SYS_}) { $platforms{$'} = $op; } 401 } 402 403 return %platforms; 404} 405 406sub _parse_features { 407 my $self = shift; 408 my @defs = @_; 409 410 my %features = (); 411 foreach (@defs) { 412 m{^(!)?}; 413 my $op = !(defined $1 && $1 eq '!'); 414 my $def = $'; 415 416 if ($def =~ m{^ZLIB$}) { $features{$&} = $op; } 417 if ($def =~ m{^OPENSSL_USE_}) { $features{$'} = $op; } 418 if ($def =~ m{^OPENSSL_NO_}) { $features{$'} = !$op; } 419 } 420 421 return %features; 422} 423 424sub _adjust_version { 425 my $self = shift; 426 my $version = shift; 427 my $baseversion = $self->{baseversion}; 428 429 $version = $baseversion 430 if ($baseversion ne '*' && $version ne '*' 431 && cmp_versions($baseversion, $version) > 0); 432 433 return $version; 434} 435 436=item B<< $ordinals->add SOURCE, NAME, TYPE, LIST >> 437 438Adds a new item from file SOURCE named NAME with the type TYPE, 439and a set of C macros in 440LIST that are expected to be defined or undefined to use this symbol, if 441any. For undefined macros, they each must be prefixed with a C<!>. 442 443If this symbol already exists in loaded data, it will be rewritten using 444the new input data, but will keep the same ordinal number and version. 445If it's entirely new, it will get a '?' and the current default version. 446 447=cut 448 449sub add { 450 my $self = shift; 451 my $source = shift; # file where item was defined 452 my $name = shift; 453 my $type = shift; # FUNCTION or VARIABLE 454 my @defs = @_; # Macros from #ifdef and #ifndef 455 # (the latter prefixed with a '!') 456 457 # call signature for debug output 458 my $verbsig = "add('$name' , '$type' , [ " . join(', ', @defs) . " ])"; 459 460 croak __PACKAGE__."->add got a bad type '$type'" 461 unless $type eq 'FUNCTION' || $type eq 'VARIABLE'; 462 463 my %platforms = _parse_platforms(@defs); 464 my %features = _parse_features(@defs); 465 466 my @items = $self->items(filter => f_name($name)); 467 my $version = @items ? $items[0]->version() : $self->{currversion}; 468 my $intnum = @items ? $items[0]->intnum() : ++$self->{maxnum}; 469 my $number = @items ? $items[0]->number() : '?'; 470 print STDERR "DEBUG[",__PACKAGE__,":add] $verbsig\n", 471 @items ? map { "\t".$_->to_string()."\n" } @items : "No previous items\n", 472 if $self->{debug}; 473 @items = grep { $_->exists() } @items; 474 475 my $new_item = 476 OpenSSL::Ordinals::Item->new( source => $source, 477 name => $name, 478 type => $type, 479 number => $number, 480 intnum => $intnum, 481 version => 482 $self->_adjust_version($version), 483 exists => 1, 484 platforms => { %platforms }, 485 features => [ 486 grep { $features{$_} } keys %features 487 ] ); 488 489 push @items, $new_item; 490 print STDERR "DEBUG[",__PACKAGE__,"::add] $verbsig\n", map { "\t".$_->to_string()."\n" } @items 491 if $self->{debug}; 492 $self->_putback(@items); 493 494 # If an alias was defined beforehand, add an item for it now 495 my $alias = $self->{aliases}->{$name}; 496 delete $self->{aliases}->{$name}; 497 498 # For the caller to show 499 my @returns = ( $new_item ); 500 push @returns, $self->add_alias($source, $alias->{name}, $name, @{$alias->{defs}}) 501 if defined $alias; 502 return @returns; 503} 504 505=item B<< $ordinals->add_alias SOURCE, ALIAS, NAME, LIST >> 506 507Adds an alias ALIAS for the symbol NAME from file SOURCE, and a set of C macros 508in LIST that are expected to be defined or undefined to use this symbol, if any. 509For undefined macros, they each must be prefixed with a C<!>. 510 511If this symbol already exists in loaded data, it will be rewritten using 512the new input data. Otherwise, the data will just be store away, to wait 513that the symbol NAME shows up. 514 515=cut 516 517sub add_alias { 518 my $self = shift; 519 my $source = shift; 520 my $alias = shift; # This is the alias being added 521 my $name = shift; # For this name (assuming it exists) 522 my @defs = @_; # Platform attributes for the alias 523 524 # call signature for debug output 525 my $verbsig = 526 "add_alias('$source' , '$alias' , '$name' , [ " . join(', ', @defs) . " ])"; 527 528 croak "You're kidding me... $alias == $name" if $alias eq $name; 529 530 my %platforms = _parse_platforms(@defs); 531 my %features = _parse_features(@defs); 532 533 croak "Alias with associated features is forbidden\n" 534 if %features; 535 536 my $f_byalias = f_name($alias); 537 my $f_byname = f_name($name); 538 my @items = $self->items(filter => $f_byalias); 539 foreach my $item ($self->items(filter => $f_byname)) { 540 push @items, $item unless grep { $_ == $item } @items; 541 } 542 @items = grep { $_->exists() } @items; 543 544 croak "Alias already exists ($alias => $name)" 545 if scalar @items > 1; 546 if (scalar @items == 0) { 547 # The item we want to alias for doesn't exist yet, so we cache the 548 # alias and hope the item we're making an alias of shows up later 549 $self->{aliases}->{$name} = { source => $source, 550 name => $alias, defs => [ @defs ] }; 551 552 print STDERR "DEBUG[",__PACKAGE__,":add_alias] $verbsig\n", 553 "\tSet future alias $alias => $name\n" 554 if $self->{debug}; 555 return (); 556 } elsif (scalar @items == 1) { 557 # The rule is that an alias is more or less a copy of the original 558 # item, just with another name. Also, the platforms given here are 559 # given to the original item as well, with opposite values. 560 my %alias_platforms = $items[0]->platforms(); 561 foreach (keys %platforms) { 562 $alias_platforms{$_} = !$platforms{$_}; 563 } 564 # We supposedly do now know how to do this... *ahem* 565 $items[0]->{platforms} = { %alias_platforms }; 566 567 my $number = 568 $items[0]->number() =~ m|^\?| ? '?+' : $items[0]->number(); 569 my $alias_item = OpenSSL::Ordinals::Item->new( 570 source => $source, 571 name => $alias, 572 type => $items[0]->type(), 573 number => $number, 574 intnum => $items[0]->intnum(), 575 version => $self->_adjust_version($items[0]->version()), 576 exists => $items[0]->exists(), 577 platforms => { %platforms }, 578 features => [ $items[0]->features() ] 579 ); 580 push @items, $alias_item; 581 582 print STDERR "DEBUG[",__PACKAGE__,":add_alias] $verbsig\n", 583 map { "\t".$_->to_string()."\n" } @items 584 if $self->{debug}; 585 $self->_putback(@items); 586 587 # For the caller to show 588 return ( $alias_item->to_string() ); 589 } 590 croak "$name has an alias already (trying to add alias $alias)\n", 591 "\t", join(", ", map { $_->name() } @items), "\n"; 592} 593 594=item B<< $ordinals->set_version VERSION >> 595 596=item B<< $ordinals->set_version VERSION BASEVERSION >> 597 598Sets the default version for new symbol to VERSION. 599 600If given, BASEVERSION sets the base version, i.e. the minimum version 601for all symbols. If not given, it will be calculated as follows: 602 603=over 4 604 605If the given version is '*', then the base version will also be '*'. 606 607If the given version starts with '0.', the base version will be '0.0.0'. 608 609If the given version starts with '1.0.', the base version will be '1.0.0'. 610 611If the given version starts with '1.1.', the base version will be '1.1.0'. 612 613If the given version has a first number C<N> that's greater than 1, the 614base version will be formed from C<N>: 'N.0.0'. 615 616=back 617 618=cut 619 620sub set_version { 621 my $self = shift; 622 # '*' is for "we don't care" 623 my $version = shift // '*'; 624 my $baseversion = shift // '*'; 625 626 if ($baseversion eq '*') { 627 $baseversion = $version; 628 if ($baseversion ne '*') { 629 if ($baseversion =~ m|^(\d+)\.|, $1 > 1) { 630 $baseversion = "$1.0.0"; 631 } else { 632 $baseversion =~ s|^0\..*$|0.0.0|; 633 $baseversion =~ s|^1\.0\..*$|1.0.0|; 634 $baseversion =~ s|^1\.1\..*$|1.1.0|; 635 636 die 'Invalid version' 637 if ($baseversion ne '0.0.0' 638 && $baseversion !~ m|^1\.[01]\.0$|); 639 } 640 } 641 } 642 643 die 'Invalid base version' 644 if ($baseversion ne '*' && $version ne '*' 645 && cmp_versions($baseversion, $version) > 0); 646 647 $self->{currversion} = $version; 648 $self->{baseversion} = $baseversion; 649 foreach ($self->items(filter => sub { $_[0] eq '*' })) { 650 $_->{version} = $self->{currversion}; 651 } 652 return 1; 653} 654 655=item B<< $ordinals->invalidate >> 656 657Invalidates the whole working database. The practical effect is that all 658symbols are set to not exist, but are kept around in the database to retain 659ordinal numbers and versions. 660 661=cut 662 663sub invalidate { 664 my $self = shift; 665 666 foreach (@{$self->{contents}}) { 667 foreach (@{$_ // []}) { 668 $_->{exists} = 0; 669 } 670 } 671 $self->{stats} = {}; 672} 673 674=item B<< $ordinals->validate >> 675 676Validates the current working database by collection statistics on how many 677symbols were added and how many were changed. These numbers can be retrieved 678with B<< $ordinals->stats >>. 679 680=cut 681 682sub validate { 683 my $self = shift; 684 685 $self->{stats} = {}; 686 for my $i (1..$self->{maxnum}) { 687 if ($i > $self->{loaded_maxnum} 688 || (!@{$self->{loaded_contents}->[$i] // []} 689 && @{$self->{contents}->[$i] // []})) { 690 $self->{stats}->{new}++; 691 } 692 if ($i <= $self->{maxassigned}) { 693 $self->{stats}->{assigned}++; 694 } else { 695 $self->{stats}->{unassigned}++; 696 } 697 next if ($i > $self->{loaded_maxnum}); 698 699 my @loaded_strings = 700 map { $_->to_string() } @{$self->{loaded_contents}->[$i] // []}; 701 my @current_strings = 702 map { $_->to_string() } @{$self->{contents}->[$i] // []}; 703 704 foreach my $str (@current_strings) { 705 @loaded_strings = grep { $str ne $_ } @loaded_strings; 706 } 707 if (@loaded_strings) { 708 $self->{stats}->{modified}++; 709 } 710 } 711} 712 713=item B<< $ordinals->stats >> 714 715Returns the statistics that B<validate> calculate. 716 717=cut 718 719sub stats { 720 my $self = shift; 721 722 return %{$self->{stats}}; 723} 724 725=back 726 727=head2 Data elements 728 729Data elements, which is each line in an ordinals file, are instances 730of a separate class, OpenSSL::Ordinals::Item, with its own methods: 731 732=over 4 733 734=cut 735 736package OpenSSL::Ordinals::Item; 737 738use strict; 739use warnings; 740use Carp; 741 742=item B<new> I<%options> 743 744Creates a new instance of the C<OpenSSL::Ordinals::Item> class. It takes 745options in keyed pair form, i.e. a series of C<< key => value >> pairs. 746Available options are: 747 748=over 4 749 750=item B<< source => FILENAME >>, B<< from => STRING >> 751 752This will create a new item from FILENAME, filled with data coming from STRING. 753 754STRING must conform to the following EBNF description: 755 756 ordinal string = symbol, spaces, ordinal, spaces, version, spaces, 757 exist, ":", platforms, ":", type, ":", features; 758 spaces = space, { space }; 759 space = " " | "\t"; 760 symbol = ( letter | "_" ), { letter | digit | "_" }; 761 ordinal = number | "?" | "?+"; 762 version = number, "_", number, "_", number, [ letter, [ letter ] ]; 763 exist = "EXIST" | "NOEXIST"; 764 platforms = platform, { ",", platform }; 765 platform = ( letter | "_" ) { letter | digit | "_" }; 766 type = "FUNCTION" | "VARIABLE"; 767 features = feature, { ",", feature }; 768 feature = ( letter | "_" ) { letter | digit | "_" }; 769 number = digit, { digit }; 770 771(C<letter> and C<digit> are assumed self evident) 772 773=item B<< source => FILENAME >>, B<< name => STRING >>, B<< number => NUMBER >>, 774 B<< version => STRING >>, B<< exists => BOOLEAN >>, B<< type => STRING >>, 775 B<< platforms => HASHref >>, B<< features => LISTref >> 776 777This will create a new item with data coming from the arguments. 778 779=back 780 781=cut 782 783sub new { 784 my $class = shift; 785 786 if (ref($_[0]) eq $class) { 787 return $class->new( map { $_ => $_[0]->{$_} } keys %{$_[0]} ); 788 } 789 790 my %opts = @_; 791 792 croak "No argument given" unless %opts; 793 794 my $instance = undef; 795 if ($opts{from}) { 796 my @a = split /\s+/, $opts{from}; 797 798 croak "Badly formatted ordinals string: $opts{from}" 799 unless ( scalar @a == 4 800 && $a[0] =~ /^[A-Za-z_][A-Za-z_0-9]*$/ 801 && $a[1] =~ /^\d+|\?\+?$/ 802 && $a[2] =~ /^(?:\*|\d+_\d+_\d+[a-z]{0,2})$/ 803 && $a[3] =~ /^ 804 (?:NO)?EXIST: 805 [^:]*: 806 (?:FUNCTION|VARIABLE): 807 [^:]* 808 $ 809 /x ); 810 811 my @b = split /:/, $a[3]; 812 %opts = ( source => $opts{source}, 813 name => $a[0], 814 number => $a[1], 815 version => $a[2], 816 exists => $b[0] eq 'EXIST', 817 platforms => { map { m|^(!)?|; $' => !$1 } 818 split /,/,$b[1] }, 819 type => $b[2], 820 features => [ split /,/,$b[3] // '' ] ); 821 } 822 823 if ($opts{name} && $opts{version} && defined $opts{exists} && $opts{type} 824 && ref($opts{platforms} // {}) eq 'HASH' 825 && ref($opts{features} // []) eq 'ARRAY') { 826 my $version = $opts{version}; 827 $version =~ s|_|.|g; 828 829 $instance = { source => $opts{source}, 830 name => $opts{name}, 831 type => $opts{type}, 832 number => $opts{number}, 833 intnum => $opts{intnum}, 834 version => $version, 835 exists => !!$opts{exists}, 836 platforms => { %{$opts{platforms} // {}} }, 837 features => [ sort @{$opts{features} // []} ] }; 838 } else { 839 croak __PACKAGE__."->new() called with bad arguments\n". 840 join("", map { " $_\t=> ".$opts{$_}."\n" } sort keys %opts); 841 } 842 843 return bless $instance, $class; 844} 845 846sub DESTROY { 847} 848 849=item B<< $item->name >> 850 851The symbol name for this item. 852 853=item B<< $item->number >> (read-write) 854 855The positional number for this item. 856 857This may be '?' for an unassigned symbol, or '?+' for an unassigned symbol 858that's an alias for the previous symbol. '?' and '?+' must be properly 859handled by the caller. The caller may change this to an actual number. 860 861=item B<< $item->version >> (read-only) 862 863The version number for this item. Please note that these version numbers 864have underscore (C<_>) as a separator for the version parts. 865 866=item B<< $item->exists >> (read-only) 867 868A boolean that tells if this symbol exists in code or not. 869 870=item B<< $item->platforms >> (read-only) 871 872A hash table reference. The keys of the hash table are the names of 873the specified platforms, with a value of 0 to indicate that this symbol 874isn't available on that platform, and 1 to indicate that it is. Platforms 875that aren't mentioned default to 1. 876 877=item B<< $item->type >> (read-only) 878 879C<FUNCTION> or C<VARIABLE>, depending on what the symbol represents. 880Some platforms do not care about this, others do. 881 882=item B<< $item->features >> (read-only) 883 884An array reference, where every item indicates a feature where this symbol 885is available. If no features are mentioned, the symbol is always available. 886If any feature is mentioned, this symbol is I<only> available when those 887features are enabled. 888 889=cut 890 891our $AUTOLOAD; 892 893# Generic getter 894sub AUTOLOAD { 895 my $self = shift; 896 my $funcname = $AUTOLOAD; 897 (my $item = $funcname) =~ s|.*::||g; 898 899 croak "$funcname called as setter" if @_; 900 croak "$funcname invalid" unless exists $self->{$item}; 901 return $self->{$item} if ref($self->{$item}) eq ''; 902 return @{$self->{$item}} if ref($self->{$item}) eq 'ARRAY'; 903 return %{$self->{$item}} if ref($self->{$item}) eq 'HASH'; 904} 905 906=item B<< $item->intnum >> (read-write) 907 908Internal positional number. If I<< $item->number >> is '?' or '?+', the 909caller can use this to set a number for its purposes. 910If I<< $item->number >> is a number, I<< $item->intnum >> should be the 911same 912 913=cut 914 915# Getter/setters 916sub intnum { 917 my $self = shift; 918 my $value = shift; 919 my $item = 'intnum'; 920 921 croak "$item called with extra arguments" if @_; 922 $self->{$item} = "$value" if defined $value; 923 return $self->{$item}; 924} 925 926sub number { 927 my $self = shift; 928 my $value = shift; 929 my $item = 'number'; 930 931 croak "$item called with extra arguments" if @_; 932 $self->{$item} = "$value" if defined $value; 933 return $self->{$item}; 934} 935 936=item B<< $item->to_string >> 937 938Converts the item to a string that can be saved in an ordinals file. 939 940=cut 941 942sub to_string { 943 my $self = shift; 944 945 croak "Too many arguments" if @_; 946 my %platforms = $self->platforms(); 947 my @features = $self->features(); 948 my $version = $self->version(); 949 $version =~ s|\.|_|g; 950 return sprintf "%-39s %s\t%s\t%s:%s:%s:%s", 951 $self->name(), 952 $self->number(), 953 $version, 954 $self->exists() ? 'EXIST' : 'NOEXIST', 955 join(',', (map { ($platforms{$_} ? '' : '!') . $_ } 956 sort keys %platforms)), 957 $self->type(), 958 join(',', @features); 959} 960 961=back 962 963=head2 Comparators and filters 964 965For the B<< $ordinals->items >> method, there are a few functions to create 966comparators based on specific data: 967 968=over 4 969 970=cut 971 972# Go back to the main package to create comparators and filters 973package OpenSSL::Ordinals; 974 975# Comparators... 976 977=item B<by_name> 978 979Returns a comparator that will compare the names of two OpenSSL::Ordinals::Item 980objects. 981 982=cut 983 984sub by_name { 985 return sub { $_[0]->name() cmp $_[1]->name() }; 986} 987 988=item B<by_number> 989 990Returns a comparator that will compare the ordinal numbers of two 991OpenSSL::Ordinals::Item objects. 992 993=cut 994 995sub by_number { 996 return sub { $_[0]->intnum() <=> $_[1]->intnum() }; 997} 998 999=item B<by_version> 1000 1001Returns a comparator that will compare the version of two 1002OpenSSL::Ordinals::Item objects. 1003 1004=cut 1005 1006sub by_version { 1007 return sub { 1008 # cmp_versions comes from OpenSSL::Util 1009 return cmp_versions($_[0]->version(), $_[1]->version()); 1010 } 1011} 1012 1013=back 1014 1015There are also the following filters: 1016 1017=over 4 1018 1019=cut 1020 1021# Filters... these are called by grep, the return sub must use $_ for 1022# the item to check 1023 1024=item B<f_version VERSION> 1025 1026Returns a filter that only lets through symbols with a version number 1027matching B<VERSION>. 1028 1029=cut 1030 1031sub f_version { 1032 my $version = shift; 1033 1034 croak "No version specified" 1035 unless $version && $version =~ /^\d+\.\d+\.\d+[a-z]{0,2}$/; 1036 1037 return sub { $_[0]->version() eq $version }; 1038} 1039 1040=item B<f_number NUMBER> 1041 1042Returns a filter that only lets through symbols with the ordinal number 1043matching B<NUMBER>. 1044 1045NOTE that this returns a "magic" value that can not be used as a function. 1046It's only useful when passed directly as a filter to B<items>. 1047 1048=cut 1049 1050sub f_number { 1051 my $number = shift; 1052 1053 croak "No number specified" 1054 unless $number && $number =~ /^\d+$/; 1055 1056 return [ F_NUMBER, $number ]; 1057} 1058 1059 1060=item B<f_name NAME> 1061 1062Returns a filter that only lets through symbols with the symbol name 1063matching B<NAME>. 1064 1065NOTE that this returns a "magic" value that can not be used as a function. 1066It's only useful when passed directly as a filter to B<items>. 1067 1068=cut 1069 1070sub f_name { 1071 my $name = shift; 1072 1073 croak "No name specified" 1074 unless $name; 1075 1076 return [ F_NAME, $name ]; 1077} 1078 1079=back 1080 1081=head1 AUTHORS 1082 1083Richard Levitte E<lt>levitte@openssl.orgE<gt>. 1084 1085=cut 1086 10871; 1088