#!/usr/common/bin/perl -w
# This script contains subroutines to check printer status
############################################################
# 10/11/99 rcc - Add "printer_idle"; Remove "service request" as a "down"
# 09/24/99 rcc - Add "get_pagecount"
# 08/01/99 rcc - Convert NPStat.pl to Perl 5 module npstatlib.pm
# 04/09/99 rcc - Reduce SNMP retries from 5 to 2
# 10/01/98 rcc
############################################################

package npstatlib;

# "use diagnostics" should be commented out for production environment
#use diagnostics;
use strict;

use vars qw{ @ISA @EXPORT };
require Exporter;
@ISA = qw{ Exporter };
@EXPORT = qw{
   get_status
   err2str
   printer_ok
   get_printerlist
   get_printerinfo
   get_allprinterinfo
   get_pagecount
};

# exported
use vars qw{
};

# non-exported but global to the package:
use vars qw{
   $PSTAT_SERVICE_REQ
   $PSTAT_OFFLINE
   $PSTAT_PAPER_JAM
   $PSTAT_DOOR_OPEN
   $PSTAT_TONER_OUT
   $PSTAT_TONER_LOW
   $PSTAT_PAPER_OUT
   $PSTAT_PAPER_LOW
   $PSTAT_IDLE
   $PSTAT_PRINTING
   $PSTAT_WARMING_UP
   $PSTAT_UNREACHABLE
   $PSTAT_MAX
   %statmsg
   %prtstatmsg
};

# Loads modules from source directory if executed in source directory
use lib qw(. /usr/local/netprint/lib);
use npparams;
use SNMP_mib;
use SNMP_Session;
use BER;

# Note:  The bit definitions for the first 8 of the items below are taken
# from the description of the values for hrPrinterDetectedErrorState in
# RFC1759.
BEGIN {
   $PSTAT_SERVICE_REQ = 1;
   $PSTAT_OFFLINE = 2;
   $PSTAT_PAPER_JAM = 4;
   $PSTAT_DOOR_OPEN = 8;
   $PSTAT_TONER_OUT = 16;
   $PSTAT_TONER_LOW = 32;
   $PSTAT_PAPER_OUT = 64;
   $PSTAT_PAPER_LOW = 128;
   $PSTAT_IDLE = 256;
   $PSTAT_PRINTING = 512;
   $PSTAT_WARMING_UP = 1024;
   $PSTAT_UNREACHABLE = 2048;
   $PSTAT_MAX = 2048;

   %statmsg =
       (
        $PSTAT_SERVICE_REQ, 'Service Requested',
        $PSTAT_OFFLINE, 'Offline',
        $PSTAT_PAPER_JAM, 'Paper Jam',
        $PSTAT_DOOR_OPEN, 'Door Open',
        $PSTAT_TONER_OUT, 'Toner Out',
        $PSTAT_TONER_LOW, 'Toner Low',
        $PSTAT_PAPER_OUT, 'Paper Out',
        $PSTAT_PAPER_LOW, 'Paper Low',
        $PSTAT_IDLE, 'Idle',
        $PSTAT_PRINTING, 'Printing',
        $PSTAT_WARMING_UP, 'Warming Up',
        $PSTAT_UNREACHABLE, 'DISCONNECTED, OFFLINE, OR NOT RESPONDING',
        );

   %prtstatmsg =
       (
        3, $PSTAT_IDLE,
        4, $PSTAT_PRINTING,
        5, $PSTAT_WARMING_UP,
        );
}


########################################################
#
# get_status($printer)
#
# Uses SNMP to find the status and returns an errorcode
# which can be deciphered in err2str, which will return an array of
# strings specifying the error(s) occuring.

sub get_status {
   my($printer) = @_;
   my(%pinfo);
   my($ip, $community, $session, $type, $devstat, $prtstat, $error);

   if ( ! defined(%pinfo = get_printerinfo($printer)) ) {
       return(undef);
   }
   if ( $pinfo{VIRTUAL} =~ /^[yY]/ ) {
       return($PSTAT_IDLE);
   }
   $ip = $pinfo{IP};
   $community = 'public';

   # The following line can be commented out for debugging purposes.
   $SNMP_Session::suppress_warnings = 1;
   snmp_initmib();
   if ( ! ($session = SNMP_Session->open ($ip, $community, 161)) ) {
       warn "Couldn't open SNMP session to $printer (at $ip): $SNMP_Session::errmsg";
       return($PSTAT_UNREACHABLE);
   }
   $session->set_retries(2);
   ($type, $devstat, $prtstat, $error) = snmp_get($session, qw(sysDescr.0 hrDeviceStatus.1 hrPrinterStatus.1 hrPrinterDetectedErrorState.1));
   if ( ! defined($type) ) {
       return($PSTAT_UNREACHABLE);
   }

   my($tmp) = unpack("C", $error);
   if ( defined($prtstatmsg{$prtstat}) ) {
       $tmp |= $prtstatmsg{$prtstat};
   }
   return($tmp);
}

#########################################################
# err2str ($code)
#
# Given an errorcode, this sub will convert the
# code to an array of strings, each ending in a newline.

sub err2str {
   my($code) = @_;
   my(@msgs);
   my($i);

   @msgs = ();
   for ( $i = 1; $i <= $PSTAT_MAX; $i <<= 1 ) {
       if ( $i & $code ) {
           push(@msgs, $statmsg{$i});
       }
   }
   return(@msgs);
}


#########################################################
# printer_ok($code)
#
# Given a printer status code, return TRUE for printer OK, and FALSE
# for printer not functioning.

sub printer_ok {
   my($code) = @_;
   return( ! ($code & ($PSTAT_OFFLINE | $PSTAT_PAPER_JAM | $PSTAT_DOOR_OPEN | $PSTAT_TONER_LOW | $PSTAT_TONER_OUT | $PSTAT_PAPER_OUT | $PSTAT_UNREACHABLE)) );
}


#########################################################
# printer_idle($code)
#
# Given a printer status code, return TRUE for printer idle, and FALSE
# for any other printer status.

sub printer_idle {
   my($code) = @_;
   return( $code == $PSTAT_IDLE );
}


#########################################################
# get_printerlist
# get_printerinfo
# get_allprinterinfo
#
# More convenient and rigorous versions of get_prt_cfg.
#  @list = get_printerlist()        places a list of all printers in $list.
#  %info = get_printerinfo($pname)  returns a hash of the printer info for
#                                   $printer.
#  %info = get_allprinterinfo()    returns a hash of hashes containing all
#                                   the printer info in the database
#
# All routines return 'undef' for error.

# global variables (local to this package)
my(%printerinfo);
my($pi_timestamp);

sub get_printerlist {
   my(%pinfo);

   if ( ! defined(%pinfo = get_allprinterinfo()) ) {
       return(undef);
   }
   return(sort(keys(%pinfo)));
}

sub get_printerinfo {
   my($printer);
   ($printer) = @_;
   my(%pinfo);

   if ( ! defined(%pinfo = get_allprinterinfo()) ) {
       return(undef);
   }
   return(%{$pinfo{$printer}});
}

sub get_allprinterinfo {
   my($mtime, $l, $tmp, @items, $item, $printer);

   $mtime = (stat($PRTCFG))[9];
#    print "file = $PRTCFG;  timestamp = $pi_timestamp;  mtime = $mtime\n";
   if ( $pi_timestamp != $mtime ) {
#       print "Reread PRTCFG\n";
       %printerinfo = ();
       if ( ! open(CFGFILE, $PRTCFG) ) {
           print "Unable to open $PRTCFG $!\n";
           return(undef);
       }
       while ( defined($l = <CFGFILE>) ) {
           chomp;
           next if $l =~ /^#|^$/;
           @items = split(/::/, $l);
           $tmp = $items[0];
           if ( ! ($tmp =~ /^NAME=(.+)$/) ) {
               print "Bad NAME entry in $PRTCFG\n";
               next;
           }
           $printer = $1;
           foreach $item ( @items ) {
               if ( ! ($item =~ /^(.+)=(.+)$/) ) {
                   print "Bad parameter entry in $PRTCFG\n";
                   next;
               }
               $printerinfo{$printer}{$1} = $2;
           }
       }
       close(CFGFILE);
       $pi_timestamp = $mtime;
   }
   return(%printerinfo);
}


#########################################################
sub get_pagecount {
   my($printer) = @_;
   my(%pinfo);
   my(@snmpvals);
   my($session);

   %pinfo = get_printerinfo($printer);

   # The following line can be commented out for debugging purposes.
   $SNMP_Session::suppress_warnings = 1;
   snmp_initmib();

   unless ( $session = SNMP_Session->open($pinfo{IP}, 'public', 161) ) {
       return(-1);
   }
   @snmpvals = snmp_get($session, qw(prtMarkerLifeCount.1.1));
   unless ( $#snmpvals >= 0 ) {
       $session->close();
       return(-1);             # Will come here if 'noSuchName' error!!
   }
   $session->close();
   return($snmpvals[0]);
}

1;