#! /usr/bin/perl

# Copyright (C) 2005  Nick Urbanik <nicku@nicku.org>

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

use warnings;
use strict;

use DBI;

sub logit { print STDERR @_, "\n" }
sub dielog { die @_ }


use constant CONFIG_FILE => "/etc/games.rc";
use constant DEBUG => 1;
# The following three constants are used as keys to access parts of each
# piece of data in %config.  Use constants to help detect mistypings.
use constant LIMIT => "limit";
use constant USERS => "users";
use constant PROCS => "procs";

our %config;
our %playing_times;

# The format of the configuration file is:
# groupname1 user processname1 processname2 ... processnamen limit_seconds
# groupname2 user processname1 processname2 ... processnamen limit_seconds
# ...

# Note that "group" in this program refers to something totally unrelated
# to a POSIX process group.  We just mean a set of programs that we place
# one particular time limit on.

sub read_config_orig() {
    open CFG, '<', CONFIG_FILE
        or dielog "Cannot open config file @{[CONFIG_FILE]}: $!";
    while ( <CFG> ) {
        chomp;
        # Comments allowed.  Blank lines skipped.
        s/#.*$//;
        next if /^\s*$/;
        logit "read from @{[CONFIG_FILE]}: $_";
        my ( $group, $user, $seconds, @procs ) =  split;
        $seconds =~ /^\d+$/
            or dielog "Bad time limit in @{[CONFIG_FILE]}: '$_'\n";
        $config{$group} = {
                           user => $user,
                           procs => [ @procs ],
                           seconds => $seconds
                          };
        logit "stored: group=$group, user=$user, procs=@procs, ",
            "seconds=$seconds" if DEBUG;
    }
    close CFG or dielog "Cannot close config file @{[CONFIG_FILE]}: $!";
}

# procs groupname_1 processname_1 processname_2 ... processname_n
# users groupname_1 username_1 username_2 ... username_m
# limit groupname_1 seconds
# procs groupname_2 processname_1 processname_2 ... processname_n
# users groupname_2 username_1 username_2 ... username_m
# limit groupname_2 seconds
# ...

sub read_config() {
    open CFG, '<', CONFIG_FILE
        or dielog "Cannot open config file @{[CONFIG_FILE]}: $!";
    while ( <CFG> ) {
        chomp;
        # Comments allowed.  Blank lines skipped.
        s/#.*$//;
        next if /^\s*$/;
        logit "read from @{[CONFIG_FILE]}: $_";
        my ( $line_type, $group, @data ) =  split;
        if ( $line_type eq LIMIT ) {
            $config{$group}{$line_type} = shift @data;
        } else {
            $config{$group}{$line_type} = [ @data ];
        }
        $playing_times{$group} = 0;
    }
    close CFG or dielog "Cannot close config file @{[CONFIG_FILE]}: $!";
}

sub check_config() {
    foreach my $group ( sort keys %config ) {
        foreach my $line_type ( LIMIT, USERS, PROCS ) {
            dielog "No '$line_type' for group $group in configuration"
                unless exists $config{$group}{$line_type}
                    and defined $config{$group}{$line_type};
        }
        foreach my $line_type ( sort keys %{$config{$group}} ) {
            if ( $line_type eq LIMIT ) {
                $config{$group}{$line_type} =~ /^\d+$/
                    or dielog "Bad time limit '$config{$group}{$line_type}' ",
                        "in configuration\n";
            } elsif ( $line_type eq USERS ) {
                dielog "No users for group $group"
                    unless @{$config{$group}{$line_type}};
                foreach my $user ( @{$config{$group}{$line_type}} ) {
                    # See ~/RPM/BUILD/shadow-4.0.3/libmisc/chkname.c,
                    # https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=157577
                    dielog "Bad user name $user in group $group"
                        unless $user =~
                        /^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,30}[a-zA-Z0-9_.$-]?$/;
                }
            } elsif ( $line_type eq PROCS ) {
                dielog "No process names for group $group"
                    unless @{$config{$group}{$line_type}};
                foreach my $proc ( @{$config{$group}{$line_type}} ) {
                    dielog "Bad process name $proc in group $group"
                        unless length $proc;
                }
            }
        }
    }
}

sub print_config(\%) {
    my ( $config ) = @_;
    foreach my $group ( sort keys %$config ) {
        foreach my $line_type ( sort keys %{$config{$group}} ) {
            print "$line_type $group ";
            if ( $line_type eq LIMIT ) {
                print "$config->{$group}{$line_type}\n";
            } else {
                print join( " ", @{$config->{$group}{$line_type}} ), "\n";
            }
        }
    }
}


sub write_config_to_db($) {
    my ( $dbh ) = @_;
    foreach my $group ( keys %config ) {
        my $statement = <<STMT;
        INSERT INTO limits (
            groupname,
            limit_seconds
        )
        VALUES ( '$group', '$config{$group}{seconds}' )
STMT
        my $sth = $dbh->prepare( $statement );
        $sth->execute or die "Unable to execute $statement: $!";
        foreach my $procname ( @{$config{$group}{PROCS()}} ) {
            $statement = "
            INSERT INTO processes (
                process_name,
                groupname
            )
            VALUES ( '$procname', '$group' )
            ";
            $sth = $dbh->prepare( $statement );
            $sth->execute or die "Unable to execute $statement: $!";
        }
        foreach my $username ( @{$config{$group}{USERS()}} ) {
            $statement = "
            INSERT INTO users (
                username,
                groupname
            )
            VALUES ( '$username', '$group' )
            ";
            $sth = $dbh->prepare( $statement );
            $sth->execute or die "Unable to execute $statement: $!";
            $sth->finish;
        }
    }
    $dbh->commit;
}

sub read_config_from_db_orig($) {
    my ( $dbh ) = @_;
#     my $statement = "
#     SELECT groupname
#         FROM limits
#     ";
#     my $sth = $dbh->prepare( $statement );
#     $sth->execute or die "Unable to execute $statement: $!";
#     my @groups = $sth->fetchrow_array;
    
#     my $statement = "
#     SELECT limits.groupname, username, limit_seconds, process_name
#         FROM limits, processes
#         WHERE limits.groupname = processes.groupname
#     ";
#     my $sth = $dbh->prepare( $statement );
#     $sth->execute or die "Unable to execute $statement: $!";
#     while ( my @row = $sth->fetchrow_array ) {
#         print "ROW: @row\n";
#     }
# No good: results in one process per line.
# ROW: better nicku 300 frozen-bubble
# ROW: better nicku 300 xboard
# ROW: silly nicku 120 ktron
# ROW: silly nicku 120 ksnake
    my $statement = "
    SELECT groupname, username, limit_seconds
        FROM limits
    ";
    my $sth = $dbh->prepare( $statement );
    $sth->execute or die "Unable to execute $statement: $!";
    while ( my ( $group, $user, $seconds ) = $sth->fetchrow_array ) {
        $config{$group}{user} = $user;
        $config{$group}{seconds} = $seconds;
    }
    $sth->finish;

    foreach my $group ( keys %config ) {
        $statement = "
        SELECT process_name
            FROM processes
            WHERE groupname = '$group'
        ";
        $sth = $dbh->prepare( $statement );
        $sth->execute or die "Unable to execute $statement: $!";
        while ( my ( $procname ) = $sth->fetchrow_array ) {
            push @{$config{$group}{procs}}, $procname;
        }
        $sth->finish;
    }
}

sub read_config_from_db($) {
    my ( $dbh ) = @_;
    my $statement = "
    SELECT groupname, limit_seconds
        FROM limits
    ";
    my $sth = $dbh->prepare( $statement );
    $sth->execute or die "Unable to execute $statement: $!";
    while ( my ( $group, $seconds ) = $sth->fetchrow_array ) {
        $config{$group}{LIMIT()} = $seconds;
    }
    $sth->finish;

    foreach my $group ( keys %config ) {
        $statement = "
        SELECT process_name
            FROM processes
            WHERE groupname = '$group'
        ";
        $sth = $dbh->prepare( $statement );
        $sth->execute or die "Unable to execute $statement: $!";
        while ( my ( $procname ) = $sth->fetchrow_array ) {
            push @{$config{$group}{PROCS()}}, $procname;
        }
        $statement = "
        SELECT username
            FROM users
            WHERE groupname = '$group'
        ";
        $sth = $dbh->prepare( $statement );
        $sth->execute or die "Unable to execute $statement: $!";
        while ( my ( $user ) = $sth->fetchrow_array ) {
            push @{$config{$group}{USERS()}}, $user;
        }
        $sth->finish;
    }
}

sub print_config_orig(\%) {
    my $config = shift;
    foreach my $group ( keys %$config ) {
        print "$group\t$config->{$group}{user}\t$config->{$group}{seconds}\t";
        print join( "\t", @{$config->{$group}{procs}} ), "\n";
    }
}

BEGIN{
    require "$ENV{HOME}/.gamesrc";
}

use Sys::Hostname;
our $dbhost = hostname eq DBHOST ? "localhost" : DBHOST;

#read_config;
my $dbh = DBI->connect( "dbi:Pg:dbname=@{[DBNAME]};host=$dbhost", USER, PW,
                       {AutoCommit => 0} )
    or die "Unable to connect to $dbhost: $!";

#write_config_to_db $dbh;
read_config_from_db $dbh;
check_config;

print_config %config;

$dbh->commit;

$dbh->disconnect;
