#! /usr/pkg/bin/perl -w
use lib '/usr/pkg/lib/perl'; use INN::Config;

# batch-active-update
# Author: David Lawrence <tale@isc.org>

# Reads a series of ctlinnd newgroup/rmgroup/changegroup commands, such as
# is output by docheckgroups and actsync, and efficiently handles them all at
# once.  Input can come from command-line files or stdin, a la awk/sed.

use strict;

my $oldact = $INN::Config::active;         # active file location
my $newact = "$oldact.new$$";              # temporary name for new active file
my $actime = $INN::Config::activetimes;    # active.times file
my $pausemsg = 'batch active update, ok';  # message to be used for pausing?
my $diff_flags = '';    # flags for diff(1); default chosen if null
my $changes = 0;        # number of changes to do

$0 =~ s#^.*/##;

die "$0: must run as $INN::Config::newsuser user"
  unless $> == (getpwnam($INN::Config::newsuser))[2];

my $debug = -t STDOUT ? 1 : 0;

local $| = 1;           # show output as it happens (for an rsh/ssh pipe)

# Guess at best flags for a condensed diff listing.  The
# checks for alternative operating systems is incomplete.
unless ($diff_flags) {
    if (`diff -v 2>&1` =~ /GNU/) {
        $diff_flags = '-U0';
    } elsif ($^O =~ /^(dec_osf|solaris)$/) {
        $diff_flags = '-C0';
    } elsif ($^O eq 'nextstep') {
        $diff_flags = '-c0';
    } else {
        $diff_flags = '-c';
    }
}

print "reading list of groups to update\n" if $debug;

my (%toadd, %todelete, %tochange);

# Read the commands given to mod-active.
while (<>) {
    if (/^\s*\S*ctlinnd newgroup (\S+) (\S+)/) {
        $toadd{$1} = $2;
        $changes++;
    } elsif (/^\s*\S*ctlinnd rmgroup (\S+)/) {
        $todelete{$1} = 1;
        $changes++;
    } elsif (/^\s*\S*ctlinnd changegroup (\S+) (\S+)/) {
        $tochange{$1} = $2;
        $changes++;
    }
}

if ($changes == 0) {
    print "active file not changed\n" if $debug;
    exit 0;
}

print "$changes change(s) to do\n" if $debug;

ctlinnd("pause $pausemsg");

open(my $OLDACT, '<', $oldact) || die "$0: open $oldact: $!\n";
open(my $NEWACT, '>', $newact) || die "$0: open $newact: $!\n";

print "rewriting active file\n" if $debug;

# Read the current active file.  The beginning of each line is
# the name of an existing newsgroup.
while (<$OLDACT>) {
    my $group = (split)[0];
    next if exists $todelete{$group};
    s/ \S+$/ $tochange{$group}/ if exists $tochange{$group};
    delete $toadd{$group};    # The newsgroup already exists.
    if (!print $NEWACT $_) {
        # Do not forget to restart INN before dying.
        ctlinnd("go $pausemsg");
        die "$0: writing $newact failed ($!), aborting\n";
    }
}

for (sort keys %toadd) {
    my $add = "$_ 0000000000 0000000001 $toadd{$_}\n";
    if (!print $NEWACT $add) {
        # Do not forget to restart INN before dying.
        ctlinnd("go $pausemsg");
        die "$0: writing $newact failed ($!), aborting\n";
    }
}

close($OLDACT) || warn "$0: close $oldact: $!\n";
close($NEWACT) || warn "$0: close $newact: $!\n";

if (!rename "$oldact", "$oldact.old") {
    warn "$0: rename $oldact $oldact.old: $!\n";
}

if (!rename "$newact", "$oldact") {
    # Do not restart INN here: we no longer have a valid active file!
    die "$0: rename $newact $oldact: $!\n";
}

ctlinnd("reload active 'updated from checkgroups'");
system "diff $diff_flags $oldact.old $oldact";
ctlinnd("go $pausemsg");

print "updating $actime\n" if $debug;
if (open(my $TIMES, '>>', $actime)) {
    my $time = time;
    for (sort keys %toadd) {
        print($TIMES "$_ $time checkgroups-update\n") || last;
    }
    close($TIMES) || warn "$0: close $actime: $!\n";
} else {
    warn "$0: $actime not updated: $!\n";
}

print "chmoding active files\n" if $debug;
if (!chmod 0664, $oldact, "$oldact.old", $actime) {
    warn "$0: chmod $oldact $oldact.old $actime: $!\n";
}

exit 0;

sub ctlinnd {
    my ($command) = @_;

    print "ctlinnd $command\n" if $debug;
    if (system "$INN::Config::newsbin/ctlinnd -s $command") {
        die "$0: \"$command\" failed, aborting\n";
    }
}
