diff options
author | An-Cheng Huang <ancheng@vyatta.com> | 2010-07-28 14:30:32 -0700 |
---|---|---|
committer | An-Cheng Huang <ancheng@vyatta.com> | 2010-07-28 14:30:32 -0700 |
commit | 639c835bc2730a4fbffd915f5b2028a68375ee7a (patch) | |
tree | 203d61e1d5e8ef422d6aba3851d2f83a1f838b6b /lib/Vyatta/Config.pm | |
parent | 0247864ef578ac05bbac8dc5175e674ce7b82714 (diff) | |
download | vyatta-cfg-639c835bc2730a4fbffd915f5b2028a68375ee7a.tar.gz vyatta-cfg-639c835bc2730a4fbffd915f5b2028a68375ee7a.zip |
add new cstore library
Diffstat (limited to 'lib/Vyatta/Config.pm')
-rwxr-xr-x | lib/Vyatta/Config.pm | 1309 |
1 files changed, 498 insertions, 811 deletions
diff --git a/lib/Vyatta/Config.pm b/lib/Vyatta/Config.pm index 8bcd84f..8067e05 100755 --- a/lib/Vyatta/Config.pm +++ b/lib/Vyatta/Config.pm @@ -21,16 +21,14 @@ package Vyatta::Config; use strict; -use Vyatta::ConfigDOMTree; use File::Find; +use lib '/opt/vyatta/share/perl5'; +use Cstore; + my %fields = ( - _changes_only_dir_base => $ENV{VYATTA_CHANGES_ONLY_DIR}, - _new_config_dir_base => $ENV{VYATTA_TEMP_CONFIG_DIR}, - _active_dir_base => $ENV{VYATTA_ACTIVE_CONFIGURATION_DIR}, - _vyatta_template_dir => $ENV{VYATTA_CONFIG_TEMPLATE}, - _current_dir_level => "/", _level => undef, + _cstore => undef, ); sub new { @@ -39,892 +37,546 @@ sub new { my $self = { %fields, }; - bless $self, $class; + $self->{_cstore} = new Cstore(); return $self; } -sub _set_current_dir_level { - my ($self) = @_; - my $level = $self->{_level}; - - $level =~ s/\//%2F/g; - $level =~ s/\s+/\//g; - - $self->{_current_dir_level} = "/$level"; - return $self->{_current_dir_level}; +sub get_path_comps { + my ($self, $pstr) = @_; + $pstr = '' if (!defined($pstr)); + $pstr = "$self->{_level} $pstr" if (defined($self->{_level})); + $pstr =~ s/^\s+//; + $pstr =~ s/\s+$//; + my @path_comps = split /\s+/, $pstr; + return \@path_comps; +} + +############################################################ +# low-level API functions that have been converted to use +# the cstore library. +############################################################ + +###### +# observers of current working config or active config during a commit. +# * MOST users of this API should use these functions. +# * these functions MUST NOT worry about the "deactivated" state, i.e., +# deactivated nodes are equivalent to having been deleted for these +# functions. in other words, these functions are NOT "deactivate-aware". +# * functions that can be used to observe "active config" can be used +# outside a commit as well (only when observing active config, of course). +# +# note: these functions accept a third argument "$include_deactivated", but +# it is for error checking purposes to ensure that all legacy +# invocations have been fixed. the functions MUST NOT be called +# with this argument. +my $DIE_DEACT_MSG = 'This function is NOT deactivate-aware'; + +## exists("path to node") +# Returns true if specified node exists in working config. +sub exists { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 + if ($self->{_cstore}->cfgPathExists($self->get_path_comps($path), undef)); + return; # note: this return is needed. can't just return the return value + # of the above function since some callers expect "undef" + # as false. } -## setLevel("level") -# if "level" is supplied, set the current level of the hierarchy we are working on -# return the current level -sub setLevel { - my ($self, $level) = @_; - - $self->{_level} = $level if defined($level); - $self->_set_current_dir_level(); - - return $self->{_level}; +## existsOrig("path to node") +# Returns true if specified node exists in active config. +sub existsOrig { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 + if ($self->{_cstore}->cfgPathExists($self->get_path_comps($path), 1)); + return; # note: this return is needed. } ## listNodes("level") -# return array of all nodes at "level" -# level is relative +# return array of all child nodes at "level" in working config. sub listNodes { - my ($self, $path, $disable) = @_; - my @nodes = (); - - my $rpath = ""; - if ($path) { - $path =~ s/\//%2F/g; - $path =~ s/\s+/\//g; - $rpath = $self->{_current_dir_level} . "/" . $path; - } else { - $rpath = $self->{_current_dir_level}; - } - $path = $self->{_new_config_dir_base} . $rpath; - - #print "DEBUG Vyatta::Config->listNodes(): path = $path\n"; - opendir my $dir, $path or return (); - @nodes = grep !/^\./, readdir $dir; - closedir $dir; - - my @nodes_modified = (); - while (@nodes) { - my $tmp = pop (@nodes); - $tmp =~ s/\n//g; - #print "DEBUG Vyatta::Config->listNodes(): node = $tmp\n"; - my $ttmp = $rpath . "/" . $tmp; - $tmp =~ s/%2F/\//g; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - if (!defined($status) || $status eq 'active') { - push @nodes_modified, $tmp; - } - } - else { - push @nodes_modified, $tmp; - } - } - - return @nodes_modified; -} - -## isActive("path") -# return true|false based on whether node path has -# been processed or is active -sub isActive { - my ($self, $path, $disable) = @_; - my @nodes = (); - - my @comp_node = split " ", $path; - - my $comp_node = pop(@comp_node); - if (!defined $comp_node) { - return 1; - } - - my $rel_path = join(" ",@comp_node); - - my @nodes_modified = $self->listOrigPlusComNodes($rel_path,$disable); - foreach my $node (@nodes_modified) { - if ($node eq $comp_node) { - return 0; - } - } - return 1; -} - -## listNodes("level") -# return array of all nodes (active plus currently committed) at "level" -# level is relative -sub listOrigPlusComNodes { - my ($self, $path, $disable) = @_; - my @nodes = (); - - my @nodes_modified = $self->listNodes($path,$disable); - - #convert array to hash - my %coll; - my $coll; - @coll{@nodes_modified} = @nodes_modified; - - my $level = $self->{_level}; - if (! defined $level) { - $level = ""; - } - - my $dir_path = $level; - if (defined $path) { - $dir_path .= " " . $path; - } - $dir_path =~ s/ /\//g; - $dir_path = "/".$dir_path; - - #now test against the inprocess file in the system -# my $com_file = "/tmp/.changes_$$"; - my $com_file = "/tmp/.changes"; - if (-e $com_file) { - open my $file, "<", $com_file; - foreach my $line (<$file>) { - my @node = split " ", $line; #split on space - #$node[1] is of the form: system/login/blah - #$coll is of the form: blah - -# print("comparing: $dir_path and $level to $node[1]\n"); - - #first only consider $path matches against $node[1] - if (!defined $dir_path || $node[1] =~ m/^$dir_path/) { - #or does $node[1] match the beginning of the line for $path - - #if yes, then split the right side and find against the hash for the value... - my $tmp; - if (defined $dir_path) { - $tmp = substr($node[1],length($dir_path)); - } - else { - $tmp = $node[1]; - } - - if (!defined $tmp || $tmp eq '') { - next; - } - - my @child = split "/",$tmp; - my $child; - -# print("tmp: $tmp, $child[0], $child[1]\n"); - if ($child[0] =~ /^\s*$/ || !defined $child[0] || $child[0] eq '') { - shift(@child); - } - -# print("child value is: >$child[0]<\n"); - - #now can we find this entry in the hash? - #if '-' this is a delete and need to remove from hash - if ($node[0] eq "-") { - if (!defined $child[1]) { - delete($coll{$child[0]}); - } - } - #if '+' this is a set and need to add to hash - elsif ($node[0] eq "+" && $child[0] ne '') { - $coll{$child[0]} = '1'; - } - } - } - close $file; - close $com_file; - } - -#print "coll count: ".keys(%coll); - - #now convert hash to array and return - @nodes_modified = (); - @nodes_modified = keys(%coll); - return @nodes_modified; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetChildNodes( + $self->get_path_comps($path), undef); + return @{$ref}; } ## listOrigNodes("level") -# return array of all original nodes (i.e., before any current change; i.e., -# in "working") at "level" -# level is relative +# return array of all child nodes at "level" in active config. sub listOrigNodes { - my ($self, $path, $disable) = @_; - my @nodes = (); - - my $rpath = ""; - if (defined $path) { - $path =~ s/\//%2F/g; - $path =~ s/\s+/\//g; - $rpath = $self->{_current_dir_level} . "/" . $path; - } - else { - $rpath = $self->{_current_dir_level}; - } - $path = $self->{_active_dir_base} . $rpath; - - #print "DEBUG Vyatta::Config->listNodes(): path = $path\n"; - opendir my $dir, "$path" or return (); - @nodes = grep !/^\./, readdir $dir; - closedir $dir; - - my @nodes_modified = (); - while (@nodes) { - my $tmp = pop (@nodes); - $tmp =~ s/\n//g; - #print "DEBUG Vyatta::Config->listNodes(): node = $tmp\n"; - my $ttmp = $rpath . "/" . $tmp; - $tmp =~ s/%2F/\//g; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - if (!defined($status) || $status eq 'local') { - push @nodes_modified, $tmp; - } - } - else { - push @nodes_modified, $tmp; - } - } - - return @nodes_modified; -} - -## returnParent("level") -# return the name of parent node relative to the current hierarchy -# in this case "level" is set to the parent dir ".. .." -# for example -sub returnParent { - my ($self, $node) = @_; - my @x, my $tmp; - - # split our hierarchy into vars on a stack - my @level = split /\s+/, $self->{_level}; - - # count the number of parents we need to lose - # and then pop 1 less - @x = split /\s+/, $node; - for ($tmp = 1; $tmp < @x; $tmp++) { - pop @level; - } - - # return the parent - $tmp = pop @level; - return $tmp; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetChildNodes( + $self->get_path_comps($path), 1); + return @{$ref}; } ## returnValue("node") -# returns the value of "node" or undef if the node doesn't exist . -# node is relative +# return value of specified single-value node in working config. +# return undef if fail to get value (invalid node, node doesn't exist, +# not a single-value node, etc.). sub returnValue { - my ( $self, $node, $disable ) = @_; - my $tmp; - - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - #getDeactivated - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - #only return value if status is not disabled (i.e. local or both) - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - if (!defined($status) || $status eq 'active') { - return unless - open my $file, '<', - "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node/node.val"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - } - } - else { - return unless - open my $file, '<', - "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node/node.val"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - } - return $tmp; -} - -## returnComment("node") -# returns the value of "node" or undef if the node doesn't exist . -# node is relative -sub returnComment { - my ( $self, $node ) = @_; - my $tmp = undef; - - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - return unless - open my $file, '<', - "$self->{_new_config_dir_base}/$node/.comment"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - return $tmp; -} - -## returnOrigPlusComValue("node") -# returns the value of "node" or undef if the node doesn't exist . -# node is relative -sub returnOrigPlusComValue { - my ( $self, $path, $disable ) = @_; - - my $tmp = returnValue($self,$path,$disable); - - my $level = $self->{_level}; - if (! defined $level) { - $level = ""; - } - my $dir_path = $level; - if (defined $path) { - $dir_path .= " " . $path; - } - $dir_path =~ s/ /\//g; - $dir_path = "/".$dir_path."/value"; - - #now need to compare this against what I've done - my $com_file = "/tmp/.changes"; - if (-e $com_file) { - open my $file, "<", $com_file; - foreach my $line (<$file>) { - my @node = split " ", $line; #split on space - if (index($node[1],$dir_path) != -1) { - #found, now figure out if this a set or delete - if ($node[0] eq '+') { - my $pos = rindex($node[1],"/value:"); - $tmp = substr($node[1],$pos+7); - } - else { - $tmp = ""; - } - last; - } - } - close $file; - close $com_file; - } - return $tmp; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->{_cstore}->cfgPathGetValue($self->get_path_comps($path), + undef); } - ## returnOrigValue("node") -# returns the original value of "node" (i.e., before the current change; i.e., -# in "working") or undef if the node doesn't exist. -# node is relative +# return value of specified single-value node in active config. +# return undef if fail to get value (invalid node, node doesn't exist, +# not a single-value node, etc.). sub returnOrigValue { - my ( $self, $node, $disable ) = @_; - my $tmp; - - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - - #getDeactivated - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - #only return value if status is not disabled (i.e. local or both) - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - if (!defined($status) || $status eq 'local') { - my $filepath = "$self->{_active_dir_base}$self->{_current_dir_level}/$node"; - - return unless open my $file, '<', "$filepath/node.val"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - } - } - else { - my $filepath = "$self->{_active_dir_base}$self->{_current_dir_level}/$node"; - - return unless open my $file, '<', "$filepath/node.val"; - - read $file, $tmp, 16384; - close $file; - - $tmp =~ s/\n$//; - } - return $tmp; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->{_cstore}->cfgPathGetValue($self->get_path_comps($path), 1); } ## returnValues("node") -# returns an array of all the values of "node", or an empty array if the values do not exist. -# node is relative +# return array of values of specified multi-value node in working config. +# return empty array if fail to get value (invalid node, node doesn't exist, +# not a multi-value node, etc.). sub returnValues { - my $val = returnValue(@_); - my @values = (); - if (defined($val)) { - @values = split("\n", $val); - } - return @values; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetValues($self->get_path_comps($path), + undef); + return @{$ref}; } -## returnValues("node") -# returns an array of all the values of "node", or an empty array if the values do not exist. -# node is relative -sub returnOrigPlusComValues { - my ( $self, $path, $disable ) = @_; - my @values = returnOrigValues($self,$path,$disable); +## returnOrigValues("node") +# return array of values of specified multi-value node in active config. +# return empty array if fail to get value (invalid node, node doesn't exist, +# not a multi-value node, etc.). +sub returnOrigValues { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetValues($self->get_path_comps($path), + 1); + return @{$ref}; +} + +###### +# observers of the "effective" config. +# they can be used +# (1) outside a config session (e.g., op mode, daemons, callbacks, etc.). +# OR +# (2) during a config session +# +# HOWEVER, NOTE that the definition of "effective" is different under these +# two scenarios. +# (1) when used outside a config session, "effective" == "active". +# in other words, in such cases the effective config is the same +# as the running config. +# +# (2) when used during a config session, a config path (leading to either +# a "node" or a "value") is "effective" if it is "in effect" at the +# time when these observers are called. more detailed info can be +# found in the library code. +# +# originally, these functions are exclusively for use during config +# sessions. however, for some usage scenarios, it is useful to have a set +# of API functions that can be used both during and outside config +# sessions. therefore, definition (1) is added above for convenience. +# +# for example, a developer can use these functions in a script that can +# be used both during a commit action and outside config mode, as long as +# the developer is clearly aware of the difference between the above two +# definitions. +# +# note that when used outside a config session (i.e., definition (1)), +# these functions are equivalent to the observers for the "active" config. +# +# to avoid any confusiton, when possible (e.g., in a script that is +# exclusively used in op mode), developers should probably use those +# "active" observers explicitly when outside a config session instead +# of these "effective" observers. +# +# it is also important to note that when used outside a config session, +# due to race conditions, it is possible that the "observed" active config +# becomes out-of-sync with the config that is actually "in effect". +# specifically, this happens when two things occur simultaneously: +# (a) an observer function is called from outside a config session. +# AND +# (b) someone invokes "commit" inside a config session (any session). +# +# this is because "commit" only updates the active config at the end after +# all commit actions have been executed, so before the update happens, +# some config nodes have already become "effective" but are not yet in the +# "active config" and therefore are not observed by these functions. +# +# note that this is only a problem when the caller is outside config mode. +# in such cases, the caller (which could be an op-mode command, a daemon, +# a callback script, etc.) already must be able to handle config changes +# that can happen at any time. if "what's configured" is more important, +# using the "active config" should be fine as long as it is relatively +# up-to-date. if the actual "system state" is more important, then the +# caller should probably just check the system state in the first place +# (instead of using these config observers). + +## isEffective("path") +# return whether "path" is in "active" config when used outside config +# session, +# OR +# return whether "path" is "effective" during current commit. +# see above discussion about the two different definitions. +# +# "effective" means the path is in effect, i.e., any of the following is true: +# (1) active && working +# path is in both active and working configs, i.e., unchanged. +# (2) !active && working && committed +# path is not in active, has been set in working, AND has already +# been committed, i.e., "commit" has already processed the +# addition/update of the path. +# (3) active && !working && !committed +# path is in active, has been deleted from working, AND +# has NOT been committed yet, i.e., "commit" (per priority) has not +# processed the deletion of the path yet (or has processed it but +# the action failed). +# +# note: during commit, deactivate has the same effect as delete. so as +# far as this function (and any other commit observer functions) is +# concerned, deactivated nodes don't exist. +sub isEffective { + my ($self, $path) = @_; + return 1 + if ($self->{_cstore}->cfgPathEffective($self->get_path_comps($path))); + return; # note: this return is needed. +} - #now parse the commit accounting file. - my $level = $self->{_level}; - if (! defined $level) { - $level = ""; - } - my $dir_path = $level; - if (defined $path) { - $dir_path .= " " . $path; - } - $dir_path =~ s/ /\//g; - $dir_path = "/".$dir_path."/value"; - - #now need to compare this against what I've done - my $com_file = "/tmp/.changes"; - if (-e $com_file) { - open my $file, "<", $com_file; - foreach my $line (<$file>) { - my @node = split " ", $line; #split on space - if (index($node[1],$dir_path) != -1) { - #found, now figure out if this a set or delete - my $pos = rindex($node[1],"/value:"); - my $tmp = substr($node[1],$pos+7); - my $i = 0; - my $match = 0; - foreach my $v (@values) { - if ($v eq $tmp) { - $match = 1; - last; - } - $i = $i + 1; - } - if ($node[0] eq '+') { - #add value - if ($match == 0) { - push(@values,$tmp); - } - } - else { - #remove value - if ($match == 1) { - splice(@values,$i); - } - } - } - } - close $file; - close $com_file; - } - return @values; +## isActive("path") +# XXX this is the original API function. name is confusing ("active" could +# be confused with "orig") but keep it for compatibility. +# just call isEffective(). +# also, original function accepts "$disable" flag, which doesn't make +# sense. for commit purposes, deactivated should be equivalent to +# deleted. +sub isActive { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->isEffective($path); +} + +## listEffectiveNodes("level") +# return array of "effective" child nodes at "level" during current commit. +# see isEffective() for definition of "effective". +sub listEffectiveNodes { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetEffectiveChildNodes( + $self->get_path_comps($path)); + return @{$ref}; +} + +## listOrigPlusComNodes("level") +# XXX this is the original API function. name is confusing (it's neither +# necessarily "orig" nor "plus") but keep it for compatibility. +# just call listEffectiveNodes(). +# also, original function accepts "$disable" flag, which doesn't make +# sense. for commit purposes, deactivated should be equivalent to +# deleted. +sub listOrigPlusComNodes { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->listEffectiveNodes($path); } -## returnOrigValues("node") -# returns an array of all the original values of "node" (i.e., before the -# current change; i.e., in "working"), or an empty array if the values do not -# exist. -# node is relative -sub returnOrigValues { - my $val = returnOrigValue(@_); - my @values = (); - if (defined($val)) { - @values = split("\n", $val); - } - return @values; +## returnEffectiveValue("node") +# return "effective" value of specified "node" during current commit. +sub returnEffectiveValue { + my ($self, $path) = @_; + return $self->{_cstore}->cfgPathGetEffectiveValue( + $self->get_path_comps($path)); } -## exists("node") -# Returns true if the "node" exists. -sub exists { - my ( $self, $node, $disable ) = @_; - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - #getDeactivated() - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - #only return value if status is not disabled (i.e. local or both) - if (defined($status) && ($status eq 'both' || $status eq 'local')) { #if a .disable is in local or active or both then return false - return; - } - } - return ( -d "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node" ); +## returnOrigPlusComValue("node") +# XXX this is the original API function. just call returnEffectiveValue(). +# also, original function accepts "$disable" flag. +sub returnOrigPlusComValue { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->returnEffectiveValue($path); } -## existsOrig("node") -# Returns true if the "original node" exists. -sub existsOrig { - my ( $self, $node, $disable ) = @_; - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - #getDeactivated() - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - #only return value if status is not disabled (i.e. local or both) - if (defined($status) && ($status eq 'both' || $status eq 'active')) { #if a .disable is in local or active or both then return false - return; - } - } +## returnEffectiveValues("node") +# return "effective" values of specified "node" during current commit. +sub returnEffectiveValues { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetEffectiveValues( + $self->get_path_comps($path)); + return @{$ref}; +} - return ( -d "$self->{_active_dir_base}$self->{_current_dir_level}/$node" ); +## returnOrigPlusComValues("node") +# XXX this is the original API function. just call returnEffectiveValues(). +# also, original function accepts "$disable" flag. +sub returnOrigPlusComValues { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return $self->returnEffectiveValues($path); } ## isDeleted("node") -# is the "node" deleted. node is relative. returns true or false +# whether specified node has been deleted in working config sub isDeleted { - my ($self, $node, $disable) = @_; - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - - my $filepathAct - = "$self->{_active_dir_base}$self->{_current_dir_level}/$node"; - my $filepathNew - = "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node"; - - #getDeactivated() - my $ttmp = $self->{_current_dir_level} . "/" . $node; - $ttmp =~ s/\// /g; - if (!defined $disable) { - my ($status, undef) = $self->getDeactivated($ttmp); - #only return value if status is not disabled (i.e. local or both) - if (defined($status) && $status eq 'local') { - return (-e $filepathAct); - } - } - - return ((-e $filepathAct) && !(-e $filepathNew)); + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 if ($self->{_cstore}->cfgPathDeleted($self->get_path_comps($path))); + return; # note: this return is needed. } ## listDeleted("level") -# return array of deleted nodes in the "level" -# "level" defaults to current +# return array of deleted nodes at specified "level" sub listDeleted { - my ($self, $path, $disable) = @_; - my @new_nodes = $self->listNodes($path,$disable); - my @orig_nodes = $self->listOrigNodes($path,$disable); - my %new_hash = map { $_ => 1 } @new_nodes; - my @deleted = grep { !defined($new_hash{$_}) } @orig_nodes; - return @deleted; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetDeletedChildNodes( + $self->get_path_comps($path)); + return @{$ref}; } -## getAllDeactivated() -# returns array of all deactivated nodes. -# -my @all_deactivated_nodes; -sub getAllDeactivated { - my ($self, $path) = @_; - my $start_dir = $self->{_active_dir_base}; - find ( \&wanted, $start_dir ); - return @all_deactivated_nodes; -} -sub wanted { - if ( $_ eq '.disable' ) { - my $f = $File::Find::name; - #now strip off leading path and trailing file - $f = substr($f, length($ENV{VYATTA_ACTIVE_CONFIGURATION_DIR})); - $f = substr($f, 0, length($f)-length("/.disable")); - $f =~ s/\// /g; - push @all_deactivated_nodes, $f; - } -} - - -## isDeactivated("node") -# returns back whether this node is in an active (false) or -# deactivated (true) state. -sub getDeactivated { - my ($self, $node) = @_; - - if (!defined $node) { - $node = $self->{_level}; - } - - # let's setup the filepath for the change_dir - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - #now walk up parent in local and in active looking for '.disable' file - $node =~ s/ /\//g; - - while (1) { - my $filepath = "$self->{_new_config_dir_base}/$node"; - my $filepathActive = "$self->{_active_dir_base}/$node"; - - my $local = $filepath . "/.disable"; - my $active = $filepathActive . "/.disable"; - - if (-e $local && -e $active) { - return ("both",$node); - } - elsif (-e $local && !(-e $active)) { - return ("local",$node); - } - elsif (!(-e $local) && -e $active) { - return ("active",$node); - } - my $pos = rindex($node, "/"); - if ($pos == -1) { - last; - } - $node = substr($node,0,$pos); - } - return (undef,undef); +## isAdded("node") +# whether specified node has been added in working config +sub isAdded { + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 if ($self->{_cstore}->cfgPathAdded($self->get_path_comps($path))); + return; # note: this return is needed. } ## isChanged("node") -# will check the change_dir to see if the "node" has been changed from a previous -# value. returns true or false. +# whether specified node has been changed in working config +# XXX behavior is different from original implementation, which was +# inconsistent between deleted nodes and deactivated nodes. +# see cstore library source for details. +# basically, a node is "changed" if it's "added", "deleted", or +# "marked changed" (i.e., if any descendant changed). sub isChanged { - my ($self, $node, $disable) = @_; - - # let's setup the filepath for the change_dir - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - my $filepath = "$self->{_changes_only_dir_base}$self->{_current_dir_level}/$node"; - - if (!defined $disable) { - my ($status,undef) = $self->getDeactivated($self->{_level}." ".$node); - if (defined $status && ($status eq 'active' || $status eq 'local')) { - return (defined $status); - } - } - - - # if the node exists in the change dir, it's modified. - return (-e $filepath); -} - -## isAdded("node") -# will compare the new_config_dir to the active_dir to see if the "node" has -# been added. returns true or false. -sub isAdded { - my ($self, $node, $disable) = @_; - - #print "DEBUG Vyatta::Config->isAdded(): node $node\n"; - # let's setup the filepath for the modify dir - $node =~ s/\//%2F/g; - $node =~ s/\s+/\//g; - my $filepathNewConfig = "$self->{_new_config_dir_base}$self->{_current_dir_level}/$node"; - - #print "DEBUG Vyatta::Config->isAdded(): filepath $filepathNewConfig\n"; - - # if the node doesn't exist in the modify dir, it's not - # been added. so short circuit and return false. - return unless (-e $filepathNewConfig); - - # now let's setup the path for the working dir - my $filepathActive = "$self->{_active_dir_base}$self->{_current_dir_level}/$node"; - - if (!defined $disable) { - my ($status,undef) = $self->getDeactivated($self->{_level}." ".$node); - if (defined $status && ($status eq 'active')) { - return (defined $status); - } - } - - # if the node is in the active_dir it's not new - return (! -e $filepathActive); + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + return 1 if ($self->{_cstore}->cfgPathChanged($self->get_path_comps($path))); + return; # note: this return is needed. } ## listNodeStatus("level") -# return a hash of the status of nodes at the current config level +# return a hash of status of child nodes at specified level. # node name is the hash key. node status is the hash value. -# node status can be one of deleted, added, changed, or static +# node status can be one of "deleted", "added", "changed", or "static". sub listNodeStatus { - my ($self, $path, $disable) = @_; - my @nodes = (); - my %nodehash = (); - - # find deleted nodes first - @nodes = $self->listDeleted($path,$disable); - foreach my $node (@nodes) { - if ($node =~ /.+/) { $nodehash{$node} = "deleted" }; - } - - @nodes = (); - @nodes = $self->listNodes($path,$disable); - foreach my $node (@nodes) { - if ($node =~ /.+/) { - my $status = undef; - if (!defined $disable) { - ($status,undef) = $self->getDeactivated($self->{_level}." ".$node); - } - my $nodepath = $node; - $nodepath = "$path $node" if ($path); - #print "DEBUG Vyatta::Config->listNodeStatus(): node $node\n"; - # No deleted nodes -- added, changed, ot static only. - if (defined $status && $status eq 'local') { $nodehash{$node} = "deleted"; } - elsif (defined $status && $status eq 'active') { $nodehash{$node} = "added"; } - elsif ($self->isAdded($nodepath,'true')) { $nodehash{$node} = "added"; } - elsif ($self->isChanged($nodepath,'true')) { $nodehash{$node} = "changed"; } - else { $nodehash{$node} = "static"; } - } - } - - return %nodehash; -} - -############ DOM Tree ################ - -#Create active DOM Tree -sub createActiveDOMTree { - - my $self = shift; - - my $tree = new Vyatta::ConfigDOMTree($self->{_active_dir_base} . $self->{_current_dir_level},"active"); - - return $tree; + my ($self, $path, $include_deactivated) = @_; + die $DIE_DEACT_MSG if (defined($include_deactivated)); + my $ref = $self->{_cstore}->cfgPathGetChildNodesStatus( + $self->get_path_comps($path)); + return %{$ref}; } -#Create changes only DOM Tree -sub createChangesOnlyDOMTree { - - my $self = shift; - - my $tree = new Vyatta::ConfigDOMTree($self->{_changes_only_dir_base} . $self->{_current_dir_level}, - "changes_only"); - - return $tree; +## getTmplChildren("level") +# return list of child nodes in the template hierarchy at specified level. +sub getTmplChildren { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->tmplGetChildNodes($self->get_path_comps($path)); + return @{$ref}; } -#Create new config DOM Tree -sub createNewConfigDOMTree { - - my $self = shift; - my $level = $self->{_new_config_dir_base} . $self->{_current_dir_level}; - - return new Vyatta::ConfigDOMTree($level, "new_config"); +## validateTmplPath("path") +# return whether specified path is a valid template path +sub validateTmplPath { + my ($self, $path, $validate_vals) = @_; + return 1 if ($self->{_cstore}->validateTmplPath($self->get_path_comps($path), + $validate_vals)); + return; # note: this return is needed. } - -###### functions for templates ###### - -# $1: array representing the config node path. -# returns the filesystem path to the template of the specified node, -# or undef if the specified node path is not valid. -sub getTmplPath { - my $self = shift; - my @cfg_path = @{$_[0]}; - my $tpath = $self->{_vyatta_template_dir}; - for my $p (@cfg_path) { - if (-d "$tpath/$p") { - $tpath .= "/$p"; - next; +## parseTmplAll("path") +# return hash ref of parsed template of specified path, undef if path is +# invalid. note: if !allow_val, path must terminate at a "node", not "value". +sub parseTmplAll { + my ($self, $path, $allow_val) = @_; + my $href = $self->{_cstore}->getParsedTmpl($self->get_path_comps($path), + $allow_val); + if (defined($href)) { + # some conversions are needed + if (defined($href->{is_value}) and $href->{is_value} eq '1') { + $href->{is_value} = 1; } - if (-d "$tpath/node.tag") { - $tpath .= "/node.tag"; - next; + if (defined($href->{multi}) and $href->{multi} eq '1') { + $href->{multi} = 1; + } + if (defined($href->{tag}) and $href->{tag} eq '1') { + $href->{tag} = 1; + } + if (defined($href->{limit})) { + $href->{limit} = int($href->{limit}); } - # the path is not valid! - return; } - return $tpath; + return $href; } -sub isTagNode { - my $self = shift; - my $cfg_path_ref = shift; - my $tpath = $self->getTmplPath($cfg_path_ref); - return unless $tpath; +sub hasTmplChildren { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->tmplGetChildNodes($self->get_path_comps($path)); + return if (!defined($ref)); + return (scalar(@{$ref}) > 0); +} + + +###### +# "deactivate-aware" observers of current working config or active config. +# * MUST ONLY be used by operations that NEED to distinguish between +# deactivated nodes and deleted nodes. below is the list of operations +# that are allowed to use these functions: +# * configuration output (show, save, load) +# +# operations that are not on the above list MUST NOT use these +# "deactivate-aware" functions. + +## deactivated("node") +# return whether specified node is deactivated in working config. +# note that this is different from "marked deactivated". if a node is +# "marked deactivated", then the node itself and any descendants are +# "deactivated". +sub deactivated { + my ($self, $path) = @_; + return 1 + if ($self->{_cstore}->cfgPathDeactivated($self->get_path_comps($path), + undef)); + return; # note: this return is needed. +} + +## deactivatedOrig("node") +# return whether specified node is deactivated in active config. +sub deactivatedOrig { + my ($self, $path) = @_; + return 1 + if ($self->{_cstore}->cfgPathDeactivated($self->get_path_comps($path), 1)); + return; # note: this return is needed. +} + +## returnValuesDA("node") +# DA version of returnValues() +sub returnValuesDA { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetValuesDA($self->get_path_comps($path), + undef); + return @{$ref}; +} + +## returnOrigValuesDA("node") +# DA version of returnOrigValues() +sub returnOrigValuesDA { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetValuesDA($self->get_path_comps($path), + 1); + return @{$ref}; +} + +## returnValueDA("node") +# DA version of returnValue() +sub returnValueDA { + my ($self, $path) = @_; + return $self->{_cstore}->cfgPathGetValueDA($self->get_path_comps($path), + undef); +} + +## returnOrigValueDA("node") +# DA version of returnOrigValue() +sub returnOrigValueDA { + my ($self, $path) = @_; + return $self->{_cstore}->cfgPathGetValueDA($self->get_path_comps($path), 1); +} + +## listOrigNodesDA("level") +# DA version of listOrigNodes() +sub listOrigNodesDA { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetChildNodesDA( + $self->get_path_comps($path), 1); + return @{$ref}; +} + +## listNodeStatusDA("level") +# DA version of listNodeStatus() +sub listNodeStatusDA { + my ($self, $path) = @_; + my $ref = $self->{_cstore}->cfgPathGetChildNodesStatusDA( + $self->get_path_comps($path)); + return %{$ref}; +} - return (-d "$tpath/node.tag"); +## returnComment("node") +# return comment of "node" or undef if comment doesn't exist +sub returnComment { + my ($self, $path) = @_; + return $self->{_cstore}->cfgPathGetComment($self->get_path_comps($path), + undef); } -sub hasTmplChildren { - my $self = shift; - my $cfg_path_ref = shift; - my $tpath = $self->getTmplPath($cfg_path_ref); - return unless $tpath; - opendir (my $tdir, $tpath) or return; - my @tchildren = grep !/^node\.def$/, (grep !/^\./, (readdir $tdir)); - closedir $tdir; +############################################################ +# high-level API functions (not using the cstore library directly) +############################################################ - return (scalar(@tchildren) > 0); +## setLevel("level") +# set the current level of config hierarchy to specified level (if defined). +# return the current level. +sub setLevel { + my ($self, $level) = @_; + $self->{_level} = $level if defined($level); + return $self->{_level}; } -# $cfg_path_ref: ref to array containing the node path. -# returns ($is_multi, $is_text, $default), -# or undef if specified node is not valid. +## returnParent("..( ..)*") +# return the name of ancestor node relative to the current level. +# each level up is represented by a ".." in the argument. +sub returnParent { + my ($self, $ppath) = @_; + my @pcomps = @{$self->get_path_comps()}; + # we could call split in scalar context but that generates a warning + my @dummy = split(/\s+/, $ppath); + my $num = scalar(@dummy); + return if ($num > scalar(@pcomps)); + return $pcomps[-$num]; +} + +## parseTmpl("path") +# parse template of specified path and return ($is_multi, $is_text, $default) +# or undef if specified path is not valid. sub parseTmpl { - my $self = shift; - my $cfg_path_ref = shift; - my ($is_multi, $is_text, $default) = (0, 0, undef); - my $tpath = $self->getTmplPath($cfg_path_ref); - return unless $tpath; - - if (! -r "$tpath/node.def") { - return ($is_multi, $is_text); - } - - open (my $tmpl, '<', "$tpath/node.def") - or return ($is_multi, $is_text); - foreach (<$tmpl>) { - if (/^multi:/) { - $is_multi = 1; - } - if (/^type:\s+txt\s*$/) { - $is_text = 1; - } - if (/^default:\s+(\S+)\s*$/) { - $default = $1; - } - } - close $tmpl; + my ($self, $path) = @_; + my $href = $self->parseTmplAll($path); + return if (!defined($href)); + my $is_multi = $href->{multi}; + my $is_text = (defined($href->{type}) and $href->{type} eq 'txt'); + my $default = $href->{default}; return ($is_multi, $is_text, $default); } -# $cfg_path: config path of the node. -# returns a hash ref containing attributes in the template -# or undef if specified node is not valid. -sub parseTmplAll { - my ($self, $cfg_path) = @_; - my @pdirs = split(/ +/, $cfg_path); - my %ret = (); - my $tpath = $self->getTmplPath(\@pdirs); - return unless $tpath; - - open(my $tmpl, '<', "$tpath/node.def") - or return; - foreach (<$tmpl>) { - if (/^multi:\s*(\S+)?$/) { - $ret{multi} = 1; - $ret{limit} = $1; - } elsif (/^tag:\s*(\S+)?$/) { - $ret{tag} = 1; - $ret{limit} = $1; - } elsif (/^type:\s*(\S+),\s*(\S+)\s*$/) { - $ret{type} = $1; - $ret{type2} = $2; - } elsif (/^type:\s*(\S+)\s*$/) { - $ret{type} = $1; - } elsif (/^default:\s*(\S.*)\s*$/) { - $ret{default} = $1; - if ($ret{default} =~ /^"(.*)"$/) { - $ret{default} = $1; - } - } elsif (/^help:\s*(\S.*)$/) { - $ret{help} = $1; - } elsif (/^enumeration:\s*(\S+)$/) { - $ret{enum} = $1; - } - } - close($tmpl); - return \%ret; +## isTagNode("path") +# whether specified path is a tag node. +sub isTagNode { + my ($self, $path) = @_; + my $href = $self->parseTmplAll($path); + return (defined($href) and $href->{tag}); } -# $cfg_path: config path of the node. -# returns the list of the node's children in the template hierarchy. -sub getTmplChildren { - my ($self, $cfg_path) = @_; - my @pdirs = split(/ +/, $cfg_path); - my $tpath = $self->getTmplPath(\@pdirs); - return () unless $tpath; - - opendir (my $tdir, $tpath) or return; - my @tchildren = grep !/^node\.def$/, (grep !/^\./, (readdir $tdir)); - closedir $tdir; - - return @tchildren; +## isLeafNode("path") +# whether specified path is a "leaf node", i.e., single-/multi-value node. +sub isLeafNode { + my ($self, $path) = @_; + my $href = $self->parseTmplAll($path, 1); + return (defined($href) and !$href->{is_value} and $href->{type} + and !$href->{tag}); } -###### misc functions ###### +## isLeafValue("path") +# whether specified path is a "leaf value", i.e., value of a leaf node. +sub isLeafValue { + my ($self, $path) = @_; + my $href = $self->parseTmplAll($path, 1); + return (defined($href) and $href->{is_value} and !$href->{tag}); +} # compare two value lists and return "deleted" and "added" lists. # since this is for multi-value nodes, there is no "changed" (if a value's @@ -988,4 +640,39 @@ sub compareValueLists { return %comp_hash; } +############################################################ +# API functions that have not been converted +############################################################ + +# XXX the following function should not be needed. the only user is +# ConfigLoad, which uses this to get all deactivated nodes in active +# config and then reactivates everything on load. +# +# this works for "load" but not for "merge", which incorrectly +# reactivates all deactivated nodes even if they are not in the config +# file to be merged. see bug 5746. +# +# how to get rid of this function depends on how bug 5746 is going +# to be fixed. +## getAllDeactivated() +# returns array of all deactivated nodes. +my @all_deactivated_nodes; +sub getAllDeactivated { + my ($self, $path) = @_; + my $start_dir = $ENV{VYATTA_ACTIVE_CONFIGURATION_DIR}; + find ( \&wanted, $start_dir ); + return @all_deactivated_nodes; +} +sub wanted { + if ( $_ eq '.disable' ) { + my $f = $File::Find::name; + #now strip off leading path and trailing file + $f = substr($f, length($ENV{VYATTA_ACTIVE_CONFIGURATION_DIR})); + $f = substr($f, 0, length($f)-length("/.disable")); + $f =~ s/\// /g; + push @all_deactivated_nodes, $f; + } +} + 1; + |