The following is something I wrote to handling accounting in LPRng
and to allow the proper completion of account handling scripts
(secondary accounting).

In our case the seconary accounting scripts are for student print
quotas, jobs from which only come from secured systems the student must
login to to use, allowing use to ensure the username is correct.

It also provides a log file something like...
  2002-11-29 04:17:08   56128   1 [EMAIL PROTECTED]  (STDIN)
  2002-11-29 04:19:38   56129   1 [EMAIL PROTECTED]  (STDIN)
  2002-11-29 11:58:16   56151 --- Found unfinished Job -- Finalising Job Now
  2002-11-29 04:23:07   56151  22 [EMAIL PROTECTED]  DF_RapidDeployment.pdf
  2002-11-29 11:58:46   56152   1 [EMAIL PROTECTED]  Deployment.pdf

The above shows the script also detected a unfinished job and finished
it accordingly. It will also detect if the people are printing to the
printer using other means that this LPRng server or if a HP printer was
reset (pagecount was rounded down by 10)

The script documents itself, so please read.
I present it here ASIS for comment and use by others.

  Anthony Thyssen ( System Programmer )    http://www.sct.gu.edu.au/~anthony/
 -----------------------------------------------------------------------------
   If everything seems to be going well,
        you obviously don't know what the hell you are doing.
 -----------------------------------------------------------------------------
     Anthony's Home is his Castle     http://www.sct.gu.edu.au/~anthony/

#!/usr/bin/perl
#
# LPRng, IFHP Accounting Sanatiser
#
# This script performs all the extra handling of pagecount accounting in
# LPRng. It is especially designed to handle unusual conditions, such as
# aborted print jobs, printer resets, and detection of printing from a
# source, outside of this print queue.
#
# Accounting records, are kept in a seperate file to the normal LPRng "acct"
# file (to avoid truncation by "checkpc"), and not only includes the user and
# number of pages printed, as in the old BSD printing accounting, but also the
# filename printed, date and time, and the current pagecount of the printer
# when the job was completed.  Basically an easy to read and parse summary of
# the highly verbose LPRng "acct" file.
#
# Included in this report are extra notes (starting with a "---") which
# indicate unusal occurances, such as jobs which terminated abnormally,
# requiring special "cleanup", detected HP printer resets (between jobs), and
# if someone used the printer "Out of Band" (EG directly and not via LPRng).
#
# Side Effects...
# As a bonus, this script will also clean up those annoying "ifhp" conversion
# files that the IFHP filter loves to leave behind.
#
# WARNING: as the script stands, it will ignore "of" filter "start" and "end"
# account actions of the IFHP filter, being designed to deal with individual
# files being printed by the user.  It will also ignore miss-use of the script
# when called using the "as" or "ae" printcap entries.
#
# INSTALLATION & OPERATION...
#
# To use this program you only need to either add the line "accounting="
# to the IFHP configuration file,  OR add this to the printcap file.
#   :ifhp=accounting=/path/to/this/script
#
# I myself prefer the former as it leaves the :ifhp= printcap free for
# model specifications and special cases.
#
# You can also further modify this scripts behaviour from the printcap. Here
# is a list of printcap entries and there normal defaults that are understood.
# Note that they are NOT normal LPRng options, so only this program should be
# using them.
#
#   :filter_debug                 Dump a file of Arguments and Environment
#   :acct_file=acct_summary       A readable summary of print accounting
#   :acct_pc_file=acct_pagecount  Last page count retrieved by ifhp
#   :acct_jobinfo=acct_jobinfo    Info on current job in progress (as a backup)
#   :acct_quota_start=            Secondary quota handler (job start)
#   :acct_quota_end=              Secondary quota handler (job end)
#
# The last two options above are hooks to allow this script to call secondary
# script for quota handling if desired. These programs are called with exactly
# the same options that IFHP uses.
#
# If the last job was incomplete when the next job starts, this script will
# finish that job first before proceeding. That is the "acct_quota_end" script
# script will ALWAYS be called if the "acct_quota_start" script returned "OK"
# (status 0) to proceed. This is regardless of if it was aborted by the user,
# the printer was reset, or a power failure occured.
#
# WARNING: A negative pages used (-b) could be passed to the "acct_quota_end"
# program in aborted situations.  This indicates that something very weird
# happened, such as a HP printer was power cycled or the printer was replace
# while the last job was still in progress.  It is left up to that script on
# how it should fix that situation, commonly just reset it to 0 as unknown.
#
# In the account summary aborted situations are flaged as such, but the
# completed job will be listed with a page count of 0 if count was negitive.
# For a typical HP power cycle this would only gain the user at most, 9 pages.
# If you find the printer is being reset multiple time, it may be that it was
# the user listed next, attempting to gain free printouts.
#
# See LPRng-HowTo, section 18.5 for details of the HP printer page count
# eccentricity.
#
# With this script you can if you want also turn off or reassign the highly
# verbose accounting file, which checkpc would normally truncate. We just
# renamed it (See below)
#
# Full Working Printcap Example...
#
#   # Common to all printers
#   .common:
#       :sh:mx=0:send_try=1
#
#   # Postscript Printer Accounting
#   .ifhp:
#       :filter=/opt/lprng/libexec/filters/ifhp
#       :ifhp=accounting=/opt/lprng/libexec/acct/acct
#       :af=acct_raw_lprng
#       :acct_summery=acct
#
#   # Secondary Accounting for student printers
#   .acct_student:
#       :acct_quota_start=/opt/lprng/libexec/acct/pquota_start
#       :acct_quota_end=/opt/lprng/libexec/acct/pquota_end
#
#   # Student printer using all the above.
#   lwa|student_lab_A
#       :server
#       :tc=.common,.ifhp,.acct_student
#       :lp=hplwa.student-labs%9100
#
#
# Anthony Thyssen  22 November 2002   <[EMAIL PROTECTED]>
#
# Comments welcome.
#

# ----------------------------------------------------------------------------
use strict;
use warnings;
use Getopt::Std;
use FindBin;
my $prog = $FindBin::Script;
my $bindir = $FindBin::RealBin;

my $cleanup = 1;     # clean up the old ifhp temporary files?
my $debug = ( $ENV{PRINTCAP_ENTRY} =~ /:filter_debug\b/ );

if ( $debug ) {
  open( DB, ">$ARGV[0].debug" );
  print DB "-------- ARGS ------\n";
  print DB map { "$_\n" } ( $0, @ARGV );
  print DB "-------- ENV -------\n";
  print DB `id`;
  print DB map { "$_=$ENV{$_}\n" } keys %ENV;
  print DB "-------- LS ---------\n";
  print DB `ls -C`;
  print DB "---------------------\n";
  close DB;
}

sub JSUCC   {  0 };
sub JRETRY  { 32 };  # JFAIL actually
sub JABORT  { 33 };
sub JREMOVE { 34 };

my $acct_stage = shift;   # When was this script called


# -------- Ignore these Accounting actions ----------
if ( grep {$acct_stage eq $_} qw( jobstart jobend start end )  ) {
  # Accounting script called from unusable situation -- IGNORE
  exit JSUCC;
}

# ------------------ Initialization --------------------

# Decode printcap entry -- from LPRng example scripts
my %printcap;
if ( $ENV{'PRINTCAP_ENTRY'} ) {
 map {  if( m/^\s*:([^=]+)=(.*)/ ){ $printcap{$1}=$2; }
     elsif( m/^\s*:([^=]+)\@$/   ){ $printcap{$1}="0"; }
     elsif( m/^\s*:([^=]+)/      ){ $printcap{$1}="1"; }
     elsif( m/^\s*([^|]+)/       ){ $printcap{"Printer"}=$1; }
   } split( "\n", $ENV{'PRINTCAP_ENTRY'});
}

# Retract the parameters we use and thier default settings
my $acct_file   = $printcap{'acct_file'}        || "acct_summary";
my $check_point = $printcap{'acct_jobinfo'}     || "acct_jobinfo";
my $pagecount   = $printcap{'acct_pc_file'}     || "acct_pagecount";
my $prog_start  = $printcap{'acct_quota_start'} || undef;
my $prog_end    = $printcap{'acct_quota_end'}   || undef;

# This is a specific to the authors own print server accounting
# Undefine if you don't what the domain removed
#my $domain_re  = undef;              # leave the hostname as is.
#my $domain_re  = qr/\..*/;           # remove the domain from the hostanme
my $domain_re   = qr/\.gu\.edu\.au$/; # remove this RE from acct hostname

# Getopt::Std options...
# All switch options except 'c' has an argument. (ifhp does not pass '-c')
# WARNING: if a option that was not expected is found, it will be parsed as
# a list of single litter switches, which can confuse the module
#
#my $acct_options =  "A:B:C:D:...Z:a:b:c:d:e:f:...";  # including 'c'
my $acct_options = join(':', 'A'..'Z', 'a'..'z', '');


# ------------------- Start Accounting -----------------
if ( $acct_stage eq "filestart" ) {
  # Called from IHFP running as a output filter (":of")
  # The start of a users job with one or more files to print.
  #
  my (@args, %args) = @ARGV;  # save the arguments to pass to second handler
  getopts( $acct_options, \%args );  # decoce arguments to this print job

  # --- Was the last job completed ---
  if ( -f $check_point ) {
    # The check point file exists  -- the last job did not complete!
    # Recover the start info of the last job and complete its accounting

    # Recover the start arguments of the previous job
    open(CP, "$check_point" )
       or die "Unable to read file \"$check_point\": $!\n";
    chomp( @ARGV = <CP> );
    close CP;

    my %job;            # save the arguments to pass to second handler
    getopts( $acct_options, \%job );  # arguments from previous job start

    # Fix up the arguments for the end of the job
    $job{b} = $args{p} - $job{p};   # number of pages used
    $job{p} = $args{p};             # what the printer pagecount is now!

    # Add a summary to account file
    open(ACCT, ">>$acct_file")
       or die "Unable to append file \"$acct_file\": $!\n";
      # note the special condition in the accounting summary file
      my $t = $args{t}; substr($t, 10, 1) = ' '; substr($t, 19) = '';
      printf ACCT "%s %7d %s\n", $t, $job{p},
                  "--- Found unfinished Job -- Finalising Job Now";
      my $b = $job{b};  # the final page usage
      if (  $b < 0 ) {  # is users page usage negative?
        if (  $job{p} % 10 == 0  ) { # page count an increment of ten?
          printf ACCT "%s %7d %3d %s\n", $t, $job{p}, $b,
                             "--- HP printer reset detected!";
        } else {
          printf ACCT "%s %7d %3d %s\n", $t, $job{p}, $b,
                             "--- Printer page counter went backward!";
        }
        $b = 0; # zero the negative page count for this users job (noted above)
      }
      # output the final record of the print job - use the jobs start time!
      $t = $job{t}; substr($t, 10, 1) = ' '; substr($t, 19) = '';
      my $h = $job{H}; $h =~ s/$domain_re// if defined $domain_re;
      printf ACCT "%s %7d %3d %s\@%s  %s\n",
                            $t, $job{p}, $b, $job{n}, $h, $args{f};
    close ACCT;

    # replace those two options in the start arguements for secondary accting
    my( @job ) = map( "-$_$job{$_}", keys %job );

    # Call the secondary accounting program to do any else needed
    if ( $prog_end ) {
      #print STDERR "Running secondary accounting $prog_end\n";
      system($prog_end, "fileend", @job);
    }

    # Clean up the ifhp conversion files from last job
    if ( $cleanup ) {
      opendir(D,".");
      my( @files ) = readdir(D);
      closedir(D);
      unlink( grep(/^ifhp......$/, @files) );
    }
  }

  # --- Out of Band Data Check ---
  # Compare this page count with the previous pagecount
  # looking for data printed out of band, or HP printer resets.
  elsif( -f $pagecount ) {
    my $pc;
    if ( open(PC, $pagecount) ) {
      chomp( $pc = <PC> );
      close PC;
    }
    if ( $pc && $pc =~ /^\d+$/ && $pc != $args{p} ) {
      # something is fishy, the old pagecount that does not match current value
      open(ACCT, ">>$acct_file")
            or die "Unable to append file \"$acct_file\": $!\n";
      my $t = $args{t}; substr($t, 10, 1) = ' '; substr($t, 19) = '';
      if (  $args{p} < $pc   &&   $args{p} == $pc - $pc % 10  ) {
        printf ACCT "%s %7d %3d %s\n", $t, $args{p}, $args{p}-$pc,
                               "--- HP printer reset detected!";
      }
      elsif (  $args{p} < $pc  ) {
        printf ACCT "%s %7d %3d %s\n", $t, $args{p}, $args{p}-$pc,
                               "--- Page counter went backward!";
      }
      else {
        printf ACCT "%s %7d %3d %s\n", $t, $args{p}, $args{p}-$pc,
                               "--- Pages printed Out of Band!";
      }
      close ACCT;
    }
  }

  # --- update the pagecount file ---
  # Note that this is not needed really at the filestart, but it doesn't hurt.
  open( PC, ">$pagecount");
  print PC $args{p}, "\n";
  close PC;

  # --- checkpoint new job ---
  # create a new check point file for this job
  open(CP, ">$check_point" )
       or die "Unable to open check point file \"$check_point\": $!\n";
  print CP map { "$_\n" } ( @args );
  close CP;

  # --- call secondary accounting ---
  if ( $prog_start ) {
    #print STDERR "Running secondary accounting $prog_start\n";
    my $return = system($prog_start, $acct_stage, @args );
    if ( $return ) {
      # Secondary accounting dod not return OK!
      # Clean up and log it in account summery file!
      print STDERR "Secondary Accounting returned : ",
                                          $return>>8, " -- ABORTING\n";
      unlink($check_point);   # check point not needed any more
      open(ACCT, ">>$acct_file")
          or die "Unable to append file \"$acct_file\": $!\n";
      my $t = $args{t}; substr($t, 10, 1) = ' '; substr($t, 19) = '';
      my $h = $args{H}; $h =~ s/$domain_re// if defined $domain_re;
      printf ACCT "%s %7d %3d %s\@%s  %s\n",
                     $t, $args{p}, 0, $args{n}, $h, $args{f}. " --- DENIED";
      close ACCT;
      #exit JABORT;  # return ABORT job
      exit JREMOVE;  # forced job REMOVAL
    }
  }

  exit JSUCC;  # return OK to proceed to IFHP filter
}

# --------------- Finish Accounting --------------
if ( $acct_stage eq "fileend" ) {

  # FUTURE: check if the last job check point point actually matches this job?

  unlink($check_point);   # check point not needed any more

  my ( @job, %job ) = @ARGV;  # save the arguments to pass to second handler
  getopts( $acct_options, \%job );  # arguments from previous job start

  # --- update the pagecount file ---
  open( PC, ">$pagecount");
  print PC $job{p}, "\n";
  close PC;

  # --- add a summary to account file ---
  open(ACCT, ">>$acct_file")
      or die "Unable to append file \"$acct_file\": $!\n";
    my $t = $job{t}; substr($t, 10, 1) = ' '; substr($t, 19) = '';
    my $h = $job{H}; $h =~ s/$domain_re// if defined $domain_re;
    printf ACCT "%s %7d %3d %s\@%s  %s\n",
                               $t, @job{qw( p b n )}, $h, $job{f};
  close ACCT;

  # --- call secondary accounting ---
  if ( $prog_end ) {
    #print STDERR "Secondary accounting $prog_end\n";
    system($prog_end, "fileend", @job);
  }

  # Clean up the ifhp conversion files of this job
  if ( $cleanup ) {
    opendir(D,".");
    my( @files ) = readdir(D);
    closedir(D);
    unlink( grep(/^ifhp......$/, @files) );
  }

  # done
  exit JSUCC;
}

# ----------------------------
# We should not reach this point!

print STDERR "$prog: Accounting error, unknown acct stage ",
             "\"$acct_stage\" -- ABORTING\n";
exit JRETRY; # Job Fail -- Retry later




Reply via email to