Last active
October 23, 2019 12:49
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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