use strict;
use IO::File;
use Data::Dumper;
+use POSIX;
use vars qw($quis
$mode $doall
" -v --verbose extra verbose\n";
}
-cfg_fail("config filename $etcfile should have been absolute path of a file")
- unless $etcfile =~ m,^/, && $etcfile !~ m,/$,;
+cfg_fail("config filename $etcfile should not be directory")
+ if $etcfile =~ m,/$,;
use vars qw($default_dir);
-$default_dir= $etcfile;
-$default_dir =~ s,/[^/]+$,,;
+$default_dir= $etcfile =~ m,^.*/, ? $& : './';
use vars qw($slave_dir $slave_prefix $slave_suffix);
$slave_dir= 'slave';
debug_dump('@zone_cfg_list %zone_cfg');
process_zones($mode ? @zone_cfg_list : @ARGV);
debug_dump('%output_contents');
+output_files() if $install;
exit 0;
sub read_config ($) {
my ($if) = @_;
- my ($fh,$z,@self,$before, $mod,$dir,$prefix,$suffix,$lprefix,$lsuffix);
+ my ($fh,$z,@self,$before,
+ $mod,$dir,$prefix,$suffix,$subfile,$lprefix,$lsuffix,$zf);
local ($_);
$fh= new IO::File $if,'r' or cfg_fail("open $if:\n $!");
@self_soa= @self if $1 ne '-ns';
} elsif (m/^self\-addr\s+([0-9. \t]+)/) {
@self_addr= split /\s+/, $1;
- } elsif (m/^primary\-dir([*?]?)\s+(\S+)((?:\s+(\S+))?:\s+(\S+))?$/) {
- ($mod, $dir, $prefix, $suffix) = ($1,qualify($2),$3,$4);
- $suffix= '_db' if !defined $suffix;
- $prefix= '' if !defined $prefix;
+ } elsif (m,^
+ primary\-dir ([*?]?)
+ \s+ (\S+)/([^/ \t]*)
+ (?: \s+ ([^/ \t]*) (?: (/.+) )?
+ )?
+ $,x) {
+ ($mod, $dir, $prefix, $suffix, $subfile) =
+ ($1,qualify($2),$3,$4,$5);
+ $suffix= '' if !defined $suffix;
+ $subfile= '' if !defined $subfile;
+ $suffix= '_db' if !length $suffix && !length $subfile;
+ if (-d "$dir/$prefix") { $dir.='/'; $dir.=$prefix; $prefix=''; }
opendir D, $dir or cfg_fail("open primary-dir $dir:\n $!");
$lprefix= length $prefix; $lsuffix= length $suffix;
while (defined($_= readdir D)) {
next unless substr($_,0,$lprefix) eq $prefix;
next unless substr($_,length($_)-$lsuffix) eq $suffix;
$z= substr($_,$lprefix,length($_)-($lprefix+$lsuffix));
- zone_conf($z,'primary','p',$mod,"$dir/$_");
+ $zf= $dir.'/'.$prefix.$z.$suffix.$subfile;
+ if (!stat $zf) {
+ next if length $subfile && $! == &ENOENT;
+ cfg_fail("cannot stat zonefile $zf:\n $!");
+ }
+ -f _ or cfg_fail("zonefile $zf is not a plain file");
+ zone_conf($z,'primary','p',$mod,$zf);
}
closedir D or cfg_fail("close primary-dir $dir:\n $!");
} elsif (m/^primary([*?]?)\s+(\S+)\s+(\S+)$/) {
zone_conf($2,'published','s',$1,'',$3);
} elsif (m/^stealth([*?]?)\s+(\S+)\s+([0-9. \t]+)$/) {
zone_conf($2,'stealth','u',$1,'',split /\s+/, $3);
- } elsif (m/^slave\-dir\s+(\S+)((?:\s+(\S+))?:\s+(\S+))?$/) {
+ } elsif (m/^slave\-dir\s+(\S+)(?:(?:\s+(\S+))\s+(\S+))?$/) {
($slave_dir, $slave_prefix, $slave_suffix) = (qualify($1),$2,$3);
+ $slave_prefix='' if !defined $slave_prefix;
+ $slave_suffix='' if !defined $slave_suffix;
} elsif (m/^output\s+bind8\+(\S+)$/) {
cfg_fail("default output may not apply to only some zones")
if @zone_cfg_list && length $default_output;
sub qualify ($) {
my ($i) = @_;
- $i= "$default_dir/$i" unless $i =~ m,^/,;
+ $i= "$default_dir$i" unless $i =~ m,^/,;
return $i;
}
unless length $default_output;
set_output($default_output);
}
- cfg_fail("redefined zone $zone") if exists $zone_cfg{$zone};
+ cfg_fail("redefined zone $zone\n".
+ " earlier definition $zone_cfg{$zone}{'where'}")
+ if exists $zone_cfg{$zone};
+ $zone_cfg{$zone}{'where'}= $where;
$zone_cfg{$zone}{'file'}= $file;
$zone_cfg{$zone}{'style_p'}= $style.$mod;
$zone_cfg{$zone}{'s'}= $sabbr.$mod; # p)rimary s)econdary u)npub f)oreign
};
zone_warning("checks failed: $@") if length $@;
}
- zone_output() if $install;
+ $output_contents{$$cfg{'output'}} .= zone_output()
+ if $install;
}
print STDERR "$quis: $warnings warnings\n" or die $!
if $warnings;
}
}
if ($cfg->{'s'} =~ m/s/ && !$delg_to_us) {
- zone_warnmore("we are supposedly published secondary,".
- " but not listed as a nameserver");
+ zone_warning("we are supposedly published secondary,".
+ " but not listed as a nameserver");
}
}
#-------------------- outputting
sub zone_output () {
- $output_contents{$$cfg{'output'}}.=
- sprintf(<<'END',
-zone "%s" {
- type %s;
- file "%s";
-};
-END
- $zone,
- $$cfg{'s'} =~ m/p/ ? 'master' : 'slave',
- $$cfg{'file'});
+ my ($o,$m);
+
+ $o= "zone \"$zone\" {\n";
+ if ($$cfg{'s'} =~ m/p/) {
+ $o.= " type master;\n";
+ } else {
+ $o.= " type slave;\n".
+ " masters {\n";
+ foreach $m (@{ $$cfg{'servers'} }) { $o.= " $m;\n"; }
+ $o.= " };\n";
+ }
+ $o.= " file \"$$cfg{'file'}\";\n";
+ $o.= "};\n";
+ return $o;
}
+sub output_files () {
+ my ($fn,$ofn,$mfn,$l,$dir, $maxmode,$h,@to_install);
+
+ foreach $ofn (keys %output_contents) {
+ $fn= $ofn; $mfn= "output file $fn";
+ for (;;) {
+ if (!lstat $fn) {
+ $! == &ENOENT or die "$quis: stat $mfn:\n $!\n";
+ $maxmode= 0666;
+ last;
+ } elsif (-f _) {
+ $maxmode= (stat _)[2];
+ last;
+ } elsif (-l _) {
+ defined($l= readlink $fn)
+ or die "$quis: readlink $mfn:\n $!\n";
+ $dir= $fn =~ m,^.*/, ? $& : './';
+ $fn= "$dir$l" unless $l =~ m,^/,;
+ $mfn= "output file $fn (symlink target of $ofn)";
+ } else {
+ die "$quis: output file $mfn exists but is not a file".
+ " (or symlink to one)";
+ }
+ }
+ unlink "$fn.new" or $! == &ENOENT or
+ die "$quis: cannot clear out old .new version of $mfn:\n $!";
+ $h= new IO::File "$fn.new",'w',$maxmode
+ or die("$quis: create .new version of $mfn:\n $!");
+ print $h
+ "# generated by $quis, do not edit\n",
+ $output_contents{$ofn}
+ or die "$quis: write data to .new version of $mfn:\n $!";
+ $h->close
+ or die "$quis: close .new version of $mfn:\n $!";
+ push @to_install, $fn,$mfn;
+ }
+
+ while (($fn,$mfn, @to_install) = @to_install) {
+ rename "$fn.new",$fn
+ or die "$quis: install new version of $mfn:\n $!";
+ }
+}
#-------------------- general utilities