#!/usr/bin/perl -w

use strict;
use vars qw( @part_export );
use subs qw(myshutdown);
use POSIX qw(:sys_wait_h);
#use IO::File;
use FS::Daemon qw(daemonize1 drop_root logfile daemonize2 sigint sigterm);
use FS::UID qw(adminsuidsetup); #forksuidsetup driver_name dbh myconnect);
use FS::Record qw(qsearch); # qsearchs);
use FS::part_export;
#use FS::svc_acct;
#use FS::cust_svc;

my $user = shift or die &usage;

#daemonize1('freeside-sqlradius-radacctd', $user); #keep unique pid files w/multi installs
daemonize1('freeside-sqlradius-radacctd');

drop_root();

#$ENV{HOME} = (getpwuid($>))[7]; #for ssh

adminsuidsetup $user;

logfile( "%%%FREESIDE_LOG%%%/sqlradius-radacctd-log.". $FS::UID::datasrc );

daemonize2();

#--

#don't just look for ->can('usage_sessions'), we're sqlradius-specific
# (radiator is supposed to be setup with a radacct table)

@part_export =
  qsearch('part_export', { 'exporttype' => 'sqlradius' } );
push @part_export,
  qsearch('part_export', { 'exporttype' => 'sqlradius_withdomain' } );
push @part_export,
  qsearch('part_export', { 'exporttype' => 'radiator' } );

@part_export = grep { ! $_->option('ignore_accounting') } @part_export;

die "no sqlradius, sqlradius_withdomain or radiator exports without".
    " ignore_accounting"
  unless @part_export;

while (1) {

  #fork off one kid per export (machine)
  # _>{'_radacct_kid'} is an evil kludge
  foreach my $part_export ( grep ! $_->{'_radacct_kid'}, @part_export ) {
 
    defined( my $pid = fork ) or do {
      warn "WARNING: can't fork to spawn child for ". $part_export->machine;
      next;
    };

    if ( $pid ) {
      $part_export->{'_radacct_kid'} = $pid;
      warn "child $pid spawned for ". $part_export->machine;
    } else { #kid time

      adminsuidsetup($user); #get our own db handle

      until ( sigint || sigterm ) {
        $part_export->update_svc_acct();
        sleep 1;
      }

      warn "child for ". $part_export->machine. " done";
      exit;

    } #eo kid

  }

  #reap up any kids that died...
  &reap_kids;

  myshutdown() if sigterm() || sigint();

  sleep 5;
}

#-- 

sub myshutdown {
  &reap_kids;

  #kill all the kids
  kill 'TERM', $_ foreach grep $_, map $_->{'_radacct_kid'}, @part_export;

  my $wait = 12; #wait up to 1 minute
  while ( ( grep $_->{'_radacct_kid'}, @part_export ) && $wait-- ) {
    warn "waiting for children to terminate";
    sleep 5;
    &reap_kids;
  }
  warn "abandoning children" if grep $_->{'_radacct_kid'}, @part_export;
  die "exiting";
}

sub reap_kids {
  #warn "reaping kids\n";
  foreach my $part_export ( grep $_->{'_radacct_kid'}, @part_export ) {
    my $pid = $part_export->{'_radacct_kid'};
    my $kid = waitpid($pid, WNOHANG);
    if ( $kid > 0 ) {
      $part_export->{'_radacct_kid'} = '';
    }
  }
  #warn "done reaping\n";
}

sub usage {
  die "Usage:\n\n  freeside-sqlradius-radacctd user\n";
}

=head1 NAME

freeside-sqlradius-radacctd - Real-time radacct import daemon

=head1 SYNOPSIS

  freeside-sqlradius-radacctd username

=head1 DESCRIPTION

Imports records from an the SQL radacct tables of all sqlradius, 
sqlradius_withdomain and radiator exports (except those with the
ignore_accounting flag) and updates the svc_acct.seconds for each account.
Runs as a daemon and updates the database in real-time.

B<username> is a username added by freeside-adduser.

=head1 RADIUS DATABASE CHANGES

ALTER TABLE radacct ADD COLUMN FreesideStatus varchar(32) NULL;

If you want to ignore the existing accountg records, also do:

UPDATE radacct SET FreesideStatus = 'done' WHERE FreesideStatus IS NULL;

=head1 SEE ALSO

=cut

1;