#! /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 limit_seconds processname1 processname2 ... processnamen
# groupname2 user limit_seconds processname1 processname2 ... processnamen
# ...

# 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]}: $!";
}


sub write_config_to_db_orig($) {
    my ( $dbh ) = @_;
    foreach my $group ( keys %config ) {
        
        my $statement = <<STMT;
        INSERT INTO limits (
            groupname,
            username,
            limit_seconds
        )
        VALUES ( '$group', '$config{$group}{user}', '$config{$group}{seconds}' )
STMT
        my $sth = $dbh->prepare( $statement );
        $sth->execute or die "Unable to execute $statement: $!";
        $sth->finish;
        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: $!";
            $sth->finish;
        }
    }
}

# 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() {
    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}{LIMIT()}' )
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 setup_times($\%) {
    my ( $dbh, $config ) = @_;
    foreach my $group ( keys %$config ) {
        my $statement = "
        INSERT INTO times
        ( groupname, group_playing_time, date )
        VALUES ( '$group', 0, 'today' )
        ";
        $dbh->do( $statement ) or die "Unable to execute $statement: $!";
    }
    $dbh->commit;
}

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

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

read_config;
check_config;
print_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;
setup_times $dbh, %config;

$dbh->commit;

$dbh->disconnect;
