# 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;