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