#!/usr/bin/perl -w
#
# export-control -- Export the newsgroup database as an active file.
#
# Copyright 2003, 2008, 2011 Russ Allbery <eagle@eyrie.org>
#
# SPDX-License-Identifier: MIT

##############################################################################
# Site configuration
##############################################################################

# Path to the active newsgroup database.
our $ACTIVE = '/srv/control/active.db';

# Path to the control.ctl file to use.
our $CONTROL = '/srv/control/control.ctl';

# The directory in which to look for log files.
our $LOGS = '/srv/control/logs';

# Directory in which to create the active and newsgroups files.
our $ROOT = '/srv/control/export';

##############################################################################
# Modules and declarations
##############################################################################

require 5.006;

use strict;

use DB_File ();
use Fcntl qw(LOCK_EX);
use File::Copy qw(copy);

##############################################################################
# Implementation
##############################################################################

# Takes in a newsgroup name and returns the right number of tabs after it for
# an entry in a newsgroups file.
sub tabs {
    my $newsgroup = shift;
    my $extra = int ((23 - length $newsgroup) / 8);
    $extra = $extra > 0 ? $extra : 0;
    return ("\t" x (1 + $extra));
}

# Trim extraneous garbage from the path.
my $fullpath = $0;
$0 =~ s%.*/%%;

# Get permissions right.
umask 002;

# Create the active and newsgroups files.
open (ACTIVE, "> $ROOT/.active.new")
    or die "$0: cannot create $ROOT/.active.new: $!\n";
open (NEWSGROUPS, "> $ROOT/.newsgroups.new")
    or die "$0: cannot create $ROOT/.newsgroups.new: $!\n";

# Open and lock the database.
my %db;
open (LOCK, "+> $ACTIVE.lock") or die "$0: cannot open $ACTIVE.lock: $!";
flock (LOCK, LOCK_EX) or die "$0: cannot lock $ACTIVE.lock: $!";
tie (%db, 'DB_File', $ACTIVE) or die "$0: cannot tie $ACTIVE: $!";

# Do the export.
for my $group (sort keys %db) {
    my ($mode, $desc) = split (' ', $db{$group}, 2);
    print ACTIVE "$group 0000000000 0000000001 $mode\n";
    print NEWSGROUPS $group, tabs ($group), "$desc\n";
}
untie %db;
close LOCK;
close ACTIVE;
close NEWSGROUPS;

# Create the final versions.
rename ("$ROOT/.active.new", "$ROOT/active")
    or die "$0: cannot rename $ROOT/.active.new to $ROOT/active\n";
rename ("$ROOT/.newsgroups.new", "$ROOT/newsgroups")
    or die "$0: cannot rename $ROOT/.newsgroups.new to $ROOT/newsgroups\n";
system ("gzip -9 -c '$ROOT/active' > '$ROOT/active.gz'") == 0
    or die "$0: gzip of active exited with status ", ($? >> 8), "\n";
system ("gzip -9 -c '$ROOT/newsgroups' > '$ROOT/newsgroups.gz'") == 0
    or die "$0: gzip of newsgroups exited with status ", ($? >> 8), "\n";
system ("bzip2 -kf '$ROOT/active'") == 0
    or die "$0: bzip2 of active exited with status ", ($? >> 8), "\n";
system ("bzip2 -kf '$ROOT/newsgroups'") == 0
    or die "$0: bzip2 of newsgroups exited with status ", ($? >> 8), "\n";

# Copy some other files into the export directory.
copy ($CONTROL, "$ROOT/control.ctl");
my ($mtime) = (stat $CONTROL)[9];
utime 0, $mtime, "$ROOT/control.ctl";
unless (-d "$ROOT/LOGS") {
    mkdir "$ROOT/LOGS" or die "$0: cannot mkdir $ROOT/LOGS: $!\n";
}
opendir (LOGS, $LOGS) or die "$0: cannot opendir $LOGS: $!\n";
for (grep { /^log\.\d{4}-\d\d$/ } readdir LOGS) {
    copy ("$LOGS/$_", "$ROOT/LOGS/$_");
    chmod 0664, "$ROOT/LOGS/$_";
    my ($mtime) = (stat "$LOGS/$_")[9];
    utime 0, $mtime, "$ROOT/LOGS/$_";
}
closedir LOGS;

##############################################################################
# Documentation
##############################################################################

=head1 NAME

export-control - Export the newsgroup database as an active file

=head1 SYNOPSIS

B<export-control>

=head1 DESCRIPTION

This program takes the database maintained by B<process-control> and
B<update-control> and exports it as an active and newsgroups file in a
format suitable for import into news software or for use with tools like
actsync(8).

B<export-control> generates an active file numered so that all of the groups
are empty and a standard-format newsgroups file as well as versions of both
files compressed with B<gzip> and B<bzip2>.  At the same time, it also
copies the current F<control.ctl> file into the export directory and copies
any log files that it finds into a F<LOGS> subdirectory.

It is meant to be run periodically from cron to update a directory that is
exported to other systems somehow (such as by a web server).

=head1 FILES

=over 4

=item F</srv/control/active.db>

The database of active groups, updated as described above.  It is locked
against simultaneous access by using fcntl locking on a file by the same
name but with C<.lock> appended.

=item F</srv/control/control.ctl>

The F<control.ctl> file used by B<process-control> to determine which
control messages should be applied to the active newsgroups database.  Just
copied into the export directory by B<export-control>.

=item F</srv/control/export>

The export directory into which the generated active and newsgroups files
are put.  Logs are copied into a LOGS subdirectory.  Other files in this
directory are left untouched.

=item F</srv/control/logs/log.%Y-%m>

Where actions are logged by B<process-control> and B<update-control>.  %Y is
replaced by the current four-digit year and %m by the current two digit
month.  B<export-control> scans for any files fitting this name pattern and
copies them into a LOGS subdirectory of the export directory.

=back

=head1 AUTHOR

Russ Allbery <eagle@eyrie.org>

=head1 SEE ALSO

process-control(1), update-control(1)

This script is part of the control-archive package.  The control-archive web
page at L<https://www.eyrie.org/~eagle/software/control-archive/> will have
the current version of the package.

=cut
