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