#!/usr/bin/perl -w

# $Id: jailadmin 138 2004-11-19 21:04:24Z kirk $

# This software was written by Kirk Strauser <kirk@strauser.com>, and may be
# freely distributed under the terms of the BSD License.
#
# Please submit any changes to this program back to the author so that they
# can be easily distributed to other others who might be interested.

use strict;
use Jail;


############################################################
#### Variable declaration                               ####
############################################################
my $server;
my $oper;
my @serverlist = ();
my @jails = Jail::getJailList();


############################################################
#### Configuration options                              ####
############################################################
my %opt =
    (
    'psstyle' => 'new'
    );


############################################################
#### Argument processing                                ####
############################################################

if (@ARGV < 1)
{
    print <<__EOHELP__;
Usage: $0 operation serverspec

Where serverspec is the name of one a jail environments to act upon, a
space-seperated list of names, or "all"; and operation is one of:

    list    List the names of all configured jails
    start   Launch the server
    stop    Stop all processes in the server (really nasty right now)
    status  Display a list of active processes in the server

__EOHELP__

    exit -1;
}

$oper = shift @ARGV;
@serverlist = Jail::canonicalizeList(@ARGV);

############################################################
#### Main                                               ####
############################################################

if ($oper eq 'list')
{
    print "Configured jails:\n";
    foreach $server (@jails)
    {
        print "    $server\n";
    }
    exit;
}

########################################
## Get a jail's status
########################################
if ($oper eq 'status')
{
    foreach $server (sort @serverlist)
    {
        print "Server: $server\n\n";

        unless (Jail::isRunning($server))
        {
            print "That server is not running.\n\n\n";
            next;
        }

        # The following code isn't pretty, but it would take a lot of
        # space to do this stuff elegantly.  Basically, it creates a
        # table of process data with each column justified
        # appropriately, either with a fixed format, or with one
        # relative to the widest entry in that column.  I'm sure that
        # there are CPAN modules that do the same thing, but an
        # important design requirement for this project is that it not
        # rely on any outside dependencies unless absolutely
        # necessary.

        my %procinfo = Jail::getProcInfo($server);
        my @pids = keys %procinfo;

        # The key in this hash is the title of the resulting table.
        # The "keyf" field is the printf formatting string that
        # affects the contents of the cell.  The "titlef" field is the
        # printf formatting string that affects the title cell.  The
        # "order" field controls the sorting order of the fields.  The
        # "key" field is the key in the %procinfo hash that contains
        # the cell's value.

        my %coldef =
            (
            'USER'    => { 'keyf'   => '%%-%ds',
                            'order'  => 1 },
            'PID'     => { 'keyf'   => '%%%ds',
                            'order'  => 2 },
            '%CPU'    => { 'keyf'   => '%%5.1f',
                            'order'  => 3 },
            '%MEM'    => { 'keyf'   => '%%5.1f',
                            'order'  => 4 },
            'RSS'     => { 'keyf'   => '%%%ds',
                            'order'  => 5 },
            'TT'      => { 'keyf'   => '%%-%ds',
                            'order'  => 6 },
            'STAT'    => { 'keyf'   => '%%-%ds',
                            'order'  => 7 },
            'TIME'    => { 'keyf'   => '%%%ds',
                            'order'  => 8 },
            'COMMAND' => { 'keyf'   => '%%s',
                            'order' => 9 }
            );

        # Get the list of column titles, sorted by the numeric value
        # in "order".
        my @columntitles = sort { $coldef{$a}{'order'} <=> $coldef{$b}{'order'} } keys %coldef;

        # Calculate the column key from the column title if needed
        foreach my $name (@columntitles)
        {
            next if defined $coldef{$name}{'key'};
            my $key = lc $name;
            $key =~ s/^\W+//;
            $coldef{$name}{'key'} = $key;
        }

        # Find the longest value in each column
        foreach my $name (@columntitles)
        {
            my $longest = (sort { $b <=> $a }
                        length $name,
                        map { length $procinfo{$_}{$coldef{$name}{'key'}} } @pids)[0];

            # Use that information to build the "keyf" field.
            $coldef{$name}{'keyf'} = sprintf $coldef{$name}{'keyf'}, $longest;

            # If the "titlef" field is defined, then use the length
            # information to build it.
            if (defined $coldef{$name}{'titlef'})
            {
                $coldef{$name}{'titlef'} = sprintf $coldef{$name}{'titlef'}, $longest;
            }
            # If it's not, then base it off the "keyf" field.
            else
            {
                $coldef{$name}{'titlef'} = $coldef{$name}{'keyf'};
                # Replace printf strings like "%5.1f" with "%5s".
                $coldef{$name}{'titlef'} =~ s/(\d+).*$/$1s/;
            }
        }

        # Print the table header
        my $formatstring = join ' ', map { $coldef{$_}{'titlef'} } @columntitles;
        printf "$formatstring\n", @columntitles;

        # Print the table body
        $formatstring = join ' ', map { $coldef{$_}{'keyf'} } @columntitles;
        foreach my $pid (sort @pids)
        {
            printf "$formatstring\n", map { $procinfo{$pid}{$coldef{$_}{'key'}} } @ columntitles;
        }

        print "\n\n";
    }
}

########################################
## Start a jail
########################################
elsif ($oper eq 'start')
{
    foreach $server (sort @serverlist)
    {
        print "Starting server $server...\n";

        # Check for processes already running in that jailspace
        if (Jail::isRunning($server))
        {
            print STDERR "ERROR: That jail already seems to be running\n";
            next;
        }

        Jail::mount($server);
        Jail::launch($server);
    }
}

########################################
## Stop a jail
########################################
elsif ($oper eq 'stop')
{
    my @queue = @serverlist;
    my %state;

    my $initialstate = 1;

    foreach my $i (1 .. Jail::maxparallel())
    {
        my $server = shift @queue;
        last unless $server;
        $state{$server} = $initialstate;
    }

    while (keys %state)
    {
        my $sleep = 0;

        foreach my $server (sort (keys %state))
        {
            print "--- Server: $server state $state{$server}\n";

            my $sleeprequest;
            ($state{$server}, $sleeprequest) = Jail::stop($server, $state{$server});
            $sleep = $sleeprequest if $sleeprequest > $sleep;

            if (not $state{$server})
            {
                # Remove this server from the action list
                delete $state{$server};

                # Add a new server if any are left.
                my $server = shift @queue;
                $state{$server} = $initialstate if $server;
            }
        }

        # Now, sleep for a few seconds if any of the servers need it.
        sleep $sleep if $sleep;
    }
}

else
{
    print STDERR <<__EOOPTIONS__;
ERROR: "$oper" is not a valid operation.  The requested operation must be
one of 'start', 'stop', or 'status'
__EOOPTIONS__
    exit -1;
}
