2
0

Ordinals.pm 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  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. package OpenSSL::Ordinals;
  9. use strict;
  10. use warnings;
  11. use Carp;
  12. use Scalar::Util qw(blessed);
  13. use OpenSSL::Util;
  14. use constant {
  15. # "magic" filters, see the filters at the end of the file
  16. F_NAME => 1,
  17. F_NUMBER => 2,
  18. };
  19. =head1 NAME
  20. OpenSSL::Ordinals - a private module to read and walk through ordinals
  21. =head1 SYNOPSIS
  22. use OpenSSL::Ordinals;
  23. my $ordinals = OpenSSL::Ordinals->new(from => "foo.num");
  24. # or alternatively
  25. my $ordinals = OpenSSL::Ordinals->new();
  26. $ordinals->load("foo.num");
  27. foreach ($ordinals->items(comparator => by_name()) {
  28. print $_->name(), "\n";
  29. }
  30. =head1 DESCRIPTION
  31. This is a OpenSSL private module to load an ordinals (F<.num>) file and
  32. write out the data you want, sorted and filtered according to your rules.
  33. An ordinals file is a file that enumerates all the symbols that a shared
  34. library or loadable module must export. Each of them have a unique
  35. assigned number as well as other attributes to indicate if they only exist
  36. on a subset of the supported platforms, or if they are specific to certain
  37. features.
  38. The unique numbers each symbol gets assigned needs to be maintained for a
  39. shared library or module to stay compatible with previous versions on
  40. platforms that maintain a transfer vector indexed by position rather than
  41. by name. They also help keep information on certain symbols that are
  42. aliases for others for certain platforms, or that have different forms
  43. on different platforms.
  44. =head2 Main methods
  45. =over 4
  46. =cut
  47. =item B<new> I<%options>
  48. Creates a new instance of the C<OpenSSL::Ordinals> class. It takes options
  49. in keyed pair form, i.e. a series of C<key =E<gt> value> pairs. Available
  50. options are:
  51. =over 4
  52. =item B<from =E<gt> FILENAME>
  53. Not only create a new instance, but immediately load it with data from the
  54. ordinals file FILENAME.
  55. =back
  56. =cut
  57. sub new {
  58. my $class = shift;
  59. my %opts = @_;
  60. my $instance = {
  61. filename => undef, # File name registered when loading
  62. loaded_maxnum => 0, # Highest allocated item number when loading
  63. loaded_contents => [], # Loaded items, if loading there was
  64. maxnum => 0, # Current highest allocated item number
  65. contents => [], # Items, indexed by number
  66. name2num => {}, # Name to number dictionary
  67. aliases => {}, # Aliases cache.
  68. stats => {}, # Statistics, see 'sub validate'
  69. debug => $opts{debug},
  70. };
  71. bless $instance, $class;
  72. $instance->set_version($opts{version});
  73. $instance->load($opts{from}) if defined($opts{from});
  74. return $instance;
  75. }
  76. =item B<$ordinals-E<gt>load FILENAME>
  77. Loads the data from FILENAME into the instance. Any previously loaded data
  78. is dropped.
  79. Two internal databases are created. One database is simply a copy of the file
  80. contents and is treated as read-only. The other database is an exact copy of
  81. the first, but is treated as a work database, i.e. it can be modified and added
  82. to.
  83. =cut
  84. sub load {
  85. my $self = shift;
  86. my $filename = shift;
  87. croak "Undefined filename" unless defined($filename);
  88. my @tmp_contents = ();
  89. my %tmp_name2num = ();
  90. my $max_num = 0;
  91. open F, '<', $filename or croak "Unable to open $filename";
  92. while (<F>) {
  93. s|\R$||; # Better chomp
  94. s|#.*||;
  95. next if /^\s*$/;
  96. my $item = OpenSSL::Ordinals::Item->new(from => $_);
  97. my $num = $item->number();
  98. croak "Disordered ordinals, $num < $max_num"
  99. if $num < $max_num;
  100. $max_num = $num;
  101. push @{$tmp_contents[$item->number()]}, $item;
  102. $tmp_name2num{$item->name()} = $item->number();
  103. }
  104. close F;
  105. $self->{contents} = [ @tmp_contents ];
  106. $self->{name2num} = { %tmp_name2num };
  107. $self->{maxnum} = $max_num;
  108. $self->{filename} = $filename;
  109. # Make a deep copy, allowing {contents} to be an independent work array
  110. foreach my $i (1..$max_num) {
  111. if ($tmp_contents[$i]) {
  112. $self->{loaded_contents}->[$i] =
  113. [ map { OpenSSL::Ordinals::Item->new($_) }
  114. @{$tmp_contents[$i]} ];
  115. }
  116. }
  117. $self->{loaded_maxnum} = $max_num;
  118. return 1;
  119. }
  120. =item B<$ordinals-E<gt>rewrite>
  121. If an ordinals file has been loaded, it gets rewritten with the data from
  122. the current work database.
  123. =cut
  124. sub rewrite {
  125. my $self = shift;
  126. $self->write($self->{filename});
  127. }
  128. =item B<$ordinals-E<gt>write FILENAME>
  129. Writes the current work database data to the ordinals file FILENAME.
  130. This also validates the data, see B<$ordinals-E<gt>validate> below.
  131. =cut
  132. sub write {
  133. my $self = shift;
  134. my $filename = shift;
  135. croak "Undefined filename" unless defined($filename);
  136. $self->validate();
  137. open F, '>', $filename or croak "Unable to open $filename";
  138. foreach ($self->items(by => by_number())) {
  139. print F $_->to_string(),"\n";
  140. }
  141. close F;
  142. $self->{filename} = $filename;
  143. $self->{loaded_maxnum} = $self->{maxnum};
  144. return 1;
  145. }
  146. =item B<$ordinals-E<gt>items> I<%options>
  147. Returns a list of items according to a set of criteria. The criteria is
  148. given in form keyed pair form, i.e. a series of C<key =E<gt> value> pairs.
  149. Available options are:
  150. =over 4
  151. =item B<sort =E<gt> SORTFUNCTION>
  152. SORTFUNCTION is a reference to a function that takes two arguments, which
  153. correspond to the classic C<$a> and C<$b> that are available in a C<sort>
  154. block.
  155. =item B<filter =E<gt> FILTERFUNCTION>
  156. FILTERFUNTION is a reference to a function that takes one argument, which
  157. is every OpenSSL::Ordinals::Item element available.
  158. =back
  159. =cut
  160. sub items {
  161. my $self = shift;
  162. my %opts = @_;
  163. my $comparator = $opts{sort};
  164. my $filter = $opts{filter} // sub { 1; };
  165. my @l = undef;
  166. if (ref($filter) eq 'ARRAY') {
  167. # run a "magic" filter
  168. if ($filter->[0] == F_NUMBER) {
  169. my $index = $filter->[1];
  170. @l = $index ? @{$self->{contents}->[$index] // []} : ();
  171. } elsif ($filter->[0] == F_NAME) {
  172. my $index = $self->{name2num}->{$filter->[1]};
  173. @l = $index ? @{$self->{contents}->[$index] // []} : ();
  174. } else {
  175. croak __PACKAGE__."->items called with invalid filter";
  176. }
  177. } elsif (ref($filter) eq 'CODE') {
  178. @l = grep { $filter->($_) }
  179. map { @{$_ // []} }
  180. @{$self->{contents}};
  181. } else {
  182. croak __PACKAGE__."->items called with invalid filter";
  183. }
  184. return sort { $comparator->($a, $b); } @l
  185. if (defined $comparator);
  186. return @l;
  187. }
  188. # Put an array of items back into the object after having checked consistency
  189. # If there are exactly two items:
  190. # - They MUST have the same number
  191. # - For platforms, both MUST hold the same ones, but with opposite values
  192. # - For features, both MUST hold the same ones.
  193. # If there's just one item, just put it in the slot of its number
  194. # In all other cases, something is wrong
  195. sub _putback {
  196. my $self = shift;
  197. my @items = @_;
  198. if (scalar @items < 1 || scalar @items > 2) {
  199. croak "Wrong number of items: ", scalar @items, " : ",
  200. join(", ", map { $_->name() } @items), "\n";
  201. }
  202. if (scalar @items == 2) {
  203. # Collect some data
  204. my %numbers = ();
  205. my %versions = ();
  206. my %features = ();
  207. foreach (@items) {
  208. $numbers{$_->number()} = 1;
  209. $versions{$_->version()} = 1;
  210. foreach ($_->features()) {
  211. $features{$_}++;
  212. }
  213. }
  214. # Check that all items we're trying to put back have the same number
  215. croak "Items don't have the same numeral: ",
  216. join(", ", map { $_->name()." => ".$_->number() } @items), "\n"
  217. if (scalar keys %numbers > 1);
  218. croak "Items don't have the same version: ",
  219. join(", ", map { $_->name()." => ".$_->version() } @items), "\n"
  220. if (scalar keys %versions > 1);
  221. # Check that both items run with the same features
  222. foreach (@items) {
  223. }
  224. foreach (keys %features) {
  225. delete $features{$_} if $features{$_} == 2;
  226. }
  227. croak "Features not in common between ",
  228. $items[0]->name(), " and ", $items[1]->name(), ":",
  229. join(", ", sort keys %features), "\n"
  230. if %features;
  231. # Check that all platforms exist in both items, and have opposite values
  232. my @platforms = ( { $items[0]->platforms() },
  233. { $items[1]->platforms() } );
  234. foreach my $platform (keys %{$platforms[0]}) {
  235. if (exists $platforms[1]->{$platform}) {
  236. if ($platforms[0]->{$platform} != !$platforms[1]->{$platform}) {
  237. croak "Platforms aren't opposite: ",
  238. join(", ",
  239. map { my %tmp_h = $_->platforms();
  240. $_->name().":".$platform
  241. ." => "
  242. .$tmp_h{$platform} } @items),
  243. "\n";
  244. }
  245. # We're done with these
  246. delete $platforms[0]->{$platform};
  247. delete $platforms[1]->{$platform};
  248. }
  249. }
  250. # If there are any remaining platforms, something's wrong
  251. if (%{$platforms[0]} || %{$platforms[0]}) {
  252. croak "There are platforms not in common between ",
  253. $items[0]->name(), " and ", $items[1]->name(), "\n";
  254. }
  255. }
  256. $self->{contents}->[$items[0]->number()] = [ @items ];
  257. }
  258. sub _parse_platforms {
  259. my $self = shift;
  260. my @defs = @_;
  261. my %platforms = ();
  262. foreach (@defs) {
  263. m{^(!)?};
  264. my $op = !(defined $1 && $1 eq '!');
  265. my $def = $';
  266. if ($def =~ m{^_?WIN32$}) { $platforms{$&} = $op; }
  267. if ($def =~ m{^__FreeBSD__$}) { $platforms{$&} = $op; }
  268. # For future support
  269. # if ($def =~ m{^__DragonFly__$}) { $platforms{$&} = $op; }
  270. # if ($def =~ m{^__OpenBSD__$}) { $platforms{$&} = $op; }
  271. # if ($def =~ m{^__NetBSD__$}) { $platforms{$&} = $op; }
  272. if ($def =~
  273. m{^OPENSSL_(EXPORT_VAR_AS_FUNCTION)$}) { $platforms{$1} = $op; }
  274. if ($def =~ m{^OPENSSL_SYS_}) { $platforms{$'} = $op; }
  275. }
  276. return %platforms;
  277. }
  278. sub _parse_features {
  279. my $self = shift;
  280. my @defs = @_;
  281. my %features = ();
  282. foreach (@defs) {
  283. m{^(!)?};
  284. my $op = !(defined $1 && $1 eq '!');
  285. my $def = $';
  286. if ($def =~ m{^ZLIB$}) { $features{$&} = $op; }
  287. if ($def =~ m{^OPENSSL_USE_}) { $features{$'} = $op; }
  288. if ($def =~ m{^OPENSSL_NO_}) { $features{$'} = !$op; }
  289. if ($def =~ m{^DEPRECATEDIN_(.*)$}) { $features{$&} = !$op; }
  290. }
  291. return %features;
  292. }
  293. sub _adjust_version {
  294. my $self = shift;
  295. my $version = shift;
  296. my $baseversion = $self->{baseversion};
  297. $version = $baseversion
  298. if ($baseversion ne '*' && $version ne '*'
  299. && cmp_versions($baseversion, $version) > 0);
  300. return $version;
  301. }
  302. =item B<$ordinals-E<gt>add NAME, TYPE, LIST>
  303. Adds a new item named NAME with the type TYPE, and a set of C macros in
  304. LIST that are expected to be defined or undefined to use this symbol, if
  305. any. For undefined macros, they each must be prefixed with a C<!>.
  306. If this symbol already exists in loaded data, it will be rewritten using
  307. the new input data, but will keep the same ordinal number and version.
  308. If it's entirely new, it will get a new number and the current default
  309. version. The new ordinal number is a simple increment from the last
  310. maximum number.
  311. =cut
  312. sub add {
  313. my $self = shift;
  314. my $name = shift;
  315. my $type = shift; # FUNCTION or VARIABLE
  316. my @defs = @_; # Macros from #ifdef and #ifndef
  317. # (the latter prefixed with a '!')
  318. # call signature for debug output
  319. my $verbsig = "add('$name' , '$type' , [ " . join(', ', @defs) . " ])";
  320. croak __PACKAGE__."->add got a bad type '$type'"
  321. unless $type eq 'FUNCTION' || $type eq 'VARIABLE';
  322. my %platforms = _parse_platforms(@defs);
  323. my %features = _parse_features(@defs);
  324. my @items = $self->items(filter => f_name($name));
  325. my $version = @items ? $items[0]->version() : $self->{currversion};
  326. my $number = @items ? $items[0]->number() : ++$self->{maxnum};
  327. print STDERR "DEBUG[",__PACKAGE__,":add] $verbsig\n",
  328. @items ? map { "\t".$_->to_string()."\n" } @items : "No previous items\n",
  329. if $self->{debug};
  330. @items = grep { $_->exists() } @items;
  331. my $new_item =
  332. OpenSSL::Ordinals::Item->new( name => $name,
  333. type => $type,
  334. number => $number,
  335. version =>
  336. $self->_adjust_version($version),
  337. exists => 1,
  338. platforms => { %platforms },
  339. features => [
  340. grep { $features{$_} } keys %features
  341. ] );
  342. push @items, $new_item;
  343. print STDERR "DEBUG[",__PACKAGE__,"::add] $verbsig\n", map { "\t".$_->to_string()."\n" } @items
  344. if $self->{debug};
  345. $self->_putback(@items);
  346. # If an alias was defined beforehand, add an item for it now
  347. my $alias = $self->{aliases}->{$name};
  348. delete $self->{aliases}->{$name};
  349. # For the caller to show
  350. my @returns = ( $new_item );
  351. push @returns, $self->add_alias($alias->{name}, $name, @{$alias->{defs}})
  352. if defined $alias;
  353. return @returns;
  354. }
  355. =item B<$ordinals-E<gt>add_alias ALIAS, NAME, LIST>
  356. Adds an alias ALIAS for the symbol NAME, and a set of C macros in LIST
  357. that are expected to be defined or undefined to use this symbol, if any.
  358. For undefined macros, they each must be prefixed with a C<!>.
  359. If this symbol already exists in loaded data, it will be rewritten using
  360. the new input data. Otherwise, the data will just be store away, to wait
  361. that the symbol NAME shows up.
  362. =cut
  363. sub add_alias {
  364. my $self = shift;
  365. my $alias = shift; # This is the alias being added
  366. my $name = shift; # For this name (assuming it exists)
  367. my @defs = @_; # Platform attributes for the alias
  368. # call signature for debug output
  369. my $verbsig =
  370. "add_alias('$alias' , '$name' , [ " . join(', ', @defs) . " ])";
  371. croak "You're kidding me..." if $alias eq $name;
  372. my %platforms = _parse_platforms(@defs);
  373. my %features = _parse_features(@defs);
  374. croak "Alias with associated features is forbidden\n"
  375. if %features;
  376. my $f_byalias = f_name($alias);
  377. my $f_byname = f_name($name);
  378. my @items = $self->items(filter => $f_byalias);
  379. foreach my $item ($self->items(filter => $f_byname)) {
  380. push @items, $item unless grep { $_ == $item } @items;
  381. }
  382. @items = grep { $_->exists() } @items;
  383. croak "Alias already exists ($alias => $name)"
  384. if scalar @items > 1;
  385. if (scalar @items == 0) {
  386. # The item we want to alias for doesn't exist yet, so we cache the
  387. # alias and hope the item we're making an alias of shows up later
  388. $self->{aliases}->{$name} = { name => $alias, defs => [ @defs ] };
  389. print STDERR "DEBUG[",__PACKAGE__,":add_alias] $verbsig\n",
  390. "\tSet future alias $alias => $name\n"
  391. if $self->{debug};
  392. return ();
  393. } elsif (scalar @items == 1) {
  394. # The rule is that an alias is more or less a copy of the original
  395. # item, just with another name. Also, the platforms given here are
  396. # given to the original item as well, with opposite values.
  397. my %alias_platforms = $items[0]->platforms();
  398. foreach (keys %platforms) {
  399. $alias_platforms{$_} = !$platforms{$_};
  400. }
  401. # We supposedly do now know how to do this... *ahem*
  402. $items[0]->{platforms} = { %alias_platforms };
  403. my $alias_item = OpenSSL::Ordinals::Item->new(
  404. name => $alias,
  405. type => $items[0]->type(),
  406. number => $items[0]->number(),
  407. version => $self->_adjust_version($items[0]->version()),
  408. exists => $items[0]->exists(),
  409. platforms => { %platforms },
  410. features => [ $items[0]->features() ]
  411. );
  412. push @items, $alias_item;
  413. print STDERR "DEBUG[",__PACKAGE__,":add_alias] $verbsig\n",
  414. map { "\t".$_->to_string()."\n" } @items
  415. if $self->{debug};
  416. $self->_putback(@items);
  417. # For the caller to show
  418. return ( $alias_item->to_string() );
  419. }
  420. croak "$name has an alias already (trying to add alias $alias)\n",
  421. "\t", join(", ", map { $_->name() } @items), "\n";
  422. }
  423. =item B<$ordinals-E<gt>set_version VERSION>
  424. =item B<$ordinals-E<gt>set_version VERSION BASEVERSION>
  425. Sets the default version for new symbol to VERSION.
  426. If given, BASEVERSION sets the base version, i.e. the minimum version
  427. for all symbols. If not given, it will be calculated as follows:
  428. =over 4
  429. If the given version is '*', then the base version will also be '*'.
  430. If the given version starts with '0.', the base version will be '0.0.0'.
  431. If the given version starts with '1.0.', the base version will be '1.0.0'.
  432. If the given version starts with '1.1.', the base version will be '1.1.0'.
  433. If the given version has a first number C<N> that's greater than 1, the
  434. base version will be formed from C<N>: 'N.0.0'.
  435. =back
  436. =cut
  437. sub set_version {
  438. my $self = shift;
  439. # '*' is for "we don't care"
  440. my $version = shift // '*';
  441. my $baseversion = shift // '*';
  442. $version =~ s|-.*||g;
  443. if ($baseversion eq '*') {
  444. $baseversion = $version;
  445. if ($baseversion ne '*') {
  446. if ($baseversion =~ m|^(\d+)\.|, $1 > 1) {
  447. $baseversion = "$1.0.0";
  448. } else {
  449. $baseversion =~ s|^0\..*$|0.0.0|;
  450. $baseversion =~ s|^1\.0\..*$|1.0.0|;
  451. $baseversion =~ s|^1\.1\..*$|1.1.0|;
  452. die 'Invalid version'
  453. if ($baseversion ne '0.0.0'
  454. && $baseversion !~ m|^1\.[01]\.0$|);
  455. }
  456. }
  457. }
  458. die 'Invalid base version'
  459. if ($baseversion ne '*' && $version ne '*'
  460. && cmp_versions($baseversion, $version) > 0);
  461. $self->{currversion} = $version;
  462. $self->{baseversion} = $baseversion;
  463. foreach ($self->items(filter => sub { $_[0] eq '*' })) {
  464. $_->{version} = $self->{currversion};
  465. }
  466. return 1;
  467. }
  468. =item B<$ordinals-E<gt>invalidate>
  469. Invalidates the whole working database. The practical effect is that all
  470. symbols are set to not exist, but are kept around in the database to retain
  471. ordinal numbers and versions.
  472. =cut
  473. sub invalidate {
  474. my $self = shift;
  475. foreach (@{$self->{contents}}) {
  476. foreach (@{$_ // []}) {
  477. $_->{exists} = 0;
  478. }
  479. }
  480. $self->{stats} = {};
  481. }
  482. =item B<$ordinals-E<gt>validate>
  483. Validates the current working database by collection statistics on how many
  484. symbols were added and how many were changed. These numbers can be retrieved
  485. with B<$ordinals-E<gt>stats>.
  486. =cut
  487. sub validate {
  488. my $self = shift;
  489. $self->{stats} = {};
  490. for my $i (1..$self->{maxnum}) {
  491. if ($i > $self->{loaded_maxnum}
  492. || (!@{$self->{loaded_contents}->[$i] // []}
  493. && @{$self->{contents}->[$i] // []})) {
  494. $self->{stats}->{new}++;
  495. }
  496. next if ($i > $self->{loaded_maxnum});
  497. my @loaded_strings =
  498. map { $_->to_string() } @{$self->{loaded_contents}->[$i] // []};
  499. my @current_strings =
  500. map { $_->to_string() } @{$self->{contents}->[$i] // []};
  501. foreach my $str (@current_strings) {
  502. @loaded_strings = grep { $str ne $_ } @loaded_strings;
  503. }
  504. if (@loaded_strings) {
  505. $self->{stats}->{modified}++;
  506. }
  507. }
  508. }
  509. =item B<$ordinals-E<gt>stats>
  510. Returns the statistics that B<validate> calculate.
  511. =cut
  512. sub stats {
  513. my $self = shift;
  514. return %{$self->{stats}};
  515. }
  516. =back
  517. =head2 Data elements
  518. Data elements, which is each line in an ordinals file, are instances
  519. of a separate class, OpenSSL::Ordinals::Item, with its own methods:
  520. =over 4
  521. =cut
  522. package OpenSSL::Ordinals::Item;
  523. use strict;
  524. use warnings;
  525. use Carp;
  526. =item B<new> I<%options>
  527. Creates a new instance of the C<OpenSSL::Ordinals::Item> class. It takes
  528. options in keyed pair form, i.e. a series of C<key =E<gt> value> pairs.
  529. Available options are:
  530. =over 4
  531. =item B<from =E<gt> STRING>
  532. This will create a new item, filled with data coming from STRING.
  533. STRING must conform to the following EBNF description:
  534. ordinal string = symbol, spaces, ordinal, spaces, version, spaces,
  535. exist, ":", platforms, ":", type, ":", features;
  536. spaces = space, { space };
  537. space = " " | "\t";
  538. symbol = ( letter | "_"), { letter | digit | "_" };
  539. ordinal = number;
  540. version = number, "_", number, "_", number, [ letter, [ letter ] ];
  541. exist = "EXIST" | "NOEXIST";
  542. platforms = platform, { ",", platform };
  543. platform = ( letter | "_" ) { letter | digit | "_" };
  544. type = "FUNCTION" | "VARIABLE";
  545. features = feature, { ",", feature };
  546. feature = ( letter | "_" ) { letter | digit | "_" };
  547. number = digit, { digit };
  548. (C<letter> and C<digit> are assumed self evident)
  549. =item B<name =E<gt> STRING>, B<number =E<gt> NUMBER>, B<version =E<gt> STRING>,
  550. B<exists =E<gt> BOOLEAN>, B<type =E<gt> STRING>,
  551. B<platforms =E<gt> HASHref>, B<features =E<gt> LISTref>
  552. This will create a new item with data coming from the arguments.
  553. =back
  554. =cut
  555. sub new {
  556. my $class = shift;
  557. if (ref($_[0]) eq $class) {
  558. return $class->new( map { $_ => $_[0]->{$_} } keys %{$_[0]} );
  559. }
  560. my %opts = @_;
  561. croak "No argument given" unless %opts;
  562. my $instance = undef;
  563. if ($opts{from}) {
  564. my @a = split /\s+/, $opts{from};
  565. croak "Badly formatted ordinals string: $opts{from}"
  566. unless ( scalar @a == 4
  567. && $a[0] =~ /^[A-Za-z_][A-Za-z_0-9]*$/
  568. && $a[1] =~ /^\d+$/
  569. && $a[2] =~ /^(?:\*|\d+_\d+_\d+[a-z]{0,2})$/
  570. && $a[3] =~ /^
  571. (?:NO)?EXIST:
  572. [^:]*:
  573. (?:FUNCTION|VARIABLE):
  574. [^:]*
  575. $
  576. /x );
  577. my @b = split /:/, $a[3];
  578. %opts = ( name => $a[0],
  579. number => $a[1],
  580. version => $a[2],
  581. exists => $b[0] eq 'EXIST',
  582. platforms => { map { m|^(!)?|; $' => !$1 }
  583. split /,/,$b[1] },
  584. type => $b[2],
  585. features => [ split /,/,$b[3] // '' ] );
  586. }
  587. if ($opts{name} && $opts{version} && defined $opts{exists} && $opts{type}
  588. && ref($opts{platforms} // {}) eq 'HASH'
  589. && ref($opts{features} // []) eq 'ARRAY') {
  590. my $version = $opts{version};
  591. $version =~ s|_|.|g;
  592. $instance = { name => $opts{name},
  593. type => $opts{type},
  594. number => $opts{number},
  595. version => $version,
  596. exists => !!$opts{exists},
  597. platforms => { %{$opts{platforms} // {}} },
  598. features => [ sort @{$opts{features} // []} ] };
  599. } else {
  600. croak __PACKAGE__."->new() called with bad arguments\n".
  601. join("", map { " $_\t=> ".$opts{$_}."\n" } sort keys %opts);
  602. }
  603. return bless $instance, $class;
  604. }
  605. sub DESTROY {
  606. }
  607. =item B<$item-E<gt>name>
  608. The symbol name for this item.
  609. =item B<$item-E<gt>number>
  610. The positional number for this item.
  611. =item B<$item-E<gt>version>
  612. The version number for this item. Please note that these version numbers
  613. have underscore (C<_>) as a separator the the version parts.
  614. =item B<$item-E<gt>exists>
  615. A boolean that tells if this symbol exists in code or not.
  616. =item B<$item-E<gt>platforms>
  617. A hash table reference. The keys of the hash table are the names of
  618. the specified platforms, with a value of 0 to indicate that this symbol
  619. isn't available on that platform, and 1 to indicate that it is. Platforms
  620. that aren't mentioned default to 1.
  621. =item B<$item-E<gt>type>
  622. C<FUNCTION> or C<VARIABLE>, depending on what the symbol represents.
  623. Some platforms do not care about this, others do.
  624. =item B<$item-E<gt>features>
  625. An array reference, where every item indicates a feature where this symbol
  626. is available. If no features are mentioned, the symbol is always available.
  627. If any feature is mentioned, this symbol is I<only> available when those
  628. features are enabled.
  629. =cut
  630. our $AUTOLOAD;
  631. # Generic getter
  632. sub AUTOLOAD {
  633. my $self = shift;
  634. my $funcname = $AUTOLOAD;
  635. (my $item = $funcname) =~ s|.*::||g;
  636. croak "$funcname called as setter" if @_;
  637. croak "$funcname invalid" unless exists $self->{$item};
  638. return $self->{$item} if ref($self->{$item}) eq '';
  639. return @{$self->{$item}} if ref($self->{$item}) eq 'ARRAY';
  640. return %{$self->{$item}} if ref($self->{$item}) eq 'HASH';
  641. }
  642. =item B<$item-E<gt>to_string>
  643. Converts the item to a string that can be saved in an ordinals file.
  644. =cut
  645. sub to_string {
  646. my $self = shift;
  647. croak "Too many arguments" if @_;
  648. my %platforms = $self->platforms();
  649. my @features = $self->features();
  650. my $version = $self->version();
  651. $version =~ s|\.|_|g;
  652. return sprintf "%-39s %d\t%s\t%s:%s:%s:%s",
  653. $self->name(),
  654. $self->number(),
  655. $version,
  656. $self->exists() ? 'EXIST' : 'NOEXIST',
  657. join(',', (map { ($platforms{$_} ? '' : '!') . $_ }
  658. sort keys %platforms)),
  659. $self->type(),
  660. join(',', @features);
  661. }
  662. =back
  663. =head2 Comparators and filters
  664. For the B<$ordinals-E<gt>items> method, there are a few functions to create
  665. comparators based on specific data:
  666. =over 4
  667. =cut
  668. # Go back to the main package to create comparators and filters
  669. package OpenSSL::Ordinals;
  670. # Comparators...
  671. =item B<by_name>
  672. Returns a comparator that will compare the names of two OpenSSL::Ordinals::Item
  673. objects.
  674. =cut
  675. sub by_name {
  676. return sub { $_[0]->name() cmp $_[1]->name() };
  677. }
  678. =item B<by_number>
  679. Returns a comparator that will compare the ordinal numbers of two
  680. OpenSSL::Ordinals::Item objects.
  681. =cut
  682. sub by_number {
  683. return sub { $_[0]->number() <=> $_[1]->number() };
  684. }
  685. =item B<by_version>
  686. Returns a comparator that will compare the version of two
  687. OpenSSL::Ordinals::Item objects.
  688. =cut
  689. sub by_version {
  690. return sub {
  691. # cmp_versions comes from OpenSSL::Util
  692. return cmp_versions($_[0]->version(), $_[1]->version());
  693. }
  694. }
  695. =back
  696. There are also the following filters:
  697. =over 4
  698. =cut
  699. # Filters... these are called by grep, the return sub must use $_ for
  700. # the item to check
  701. =item B<f_version VERSION>
  702. Returns a filter that only lets through symbols with a version number
  703. matching B<VERSION>.
  704. =cut
  705. sub f_version {
  706. my $version = shift;
  707. croak "No version specified"
  708. unless $version && $version =~ /^\d+\.\d+\.\d+[a-z]{0,2}$/;
  709. return sub { $_[0]->version() eq $version };
  710. }
  711. =item B<f_number NUMBER>
  712. Returns a filter that only lets through symbols with the ordinal number
  713. matching B<NUMBER>.
  714. NOTE that this returns a "magic" value that can not be used as a function.
  715. It's only useful when passed directly as a filter to B<items>.
  716. =cut
  717. sub f_number {
  718. my $number = shift;
  719. croak "No number specified"
  720. unless $number && $number =~ /^\d+$/;
  721. return [ F_NUMBER, $number ];
  722. }
  723. =item B<f_name NAME>
  724. Returns a filter that only lets through symbols with the symbol name
  725. matching B<NAME>.
  726. NOTE that this returns a "magic" value that can not be used as a function.
  727. It's only useful when passed directly as a filter to B<items>.
  728. =cut
  729. sub f_name {
  730. my $name = shift;
  731. croak "No name specified"
  732. unless $name;
  733. return [ F_NAME, $name ];
  734. }
  735. =back
  736. =head1 AUTHORS
  737. Richard Levitte E<lt>levitte@openssl.orgE<gt>.
  738. =cut
  739. 1;