2 # Copyright 2018 The OpenSSL Project Authors. All Rights Reserved.
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
9 package OpenSSL
::Ordinals
;
14 use Scalar
::Util
qw(blessed);
18 # "magic" filters, see the filters at the end of the file
25 OpenSSL::Ordinals - a private module to read and walk through ordinals
29 use OpenSSL::Ordinals;
31 my $ordinals = OpenSSL::Ordinals->new(from => "foo.num");
33 my $ordinals = OpenSSL::Ordinals->new();
34 $ordinals->load("foo.num");
36 foreach ($ordinals->items(comparator => by_name()) {
37 print $_->name(), "\n";
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.
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
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.
64 =item B<new> I<%options>
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
72 =item B<from =E<gt> FILENAME>
74 Not only create a new instance, but immediately load it with data from the
75 ordinals file FILENAME.
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
},
96 bless $instance, $class;
98 $instance->set_version($opts{version
});
99 $instance->load($opts{from
}) if defined($opts{from
});
104 =item B<$ordinals-E<gt>load FILENAME>
106 Loads the data from FILENAME into the instance. Any previously loaded data
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
118 my $filename = shift;
120 croak
"Undefined filename" unless defined($filename);
122 my @tmp_contents = ();
123 my %tmp_name2num = ();
125 open F
, '<', $filename or croak
"Unable to open $filename";
127 s
|\R
$||; # Better chomp
131 my $item = OpenSSL
::Ordinals
::Item
->new(from
=> $_);
133 my $num = $item->number();
134 croak
"Disordered ordinals, $num < $max_num"
138 push @
{$tmp_contents[$item->number()]}, $item;
139 $tmp_name2num{$item->name()} = $item->number();
143 $self->{contents
} = [ @tmp_contents ];
144 $self->{name2num
} = { %tmp_name2num };
145 $self->{maxnum
} = $max_num;
146 $self->{filename
} = $filename;
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]} ];
156 $self->{loaded_maxnum
} = $max_num;
160 =item B<$ordinals-E<gt>rewrite>
162 If an ordinals file has been loaded, it gets rewritten with the data from
163 the current work database.
170 $self->write($self->{filename
});
173 =item B<$ordinals-E<gt>write FILENAME>
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.
182 my $filename = shift;
184 croak
"Undefined filename" unless defined($filename);
188 open F
, '>', $filename or croak
"Unable to open $filename";
189 foreach ($self->items(by
=> by_number
())) {
190 print F
$_->to_string(),"\n";
193 $self->{filename
} = $filename;
194 $self->{loaded_maxnum
} = $self->{maxnum
};
198 =item B<$ordinals-E<gt>items> I<%options>
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:
206 =item B<sort =E<gt> SORTFUNCTION>
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>
212 =item B<filter =E<gt> FILTERFUNCTION>
214 FILTERFUNTION is a reference to a function that takes one argument, which
215 is every OpenSSL::Ordinals::Item element available.
225 my $comparator = $opts{sort};
226 my $filter = $opts{filter
} // sub { 1; };
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] // []} : ();
238 croak __PACKAGE__
."->items called with invalid filter";
240 } elsif (ref($filter) eq 'CODE') {
241 @l = grep { $filter->($_) }
243 @
{$self->{contents
}};
245 croak __PACKAGE__
."->items called with invalid filter";
248 return sort { $comparator->($a, $b); } @l
249 if (defined $comparator);
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
264 if (scalar @items < 1 || scalar @items > 2) {
265 croak
"Wrong number of items: ", scalar @items, " : ",
266 join(", ", map { $_->name() } @items), "\n";
268 if (scalar @items == 2) {
274 $numbers{$_->number()} = 1;
275 $versions{$_->version()} = 1;
276 foreach ($_->features()) {
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);
289 # Check that both items run with the same features
292 foreach (keys %features) {
293 delete $features{$_} if $features{$_} == 2;
295 croak
"Features not in common between ",
296 $items[0]->name(), " and ", $items[1]->name(), ":",
297 join(", ", sort keys %features), "\n"
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: ",
308 map { my %tmp_h = $_->platforms();
309 $_->name().":".$platform
311 .$tmp_h{$platform} } @items),
315 # We're done with these
316 delete $platforms[0]->{$platform};
317 delete $platforms[1]->{$platform};
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";
326 $self->{contents
}->[$items[0]->number()] = [ @items ];
329 sub _parse_platforms
{
336 my $op = !(defined $1 && $1 eq '!');
339 if ($def =~ m{^_?WIN32$}) { $platforms{$&} = $op; }
340 if ($def =~ m{^__FreeBSD__$}) { $platforms{$&} = $op; }
342 # if ($def =~ m{^__DragonFly__$}) { $platforms{$&} = $op; }
343 # if ($def =~ m{^__OpenBSD__$}) { $platforms{$&} = $op; }
344 # if ($def =~ m{^__NetBSD__$}) { $platforms{$&} = $op; }
345 if ($def =~ m{^OPENSSL_SYS_}) { $platforms{$'} = $op; }
351 sub _parse_features
{
358 my $op = !(defined $1 && $1 eq '!');
361 if ($def =~ m{^ZLIB$}) { $features{$&} = $op; }
362 if ($def =~ m{^OPENSSL_USE_}) { $features{$'} = $op; }
363 if ($def =~ m{^OPENSSL_NO_}) { $features{$'} = !$op; }
364 if ($def =~ m{^DEPRECATEDIN_(.*)$}) { $features{$&} = !$op; }
370 sub _adjust_version {
373 my $baseversion = $self->{baseversion};
375 $version = $baseversion
376 if ($baseversion ne '*' && $version ne '*'
377 && cmp_versions($baseversion, $version) > 0);
382 =item B<$ordinals-E<gt>add NAME, TYPE, LIST>
384 Adds a new item named NAME with the type TYPE, and a set of C macros in
385 LIST that are expected to be defined or undefined to use this symbol, if
386 any. For undefined macros, they each must be prefixed with a C<!>.
388 If this symbol already exists in loaded data, it will be rewritten using
389 the new input data, but will keep the same ordinal number and version.
390 If it's entirely new
, it will get a new number
and the current
default
391 version
. The new ordinal number is a simple increment from the
last
399 my $type = shift; # FUNCTION or VARIABLE
400 my @defs = @_; # Macros from #ifdef and #ifndef
401 # (the latter prefixed with a '!')
403 # call signature for debug output
404 my $verbsig = "add('$name' , '$type' , [ " . join(', ', @defs) . " ])";
406 croak __PACKAGE__
."->add got a bad type '$type'"
407 unless $type eq 'FUNCTION' || $type eq 'VARIABLE';
409 my %platforms = _parse_platforms
(@defs);
410 my %features = _parse_features
(@defs);
412 my @items = $self->items(filter
=> f_name
($name));
413 my $version = @items ?
$items[0]->version() : $self->{currversion
};
414 my $number = @items ?
$items[0]->number() : ++$self->{maxnum
};
415 print STDERR
"DEBUG[",__PACKAGE__
,":add] $verbsig\n",
416 @items ?
map { "\t".$_->to_string()."\n" } @items : "No previous items\n",
418 @items = grep { $_->exists() } @items;
421 OpenSSL
::Ordinals
::Item
->new( name
=> $name,
425 $self->_adjust_version($version),
427 platforms
=> { %platforms },
429 grep { $features{$_} } keys %features
432 push @items, $new_item;
433 print STDERR
"DEBUG[",__PACKAGE__
,"::add] $verbsig\n", map { "\t".$_->to_string()."\n" } @items
435 $self->_putback(@items);
437 # If an alias was defined beforehand, add an item for it now
438 my $alias = $self->{aliases
}->{$name};
439 delete $self->{aliases
}->{$name};
441 # For the caller to show
442 my @returns = ( $new_item );
443 push @returns, $self->add_alias($alias->{name
}, $name, @
{$alias->{defs
}})
448 =item B<$ordinals-E<gt>add_alias ALIAS, NAME, LIST>
450 Adds an alias ALIAS for the symbol NAME, and a set of C macros in LIST
451 that are expected to be defined or undefined to use this symbol, if any.
452 For undefined macros, they each must be prefixed with a C<!>.
454 If this symbol already exists in loaded data, it will be rewritten using
455 the new input data. Otherwise, the data will just be store away, to wait
456 that the symbol NAME shows up.
462 my $alias = shift; # This is the alias being added
463 my $name = shift; # For this name (assuming it exists)
464 my @defs = @_; # Platform attributes for the alias
466 # call signature for debug output
468 "add_alias('$alias' , '$name' , [ " . join(', ', @defs) . " ])";
470 croak
"You're kidding me..." if $alias eq $name;
472 my %platforms = _parse_platforms
(@defs);
473 my %features = _parse_features
(@defs);
475 croak
"Alias with associated features is forbidden\n"
478 my $f_byalias = f_name
($alias);
479 my $f_byname = f_name
($name);
480 my @items = $self->items(filter
=> $f_byalias);
481 foreach my $item ($self->items(filter
=> $f_byname)) {
482 push @items, $item unless grep { $_ == $item } @items;
484 @items = grep { $_->exists() } @items;
486 croak
"Alias already exists ($alias => $name)"
487 if scalar @items > 1;
488 if (scalar @items == 0) {
489 # The item we want to alias for doesn't exist yet, so we cache the
490 # alias and hope the item we're making an alias of shows up later
491 $self->{aliases
}->{$name} = { name
=> $alias, defs
=> [ @defs ] };
493 print STDERR
"DEBUG[",__PACKAGE__
,":add_alias] $verbsig\n",
494 "\tSet future alias $alias => $name\n"
497 } elsif (scalar @items == 1) {
498 # The rule is that an alias is more or less a copy of the original
499 # item, just with another name. Also, the platforms given here are
500 # given to the original item as well, with opposite values.
501 my %alias_platforms = $items[0]->platforms();
502 foreach (keys %platforms) {
503 $alias_platforms{$_} = !$platforms{$_};
505 # We supposedly do now know how to do this... *ahem*
506 $items[0]->{platforms
} = { %alias_platforms };
508 my $alias_item = OpenSSL
::Ordinals
::Item
->new(
510 type
=> $items[0]->type(),
511 number
=> $items[0]->number(),
512 version
=> $self->_adjust_version($items[0]->version()),
513 exists => $items[0]->exists(),
514 platforms
=> { %platforms },
515 features
=> [ $items[0]->features() ]
517 push @items, $alias_item;
519 print STDERR
"DEBUG[",__PACKAGE__
,":add_alias] $verbsig\n",
520 map { "\t".$_->to_string()."\n" } @items
522 $self->_putback(@items);
524 # For the caller to show
525 return ( $alias_item->to_string() );
527 croak
"$name has an alias already (trying to add alias $alias)\n",
528 "\t", join(", ", map { $_->name() } @items), "\n";
531 =item B<$ordinals-E<gt>set_version VERSION>
533 =item B<$ordinals-E<gt>set_version VERSION BASEVERSION>
535 Sets the default version for new symbol to VERSION.
537 If given, BASEVERSION sets the base version, i.e. the minimum version
538 for all symbols. If not given, it will be calculated as follows:
542 If the given version is '*', then the base version will also be '*'.
544 If the given version starts with '0.', the base version will be '0.0.0'.
546 If the given version starts with '1.0.', the base version will be '1.0.0'.
548 If the given version starts with '1.1.', the base version will be '1.1.0'.
550 If the given version has a first number C<N> that's greater than 1, the
551 base version will be formed from C<N>: 'N.0.0'.
559 # '*' is for "we don't care"
560 my $version = shift // '*';
561 my $baseversion = shift // '*';
563 $version =~ s
|-.*||g
;
565 if ($baseversion eq '*') {
566 $baseversion = $version;
567 if ($baseversion ne '*') {
568 if ($baseversion =~ m
|^(\d
+)\
.|, $1 > 1) {
569 $baseversion = "$1.0.0";
571 $baseversion =~ s
|^0\
..*$|0.0.0|;
572 $baseversion =~ s
|^1\
.0\
..*$|1.0.0|;
573 $baseversion =~ s
|^1\
.1\
..*$|1.1.0|;
575 die 'Invalid version'
576 if ($baseversion ne '0.0.0'
577 && $baseversion !~ m
|^1\
.[01]\
.0$|);
582 die 'Invalid base version'
583 if ($baseversion ne '*' && $version ne '*'
584 && cmp_versions
($baseversion, $version) > 0);
586 $self->{currversion
} = $version;
587 $self->{baseversion
} = $baseversion;
588 foreach ($self->items(filter
=> sub { $_[0] eq '*' })) {
589 $_->{version
} = $self->{currversion
};
594 =item B<$ordinals-E<gt>invalidate>
596 Invalidates the whole working database. The practical effect is that all
597 symbols are set to not exist, but are kept around in the database to retain
598 ordinal numbers and versions.
605 foreach (@
{$self->{contents
}}) {
606 foreach (@
{$_ // []}) {
613 =item B<$ordinals-E<gt>validate>
615 Validates the current working database by collection statistics on how many
616 symbols were added and how many were changed. These numbers can be retrieved
617 with B<$ordinals-E<gt>stats>.
625 for my $i (1..$self->{maxnum
}) {
626 if ($i > $self->{loaded_maxnum
}
627 || (!@
{$self->{loaded_contents
}->[$i] // []}
628 && @
{$self->{contents
}->[$i] // []})) {
629 $self->{stats
}->{new
}++;
631 next if ($i > $self->{loaded_maxnum
});
634 map { $_->to_string() } @
{$self->{loaded_contents
}->[$i] // []};
635 my @current_strings =
636 map { $_->to_string() } @
{$self->{contents
}->[$i] // []};
638 foreach my $str (@current_strings) {
639 @loaded_strings = grep { $str ne $_ } @loaded_strings;
641 if (@loaded_strings) {
642 $self->{stats
}->{modified
}++;
647 =item B<$ordinals-E<gt>stats>
649 Returns the statistics that B<validate> calculate.
656 return %{$self->{stats
}};
663 Data elements, which is each line in an ordinals file, are instances
664 of a separate class, OpenSSL::Ordinals::Item, with its own methods:
670 package OpenSSL
::Ordinals
::Item
;
676 =item B<new> I<%options>
678 Creates a new instance of the C<OpenSSL::Ordinals::Item> class. It takes
679 options in keyed pair form, i.e. a series of C<key =E<gt> value> pairs.
680 Available options are:
684 =item B<from =E<gt> STRING>
686 This will create a new item, filled with data coming from STRING.
688 STRING must conform to the following EBNF description:
690 ordinal string = symbol, spaces, ordinal, spaces, version, spaces,
691 exist, ":", platforms, ":", type, ":", features;
692 spaces = space, { space };
694 symbol = ( letter | "_"), { letter | digit | "_" };
696 version = number, "_", number, "_", number, [ letter, [ letter ] ];
697 exist = "EXIST" | "NOEXIST";
698 platforms = platform, { ",", platform };
699 platform = ( letter | "_" ) { letter | digit | "_" };
700 type = "FUNCTION" | "VARIABLE";
701 features = feature, { ",", feature };
702 feature = ( letter | "_" ) { letter | digit | "_" };
703 number = digit, { digit };
705 (C<letter> and C<digit> are assumed self evident)
707 =item B<name =E<gt> STRING>, B<number =E<gt> NUMBER>, B<version =E<gt> STRING>,
708 B<exists =E<gt> BOOLEAN>, B<type =E<gt> STRING>,
709 B<platforms =E<gt> HASHref>, B<features =E<gt> LISTref>
711 This will create a new item with data coming from the arguments.
720 if (ref($_[0]) eq $class) {
721 return $class->new( map { $_ => $_[0]->{$_} } keys %{$_[0]} );
726 croak
"No argument given" unless %opts;
728 my $instance = undef;
730 my @a = split /\s+/, $opts{from
};
732 croak
"Badly formatted ordinals string: $opts{from}"
733 unless ( scalar @a == 4
734 && $a[0] =~ /^[A-Za-z_][A-Za-z_0-9]*$/
736 && $a[2] =~ /^(?:\*|\d+_\d+_\d+[a-z]{0,2})$/
740 (?
:FUNCTION
|VARIABLE
):
745 my @b = split /:/, $a[3];
746 %opts = ( name
=> $a[0],
749 exists => $b[0] eq 'EXIST',
750 platforms
=> { map { m
|^(!)?
|; $' => !$1 }
753 features => [ split /,/,$b[3] // '' ] );
756 if ($opts{name} && $opts{version} && defined $opts{exists} && $opts{type}
757 && ref($opts{platforms} // {}) eq 'HASH
'
758 && ref($opts{features} // []) eq 'ARRAY
') {
759 my $version = $opts{version};
762 $instance = { name => $opts{name},
764 number => $opts{number},
766 exists => !!$opts{exists},
767 platforms => { %{$opts{platforms} // {}} },
768 features => [ sort @{$opts{features} // []} ] };
770 croak __PACKAGE__."->new() called with bad arguments\n".
771 join("", map { " $_\t=> ".$opts{$_}."\n" } sort keys %opts);
774 return bless $instance, $class;
780 =item B<$item-E<gt>name>
782 The symbol name for this item.
784 =item B<$item-E<gt>number>
786 The positional number for this item.
788 =item B<$item-E<gt>version>
790 The version number for this item. Please note that these version numbers
791 have underscore (C<_>) as a separator the the version parts.
793 =item B<$item-E<gt>exists>
795 A boolean that tells if this symbol exists in code or not.
797 =item B<$item-E<gt>platforms>
799 A hash table reference. The keys of the hash table are the names of
800 the specified platforms, with a value of 0 to indicate that this symbol
801 isn't available on that platform
, and 1 to indicate that it is
. Platforms
802 that aren
't mentioned default to 1.
804 =item B<$item-E<gt>type>
806 C<FUNCTION> or C<VARIABLE>, depending on what the symbol represents.
807 Some platforms do not care about this, others do.
809 =item B<$item-E<gt>features>
811 An array reference, where every item indicates a feature where this symbol
812 is available. If no features are mentioned, the symbol is always available.
813 If any feature is mentioned, this symbol is I<only> available when those
814 features are enabled.
823 my $funcname = $AUTOLOAD;
824 (my $item = $funcname) =~ s|.*::||g;
826 croak "$funcname called as setter" if @_;
827 croak "$funcname invalid" unless exists $self->{$item};
828 return $self->{$item} if ref($self->{$item}) eq '';
829 return @{$self->{$item}} if ref($self->{$item}) eq 'ARRAY
';
830 return %{$self->{$item}} if ref($self->{$item}) eq 'HASH
';
833 =item B<$item-E<gt>to_string>
835 Converts the item to a string that can be saved in an ordinals file.
842 croak "Too many arguments" if @_;
843 my %platforms = $self->platforms();
844 my @features = $self->features();
845 my $version = $self->version();
846 $version =~ s|\.|_|g;
847 return sprintf "%-39s %d\t%s\t%s:%s:%s:%s",
851 $self->exists() ? 'EXIST
' : 'NOEXIST
',
852 join(',', (map { ($platforms{$_} ? '' : '!') . $_ }
853 sort keys %platforms)),
855 join(',', @features);
860 =head2 Comparators and filters
862 For the B<$ordinals-E<gt>items> method, there are a few functions to create
863 comparators based on specific data:
869 # Go back to the main package to create comparators and filters
870 package OpenSSL::Ordinals;
876 Returns a comparator that will compare the names of two OpenSSL::Ordinals::Item
882 return sub { $_[0]->name() cmp $_[1]->name() };
887 Returns a comparator that will compare the ordinal numbers of two
888 OpenSSL::Ordinals::Item objects.
893 return sub { $_[0]->number() <=> $_[1]->number() };
898 Returns a comparator that will compare the version of two
899 OpenSSL::Ordinals::Item objects.
905 # cmp_versions comes from OpenSSL::Util
906 return cmp_versions($_[0]->version(), $_[1]->version());
912 There are also the following filters:
918 # Filters... these are called by grep, the return sub must use $_ for
921 =item B<f_version VERSION>
923 Returns a filter that only lets through symbols with a version number
931 croak "No version specified"
932 unless $version && $version =~ /^\d+\.\d+\.\d+[a-z]{0,2}$/;
934 return sub { $_[0]->version() eq $version };
937 =item B<f_number NUMBER>
939 Returns a filter that only lets through symbols with the ordinal number
942 NOTE that this returns a "magic" value that can not be used as a function.
943 It's only useful
when passed directly as a filter to B
<items
>.
950 croak
"No number specified"
951 unless $number && $number =~ /^\d+$/;
953 return [ F_NUMBER
, $number ];
959 Returns a filter that only lets through symbols with the symbol name
962 NOTE that this returns a "magic" value that can not be used as a function.
963 It's only useful when passed directly as a filter to B<items>.
970 croak
"No name specified"
973 return [ F_NAME
, $name ];
980 Richard Levitte E<lt>levitte@openssl.orgE<gt>.