]> git.ipfire.org Git - thirdparty/openssl.git/blob - util/perl/OpenSSL/Ordinals.pm
c26a866493a91fa9b0c3deb681481a3bb80be3e9
[thirdparty/openssl.git] / util / perl / OpenSSL / Ordinals.pm
1 #! /usr/bin/env perl
2 # Copyright 2018 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
9 package OpenSSL::Ordinals;
10
11 use strict;
12 use warnings;
13 use Carp;
14 use Scalar::Util qw(blessed);
15 use OpenSSL::Util;
16
17 use 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
25 OpenSSL::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
42 This is a OpenSSL private module to load an ordinals (F<.num>) file and
43 write out the data you want, sorted and filtered according to your rules.
44
45 An ordinals file is a file that enumerates all the symbols that a shared
46 library or loadable module must export. Each of them have a unique
47 assigned number as well as other attributes to indicate if they only exist
48 on a subset of the supported platforms, or if they are specific to certain
49 features.
50
51 The unique numbers each symbol gets assigned needs to be maintained for a
52 shared library or module to stay compatible with previous versions on
53 platforms that maintain a transfer vector indexed by position rather than
54 by name. They also help keep information on certain symbols that are
55 aliases for others for certain platforms, or that have different forms
56 on different platforms.
57
58 =head2 Main methods
59
60 =over 4
61
62 =cut
63
64 =item B<new> I<%options>
65
66 Creates a new instance of the C<OpenSSL::Ordinals> class. It takes options
67 in keyed pair form, i.e. a series of C<key =E<gt> value> pairs. Available
68 options are:
69
70 =over 4
71
72 =item B<from =E<gt> FILENAME>
73
74 Not only create a new instance, but immediately load it with data from the
75 ordinals file FILENAME.
76
77 =back
78
79 =cut
80
81 sub 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 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'
94 debug => $opts{debug},
95 };
96 bless $instance, $class;
97
98 $instance->set_version($opts{version});
99 $instance->load($opts{from}) if defined($opts{from});
100
101 return $instance;
102 }
103
104 =item B<$ordinals-E<gt>load FILENAME>
105
106 Loads the data from FILENAME into the instance. Any previously loaded data
107 is dropped.
108
109 Two internal databases are created. One database is simply a copy of the file
110 contents and is treated as read-only. The other database is an exact copy of
111 the first, but is treated as a work database, i.e. it can be modified and added
112 to.
113
114 =cut
115
116 sub load {
117 my $self = shift;
118 my $filename = shift;
119
120 croak "Undefined filename" unless defined($filename);
121
122 my @tmp_contents = ();
123 my %tmp_name2num = ();
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 => $_);
132
133 my $num = $item->number();
134 croak "Disordered ordinals, $num < $max_num"
135 if $num < $max_num;
136 $max_num = $num;
137
138 push @{$tmp_contents[$item->number()]}, $item;
139 $tmp_name2num{$item->name()} = $item->number();
140 }
141 close F;
142
143 $self->{contents} = [ @tmp_contents ];
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
162 If an ordinals file has been loaded, it gets rewritten with the data from
163 the current work database.
164
165 =cut
166
167 sub rewrite {
168 my $self = shift;
169
170 $self->write($self->{filename});
171 }
172
173 =item B<$ordinals-E<gt>write FILENAME>
174
175 Writes the current work database data to the ordinals file FILENAME.
176 This also validates the data, see B<$ordinals-E<gt>validate> below.
177
178 =cut
179
180 sub 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};
195 return 1;
196 }
197
198 =item B<$ordinals-E<gt>items> I<%options>
199
200 Returns a list of items according to a set of criteria. The criteria is
201 given in form keyed pair form, i.e. a series of C<key =E<gt> value> pairs.
202 Available options are:
203
204 =over 4
205
206 =item B<sort =E<gt> SORTFUNCTION>
207
208 SORTFUNCTION is a reference to a function that takes two arguments, which
209 correspond to the classic C<$a> and C<$b> that are available in a C<sort>
210 block.
211
212 =item B<filter =E<gt> FILTERFUNCTION>
213
214 FILTERFUNTION is a reference to a function that takes one argument, which
215 is every OpenSSL::Ordinals::Item element available.
216
217 =back
218
219 =cut
220
221 sub items {
222 my $self = shift;
223 my %opts = @_;
224
225 my $comparator = $opts{sort};
226 my $filter = $opts{filter} // sub { 1; };
227
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
248 return sort { $comparator->($a, $b); } @l
249 if (defined $comparator);
250 return @l;
251 }
252
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
260 sub _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
329 sub _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; }
345 if ($def =~
346 m{^OPENSSL_(EXPORT_VAR_AS_FUNCTION)$}) { $platforms{$1} = $op; }
347 if ($def =~ m{^OPENSSL_SYS_}) { $platforms{$'} = $op; }
348 }
349
350 return %platforms;
351 }
352
353 sub _parse_features {
354 my $self = shift;
355 my @defs = @_;
356
357 my %features = ();
358 foreach (@defs) {
359 m{^(!)?};
360 my $op = !(defined $1 && $1 eq '!');
361 my $def = $';
362
363 if ($def =~ m{^ZLIB$}) { $features{$&} = $op; }
364 if ($def =~ m{^OPENSSL_USE_}) { $features{$'} = $op; }
365 if ($def =~ m{^OPENSSL_NO_}) { $features{$'} = !$op; }
366 if ($def =~ m{^DEPRECATEDIN_(.*)$}) { $features{$&} = !$op; }
367 }
368
369 return %features;
370 }
371
372 sub _adjust_version {
373 my $self = shift;
374 my $version = shift;
375 my $baseversion = $self->{baseversion};
376
377 $version = $baseversion
378 if ($baseversion ne '*' && $version ne '*'
379 && cmp_versions($baseversion, $version) > 0);
380
381 return $version;
382 }
383
384 =item B<$ordinals-E<gt>add NAME, TYPE, LIST>
385
386 Adds a new item named NAME with the type TYPE, and a set of C macros in
387 LIST that are expected to be defined or undefined to use this symbol, if
388 any. For undefined macros, they each must be prefixed with a C<!>.
389
390 If this symbol already exists in loaded data, it will be rewritten using
391 the new input data, but will keep the same ordinal number and version.
392 If it's entirely new, it will get a new number and the current default
393 version. The new ordinal number is a simple increment from the last
394 maximum number.
395
396 =cut
397
398 sub add {
399 my $self = shift;
400 my $name = shift;
401 my $type = shift; # FUNCTION or VARIABLE
402 my @defs = @_; # Macros from #ifdef and #ifndef
403 # (the latter prefixed with a '!')
404
405 # call signature for debug output
406 my $verbsig = "add('$name' , '$type' , [ " . join(', ', @defs) . " ])";
407
408 croak __PACKAGE__."->add got a bad type '$type'"
409 unless $type eq 'FUNCTION' || $type eq 'VARIABLE';
410
411 my %platforms = _parse_platforms(@defs);
412 my %features = _parse_features(@defs);
413
414 my @items = $self->items(filter => f_name($name));
415 my $version = @items ? $items[0]->version() : $self->{currversion};
416 my $number = @items ? $items[0]->number() : ++$self->{maxnum};
417 print STDERR "DEBUG[",__PACKAGE__,":add] $verbsig\n",
418 @items ? map { "\t".$_->to_string()."\n" } @items : "No previous items\n",
419 if $self->{debug};
420 @items = grep { $_->exists() } @items;
421
422 my $new_item =
423 OpenSSL::Ordinals::Item->new( name => $name,
424 type => $type,
425 number => $number,
426 version =>
427 $self->_adjust_version($version),
428 exists => 1,
429 platforms => { %platforms },
430 features => [
431 grep { $features{$_} } keys %features
432 ] );
433
434 push @items, $new_item;
435 print STDERR "DEBUG[",__PACKAGE__,"::add] $verbsig\n", map { "\t".$_->to_string()."\n" } @items
436 if $self->{debug};
437 $self->_putback(@items);
438
439 # If an alias was defined beforehand, add an item for it now
440 my $alias = $self->{aliases}->{$name};
441 delete $self->{aliases}->{$name};
442
443 # For the caller to show
444 my @returns = ( $new_item );
445 push @returns, $self->add_alias($alias->{name}, $name, @{$alias->{defs}})
446 if defined $alias;
447 return @returns;
448 }
449
450 =item B<$ordinals-E<gt>add_alias ALIAS, NAME, LIST>
451
452 Adds an alias ALIAS for the symbol NAME, and a set of C macros in LIST
453 that are expected to be defined or undefined to use this symbol, if any.
454 For undefined macros, they each must be prefixed with a C<!>.
455
456 If this symbol already exists in loaded data, it will be rewritten using
457 the new input data. Otherwise, the data will just be store away, to wait
458 that the symbol NAME shows up.
459
460 =cut
461
462 sub add_alias {
463 my $self = shift;
464 my $alias = shift; # This is the alias being added
465 my $name = shift; # For this name (assuming it exists)
466 my @defs = @_; # Platform attributes for the alias
467
468 # call signature for debug output
469 my $verbsig =
470 "add_alias('$alias' , '$name' , [ " . join(', ', @defs) . " ])";
471
472 croak "You're kidding me..." if $alias eq $name;
473
474 my %platforms = _parse_platforms(@defs);
475 my %features = _parse_features(@defs);
476
477 croak "Alias with associated features is forbidden\n"
478 if %features;
479
480 my $f_byalias = f_name($alias);
481 my $f_byname = f_name($name);
482 my @items = $self->items(filter => $f_byalias);
483 foreach my $item ($self->items(filter => $f_byname)) {
484 push @items, $item unless grep { $_ == $item } @items;
485 }
486 @items = grep { $_->exists() } @items;
487
488 croak "Alias already exists ($alias => $name)"
489 if scalar @items > 1;
490 if (scalar @items == 0) {
491 # The item we want to alias for doesn't exist yet, so we cache the
492 # alias and hope the item we're making an alias of shows up later
493 $self->{aliases}->{$name} = { name => $alias, defs => [ @defs ] };
494
495 print STDERR "DEBUG[",__PACKAGE__,":add_alias] $verbsig\n",
496 "\tSet future alias $alias => $name\n"
497 if $self->{debug};
498 return ();
499 } elsif (scalar @items == 1) {
500 # The rule is that an alias is more or less a copy of the original
501 # item, just with another name. Also, the platforms given here are
502 # given to the original item as well, with opposite values.
503 my %alias_platforms = $items[0]->platforms();
504 foreach (keys %platforms) {
505 $alias_platforms{$_} = !$platforms{$_};
506 }
507 # We supposedly do now know how to do this... *ahem*
508 $items[0]->{platforms} = { %alias_platforms };
509
510 my $alias_item = OpenSSL::Ordinals::Item->new(
511 name => $alias,
512 type => $items[0]->type(),
513 number => $items[0]->number(),
514 version => $self->_adjust_version($items[0]->version()),
515 exists => $items[0]->exists(),
516 platforms => { %platforms },
517 features => [ $items[0]->features() ]
518 );
519 push @items, $alias_item;
520
521 print STDERR "DEBUG[",__PACKAGE__,":add_alias] $verbsig\n",
522 map { "\t".$_->to_string()."\n" } @items
523 if $self->{debug};
524 $self->_putback(@items);
525
526 # For the caller to show
527 return ( $alias_item->to_string() );
528 }
529 croak "$name has an alias already (trying to add alias $alias)\n",
530 "\t", join(", ", map { $_->name() } @items), "\n";
531 }
532
533 =item B<$ordinals-E<gt>set_version VERSION>
534
535 =item B<$ordinals-E<gt>set_version VERSION BASEVERSION>
536
537 Sets the default version for new symbol to VERSION.
538
539 If given, BASEVERSION sets the base version, i.e. the minimum version
540 for all symbols. If not given, it will be calculated as follows:
541
542 =over 4
543
544 If the given version is '*', then the base version will also be '*'.
545
546 If the given version starts with '0.', the base version will be '0.0.0'.
547
548 If the given version starts with '1.0.', the base version will be '1.0.0'.
549
550 If the given version starts with '1.1.', the base version will be '1.1.0'.
551
552 If the given version has a first number C<N> that's greater than 1, the
553 base version will be formed from C<N>: 'N.0.0'.
554
555 =back
556
557 =cut
558
559 sub set_version {
560 my $self = shift;
561 # '*' is for "we don't care"
562 my $version = shift // '*';
563 my $baseversion = shift // '*';
564
565 $version =~ s|-.*||g;
566
567 if ($baseversion eq '*') {
568 $baseversion = $version;
569 if ($baseversion ne '*') {
570 if ($baseversion =~ m|^(\d+)\.|, $1 > 1) {
571 $baseversion = "$1.0.0";
572 } else {
573 $baseversion =~ s|^0\..*$|0.0.0|;
574 $baseversion =~ s|^1\.0\..*$|1.0.0|;
575 $baseversion =~ s|^1\.1\..*$|1.1.0|;
576
577 die 'Invalid version'
578 if ($baseversion ne '0.0.0'
579 && $baseversion !~ m|^1\.[01]\.0$|);
580 }
581 }
582 }
583
584 die 'Invalid base version'
585 if ($baseversion ne '*' && $version ne '*'
586 && cmp_versions($baseversion, $version) > 0);
587
588 $self->{currversion} = $version;
589 $self->{baseversion} = $baseversion;
590 foreach ($self->items(filter => sub { $_[0] eq '*' })) {
591 $_->{version} = $self->{currversion};
592 }
593 return 1;
594 }
595
596 =item B<$ordinals-E<gt>invalidate>
597
598 Invalidates the whole working database. The practical effect is that all
599 symbols are set to not exist, but are kept around in the database to retain
600 ordinal numbers and versions.
601
602 =cut
603
604 sub invalidate {
605 my $self = shift;
606
607 foreach (@{$self->{contents}}) {
608 foreach (@{$_ // []}) {
609 $_->{exists} = 0;
610 }
611 }
612 $self->{stats} = {};
613 }
614
615 =item B<$ordinals-E<gt>validate>
616
617 Validates the current working database by collection statistics on how many
618 symbols were added and how many were changed. These numbers can be retrieved
619 with B<$ordinals-E<gt>stats>.
620
621 =cut
622
623 sub validate {
624 my $self = shift;
625
626 $self->{stats} = {};
627 for my $i (1..$self->{maxnum}) {
628 if ($i > $self->{loaded_maxnum}
629 || (!@{$self->{loaded_contents}->[$i] // []}
630 && @{$self->{contents}->[$i] // []})) {
631 $self->{stats}->{new}++;
632 }
633 next if ($i > $self->{loaded_maxnum});
634
635 my @loaded_strings =
636 map { $_->to_string() } @{$self->{loaded_contents}->[$i] // []};
637 my @current_strings =
638 map { $_->to_string() } @{$self->{contents}->[$i] // []};
639
640 foreach my $str (@current_strings) {
641 @loaded_strings = grep { $str ne $_ } @loaded_strings;
642 }
643 if (@loaded_strings) {
644 $self->{stats}->{modified}++;
645 }
646 }
647 }
648
649 =item B<$ordinals-E<gt>stats>
650
651 Returns the statistics that B<validate> calculate.
652
653 =cut
654
655 sub stats {
656 my $self = shift;
657
658 return %{$self->{stats}};
659 }
660
661 =back
662
663 =head2 Data elements
664
665 Data elements, which is each line in an ordinals file, are instances
666 of a separate class, OpenSSL::Ordinals::Item, with its own methods:
667
668 =over 4
669
670 =cut
671
672 package OpenSSL::Ordinals::Item;
673
674 use strict;
675 use warnings;
676 use Carp;
677
678 =item B<new> I<%options>
679
680 Creates a new instance of the C<OpenSSL::Ordinals::Item> class. It takes
681 options in keyed pair form, i.e. a series of C<key =E<gt> value> pairs.
682 Available options are:
683
684 =over 4
685
686 =item B<from =E<gt> STRING>
687
688 This will create a new item, filled with data coming from STRING.
689
690 STRING must conform to the following EBNF description:
691
692 ordinal string = symbol, spaces, ordinal, spaces, version, spaces,
693 exist, ":", platforms, ":", type, ":", features;
694 spaces = space, { space };
695 space = " " | "\t";
696 symbol = ( letter | "_"), { letter | digit | "_" };
697 ordinal = number;
698 version = number, "_", number, "_", number, [ letter, [ letter ] ];
699 exist = "EXIST" | "NOEXIST";
700 platforms = platform, { ",", platform };
701 platform = ( letter | "_" ) { letter | digit | "_" };
702 type = "FUNCTION" | "VARIABLE";
703 features = feature, { ",", feature };
704 feature = ( letter | "_" ) { letter | digit | "_" };
705 number = digit, { digit };
706
707 (C<letter> and C<digit> are assumed self evident)
708
709 =item B<name =E<gt> STRING>, B<number =E<gt> NUMBER>, B<version =E<gt> STRING>,
710 B<exists =E<gt> BOOLEAN>, B<type =E<gt> STRING>,
711 B<platforms =E<gt> HASHref>, B<features =E<gt> LISTref>
712
713 This will create a new item with data coming from the arguments.
714
715 =back
716
717 =cut
718
719 sub new {
720 my $class = shift;
721
722 if (ref($_[0]) eq $class) {
723 return $class->new( map { $_ => $_[0]->{$_} } keys %{$_[0]} );
724 }
725
726 my %opts = @_;
727
728 croak "No argument given" unless %opts;
729
730 my $instance = undef;
731 if ($opts{from}) {
732 my @a = split /\s+/, $opts{from};
733
734 croak "Badly formatted ordinals string: $opts{from}"
735 unless ( scalar @a == 4
736 && $a[0] =~ /^[A-Za-z_][A-Za-z_0-9]*$/
737 && $a[1] =~ /^\d+$/
738 && $a[2] =~ /^(?:\*|\d+_\d+_\d+[a-z]{0,2})$/
739 && $a[3] =~ /^
740 (?:NO)?EXIST:
741 [^:]*:
742 (?:FUNCTION|VARIABLE):
743 [^:]*
744 $
745 /x );
746
747 my @b = split /:/, $a[3];
748 %opts = ( name => $a[0],
749 number => $a[1],
750 version => $a[2],
751 exists => $b[0] eq 'EXIST',
752 platforms => { map { m|^(!)?|; $' => !$1 }
753 split /,/,$b[1] },
754 type => $b[2],
755 features => [ split /,/,$b[3] // '' ] );
756 }
757
758 if ($opts{name} && $opts{version} && defined $opts{exists} && $opts{type}
759 && ref($opts{platforms} // {}) eq 'HASH'
760 && ref($opts{features} // []) eq 'ARRAY') {
761 my $version = $opts{version};
762 $version =~ s|_|.|g;
763
764 $instance = { name => $opts{name},
765 type => $opts{type},
766 number => $opts{number},
767 version => $version,
768 exists => !!$opts{exists},
769 platforms => { %{$opts{platforms} // {}} },
770 features => [ sort @{$opts{features} // []} ] };
771 } else {
772 croak __PACKAGE__."->new() called with bad arguments\n".
773 join("", map { " $_\t=> ".$opts{$_}."\n" } sort keys %opts);
774 }
775
776 return bless $instance, $class;
777 }
778
779 sub DESTROY {
780 }
781
782 =item B<$item-E<gt>name>
783
784 The symbol name for this item.
785
786 =item B<$item-E<gt>number>
787
788 The positional number for this item.
789
790 =item B<$item-E<gt>version>
791
792 The version number for this item. Please note that these version numbers
793 have underscore (C<_>) as a separator the the version parts.
794
795 =item B<$item-E<gt>exists>
796
797 A boolean that tells if this symbol exists in code or not.
798
799 =item B<$item-E<gt>platforms>
800
801 A hash table reference. The keys of the hash table are the names of
802 the specified platforms, with a value of 0 to indicate that this symbol
803 isn't available on that platform, and 1 to indicate that it is. Platforms
804 that aren't mentioned default to 1.
805
806 =item B<$item-E<gt>type>
807
808 C<FUNCTION> or C<VARIABLE>, depending on what the symbol represents.
809 Some platforms do not care about this, others do.
810
811 =item B<$item-E<gt>features>
812
813 An array reference, where every item indicates a feature where this symbol
814 is available. If no features are mentioned, the symbol is always available.
815 If any feature is mentioned, this symbol is I<only> available when those
816 features are enabled.
817
818 =cut
819
820 our $AUTOLOAD;
821
822 # Generic getter
823 sub AUTOLOAD {
824 my $self = shift;
825 my $funcname = $AUTOLOAD;
826 (my $item = $funcname) =~ s|.*::||g;
827
828 croak "$funcname called as setter" if @_;
829 croak "$funcname invalid" unless exists $self->{$item};
830 return $self->{$item} if ref($self->{$item}) eq '';
831 return @{$self->{$item}} if ref($self->{$item}) eq 'ARRAY';
832 return %{$self->{$item}} if ref($self->{$item}) eq 'HASH';
833 }
834
835 =item B<$item-E<gt>to_string>
836
837 Converts the item to a string that can be saved in an ordinals file.
838
839 =cut
840
841 sub to_string {
842 my $self = shift;
843
844 croak "Too many arguments" if @_;
845 my %platforms = $self->platforms();
846 my @features = $self->features();
847 my $version = $self->version();
848 $version =~ s|\.|_|g;
849 return sprintf "%-39s %d\t%s\t%s:%s:%s:%s",
850 $self->name(),
851 $self->number(),
852 $version,
853 $self->exists() ? 'EXIST' : 'NOEXIST',
854 join(',', (map { ($platforms{$_} ? '' : '!') . $_ }
855 sort keys %platforms)),
856 $self->type(),
857 join(',', @features);
858 }
859
860 =back
861
862 =head2 Comparators and filters
863
864 For the B<$ordinals-E<gt>items> method, there are a few functions to create
865 comparators based on specific data:
866
867 =over 4
868
869 =cut
870
871 # Go back to the main package to create comparators and filters
872 package OpenSSL::Ordinals;
873
874 # Comparators...
875
876 =item B<by_name>
877
878 Returns a comparator that will compare the names of two OpenSSL::Ordinals::Item
879 objects.
880
881 =cut
882
883 sub by_name {
884 return sub { $_[0]->name() cmp $_[1]->name() };
885 }
886
887 =item B<by_number>
888
889 Returns a comparator that will compare the ordinal numbers of two
890 OpenSSL::Ordinals::Item objects.
891
892 =cut
893
894 sub by_number {
895 return sub { $_[0]->number() <=> $_[1]->number() };
896 }
897
898 =item B<by_version>
899
900 Returns a comparator that will compare the version of two
901 OpenSSL::Ordinals::Item objects.
902
903 =cut
904
905 sub by_version {
906 return sub {
907 # cmp_versions comes from OpenSSL::Util
908 return cmp_versions($_[0]->version(), $_[1]->version());
909 }
910 }
911
912 =back
913
914 There are also the following filters:
915
916 =over 4
917
918 =cut
919
920 # Filters... these are called by grep, the return sub must use $_ for
921 # the item to check
922
923 =item B<f_version VERSION>
924
925 Returns a filter that only lets through symbols with a version number
926 matching B<VERSION>.
927
928 =cut
929
930 sub f_version {
931 my $version = shift;
932
933 croak "No version specified"
934 unless $version && $version =~ /^\d+\.\d+\.\d+[a-z]{0,2}$/;
935
936 return sub { $_[0]->version() eq $version };
937 }
938
939 =item B<f_number NUMBER>
940
941 Returns a filter that only lets through symbols with the ordinal number
942 matching B<NUMBER>.
943
944 NOTE that this returns a "magic" value that can not be used as a function.
945 It's only useful when passed directly as a filter to B<items>.
946
947 =cut
948
949 sub f_number {
950 my $number = shift;
951
952 croak "No number specified"
953 unless $number && $number =~ /^\d+$/;
954
955 return [ F_NUMBER, $number ];
956 }
957
958
959 =item B<f_name NAME>
960
961 Returns a filter that only lets through symbols with the symbol name
962 matching B<NAME>.
963
964 NOTE that this returns a "magic" value that can not be used as a function.
965 It's only useful when passed directly as a filter to B<items>.
966
967 =cut
968
969 sub f_name {
970 my $name = shift;
971
972 croak "No name specified"
973 unless $name;
974
975 return [ F_NAME, $name ];
976 }
977
978 =back
979
980 =head1 AUTHORS
981
982 Richard Levitte E<lt>levitte@openssl.orgE<gt>.
983
984 =cut
985
986 1;