diff options
author | John Estabrook <jestabro@vyos.io> | 2020-12-30 10:17:53 -0600 |
---|---|---|
committer | Daniil Baturin <daniil@vyos.io> | 2021-01-04 18:10:45 +0200 |
commit | 51488d471adb4b3b0c8320ae56807053c9fb4a6b (patch) | |
tree | c79aad51d5aaddb0808a99c8d461edadef3dd058 | |
parent | 46f7aca3e98472c15f37c74d4097fd5e97542cda (diff) | |
download | vyatta-cfg-system-51488d471adb4b3b0c8320ae56807053c9fb4a6b.tar.gz vyatta-cfg-system-51488d471adb4b3b0c8320ae56807053c9fb4a6b.zip |
migration: T3161: move XorpConfigParser to vyatta-cfg-system
The scripts:
vyatta_net_name
vyatta_interface_rescan
still require XorpConfigParser; all other uses have been rewritten, or
obviated. In order to retire the package vyatta-config-migrate in favor
of keeping migration scripts in vyos-1x, we need to move this module
into vyatta-cfg-system for use by the above mentioned scripts.
-rw-r--r-- | Makefile.am | 4 | ||||
-rw-r--r-- | scripts/XorpConfigParser.pm | 618 |
2 files changed, 622 insertions, 0 deletions
diff --git a/Makefile.am b/Makefile.am index 1e377aef..c5f7dfac 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,6 +5,7 @@ checkparamsonrebootdir = $(bindir)/sudo-users/check-params-on-reboot.d initddir = /etc/init.d modprobedir = /etc/modprobe.d modprobe_DATA = etc/modprobe.d/no-copybreak.conf +share_perl5dir = $(datadir)/perl5 bin_SCRIPTS = sbin_SCRIPTS = @@ -61,6 +62,9 @@ sbin_SCRIPTS += scripts/vyos-persistpath sbin_PROGRAMS = src/valid_address sbin_PROGRAMS += src/local_ip +share_perl5_DATA = +share_perl5_DATA += scripts/XorpConfigParser.pm + src_valid_address = src/valid_address.c src_local_ip = src/local_ip.c diff --git a/scripts/XorpConfigParser.pm b/scripts/XorpConfigParser.pm new file mode 100644 index 00000000..3a4035f8 --- /dev/null +++ b/scripts/XorpConfigParser.pm @@ -0,0 +1,618 @@ +# Perl module for parsing config files. + +use lib "/opt/vyatta/share/perl5/"; + +package XorpConfigParser; + +use strict; +my %data; + +my %fields = ( _data => \%data ); + +sub new { + my $that = shift; + my $class = ref($that) || $that; + my $self = { %fields, }; + + bless $self, $class; + return $self; +} + +# +# This method is used to copy nodes whose names begin with a particular string +# from one array to another. +# +# Parameters: +# +# $from Reference to the source array +# $to Reference to the destination array +# $name The string with which the beginning of the node names will be matched +# +sub copy_node { + my ( $self, $from, $to, $name ) = @_; + if ( !defined($from) || !defined($to) || !defined($name) ) { + return; + } + + foreach my $node (@$from) { + my $stringNodeNameHere = $node->{'name'}; + if ( $stringNodeNameHere =~ /^$name.*/ ) { + foreach my $nodeCheck (@$to) { + my $stringCheck = $nodeCheck->{'name'}; + if ( $name eq $stringCheck ) { + $nodeCheck->{'value'} = $node->{'value'}; + $nodeCheck->{'children'} = $node->{'children'}; + $nodeCheck->{'comment'} = $node->{'comment'}; + $nodeCheck->{'disable'} = $node->{'disable'}; + return; + } + } + push( @$to, $node ); + } + } +} + +# +# This method is used to create duplicate copies of multinodes with the name +# specified, and to return the new copies in a new array. +# +# $nodes A reference to an array of multinodes +# $name The name of the multinodes to copy into the new array +# +sub copy_multis { + my ( $self, $nodes, $name ) = @_; + + return if ( !defined($nodes) || !defined($name) ); + + my @multis; + + foreach my $node (@$nodes) { + my $stringNodeNameHere = $node->{'name'}; + if ( $stringNodeNameHere =~ /$name\s(\S+)/ ) { + my $stringNameHere = $1; + my %multi = ( + 'name' => $stringNameHere, + 'comment' => $node->{'comment'}, + 'value' => $node->{'value'}, + 'disable' => $node->{'disable'}, + 'children' => $node->{'children'} + ); + push( @multis, \%multi ); + } + } + + return @multis; +} + +# +# This method is used to comment out a particular child. +# +# $children A reference to an array of children +# $name The name of the child to comment out +# $comment The comment string that will be included inside the comment +# +sub comment_out_child { + my ( $self, $children, $name, $comment ) = @_; + if ( !defined($children) || !defined($name) ) { + return; + } + + for ( my $i = 0 ; $i < @$children ; $i++ ) { + my $stringNodeNameHere = @$children[$i]->{'name'}; + if ( $name eq $stringNodeNameHere ) { + $self->comment_out_node( @$children[$i] ); + if ( defined($comment) ) { + @$children[$i]->{'comment_out'} = $comment; + } + } + } +} + +# +# This method is used to comment out a particular node. +# +# $node A reference to the node to comment out +# +sub comment_out_node { + my ( $self, $node ) = @_; + if ( !defined($node) ) { + return; + } + + $node->{'comment_out'} = "1"; +} + +# +# This method is used to create a node with the path specified. The method +# will create parent nodes as necessary. +# +# $path A reference to the array containing the path segments +# +sub create_node { + my ( $self, $path ) = @_; + + my $hash = \%data; + SEGMENT: + foreach my $segment (@$path) { + my $children = $hash->{'children'}; + + unless ($children) { + my @new_children; + $hash->{'children'} = \@new_children; + $children = \@new_children; + } + + foreach my $child (@$children) { + my $name = $child->{'name'}; + next unless $name; + + if ( $name eq $segment ) { + $hash = $child; # record insertion point + next SEGMENT; + } + } + + my %new_hash = ( 'name' => $segment ); + + if ($hash != \%data) { + # insertion in subtree + push @$children, \%new_hash; + } else { + # special case for insertion at top put new before version comment + my @comments; + while (my $child = pop @$children) { + if ($child->{'comment'}) { + unshift @comments, $child; + } else { + push @$children, $child; + last; + } + } + + push @$children, \%new_hash; + push @$children, @comments; + } + $hash = \%new_hash; + } + return $hash; +} + +# +# This method is used to delete a child node with the name specified from an array of child nodes. +# +# $children A reference to the array of child nodes +# $name The name of the child node to delete +# +sub delete_child { + my ( $self, $children, $name ) = @_; + return if ( !defined($children) || !defined($name) ); + + for ( my $i = 0 ; $i < @$children ; $i++ ) { + my $stringNodeNameHere = @$children[$i]->{'name'}; + if ( $name eq $stringNodeNameHere ) { + @$children[$i] = undef; + } + } +} + +# +# This method is used to return a reference to the child node +# with the name specified. +# +# $children A reference to an array containing the child nodes. +# $name The name of the child node reference to which will be returned. +# +# If the child node with the name specified is not found, +# then 'undef' is returned. +# +sub find_child { + my ( $self, $children, $name ) = @_; + return if ( !defined($children) || !defined($name) ); + + foreach my $child (@$children) { + return $child if ( $name eq $child->{'name'} ); + } + return; +} + +# $ref: reference to the node to be used as the starting point. +# the same as node_exists() except that the starting point is the specified +# node (instead of root). +sub node_exists_with_ref { + my ( $self, $ref, $path ) = @_; + my @parr = split / /, $path; + if ( defined( $self->get_node_with_ref( $ref, \@parr ) ) ) { + return 1; + } + return 0; +} + +# $path: a space-delimited string representing the path to a node. +# e.g., 'interfaces ethernet eth0'. note that the path +# is relative from the root level. +# returns 1 if the specified node exists. otherwise returns 0. +sub node_exists { + my ( $self, $path ) = @_; + my @parr = split / /, $path; + + return $self->get_node( \@parr ); +} + +# $ref: reference to the node to be used as the starting point. +# the same as get_node() except that the starting point is the specified +# node (instead of root). +sub get_node_with_ref { + my ( $self, $ref, $path ) = @_; + my $hash = $ref; + + SEGMENT: + foreach my $segment (@$path) { + my $children = $hash->{'children'}; + return unless $children; + + foreach my $child (@$children) { + next unless ( $child->{'name'} eq $segment ); + + $hash = $child; + next SEGMENT; + } + + # No children matched segment + return; + } + + return $hash; +} + +# +# This method is used to return a reference to the hash +# of the node with the path specified. +# +# $path - reference to an array containing the path segments of the node. +# +# If the path is invalid, then undef is returned. +sub get_node { + my ( $self, $path ) = @_; + + return $self->get_node_with_ref( $self->{_data}, $path ); +} + +# +# Move a subtree from one place to another in hierarchy +# Assumes both $from and $to exist +# Returns undef if no match +sub move_child { + my ( $self, $from, $to, $name ) = @_; + my $source = $from->{'children'}; + return unless $source; + + for ( my $i = 0 ; $i < @$source ; $i++ ) { + my $match = @$source[$i]; + next unless $match->{'name'} eq $name; + splice @$source, $i, 1; # remove old list + + my $children = $to->{'children'}; + unless ($children) { + my @new_children; + $to->{'children'} = \@new_children; + $children = \@new_children; + } + + push @$children, $match; + return $match; + } + + return; +} + +# +# This method is used to insert a comment at a particular path. +# +# $path A reference to an array containing the path segments to the +# node for which the comment is to be inserted. The comment +# will appear above the node. +# +# If the node with the path specified does not exist, a node with empty name +# will be created for the comment. +# +sub push_comment { + my ( $self, $path, $comment ) = @_; + + my $hash = \%data; + foreach my $segment (@$path) { + my $children = $hash->{'children'}; + if ( !defined($children) ) { + my @children; + $hash->{'children'} = \@children; + $children = \@children; + } + + my $child_found = 0; + foreach my $child (@$children) { + if ( $child->{'name'} eq $segment ) { + $child_found = 1; + $hash = $child; + last; + } + } + + if ( $child_found == 0 ) { + my %new_hash = ( 'name' => $segment ); + push( @$children, \%new_hash ); + $hash = \%new_hash; + } + } + + my %new_comment = ( 'comment' => $comment ); + my $childrenPush = $hash->{'children'}; + if ( !defined($childrenPush) ) { + my @new_children; + $hash->{'children'} = \@new_children; + $childrenPush = \@new_children; + } + push( @$childrenPush, \%new_comment ); +} + +# +# This method is used to set the value of a particular node +# +# $path A reference to an array containing the path segments to the node +# $value String of the value to set +# +sub set_value { + my ( $self, $path, $value ) = @_; + + my $hash = $self->create_node($path); + if ( defined($hash) ) { + $hash->{'value'} = $value; + } +} + +# +# This method is used to set the value of a particular node +# +# $path A reference to an array containing the path segments to the node +# $value String of the value to set +# +sub set_disable { + my ( $self, $path ) = @_; + my $hash = $self->create_node($path); + if ( defined($hash) ) { + $hash->{'disable'} = 'true'; + } +} + +# +# This method is used to generate the output of the node tree in the XORP config +# file format. The output is printed out to currently selected standard out. +# +# $depth Number of indents, used when this method calls itself +# recursively, should be 0 when used. +# $hash A reference to the parent node, should be the roor node when +# used. +# +sub output { + my ( $self, $depth, $hash ) = @_; + + $hash = $self->{_data} unless $hash; + + my $comment = $hash->{'comment'}; + print '/*' . $comment . "*/\n" + if $comment; + + my $children = $hash->{'children'}; + foreach my $child (@$children) { + next unless $child; + my $name = $child->{'name'}; + + my $comment_out = $child->{'comment_out'}; + if ($comment_out) { + print "\n"; + print "/* --- $comment_out --- */\n" + if ( $comment_out ne "1" ); + print +"/* --- CONFIGURATION COMMENTED OUT DURING MIGRATION BELOW ---\n"; + } + + print " " x $depth; + my $value = $child->{'value'}; + if ($value) { + print "$name $value"; + print "\n"; + } + else { + my $print_brackets = 0; + my $children = $child->{'children'}; + if ( defined($children) && @$children > 0 ) { + $print_brackets = 1; + } + elsif ( defined($name) && !( $name =~ /\s/ ) ) { + $print_brackets = 1; + } + + if ($name) { + print "$name"; + if ($print_brackets) { + print " {"; + } + print "\n"; + } + + $self->output( $depth + 1, $child ); + if ($print_brackets) { + print " " x $depth; + print "}\n"; + } + } + + print +" --- CONFIGURATION COMMENTED OUT DURING MIGRATION ABOVE --- */\n\n" + if ($comment_out); + } +} + +# +# This method is used to parse the XORP config file specified into the internal tree +# structure that the methods above process and manipulate. +# +# $file String of the filename to parse +# +sub parse { + my ( $self, $file ) = @_; + + %data = (); + + open my $in, '<', $file + or die "Error! Unable to open file \"$file\". $!"; + + my $contents = ""; + while (<$in>) { + $contents .= $_; + } + close $in; + + my @array_contents = split( '', $contents ); + + # print scalar(@array_contents) . "\n"; + + my $length_contents = @array_contents; + my $colon = 0; + my $colon_quote = 0; + my $in_quote = 0; + my $name = ''; + my $value = undef; + my $disable = undef; + my @path; + my %tree; + + for ( my $i = 0 ; $i < $length_contents ; ) { + my $c = $array_contents[$i]; + my $cNext = $array_contents[ $i + 1 ]; + + if ( $colon == 1 ) { + my $value_end = 0; + if ( $c eq '"' ) { + $value .= $c; + if ( $colon_quote == 1 ) { + $value_end = 1; + } + else { + $colon_quote = 1; + } + } + elsif ( $c eq '\\' && $cNext eq '"' ) { + $value .= '\\"'; + $i++; + } + elsif ( defined($value) ) { + if ( ( length($value) > 0 ) || ( !( $c =~ /\s/ ) ) ) { + $value .= $c; + } + } + + if ( $colon_quote == 0 + && ( $cNext eq '}' || $cNext eq ';' || $cNext =~ /\s/ ) ) + { + $value_end = 1; + } + $i++; + + if ( $value_end == 1 ) { + if ( length($value) > 0 ) { + + # print "Path is: \"@path\" Value is: $value\n"; + $self->set_value( \@path, $value ); + $value = undef; + } + pop(@path); + $colon_quote = 0; + $colon = 0; + } + next; + } + + # ! $colon + # check for quotes + if ( $c eq '"' ) { + if ($in_quote) { + $in_quote = 0; + } + else { + $in_quote = 1; + } + $name .= '"'; + $i++; + next; + } + elsif ( $c eq '\\' && $cNext eq '"' ) { + $name .= '\\"'; + $i += 2; + next; + } + + if ( !$in_quote && $c eq '!' && $cNext eq ' ') { + $disable = 'true'; + $i += 2; + } + elsif ( !$in_quote && $c eq '/' && $cNext eq '*' ) { + my $comment_text = ''; + my $comment_end = index( $contents, '*/', $i + 2 ); + if ( $comment_end == -1 ) { + $comment_text = substr( $contents, $i + 2 ); + } + else { + $comment_text = + substr( $contents, $i + 2, $comment_end - $i - 2 ); + $i = $comment_end + 2; + } + + # print 'Comment is: "' . $comment_text . "\"\n"; + $self->push_comment( \@path, $comment_text ); + } + elsif (( !$in_quote && $c eq '{' ) + || ( $c eq ':' && !( $name =~ /\s/ ) ) + || $c eq "\n" ) + { + $name =~ s/^\s+|\s$//g; + + if ( length($name) > 0 ) { + push( @path, $name ); + + if (defined $disable && $disable eq 'true') { + $self->set_disable(\@path); + $disable = undef; + } + + # print "Path is: \"@path\" Name is: \"$name\"\n"; + $self->set_value( \@path, $value ); + $name = ''; + + if ( $c eq "\n" ) { + pop(@path); + } + if ( $c eq ':' ) { + $colon = 1; + } + } + + $i++; + } + elsif ( !$in_quote && $c eq '}' ) { + pop(@path); + $name = ''; + $disable = undef; + $i++; + } + elsif ( !$in_quote && $c eq ';' ) { + $i++; + } + else { + if ( ( length($name) > 0 ) || ( !( $c =~ /\s/ ) ) ) { + $name .= $c; + } + $i++; + } + } +} + +1; |