#!/usr/bin/perl # Author: An-Cheng Huang # Date: 2007 # Description: Perl module for generating output of the configuration. # **** License **** # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # This code was originally developed by Vyatta, Inc. # Portions created by Vyatta are Copyright (C) 2006, 2007, 2008 Vyatta, Inc. # All Rights Reserved. # **** End License **** # outputNewConfig() # prints the "new" config, i.e., the active config with any un-committed # changes. 'diff' notation is also generated to indicate the changes. # # outputActiveConfig() # prints the "active" config. suitable for "saving", for example. package Vyatta::ConfigOutput; use strict; our @EXPORT = qw(set_show_all set_hide_password outputActiveConfig outputNewConfig); use base qw(Exporter); use lib '/opt/vyatta/share/perl5'; use Vyatta::Config; use Sort::Versions; # whether to show default values my $show_all = 0; sub set_show_all { if (shift) { $show_all = 1; } } my $hide_password = 0; sub set_hide_password { if (shift) { $hide_password = 1; } } sub txt_need_quotes { $_ = shift; return 1 if (/^$/ || /[\s\*}{;]/); return 0; } my $config = undef; # $0: array ref for path # $1: display prefix # $2: node name # $3: simple show (if defined, don't show diff prefix. used for "don't show as # deleted" from displayDeletedOrigChildren.) sub displayValues { my @cur_path = @{$_[0]}; my $prefix = $_[1]; my $name = $_[2]; my $simple_show = $_[3]; my ($is_multi, $is_text, $default) = $config->parseTmpl(\@cur_path); if ($is_text) { $default =~ /^"(.*)"$/; my $txt = $1; if (!txt_need_quotes($txt)) { $default = $txt; } } my $is_password = ($name =~ /^.*(password|pre-shared-secret)$/); my $HIDE_PASSWORD = '****************'; $config->setLevel(join ' ', @cur_path); if ($is_multi) { my @ovals = $config->returnOrigValues(''); my @nvals = $config->returnValues(''); if ($is_text) { @ovals = map { (txt_need_quotes($_)) ? "\"$_\"" : "$_"; } @ovals; @nvals = map { (txt_need_quotes($_)) ? "\"$_\"" : "$_"; } @nvals; } my $idx = 0; my %ohash = map { $_ => ($idx++) } @ovals; $idx = 0; my %nhash = map { $_ => ($idx++) } @nvals; my @dlist = map { if (!defined($nhash{$_})) { $_; } else { undef; } } @ovals; if (defined($simple_show)) { foreach my $oval (@ovals) { if ($is_password && $hide_password) { $oval = $HIDE_PASSWORD; } print "$prefix$name $oval\n"; } return; } foreach my $del (@dlist) { if (defined($del)) { if ($is_password && $hide_password) { $del = $HIDE_PASSWORD; } print "-$prefix$name $del\n"; } } foreach my $nval (@nvals) { my $diff = '+'; if (defined($ohash{$nval})) { if ($ohash{$nval} != $nhash{$nval}) { $diff = '>'; } else { $diff = ' '; } } if ($is_password && $hide_password) { $nval = $HIDE_PASSWORD; } print "$diff$prefix$name $nval\n"; } } else { my $oval = $config->returnOrigValue(''); my $nval = $config->returnValue(''); if ($is_text) { if (defined($oval) && txt_need_quotes($oval)) { $oval = "\"$oval\""; } if (defined($nval) && txt_need_quotes($nval)) { $nval = "\"$nval\""; } } my %cnodes = $config->listNodeStatus(); my @cnames = sort keys %cnodes; if (defined($simple_show)) { if (!$cnodes{'def'} || $show_all) { if ($is_password && $hide_password) { $oval = $HIDE_PASSWORD; } print "$prefix$name $oval\n"; } return; } my $value = $nval; my $diff = ' '; if (!defined($oval) && defined($nval)) { $diff = '+'; } elsif (!defined($nval) && defined($oval)) { $diff = '-'; $value = $oval; } else { # both must be defined if ($oval ne $nval) { $diff = '>'; } } if (!$cnodes{'def'} || $show_all) { if ($is_password && $hide_password) { $value = $HIDE_PASSWORD; } print "$diff$prefix$name $value\n"; } } } # $0: array ref for path # $1: display prefix # $2: don't show as deleted? (if defined, config is shown as normal instead of # deleted.) sub displayDeletedOrigChildren { my @cur_path = @{$_[0]}; my $prefix = $_[1]; my $dont_show_as_deleted = $_[2]; my $dprefix = '-'; if (defined($dont_show_as_deleted)) { $dprefix = ''; } $config->setLevel(''); my @children = $config->listOrigNodes(join ' ', @cur_path); for my $child (sort @children) { if ($child eq 'node.val') { # should not happen! next; } my $is_tag = $config->isTagNode([ @cur_path, $child ]); $config->setLevel(join ' ', (@cur_path, $child)); my @cnames = sort $config->listOrigNodesNoDef(); if ($cnames[0] eq 'node.val') { displayValues([ @cur_path, $child ], $prefix, $child, $dont_show_as_deleted); } elsif ($cnames[0] eq 'def') { #ignore } elsif (scalar($#cnames) >= 0) { if ($is_tag) { @cnames = sort versioncmp @cnames; foreach my $cname (@cnames) { if ($cname eq 'node.val') { # should not happen next; } print "$dprefix$prefix$child $cname {\n"; displayDeletedOrigChildren([ @cur_path, $child, $cname ], "$prefix ", $dont_show_as_deleted); print "$dprefix$prefix}\n"; } } else { print "$dprefix$prefix$child {\n"; displayDeletedOrigChildren([ @cur_path, $child ], "$prefix ", $dont_show_as_deleted); print "$dprefix$prefix}\n"; } } else { my $has_tmpl_children = $config->hasTmplChildren([ @cur_path, $child ]); print "$dprefix$prefix$child" . ($has_tmpl_children ? " {\n$dprefix$prefix}\n" : "\n"); } } } # $0: hash ref for children status # $1: array ref for path # $2: display prefix sub displayChildren { my %child_hash = %{$_[0]}; my @cur_path = @{$_[1]}; my $prefix = $_[2]; for my $child (sort (keys %child_hash)) { if ($child eq 'node.val') { # should not happen! next; } my ($diff, $vdiff) = (' ', ' '); if ($child_hash{$child} eq 'added') { $diff = '+'; $vdiff = '+'; } elsif ($child_hash{$child} eq 'deleted') { $diff = '-'; $vdiff = '-'; } elsif ($child_hash{$child} eq 'changed') { $vdiff = '>'; } my $is_tag = $config->isTagNode([ @cur_path, $child ]); $config->setLevel(join ' ', (@cur_path, $child)); my %cnodes = $config->listNodeStatus(); my @cnames = sort keys %cnodes; #if node.val exists and ct == 0 w/o def or ct ==1 w/ def my $leaf = 0; if ($cnodes{'def'}) { if ($#cnames == 1 && $cnodes{'node.val'}) { $leaf = 1; } } else { if ($#cnames == 0 && $cnodes{'node.val'}) { $leaf = 1; } } if ($leaf == 1) { displayValues([ @cur_path, $child ], $prefix, $child); } elsif (scalar($#cnames) >= 0) { if ($is_tag) { @cnames = sort versioncmp @cnames; foreach my $cname (@cnames) { if ($cname eq 'node.val') { # should not happen next; } my $tdiff = ' '; if ($cnodes{$cname} eq 'deleted') { $tdiff = '-'; } elsif ($cnodes{$cname} eq 'added') { $tdiff = '+'; } print "$tdiff$prefix$child $cname {\n"; if ($cnodes{$cname} eq 'deleted') { displayDeletedOrigChildren([ @cur_path, $child, $cname ], "$prefix "); } else { $config->setLevel(join ' ', (@cur_path, $child, $cname)); my %ccnodes = $config->listNodeStatus(); displayChildren(\%ccnodes, [ @cur_path, $child, $cname ], "$prefix "); } print "$tdiff$prefix}\n"; } } else { print "$diff$prefix$child {\n"; if ($child_hash{$child} eq 'deleted') { # this should not happen displayDeletedOrigChildren([ @cur_path, $child ], "$prefix "); } else { displayChildren(\%cnodes, [ @cur_path, $child ], "$prefix "); } print "$diff$prefix}\n"; } } else { if ($child_hash{$child} eq 'deleted') { $config->setLevel(''); my @onodes = $config->listOrigNodes(join ' ', (@cur_path, $child)); if ($#onodes == 0 && $onodes[0] eq 'node.val') { displayValues([ @cur_path, $child ], $prefix, $child); } else { print "$diff$prefix$child {\n"; displayDeletedOrigChildren([ @cur_path, $child ], "$prefix "); print "$diff$prefix}\n"; } } else { my $has_tmpl_children = $config->hasTmplChildren([ @cur_path, $child ]); print "$diff$prefix$child" . ($has_tmpl_children ? " {\n$diff$prefix}\n" : "\n"); } } } } # @ARGV: represents the 'root' path. the output starts at this point under # the new config. sub outputNewConfig { $config = new Vyatta::Config; $config->setLevel(join ' ', @_); my %rnodes = $config->listNodeStatus(); if (scalar(keys %rnodes) > 0) { my @rn = keys %rnodes; #if node.val exists and ct == 0 w/o def or ct ==1 w/ def my $leaf = 0; if ($rnodes{'def'}) { if ($#rn == 1 && $rnodes{'node.val'}) { $leaf = 1; } } else { if ($#rn == 0 && $rnodes{'node.val'}) { $leaf = 1; } } if ($leaf == 1) { # this is a leaf value-node displayValues([ @_ ], '', $_[$#_]); } else { displayChildren(\%rnodes, [ @_ ], ''); } } else { if ($config->existsOrig() && ! $config->exists()) { # this is a deleted node print 'Configuration under "' . (join ' ', @_) . "\" has been deleted\n"; } elsif (!defined($config->getTmplPath(\@_))) { print "Specified configuration path is not valid\n"; } else { print 'Configuration under "' . (join ' ', @_) . "\" is empty\n"; } } } # @ARGV: represents the 'root' path. the output starts at this point under # the active config. sub outputActiveConfig { $config = new Vyatta::Config; $config->setLevel(join ' ', @_); displayDeletedOrigChildren([ @_ ], '', 1); } 1;