Skip to content

Instantly share code, notes, and snippets.

@tsuchm
Last active October 23, 2019 12:49
Show Gist options
  • Select an option

  • Save tsuchm/78233756dcf17725ea42450b44da2bbc to your computer and use it in GitHub Desktop.

Select an option

Save tsuchm/78233756dcf17725ea42450b44da2bbc to your computer and use it in GitHub Desktop.
CUPS backend script to count print pages. For more detail, see https://qiita.com/tsuchm/items/de3a28c6cf6293e0cfa3
#!/usr/bin/perl
=head1 NAME
cupsyslog
=head1 DESCRIPTION
This script works as an backend of CUPS, which counts how many pages
included in a given printing document and sends the accounting
information to syslog.
=head1 INSTALL
=over 4
=item (1)
Put this script into the CUPS backend directory, typically
"/usr/lib/cups/backend/cupsyslog".
=item (2)
Define the printer as follows:
lpadmin -p ps00 -v cupsyslog://escp/socket://ps00.example.net:9100 -E
This device URI consists of 3 components,
DEVICE_CLASS = cupsyslog
COUNTER_CLASS = escp
SUCCESSOR_URI = socket://ps00.example.net:9100
This script analyzes a given printing document as a printing datum for
COUNTER_CLASS, and counts how many pages included in the document.
After that, this script forwards the document to the backend specified
in SUCCESSOR_URI, and sends the printing statistics information to
syslog.
Note that DEVICE_CLASS must be equal to the basename of this script.
=back
=cut
use Cwd qw/ abs_path /;
use English qw/ $CHILD_ERROR $PROGRAM_NAME /;
use File::Basename qw/ dirname /;
use File::Spec;
use File::Temp qw/ tempfile /;
use Sys::Syslog qw/ :macros /;
use constant { BUF_SIZE => 65536, MAX_LEN => 256 };
use strict;
our %COUNTER = ( escp => \&escp_counter,
mjc => \&mjc_counter,
hpgl => \&hpgl_counter,
null => \&null_counter );
if( @ARGV == 2 ){
# Counter test mode
my $counter = $COUNTER{$ARGV[0]} or die "UNKNOWN COUNTER CLASS: $ARGV[0]\n";
open my $fh, '<:bytes', $ARGV[1] or die "Cannot open $ARGV[1]: $!\n";
printf "num_pages=%d\n", &{$counter}( $fh );
} else {
&main( @ARGV );
}
sub main {
my( $job, $user, $title, $num_copies, $options, $file ) = @_;
my $uri = $ENV{'DEVICE_URI'};
unless( $uri =~ s!\A[^:]+:/{0,2}([a-zA-Z]+)/!! ){
&syslog( LOG_CRIT, 'ILLEGAL DEVICE_URI: uri=%s', $uri );
exit 1;
}
my $counter = $COUNTER{lc($1)};
unless( $counter ){
&syslog( LOG_CRIT, 'UNKNOWN COUNTER CLASS: class=%s, uri=%s', lc($1), $ENV{'DEVICE_URI'} );
exit 1;
}
my $num_pages;
if( $file ){
open( my $fh, '<:bytes', $file ) or die;
$num_pages = &{$counter}( $fh );
} else {
my( $tmpfh, $tmpfile ) = tempfile();
binmode $tmpfh, ':bytes';
binmode *STDIN, ':bytes';
$num_pages = &{$counter}( *STDIN, $tmpfh );
close $tmpfh;
$file = $tmpfile;
}
$ENV{'DEVICE_URI'} = $uri;
my( $backend ) = ( $uri =~ m!\A([^:]+):! );
$backend = File::Spec->catfile( &dirname(&abs_path($PROGRAM_NAME)), $backend );
unless( -x $backend ){
&syslog( LOG_CRIT, 'UNKNOWN BACKEND: backend=%s, uri=%s', $backend, $uri );
exit 1;
}
system( $backend, $job, $user, $title, $num_copies, $options, $file );
if( $CHILD_ERROR == 0 ){
&syslog( LOG_INFO,
'PRINTED: user=%s, num_copies=%d, num_pages=%d, printer=%s, job="%s", title="%s", options="%s", file="%s"',
$user, $num_copies, $num_pages, $ENV{PRINTER}, $job, $title, $options, $file );
exit 0;
} else {
&syslog( LOG_WARNING, 'FAILED EXECUTING BACKEND: backend=%s, exitcode=%d', $backend, $CHILD_ERROR );
exit $CHILD_ERROR >> 8;
}
}
sub syslog {
my( $priority, $format, @argument ) = @_;
&Sys::Syslog::openlog( &abs_path($PROGRAM_NAME), 'ndelay', LOG_DAEMON );
&Sys::Syslog::syslog( $priority, $format, @argument );
&Sys::Syslog::closelog();
}
=head1 COUNTER CLASSES
=over 4
=item escp
This counter parses a given file as ESC/P laster printing datum and
returns the number of pages needed to print these documents.
=cut
sub escp_counter {
&_string_counter( "\003endpE:", @_ );
}
sub _string_counter {
my( $pat, $fh, $tmpfh ) = @_;
my $len = length($pat) - 1;
my $page = 0;
my $pre;
my $cur;
while( read( $fh, $cur, BUF_SIZE ) ){
if( $pre ){
if( index( substr( $pre, -$len ).substr( $cur, 0, $len ), $pat ) >= 0 ){
$page++;
}
}
my $pos = 0;
while( ( $pos = index( $cur, $pat, $pos ) ) >= 0 ){
$page++;
$pos += length($pat);
}
print $tmpfh $cur if $tmpfh;
$pre = $cur;
}
$page;
}
=item mjc
This counter parses a given file as printing datum of EPSON Stylus
color printer like EPSON PM-8000C, and returns the number of pages
needed to print these documents.
=cut
sub mjc_counter {
&_string_counter( "\f\033\@", @_ );
}
=item hpgl
This counter parses a given file as HP-GL printing language, and
returns the number of pages needed to print these documents.
HP-GL Reference Manual <http://www.isoplotec.co.jp/HPGL/jHPGL.htm>
=cut
sub hpgl_counter {
my( $fh, $tmpfh ) = @_;
my $page = 0;
my $pre;
my $cur;
while( read( $fh, $cur, BUF_SIZE ) ){
my $pos;
if( $pre ){
$pos = index( substr( $pre, -2 ).substr( $cur, 0, 2 ), "PG;" );
if( $pos >= 0 ){
$page++;
$pos++;
if( substr( $cur, $pos, MAX_LEN ) =~ m/\ARP(\d+);/ ){
$page += $1;
}
}
}
$pos = 0;
while( ( $pos = index( $cur, "PG;", $pos ) ) >= 0 ){
$page++;
$pos += 3;
if( substr( $cur, $pos, MAX_LEN ) =~ m/\ARP(\d+);/ ){
$page += $1;
}
}
print $tmpfh $cur if $tmpfh;
$pre = $cur;
}
$page;
}
=item null
This counter always returns 1.
=cut
sub null_counter {
my( $fh, $tmpfh ) = @_;
if( $tmpfh ){
my $cur;
while( read( $fh, $cur, BUF_SIZE ) ){
print $tmpfh $cur;
}
}
1;
}
=back
=head1 AUTHOR
=over 4
=item TSUCHIYA Masatoshi <tsuchm@gmail.com>
=back
=head1 LICENSE
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 3, 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; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
Last Update: 2011-07-14
=cut
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment