#! /usr/bin/env perl
# Copyright 2018-2021 The OpenSSL Project Authors. All Rights Reserved.
#
# Licensed under the Apache License 2.0 (the "License"). You may not use
# this file except in compliance with the License. You can obtain a copy
# in the file LICENSE in the source distribution or at
# https://www.openssl.org/source/license.html
package OpenSSL::Ordinals;
use strict;
use warnings;
use Carp;
use Scalar::Util qw(blessed);
use OpenSSL::Util;
use constant {
# "magic" filters, see the filters at the end of the file
F_NAME => 1,
F_NUMBER => 2,
};
=head1 NAME
OpenSSL::Ordinals - a private module to read and walk through ordinals
=head1 SYNOPSIS
use OpenSSL::Ordinals;
my $ordinals = OpenSSL::Ordinals->new(from => "foo.num");
# or alternatively
my $ordinals = OpenSSL::Ordinals->new();
$ordinals->load("foo.num");
foreach ($ordinals->items(comparator => by_name()) {
print $_->name(), "\n";
}
=head1 DESCRIPTION
This is a OpenSSL private module to load an ordinals (F<.num>) file and
write out the data you want, sorted and filtered according to your rules.
An ordinals file is a file that enumerates all the symbols that a shared
library or loadable module must export. Each of them have a unique
assigned number as well as other attributes to indicate if they only exist
on a subset of the supported platforms, or if they are specific to certain
features.
The unique numbers each symbol gets assigned needs to be maintained for a
shared library or module to stay compatible with previous versions on
platforms that maintain a transfer vector indexed by position rather than
by name. They also help keep information on certain symbols that are
aliases for others for certain platforms, or that have different forms
on different platforms.
=head2 Main methods
=over 4
=cut
=item B<new> I<%options>
Creates a new instance of the C<OpenSSL::Ordinals> class. It takes options
in keyed pair form, i.e. a series of C<< key => value >> pairs. Available
options are:
=over 4
=item B<< from => FILENAME >>
Not only create a new instance, but immediately load it with data from the
ordinals file FILENAME.
=back
=cut
sub new {
my $class = shift;
my %opts = @_;
my $instance = {
filename => undef, # File name registered when loading
loaded_maxnum => 0, # Highest allocated item number when loading
loaded_contents => [], # Loaded items, if loading there was
maxassigned => 0, # Current highest assigned item number
maxnum => 0, # Current highest allocated item number
contents => [], # Items, indexed by number
name2num => {}, # Name to number dictionary
aliases => {}, # Aliases cache.
stats => {}, # Statistics, see 'sub validate'
debug => $opts{debug},
};
bless $instance, $class;
$instance->set_version($opts{version});
$instance->load($opts{from}) if defined($opts{from});
return $instance;
}
=item B<< $ordinals->load FILENAME >>
Loads the data from FILENAME into the instance. Any previously loaded data
is dropped.
Two internal databases are created. One database is simply a copy of the file
contents and is treated as read-only. The other database is an exact copy of
the first, but is treated as a work database, i.e. it can be modified and added
to.
=cut
sub load {
my $self = shift;
my $filename = shift;
croak "Undefined filename" unless defined($filename);
my @tmp_contents = ();
my %tmp_name2num = ();
my $max_assigned = 0;
my $max_num = 0;
open F, '<', $filename or croak "Unable to open $filename";
while (<F>) {
s|\R$||; # Better chomp
s|#.*||;
next if /^\s*$/;
my $item = OpenSSL::Ordinals::Item->new(source => $filename, from => $_);
my $num = $item->number();
if ($num eq '?') {
$num = ++$max_num;
} elsif ($num eq '?+') {
$num = $max_num;
} else {
croak "Disordered ordinals, number sequence restarted"
if $max_num > $max_assigned && $num < $max_num;
croak "Disordered ordinals, $num < $max_num"
if $num < $max_num;
$max_assigned = $max_num = $num;
}
$item->intnum($num);
push @{$tmp_contents[$num]}, $item;
$tmp_name2num{$item->name()} = $num;
}
close F;
$self->{