# cfengine-lib.pl # Functions for parsing the cfengine config file do '../web-lib.pl'; &init_config(); do '../ui-lib.pl'; @known_sections = ( 'groups', 'control', 'homeservers', 'binservers', 'mailserver', 'mountables', 'broadcast', 'resolve', 'defaultroute', 'directories', 'miscmounts', 'files', 'ignore', 'tidy', 'required', 'links', 'disable', 'shellcommands', 'editfiles', 'processes', 'copy' ); @known_sections = sort { $text{"section_$a"} cmp $text{"section_$b"} } @known_sections; @known_cfd_sections = ( 'control', 'groups', 'admit', 'deny' ); @known_cfd_sections = sort { $text{"section_$a"} cmp $text{"section_$b"} } @known_cfd_sections; $cfengine_conf = $config{'cfengine_conf'} ? $config{'cfengine_conf'} : "$config{'cfengine_dir'}/cfengine.conf"; $cfd_conf = $config{'cfd_conf'} ? $config{'cfd_conf'} : "$config{'cfengine_dir'}/cfd.conf"; $cfrun_hosts = $config{'cfrun_hosts'} ? $config{'cfrun_hosts'} : "$config{'cfengine_dir'}/cfrun.hosts"; # get_config() # Parses the cfengine.conf file into a list of sections, each containing # a list of classes, each containing options for the section type. sub get_config { if (!defined(@get_config_cache)) { @get_config_cache = &get_config_file($cfengine_conf); } return \@get_config_cache; } # get_cfd_config() # Parses the cfd.conf file sub get_cfd_config { if (!defined(@get_cfd_config_cache)) { @get_cfd_config_cache = &get_config_file($cfd_conf); } return \@get_cfd_config_cache; } # get_config_file(file) # Parses on specified cfengine config file sub get_config_file { # Parse file into tokens local $lnum = 0; local @toks; open(CONFIG, $_[0]); while() { s/\r|\n//g; s/(^|\s+)#.*$//g; local ($s, $q) = &split_str($_); local $i; for($i=0; $i<@$s; $i++) { push(@toks, [ $s->[$i], $lnum, $q->[$i] ]); } $lnum++; } close(CONFIG); # Parse tokens into the config local (@rv, $sec, $cls, $i); push(@rv, { 'type' => 'dummy', # dummy record so that config is never empty 'index' => 0, 'line' => -1, 'eline' => -1, 'file' => $_[0] } ); for($i=0; $i<@toks; $i++) { local $t = $toks[$i]; if ($t->[0] =~ /^(\S+)::$/) { # Start of a class $cls = { 'name' => $1, 'type' => 'class', 'index' => scalar(@{$sec->{'cls'}}), 'line' => $t->[1], 'eline' => $t->[1], 'file' => $_[0] }; $sec->{'eline'} = $t->[1]; push(@{$sec->{'cls'}}, $cls); } elsif ($t->[0] =~ /^(\S+):$/) { # Start of a section $sec = { 'name' => $1, 'type' => 'section', 'index' => scalar(@rv), 'cls' => [ ], 'line' => $t->[1], 'eline' => $t->[1], 'file' => $_[0] }; undef($cls); push(@rv, $sec); } else { # Some kind of text within a class .. create all:: if needed if (!$sec) { &error("Unknown directive at line ", ($t->[1]+1)," in $_[0]"); } if (!$cls) { $cls = { 'name' => 'any', 'type' => 'class', 'implied' => 1, 'index' => scalar(@{$sec->{'cls'}}), 'line' => $t->[1], 'eline' => $t->[1], 'file' => $_[0] }; push(@{$sec->{'cls'}}, $cls); } if ($i+2 < @toks && $toks[$i+1]->[0] eq '=' && $toks[$i+2]->[0] eq '(') { # A definition with multiple values local $def = { 'name' => $t->[0], 'line' => $t->[1], 'file' => $_[0] }; $i += 3; while($i < @toks && $toks[$i]->[0] ne ')') { push(@{$def->{'values'}}, $toks[$i]->[0]); push(@{$def->{'valuequotes'}}, $toks[$i]->[2]); push(@{$def->{'valuequoted'}}, "e_str($toks[$i]->[0], $toks[$i]->[2])); $i++; } $sec->{'eline'} = $cls->{'eline'} = $toks[$i]->[1]; push(@{$cls->{'defs'}}, $def); } elsif ($t->[0] eq '{') { # A { } quoted list local $list = { 'line' => $t->[1], 'file' => $_[0] }; $i++; while($i < @toks && $toks[$i]->[0] ne '}') { push(@{$list->{'values'}}, $toks[$i]->[0]); push(@{$list->{'valuelines'}}, $toks[$i]->[1]); push(@{$list->{'valuequotes'}}, $toks[$i]->[2]); push(@{$list->{'valuequoted'}}, "e_str($toks[$i]->[0], $toks[$i]->[2])); $i++; } $sec->{'eline'} = $cls->{'eline'} = $toks[$i]->[1]; push(@{$cls->{'lists'}}, $list); } else { # A single value push(@{$cls->{'values'}}, $t->[0]); push(@{$cls->{'valuelines'}}, $t->[1]); push(@{$cls->{'valuequotes'}}, $t->[2]); push(@{$cls->{'valuequoted'}}, "e_str($t->[0], $t->[2])); $sec->{'eline'} = $cls->{'eline'} = $t->[1]; } } } # Expand import: sections local @imps = &find("import", \@rv); foreach $i (@imps) { foreach $c (@{$i->{'cls'}}) { next if ($c->{'name'} ne 'any'); # XXX only do 'any::' foreach $v (@{$c->{'values'}}) { local $fn = $v; if ($fn !~ /^\//) { # Assume relative to current config file $fn = "$config{'cfengine_dir'}/$fn"; } local @inc = &get_config_file($fn); map { $_->{'index'} += scalar(@rv) } @inc; push(@rv, @inc); } } } return @rv; } # find(name, &config) sub find { local ($c, @rv); foreach $c (@{$_[1]}) { push(@rv, $c) if ($c->{'name'} eq $_[0]); } return wantarray ? @rv : $rv[0]; } # find_value(name, &config) sub find_value { local @f = &find($_[0], $_[1]); return wantarray ? () : undef if (!@f); return wantarray ? @{$f[0]->{'values'}} : $f[0]->{'values'}->[0]; } # save_directive(&config, &old, &new) # Updates or adds an entire class or section to the config file sub save_directive { local $file = $_[1] ? $_[1]->{'file'} : $_[0]->[0]->{'file'}; local $lref = &read_file_lines($file); local @lines = &directive_lines($_[2]) if ($_[2]); if ($_[1]) { splice(@$lref, $_[1]->{'line'}, $_[1]->{'eline'} - $_[1]->{'line'} + 1, @lines); } else { splice(@$lref, $_[0]->[@{$_[0]}-1]->{'eline'}+1, 0, @lines); } } # swap_directives(&config, &directive1, &directive2) # Swaps two directives in the config file sub swap_directives { local $lref = &read_file_lines($_[1]->{'file'}); local @lines1 = @$lref[$_[1]->{'line'} .. $_[1]->{'eline'}]; local @lines2 = @$lref[$_[2]->{'line'} .. $_[2]->{'eline'}]; if ($_[1]->{'line'} < $_[2]->{'line'}) { splice(@$lref, $_[2]->{'line'}, scalar(@lines2), @lines1); splice(@$lref, $_[1]->{'line'}, scalar(@lines1), @lines2); } elsif ($_[1]->{'line'} > $_[2]->{'line'}) { splice(@$lref, $_[1]->{'line'}, scalar(@lines1), @lines2); splice(@$lref, $_[2]->{'line'}, scalar(@lines2), @lines1); } } # directive_lines(§ion|&class) # Returns an array of the lines for some section or class sub directive_lines { local (@rv, $d, $l, $i); if ($_[0]->{'type'} eq 'section') { push(@rv, "$_[0]->{'name'}:"); if (defined($_[0]->{'text'})) { return (@rv, split(/\n/, $_[0]->{'text'})); } foreach $c (@{$_[0]->{'cls'}}) { push(@rv, &directive_lines($c)); } } elsif ($_[0]->{'type'} eq 'class') { local $id = $_[0]->{'implied'} ? "\t" : "\t\t"; push(@rv, "\t$_[0]->{'name'}::") if (!$_[0]->{'implied'}); if (defined($_[0]->{'text'})) { return (@rv, split(/\n/, $_[0]->{'text'})); } foreach $d (@{$_[0]->{'defs'}}) { local @v; local $i; for($i=0; $i<@{$d->{'values'}}; $i++) { push(@v, "e_str($d->{'values'}->[$i], $d->{'valuequotes'}->[$i])); } push(@rv, "$id$d->{'name'} = ( ".join(" ", @v)." )"); } foreach $l (@{$_[0]->{'lists'}}) { push(@rv, $id."{"); push(@rv, &value_lines($l->{'values'}, $l->{'valuelines'}, $l->{'valuequotes'}, $id)); push(@rv, $id."}"); } push(@rv, &value_lines($_[0]->{'values'}, $_[0]->{'valuelines'}, $_[0]->{'valuequotes'}, $id)); } return @rv; } # value_lines(&values, &lines, "es, indent) sub value_lines { local @lines; local $i; for($i=0; $i<@{$_[0]}; $i++) { local $l = $_[1]->[$i] - $_[1]->[0]; if ($lines[$l] eq "") { $lines[$l] = $_[3]; } else { $lines[$l] .= " "; } $lines[$l] .= "e_str($_[0]->[$i], $_[2]->[$i]); } return @lines; } # quote_str(string, quote) sub quote_str { if (defined($_[1])) { return $_[1].$_[0].$_[1]; } elsif ($_[0] =~ /^\S+$/) { return $_[0]; } elsif ($_[0] !~ /"/) { return "\"$_[0]\""; } elsif ($_[0] !~ /'/) { return "'$_[0]'"; } else { return "`$_[0]`"; } } # split_str(string) sub split_str { local (@rv, @qu); local $str = $_[0]; while($str =~ /^\s*(")([^"]*)"(.*)$/s || $str =~ /^\s*(')([^"]*)'(.*)$/s || $str =~ /^\s*(`)([^"]*)`(.*)$/s || $str =~ /^\s*()(\S+)(.*)$/s) { push(@qu, $1); push(@rv, $2); $str = $3; } return ( \@rv, \@qu ); } # get_cfrun_hosts() sub get_cfrun_hosts { local (@hosts, %opts); open(HOSTS, $cfrun_hosts); while() { s/\r|\n//g; s/#.*$//; if (/^\s*(\S+)\s*=\s*(.*)/) { $opts{$1} = $2; } elsif (/^\s*(\S+)\s*(.*)/) { push(@hosts, [ $1, $2] ); } } close(HOSTS); return ( \@hosts, \%opts ); } # save_cfrun_hosts(&hosts, &opts) sub save_cfrun_hosts { open(HOSTS, ">$cfrun_hosts"); foreach $o (keys %{$_[1]}) { print HOSTS $o,"=".$_[1]->{$o},"\n"; } foreach $h (@{$_[0]}) { print HOSTS $h->[0]," ",$h->[1],"\n"; } close(HOSTS); } # show_classes_table(&config, cfd, readonly) sub show_classes_table { if (@{$_[0]}) { &show_button($_[1]) if (!$_[2]); print "\n"; print " ", ($_[1] ? "" : ""), " ", " ", "\n"; local $s; local %active; foreach $s (@{$_[0]}) { foreach $c (@{$s->{'cls'}}) { if ($s->{'name'} eq 'control') { local ($as) = &find("actionsequence", $c->{'defs'}); local $cc = $c->{'name'} eq 'any' ? 2 : 1; map { s/\..*$//; $active{$_} = $cc if ($cc > $active{$_}) } @{$as->{'values'}} if ($as); } } } foreach $s (@{$_[0]}) { next if ($s->{'name'} =~ /^import/); local (@clist, @dlist); foreach $c (@{$s->{'cls'}}) { push(@clist, $_[2] ? $c->{'name'} : "{'index'}'>$c->{'name'}"); local $desc; if ($s->{'name'} eq 'links') { local @l = &parse_links($c); if (@l > 1) { $desc = &text('index_links2', scalar(@l)); } elsif (@l) { $desc = &text('index_links', "$l[0]->{'_linkfrom'}", "$l[0]->{'_linkto'}"); } } elsif ($s->{'name'} eq 'directories' || $s->{'name'} eq 'files' || $s->{'name'} eq 'disable' || $s->{'name'} eq 'shellcommands' || $s->{'name'} eq 'tidy' || $s->{'name'} eq 'required' || $s->{'name'} eq 'disks') { local @dirs = &parse_directories($c); if (@dirs > 1) { $desc = &text('index_'.$s->{'name'}.'2', scalar(@dirs)); } elsif (@dirs) { $desc = &text('index_'.$s->{'name'}, "$dirs[0]->{'_dir'}"); } } elsif ($s->{'name'} eq 'control' && $_[1]) { local ($cmd) = &find("cfrunCommand", $c->{'defs'}); if ($cmd) { $desc = &text('index_cfrun', "$cmd->{'values'}->[0]"); } } elsif ($s->{'name'} eq 'control' && !$_[1]) { local ($as) = &find("actionsequence", $c->{'defs'}); if ($as) { local @v = @{$as->{'values'}}; if (@v > 1) { $desc = &text('index_control2', scalar(@v)); } else { $desc = &text('index_control', "$v[0]"); } } } elsif ($s->{'name'} eq 'editfiles') { local @files = map { $_->{'values'}->[0] } @{$c->{'lists'}}; if (@files > 1) { $desc = &text('index_editfiles2', scalar(@files)); } elsif (@files) { $desc = &text('index_editfiles', "$files[0]"); } } elsif ($s->{'name'} eq 'grant' || $s->{'name'} eq 'admit' || $s->{'name'} eq 'deny') { local @dirs = grep { /\// } @{$c->{'values'}}; if (@dirs > 1) { $desc = &text('index_'.$s->{'name'}.'2', scalar(@dirs)); } elsif (@dirs) { $desc = &text('index_'.$s->{'name'}, "$dirs[0]"); } } elsif ($s->{'name'} eq 'groups' || $s->{'name'} eq 'classes') { local @grs = map { $_->{'name'} } @{$c->{'defs'}}; if (@grs) { $desc = &text('index_groups', join(", ", map { "$_" } @grs)); } } elsif ($s->{'name'} eq 'copy') { local @copies = &parse_directories($c); if (@copies > 1) { $desc = &text('index_copy2', scalar(@copies)); } elsif (@copies) { $desc = &text('index_copy', "$copies[0]->{'_dir'}", "$copies[0]->{'dest'}"); } } elsif ($s->{'name'} eq 'ignore') { $desc = &text('index_ignore', scalar(@{$c->{'values'}})); } elsif ($s->{'name'} eq "processes") { local @procs = grep { !$_->{'_options'} } &parse_processes($c); if (@procs > 1) { $desc = &text('index_procs2', scalar(@procs)); } elsif (@procs) { $desc = &text('index_procs', "$procs[0]->{'_match'}"); } } elsif ($s->{'name'} eq 'miscmounts') { local @mnts = &parse_miscmounts($c); if (@mnts > 1) { $desc = &text('index_misc2', scalar(@mnts)); } elsif (@mnts) { $desc = &text('index_misc', "$mnts[0]->{'_src'}"); } } elsif ($s->{'name'} eq 'defaultroute') { $desc = &text('index_route', "$c->{'values'}->[0]"); } $desc = " " if (!$desc); push(@dlist, $desc); } next if (!@clist); local $t = $text{"section_".$s->{'name'}."_".$_[1]}; $t = $text{"section_".$s->{'name'}} if (!$t); print "\n"; if (!$_[1]) { printf "\n"; } printf "\n"; for($i=0; $i<@clist; $i++) { print "\n" if ($i != 0); print "\n"; print "\n"; print "\n" if ($i == 0); } } print "
$text{'index_active'}$text{'index_section'}$text{'index_classes'}$text{'index_details'}
\n", scalar(@clist); local $a = $active{$s->{'name'}}; print $s->{'name'} =~ /^control/ ? "
" : $a == 0 ? "$text{'no'}" : $a == 1 ? "$text{'index_maybe'}" : $text{'yes'}; print "
\n", scalar(@clist); print "\n"; print "\n"; print "\n"; } print "
",$t ? "$t ($s->{'name'})" : $s->{'name'},"\n"; #if ($s eq $_[0]->[@{$_[0]}-1]) { # print ""; # } #else { # print "", # ""; # } #if ($s eq $_[0]->[0]) { # print ""; # } #else { # print "", # ""; # } if (!$_[2]) { print "$text{'index_cadd'}
$clist[$i]$dlist[$i]
\n"; } elsif ($_[1]) { print "

$text{'cfd_none'}

\n"; } else { print "

$text{'index_none'}

\n"; } &show_button($_[1]) if (!$_[2]); } # show_button(cfd) sub show_button { print "

\n"; print "\n"; print "\n"; print "
\n"; } # parse_links(&cls) sub parse_links { local (@rv, $v); foreach $v (@{$_[0]->{'values'}}) { if ($v =~ /^(\S+)=(\S+)$/) { if (defined($rv[$#rv]->{$1})) { $rv[$#rv]->{$1} .= "\0$2"; } else { $rv[$#rv]->{$1} = $2; } } elsif ($v =~ /^[\+\-]>!?$/) { $rv[$#rv]->{'_linktype'} = $v; } else { if (!@rv || $rv[$#rv]->{'_linkfrom'} && $rv[$#rv]->{'_linkto'}) { push(@rv, { '_linkfrom' => $v } ); } else { $rv[$#rv]->{'_linkto'} = $v; } } } return @rv; } # unparse_links(&cls, link, ...) sub unparse_links { local $cls = shift(@_); local ($l, $vl = 0, @values, @valuelines); foreach $l (@_) { push(@values, $l->{'_linkfrom'}, $l->{'_linktype'}, $l->{'_linkto'}); push(@valuelines, $vl, $vl, $vl); foreach $k (keys %$l) { if ($k !~ /^_/) { local $z; foreach $z (split(/\0/, $l->{$k})) { push(@values, "$k=$z"); push(@valuelines, $vl); } } } $vl++; } $cls->{'values'} = \@values; $cls->{'valuelines'} = \@valuelines; $cls->{'valuequotes'} = [ ]; } # parse_directories(&cls) sub parse_directories { local (@rv, $v); foreach $v (@{$_[0]->{'values'}}) { if ($v =~ /^(\S+)=(\S+)$/) { if (defined($rv[$#rv]->{$1})) { $rv[$#rv]->{$1} .= "\0$2"; } else { $rv[$#rv]->{$1} = $2; } } else { push(@rv, { '_dir' => $v } ); } } return @rv; } # unparse_directories(&cls, dir, ...) sub unparse_directories { local $cls = shift(@_); local ($d, $vl = 0, @values, @valuelines); foreach $d (@_) { push(@values, $d->{'_dir'}); push(@valuelines, $vl); foreach $k (keys %$d) { if ($k !~ /^_/) { local $z; foreach $z (split(/\0/, $d->{$k})) { push(@values, "$k=$z"); push(@valuelines, $vl); } } } $vl++; } $cls->{'values'} = \@values; $cls->{'valuelines'} = \@valuelines; $cls->{'valuequotes'} = [ ]; return @rv; } # parse_processes(&cls, &optionstr) sub parse_processes { local (@rv, $v, $i); for($i=0; $i<@{$_[0]->{'values'}}; $i++) { $v = $_[0]->{'values'}->[$i]; if ($v =~ /^(\S+)=(\S+)$/) { if (defined($rv[$#rv]->{$1})) { $rv[$#rv]->{$1} .= "\0$2"; } else { $rv[$#rv]->{$1} = $2; } } elsif ($v eq "restart") { $rv[$#rv]->{'_restart'} = $_[0]->{'values'}->[++$i]; } elsif ($v eq "SetOptionString") { push(@rv, { '_options' => $_[0]->{'values'}->[++$i] } ); } else { push(@rv, { '_match' => $v } ); } } return @rv; } # unparse_processes(&cls, match, ...) sub unparse_processes { local $cls = shift(@_); local ($d, $vl = 0, @values, @valuelines, @valuequotes); foreach $d (@_) { if ($d->{'_options'}) { push(@values, "SetOptionString", $d->{'_options'}); push(@valuelines, $vl, $vl); push(@valuequotes, "", '"'); } else { push(@values, $d->{'_match'}); push(@valuelines, $vl); push(@valuequotes, '"'); foreach $k (keys %$d) { if ($k !~ /^_/) { local $z; foreach $z (split(/\0/, $d->{$k})) { push(@values, "$k=$z"); push(@valuelines, $vl); push(@valuequotes, ""); } } } if ($d->{'_restart'}) { push(@values, "restart", $d->{'_restart'}); push(@valuelines, $vl, $vl); push(@valuequotes, "", '"'); } } $vl++; } $cls->{'values'} = \@values; $cls->{'valuelines'} = \@valuelines; $cls->{'valuequotes'} = \@valuequotes; return @rv; } # unparse_shellcommands(&cls, dir, ...) sub unparse_shellcommands { local $cls = shift(@_); local ($d, $vl = 0, @values, @valuelines, @valuequotes); foreach $d (@_) { push(@values, $d->{'_dir'}); push(@valuelines, $vl); push(@valuequotes, $d->{'_dir'} !~ /"/ ? '"' : $d->{'_dir'} !~ /'/ ? "'" : "`"); foreach $k (keys %$d) { if ($k !~ /^_/) { local $z; foreach $z (split(/\0/, $d->{$k})) { push(@values, "$k=$z"); push(@valuelines, $vl); push(@valuequotes, undef); } } } $vl++; } $cls->{'values'} = \@values; $cls->{'valuelines'} = \@valuelines; $cls->{'valuequotes'} = \@valuequotes; return @rv; } # parse_miscmounts(&cls) sub parse_miscmounts { local (@rv, $v); foreach $v (@{$_[0]->{'values'}}) { if ($v =~ /^(\S+)=(\S+)$/) { if (defined($rv[$#rv]->{$1})) { $rv[$#rv]->{$1} .= "\0$2"; } else { $rv[$#rv]->{$1} = $2; } } elsif ($v !~ /\//) { $rv[$#rv]->{'mode'} = $v; } else { if (@rv && $rv[$#rv]->{'_src'} && !$rv[$#rv]->{'_dest'}) { $rv[$#rv]->{'_dest'} = $v; } else { push(@rv, { '_src' => $v } ); } } } return @rv; } # unparse_miscmounts(&cls, dir, ...) sub unparse_miscmounts { local $cls = shift(@_); local ($d, $vl = 0, @values, @valuelines); foreach $d (@_) { push(@values, $d->{'_src'}, $d->{'_dest'}); push(@valuelines, $vl, $vl); foreach $k (keys %$d) { if ($k !~ /^_/) { local $z; foreach $z (split(/\0/, $d->{$k})) { push(@values, "$k=$z"); push(@valuelines, $vl); } } } $vl++; } $cls->{'values'} = \@values; $cls->{'valuelines'} = \@valuelines; $cls->{'valuequotes'} = [ ]; return @rv; } # list_cfengine_hosts() # Returns a list of all webmin hosts running cfengine known to this module sub list_cfengine_hosts { local (@rv, $f); local $hdir = "$module_config_directory/hosts"; opendir(DIR, $hdir); foreach $f (readdir(DIR)) { if ($f =~ /^(\S+)\.host$/) { local %host = ( 'id', $1 ); &read_file("$hdir/$f", \%host); push(@rv, \%host); } } closedir(DIR); return @rv; } # list_servers() # Returns a list of all servers from the webmin servers module that can be # managed, plus this server sub list_servers { local @servers = &foreign_call("servers", "list_servers"); return ( { 'id' => 0, 'desc' => $text{'this_server'}, 'type' => 'unknown' }, grep { $_->{'user'} } @servers ); } # server_name(&server) sub server_name { return $_[0]->{'desc'} ? $_[0]->{'desc'} : $_[0]->{'host'}; } # save_cfengine_host(&host) # Add or update a managed host sub save_cfengine_host { local $hdir = "$module_config_directory/hosts"; mkdir($hdir, 0700); &write_file("$hdir/$_[0]->{'id'}.host", $_[0]); } # delete_cfengine_host(&host) sub delete_cfengine_host { unlink("$module_config_directory/hosts/$_[0]->{'id'}.host"); } # cfengine_host_version(&server) sub cfengine_host_version { local $out = &remote_eval($_[0]->{'host'}, "cfengine", '`$config{"cfengine"} -V 2>&1`'); return $out =~ /cfengine-(\S+)/ || $out =~ /GNU\s+(\S+)/ ? $1 : undef; } # show_run_form() sub show_run_form { print " $text{'run_dry'}\n"; print " $text{'yes'}\n"; print " $text{'no'}\n"; print "$text{'run_noifc'}\n"; print " $text{'yes'}\n"; print " $text{'no'} \n"; print " $text{'run_nomnt'}\n"; print " $text{'yes'}\n"; print " $text{'no'}\n"; print "$text{'run_nocmd'}\n"; print " $text{'yes'}\n"; print " $text{'no'} \n"; print " $text{'run_notidy'}\n"; print " $text{'yes'}\n"; print " $text{'no'}\n"; print "$text{'run_nolinks'}\n"; print " $text{'yes'}\n"; print " $text{'no'} \n"; print " $text{'run_verbose'}\n"; print " $text{'yes'}\n"; print " $text{'no'}\n"; } # get_cfengine_version(&dummy) sub get_cfengine_version { local $out = `$config{'cfengine'} -V 2>&1`; ${$_[0]} = $out; return $out =~ /cfengine-(\S+)/ || $out =~ /GNU\s+(\S+)/ ? $1 : undef; } 1;