VirtualBox

source: vbox/trunk/src/libs/openssl-3.1.5/util/perl/OpenSSL/Ordinals.pm@ 104078

Last change on this file since 104078 was 104078, checked in by vboxsync, 11 months ago

openssl-3.1.5: Applied and adjusted our OpenSSL changes to 3.1.4. bugref:10638

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette