Bachelor’s Thesis
Design and Implementation of
Accounting and Status Notification for
the LPRng Print Spooler
Henrik Edlund
2003-11-24
Link¨
oping University
Department of Computer and Information Science
Bachelor’s Thesis
Design and Implementation of
Accounting and Status Notification for
the LPRng Print Spooler
Henrik Edlund
2003-11-24
LITH-IDA-EX-ING--03/019--SE
Abstract
This thesis presents the design and implementation of an accounting and status
no-tification system for the LPRng print spooler. A solution using SNMP to query for
needed accounting and status data is presented, a design built upon this, and an
im-plementation produced.
As the system derives from the requirements specification, it will make no attempt to
solve all printer accounting problems or to offer compatibility with all printers, database
management systems or print spoolers. However, several solutions for printer
account-ing are discussed and the best solution derived from the requirements specification will
be chosen.
Acknowledgments
I would like to extend my gratitude to my advisor Tommy Olsson, my opponent Andreas
Lange and my colleagues at the department for their valuable assistance and support
during all phases of this thesis.
I am deeply grateful to my friends, you know who you are, for their enduring patience,
belief and trust in me. You are all essential in my life.
Thanks must also go to Patrick Powell, author of LPRng, and all members of the
LPRng mailing list for the discussion, feedback and help.
This thesis was set in LYX using the L
ATEX typesetting system on a computer running
the Slackware Linux operating system. The typefaces used are from the Computer
Modern family. Figure 4.1 and Figure 4.2 were drawn using Graphviz and retouched
using Xfig.
Contents
Abstract
iii
Acknowledgments
v
List of Figures
xi
List of Tables
xiii
I.
Preliminaries
1
1. Introduction
3
1.1. Background . . . .
3
1.2. Objective . . . .
3
1.3. Literature . . . .
4
1.4. Terminology . . . .
4
1.5. Overview
. . . .
4
2. Background
5
2.1. Accounting . . . .
5
2.2. Status Notification . . . .
5
3. Requirements
7
3.1. Common . . . .
7
3.2. Accounting . . . .
7
3.3. Status Notification . . . .
8
II. Design
11
4. Accounting
13
4.1. Security . . . .
13
4.2. Available Solutions . . . .
13
4.2.1. Pre-printing . . . .
14
4.2.2. Proprietary Software . . . .
14
4.2.3. PJL . . . .
14
4.2.4. PostScript . . . .
15
4.2.5. SNMP . . . .
16
4.2.6. Conclusion
. . . .
16
4.3. SNMP . . . .
17
4.4. LPRng . . . .
18
4.5. Database
. . . .
18
5. Status Notification
21
5.1. Security . . . .
21
5.2. Whereabouts . . . .
21
5.3. SNMP . . . .
21
III. Outcome
23
6. Implementation
25
6.1. Accounting and Status Notification Application . . . .
25
6.1.1. Starting and Ending a Job
. . . .
25
6.1.2. Accounting Status . . . .
26
6.2. Accounting Administration Application . . . .
27
6.3. Database
. . . .
27
7. Results
31
8. Conclusion and Future Work
33
8.1. Conclusion
. . . .
33
8.2. Future Work
. . . .
33
8.2.1. Credit System
. . . .
33
8.2.2. Individual Cost Per Printer . . . .
34
8.2.3. Individual Cost Per User
. . . .
34
8.2.4. And More . . . .
34
IV. Supplements
35
References
37
A. LPRng Integration
39
B. Source Code of the Accounting and Status Notification Application
41
C. Source Code of the Accounting Administration Application
55
List of Figures
4.1. ER diagram for the accounting database . . . .
18
4.2. Database schema diagram with referential integrity constraints . . . . .
19
6.1. Rendered example of accounting status in PostScript format . . . .
28
List of Tables
4.1. Interesting printer status states . . . .
17
6.1. Database tables . . . .
29
6.2. Database triggers . . . .
29
6.3. Database indexes . . . .
29
6.4. Database functions . . . .
29
6.5. Database users . . . .
30
6.6. Database privileges . . . .
30
Part I.
1. Introduction
1.1.
Background
This thesis presents part of the work that was done to deploy a completely new print
spooler with associated services at the Department of Computer and Information
Sci-ence at Link¨
oping University. This half-year project consisted of several steps.
First the project involved a look at possible candidates for a new print spooler.
Even-tually the LPRng print spooler was chosen as it could easily be adapted as a drop-in
replacement to the earlier one. Applications for handling accounting, status
notifica-tion, printer initializanotifica-tion, banner creanotifica-tion, content filtering, content conversion and
error handling were written as extensions to the print spooler.
Before deploying the system, it was heavily tested with several existing printers and
with candidate printers that were to be acquired. When this is written, the system has
been in operation with success for about half a year.
This thesis will focus on one part of the larger project, the design and implementation
of accounting and status notification.
1.2.
Objective
The objective of this thesis is to design and implement an accounting and status
notifi-cation system in accordance with the requirements laid out. There are several
require-ments on this system. The requirerequire-ments range from software compatibility to making
sure the accounting data are accurate. A full requirements specification is found in
Chapter 3.
Several solutions for printer accounting will be discussed. The best solution derived
from the requirements specification will be chosen and a system will be implemented.
This system will then be put into actual use at the Department of Computer and
Information Science.
As the system derives from the requirements specification, it will make no attempt to
solve all printer accounting problems or to offer compatibility with all printers, database
management systems or print spoolers.
1.3.
Literature
This thesis depends on several Internet sources. All of these are either standard
docu-ments, publications by reputable corporations or software manuals. The source
mate-rials were selected and used as reference on the grounds of the information needed to
be retrieved.
A great source of grapevine information, required in both the design and
implementa-tion, was the LPRng mailing list
1. Through it, many issues were raised, discussed and
solved.
1.4.
Terminology
The audience for this thesis are those with at least an undergraduate degree in
Com-puter Science. Terminology acquired during such studies will not be explained here.
Five words, or concepts if you like, related to the printing world and this thesis, will
be explained here though. The definition given is the one used in this thesis.
Duplex Printing on both sides of a sheet.
Impression A printed side of a sheet. With simplex printing there is one impression
per sheet, while with duplex printing there are two impressions per sheet.
Job A printer job can consist of one or several files, sent as one package to the print
spooler to be printed as a whole.
Quota The number of impressions the user has left to use for printing.
Sheet A rectangular piece of paper with two sides.
Simplex Printing on one side of a sheet.
1.5.
Overview
As this thesis takes off, it will begin with a background of the problem and move on to
a requirements specification from the described problem. A look at possible solutions
will be done, and after that focusing on the design of the accounting element and then
the status notification element.
Moving on from design, the implementation will then be discussed and the thesis will
conclude with a statement of results and possible future work. As appendixes, details
on LPRng integration, database data definition and source code, can be found.
1The LPRng mailing list is archived at <http://marc.theaimsgroup.com/>.
2. Background
This chapter gives a brief background to the problem and outlines solution ideas.
2.1.
Accounting
At the Department of Computer and Information Science, it has for many years been
free for students to print. However, in recent years the situation has become
unmain-tainable. There are a lot of things printed that are not related to the department’s
courses or even printed by students not connected with the department.
While the department wants to continue to offer free printing for active students and for
material related to the department’s courses, it also wants to stop the abuse of the print
system by students printing material relating to courses at other departments, and by
students not attending courses or related study programmes at the department. This
way, better service can be offered to current students in terms of printer availability.
Printers and toner cartridges will also last longer. The goal being, that for students
actually printing what they should be printing there should be no noticeable difference,
but the ability for others to print should be limited.
As one knows what courses a student is attending, one could use this information and
award printing credits for each course. Those students not attending any courses at
the moment would then be unable to print anything, or at most use any previously
saved credits if these are not cleared each semester. This should shift the usage of
the printers in a direction where they are used only when necessary and increase the
availability for the students that need them.
It would be more interesting to count impressions than to count sheets. The cost of
the toner for one impression (normal coverage) is more than twice as much as the cost
of the paper. With duplex printing, the cost of the toner will be at least four times as
much as the cost of the paper.
2.2.
Status Notification
When there is a problem with a printer at the start of or during a job, it will not be
taken care of until somebody goes to the printer and notices it. As people tend to send
jobs to the printer and to pick them up some time later, a printer problem will cause
a stop in the processing of jobs and new jobs will pile up in the printer queue. Until
somebody walks by the printer and corrects the problem nothing will be done. The
problem could be, for example, paper jam, no paper or no toner.
It would be nice if a problem could be detected and the user notified about this. If
the user becomes aware of the problem, she most likely will go to the printer, verify its
condition and take appropriate measures. This benefits both the owner of the halted
job and the owners of the jobs piled up in the queue.
As the users run a diverse multitude of platforms, it is difficult to send some sort of
direct pop-up message. However, most users have a mail client running, a client that
notifies them within minutes of a new message arriving. Sending the problem status
notification by mail ensures it works for the largest group of users and their platforms.
The lp and lpr commands support an m option where a user can request that a message
is sent by mail upon normal completion of a job. By supporting this option, the owner
of the job could continue working at her desk and still be able to pick up the job as
soon as it has finished printing.
3. Requirements
Here are the requirements for a solution to the problem, including additional
require-ments set down by the department. Each requirement is given an identification number
to the right for later reference.
3.1.
Common
Shall interoperate with printers from different manufacturers
[1]
Printers from several manufacturers may be used and, as long as they meet the
speci-fications, the solution must work with all of them without configuration exceptions.
3.2.
Accounting
Shall interoperate with LPRng
[2]
The system shall receive a user name, a job timestamp, a printer name and a job name
from LPRng. It shall also be possible to notify LPRng of what to do with the job,
which includes allowing it, retrying it later or denying it.
Shall interoperate with PostgreSQL
[3]
All data needed between sessions shall be stored in a database managed by the
depart-ment’s PostgreSQL database management system.
Shall manage access to printing by a user database
[4]
Users with printing access shall be stored in a database. Users that do not exist in the
database shall be denied access to printing. It shall be possible to drop and rename
users from the database. When a user is dropped, all associated data stored in the
database shall also be dropped.
Shall calculate and store accounting data
[5]
The number of impressions actually used during a job shall be counted in an accurate
way and stored in the database, together with user name, job timestamp, printer name
and job name.
Shall support per user quota to limit printing access
[6]
It shall be possible to enable a quota per user basis. The quota shall be checked before
each job and the user shall not be able to print if her quota is zero or below. The
user shall though be allowed to print, if the quota is above zero and the number of
impressions is unknown until after the job has finished. The number of impressions shall
be deducted from a user’s quota when the job has finished printing and the resulting
quota shall be negative if the result is negative. It shall be possible to increase or
decrease the user’s quota manually.
Shall store quota transactions history per user if quota is active
[7]
For each change to a user’s quota, an entry in the transactions history shall be made
in the database. No transactions history shall be kept for users that do not have a
quota. An entry in the transactions history shall consist of a user name, a transaction
timestamp, the change and a comment about the transaction.
Shall be able to output accounting status in PostScript format on request
[8]
The output shall include current quota (if applicable), a recent printing history (if
available), a monthly summary for the last twelve months and a recent transactions
history (if available). It shall be possible to include the output in a PostScript banner
page.
Shall send a message by mail if a user does not have access to print
[9]
If a user does not have access to print, an attempt shall be made to send the user a
message by mail about this fact.
Shall send a message by mail if a user does not have enough quota to print
[10]
If a user does not have enough quota to print, an attempt shall be made to send the
user a message by mail about this fact. The message shall include a recent transactions
history.
3.3.
Status Notification
Shall notify by mail at start of job if the printer is signaling a down status
[11]
If the printer is signaling a down status at the start of a job, an attempt shall be made
to send the user a message by mail about this. The message shall include printer name,
job name, job timestamp, and a request that the user should attempt to verify printer
condition and take appropriate measures.
Shall notify by mail during job if the printer is signaling a down status
[12]
If the printer is signaling a down status during the job, an attempt shall be made to
send the user a message by mail about this. The message shall include printer name,
job name, job timestamp, and a request that the user should attempt to verify printer
condition and take appropriate measures.
Shall notify by mail about normal completion of job if requested by the user [13]
If the user request that a message by mail is sent upon normal completion of a job, an
attempt shall be made to send the user such a message. The message shall include the
number of impressions of the job. If the user has a quota, the message shall include
the balance after the job.
Part II.
4. Accounting
This chapter is about security precautions with accounting, then a look at available
solutions and details about the chosen solution. The chapter ends with on how LPRng
is used and the design of the database.
4.1.
Security
No matter what accounting solution used, one has to make sure there is no way to
bypass it or to fool the accounting mechanism.
It is critical that only the print spooler can send jobs to the printers. The printers
should be located on their own private network or otherwise protected so that they
can only receive jobs from the print spooler. Otherwise users would be able to send
jobs directly to printers and thereby bypass any accounting solution. Printers should
of course also be configured to not listen to any other interfaces (like the USB and
parallel ports) and have their front panels locked so that printer settings cannot be
altered on location.
Further, it is important to be able to separate jobs for accounting purposes. Depending
on solution, this might include not sending the next job from the queue until the
previous has finished and been accounted for.
Part of accounting is connecting some sort of job information to a user. It is important
that one can trust the user name passed to the print spooler from the application that
submitted the job. If the user can hide her true user name or impersonate another
user, then all the accounting is in vain. An example is given in Appendix A on how to
accomplish this kind of trust with the LPRng print spooler.
4.2.
Available Solutions
There are several ways to do accounting.
A brief overview will here be given of
the solutions available, noting some pros and cons. The solutions discussed are
pre-printing, proprietary software, Hewlett-Packard’s Printer Job Language (PJL), Adobe’s
PostScript language and the Simple Network Management Protocol (SNMP).
4.2.1.
Pre-printing
These solutions are those where the counting of impressions takes place in pure software
implementations before printing and the actual printer hardware is not involved at all.
Two examples will be given here.
In modern Microsoft Windows operating systems, applications call the Graphics Device
Interface (GDI) to print [1]. The GDI calls the printer driver for information, which is
used to create the job. After job creation, the GDI knows the number of impressions of
the job and then delivers it to the spooler. If print output is produced in RAW format,
the GDI is not used.
The spooler logs the number of impressions in the event log where other software then
can check for accounting information. By bypassing the GDI and sending a RAW job
directly to the spooler, the spooler will log the job with zero impressions.
Ghostscript
1is a cross-platform interpreter for PostScript. It can be used to count the
number of impressions of a job before it is sent to the printer. It is though possible to
include certain PostScript commands that will trick Ghostscript into thinking that the
job has fewer impressions. This is usually done by testing the PostScript interpreter
to see if it has some special characteristics that are available on the actual printer and
not on the interpreter.
Any pre-printing solution suffers from the fact that the printer may not actually print
the entire job. For example, the printer could jam, or run out of paper or toner. The
user would still get charged. This means that a pre-printing solution does not fulfill
Requirement 5.
4.2.2.
Proprietary Software
Several printer manufacturers offer proprietary software solutions
2that sometimes only
work with their printers. These run on certain platforms only and often do not offer
open access to data. As a packaged solution, one can say that these do not fulfill
Requirement 2 nor Requirement 3, and sometimes not even Requirement 1.
4.2.3.
PJL
Hewlett-Packard’s Printer Job Language (PJL) is used by many existing accounting
applications
3. A connection is held open to the printer after the job has been sent to it,
periodically querying for whether the job is done or not. Before the job and when the
job has finished, the total number of impressions reported by the printer is read. The
1Ghostscript is available from <http://www.ghostscript.com/>.
2Examples of proprietary software solutions are Xerox CentreWare Web and Xerox XCounter Page
Accounting. Both are available from <http://www.xerox.com/>.
3An example accounting application using PJL is the ifhp print filter, available from <http://www.
lprng.com/>.
number of impressions used by the job can then be calculated. The PJL commands
ECHO, EOJ, INFO PAGECOUNT, JOB and USTATUS can be used to retrieve
the total number of impressions and check on printer status [2].
Pros of this solution is that almost all printers on the market today support PJL
according to their specifications. According to the grapevine, the support is in reality
sketchy. What the commands return and when, while clearly specified in the technical
reference manual, differ. One has to make sure that the commands really work and
return what they are supposed to. It is hard to know whether the number of impressions
is actually correct.
Writing a general implementation that works with several printer models from
dif-ferent manufacturers could turn into a game of rules and exceptions. Consequently,
Requirement 1 and Requirement 5 are not clearly fulfilled.
4.2.4.
PostScript
Adobe’s PostScript language is an interpretive programming language with powerful
graphics capabilities [3]. Its primary application is to describe the appearance of text,
graphical shapes, and sampled images on printed or displayed pages.
PostScript language level 2 implementations version 2011 and greater, and PostScript
language level 3 implementations feature a new system parameter PageCount [3]. Not
all products do necessarily support it. PageCount tracks the number of impressions
that have been successfully processed since manufacture, counting the number of copies
for each showpage. Impressions not physically printed are counted, making the count
inaccurate during the job, but accurate after adjustment at the end of the job. This is
what the specification says at least.
Just as with PJL, a connection is held open to the printer after the job has been sent
to it. A control sequence is sent in order to retrieve the status of the printer. Whether
you get the status this way depends on the printer. Some printers, according to reports
on the LPRng mailing list, stop responding when they run out of paper or toner. If
one manages to verify that the printer has finished with the job, commands can be
issued to retrieve the system parameter PageCount. By using the PageCount value
from before the job and the one retrieved after the job, the number of impressions used
during the job can be calculated.
PostScript is often supported by accounting applications as an alternative, or
supple-ment, to PJL. The pro side of this solution is that many printers support PostScript
today, though they have different implementations and support different language
lev-els. Whether they support the needed features may be uncertain. One may have to
use different PostScript code with each printer for it to work correctly. From this, one
can see that Requirement 1 and Requirement 5 are not clearly fulfilled.
4.2.5.
SNMP
The Simple Network Management Protocol (SNMP) is a definition of a protocol by
which management information for a network element may be inspected or altered by
logically remote users [4]. Together with companion memos which describe the
struc-ture of management information along with the management information base (MIB),
these documents provide a simple, workable architecture and system for managing
TCP/IP-based networks.
Two of these companion memos are of particular interest to us. The Host Resources
MIB provides two status objects, hrDeviceStatus and hrPrinterStatus, which
de-scribe many of the states of a printer [5].
The Printer MIB provides another
ob-ject, prtMarkerLifeCount, which gives us the count of the number of units of
measure counted during the life of the printer using units specified in the object
prtMarkerCounterUnit [6].
SNMP is independent of the process of sending the content of the job to the printer. By
using the two status objects to monitor whether the printer is done printing, and the
counter object to retrieve the number of impressions before and after a job, an accurate
number of actual printed impressions can be calculated. The counter is located in the
printer and counts only the number of impressions actually printed.
Support for SNMP in printers has become more important and has seen an upsurge
since generic SNMP-based monitoring tools have become increasingly popular. Most
printers on the market today support the two MIBs needed for the SNMP solution.
There are exceptions though, but the consensus seems to be that more printers work
with the SNMP solution compared to the PJL solution or the PostScript solution.
Requirement 1 and Requirement 5 are fulfilled as long as one makes sure the printers
in use (or being acquired) have proper SNMP and MIB support.
4.2.6.
Conclusion
With its increasing support in printers, the SNMP solution seems to be the best option.
The same general implementation would work with several printer models from different
manufacturers, as long as the printer supports SNMP and mentioned MIBs properly.
As the SNMP solution is independent of the process of sending the content of the job
to the printer, it reduces the number of places where things could go wrong by not
interfering in that process. However, even the best solution for counting the number
of impressions will fail unless it is used in an accurate and timely manner. The SNMP
solution will be discussed in more detail in Section 4.3.
Some printers only store the total number of impressions in EEPROM at certain
in-tervals. Some of these printers, like some of those from Hewlett-Packard, do not store
in EEPROM if they are powered off and will lose their current impressions count and
go back to the last saved count when powered on again. Other printers, which store
in EEPROM at intervals, return the stored total number of impressions when asked,
Printer status
hrDeviceStatus
hrPrinterStatus
Normal
running
idle
Busy/temporarily unavailable
running
printing
Non critical alert active
warning
idle or printing
Unavailable
down
other
Moving offline
warning
idle or printing
Offline
down
other
Moving online
down
warmup
Standby
running
other
Table 4.1.: Interesting printer status states
meaning that the actual impressions count and the stored count may differ
consider-ably. As these flaws affect the total number of impressions reported by the printer,
they most likely also affect all three of the PJL, PostScript and SNMP solutions. One
has to be careful to test printers for this vulnerability before acquiring and using them.
4.3.
SNMP
Table 4.1, based upon a similar table from the Printer MIB, shows some interesting
printer status states [6]. From the table it is possible to deduce that it is safe to use
the prtMarkerLifeCount object for determining the number of impressions when
hrDeviceStatus is running or warning, and hrPrinterStatus is idle or other. In
any other state one can not be certain if the printer is done with a previous or current
job. A printer with standby status is partially powered down and consequently not in
the middle of printing anything.
A hrDeviceStatus of warning indicates that the printer could have low paper or low
toner while a hrDeviceStatus of down indicates that the printer could be jammed,
have no paper, no toner or have the cover open [6]. This means that the former is a
safe state for determining the number of impressions, while the latter is not. The latter
could mean that the job is not finished and will finish as soon as the down state has
recovered. The job most likely still resides in the printer memory.
The value of prtMarkerCounterUnit can for example be impressions, sheets, hours
or meters [6]. It seems that with common home and office printers, impressions is the
common unit, and it can be assumed this is the general case.
JOB PRINTING TRANSACTION USER (1,1) (0,N) (1,1) (0,N) (1,1) (0,N) Balance User Printer Job Counter Timestamp Job Timestamp Printer Impressions Id Comment Change Id Timestamp
Figure 4.1.: ER diagram for the accounting database
4.4.
LPRng
According to the LPRng Reference Manual, LPRng can call an application at the start
of the job and at the end of the job to determine accounting information [7]. LPRng
will then pass the information needed to this application and the application can use
exit codes to indicate to LPRng what to do with the job. This fulfills Requirement 2.
Details on how to configure LPRng can be found in Appendix A.
4.5.
Database
Requirement 4, second half of Requirement 5, Requirement 6 and Requirement 7 partly
deal with the requirements on the database design. Figure 4.1 shows an ER diagram
for the accounting database.
In Figure 4.2 on the facing page, the ER diagram from Figure 4.1 has been transformed
into a database schema diagram with referential integrity constraints displayed.
USER BALANCE PRINTER COUNTER USER TIMESTAMP JOB ID USER TIMESTAMP IMPRESSIONS PRINTER JOB ID USER TIMESTAMP CHANGE COMMENT JOBS PRINTING USERS TRANSACTIONS
Figure 4.2.: Database schema diagram with referential integrity constraints
This design fulfills the database design from Requirement 4, second half of Requirement
5, Requirement 6 and Requirement 7. The functional parts of Requirement 4,
Require-ment 5, RequireRequire-ment 6 and RequireRequire-ment 7, as well as RequireRequire-ment 8, RequireRequire-ment 9
and Requirement 10, will be dealt with in the actual implementation.
5. Status Notification
Discussion in this chapter is about security considerations when it comes to status
notification, where to implement status notification and how to check printer status.
5.1.
Security
There are no critical security concerns when it comes to status notification.
The goal of status notification is to attempt to notify the user. It is for that reason
good if the user name used to contact the user can be trusted. Otherwise the wrong
user could be notified and in certain cases, personal information, like job names and
number of impressions, could be exposed. An example is given in Appendix A on how
to accomplish this kind of trust with the LPRng print spooler.
5.2.
Whereabouts
SNMP is already used by the accounting process, at the start of the job and at the
end of the job (when the job has been received by the printer and is being printed).
This means that it is possible to do the status checking described in Requirement 11
at the start of the job and the status checking described in Requirement 12 at the end
of the job. Requirement 13 is conveniently fulfilled at the end of the job when the
number of impressions is to be determined as well. LPRng passes a certain option to
the application it calls for accounting, which indicates if the user wants to be notified
of the normal completion of a job.
5.3.
SNMP
As seen in Table 4.1 on page 17, a hrDeviceStatus of down and a hrPrinterStatus
of other indicate that the printer is down. This information can be used to fulfill
Requirement 11 and Requirement 12 in the implementation of the application.
Part III.
6. Implementation
In this chapter two applications are implemented.
One for accounting and status
notification, and one for accounting administration.
The database is implemented
from the design.
6.1.
Accounting and Status Notification Application
The choice of implementation language for the application fell on Perl. Perl supports a
rapid, incremental development model. It is well known by the implementor and also
by intended future maintainers of the system. Perl has a huge library archive
1; libraries
for database access, SNMP and mail handling already exist for download and use.
A brief overview of how the implementation works will be given here with some
de-tails on certain interesting aspects. For a detailed view of the application, read the
source code, with its comments, in Appendix B. The functional parts of Requirement
4, Requirement 5, Requirement 6 and Requirement 7, as well as Requirement 8,
Re-quirement 9 and ReRe-quirement 10 will be described here. ReRe-quirement 11, ReRe-quirement
12 and Requirement 13 that relates to status notification will also be handled here.
The application can be launched in three modes; two of these will be discussed in
Section 6.1.1 and the third in Section 6.1.2. Each mode starts with a common part
where some parameters are received from LPRng. These are then verified together with
the mode of operation (which is read from standard input). After this a connection to
the database is made.
6.1.1.
Starting and Ending a Job
The start and end modes are used at the start and the end of a job. Both modes
continue after the common start by fetching an accurate number of impressions from
the printer. It is also in this step that the printer status check for the notification is
done. This step is tricky as it involves looping for a certain number of times, trying to
get an accurate value of the number of impressions, while at the same time not looping
forever. Some printers may for a second or two report a status that does not reflect
their real state (for example when the cover has been open and just has been closed),
so to make sure the reading can be trusted it has to be stable for a certain number of
readings.
If the printer is down at the start of the job, the user will be sent a message about
this. A fail status will be returned to LPRng and LPRng will eventually remove the
job from the queue after enough attempts. A job can not be allowed to be sent to the
printer without an accurate reading of the prtMarkerLifeCount object.
At the end of the job, the application will try for a longer time if the printer is down,
but will eventually give up and leave the accounting of the job until the next time a
job is started. The user will be sent a message about the down status of the printer.
If a job takes a long time to finish, the loop will be aborted and accounting for the job
will be postponed until the start of the next job.
If an accurate reading of the prtMarkerLifeCount object can be made (according
to Section 4.3 on page 17), then the application moves on to the next step. Here any
ongoing job (either left from earlier if in start mode or the current if in end mode) will
be accounted for using the value of the prtMarkerLifeCount object and a job credit
value. The job credit is either zero or one depending on if a banner page has been
printed or not. This works even if the job is printed in duplex mode, if duplex printing
is turned on during printer initialization (or in the job itself) after the banner page has
been sent in simplex mode.
If the job was a current one and the user requested to be notified at a normal completion,
then a message is sent to the user. No message is sent if a job from an earlier session
was accounted for.
If in end mode, the application now exits as the current job has been accounted for.
In start mode, the application continues. If the user is not allowed to print
(Require-ment 4), a message is sent about denied access. Then the user’s quota is checked, and
if not enough (Requirement 6) the user is denied and notified about it. As the user
now will be allowed to print, all data needed in the end mode, after the job has been
sent off to the printer, are stored in the database. These are some of the job meta data
and the value of the prtMarkerLifeCount object.
6.1.2.
Accounting Status
The accounting application can output accounting status in PostScript format, as
de-scribed in the PostScript Language Reference [3]. By calling the accounting application
with status on standard input, the standard output can be included as part of a banner
page at the current location on that page.
The PostScript command rmove is used to make relative moves [3]. Before calling the
accounting application and requesting status, the banner creation application should
move to where it wants the status. After including the status, the current point is set
in the bottom left corner of it, so that the calling application can continue working on
the banner there.
Figure 6.1 on the following page shows the rendered example of a complete
account-ing status, where current quota, printaccount-ing history, monthly summary and transactions
history are available. Each element (of the four) is only displayed when there are data
available for it.
6.2.
Accounting Administration Application
This utility application fulfills parts of Requirement 4 and Requirement 6. It enables
the administrator to create, drop and rename users. It is possible to display quota,
have it enabled or disabled, and increased or decreased manually. A simple interface
to display printing history, printing summary and transactions history is also given.
Commands are given on standard input and information is returned on standard output
(errors on standard error). Help on the syntax is given if run in interactive mode, but
data can also be piped directly to and from the application. For a detailed view of the
application, read the source code, with its comments, in Appendix C.
6.3.
Database
The implementation of the database design is done so that Requirement 3 is fulfilled.
The database data definition in SQL is written in accordance with the PostgreSQL
7.3 Reference Manual [8]. The functions are written in PL/pgSQL in accordance with
the PostgreSQL 7.3 Programmer’s Guide [9]. The entire accounting database data
definition can be found in Appendix D.
The database tables (derived from Figure 4.2 on page 19) and their assertions, described
in Table 6.1 on page 29, are created. Entity integrity and referential integrity are not
indicated in Table 6.1 but can be seen in Figure 4.2. No attributes are allowed to be
null, except for the balance attribute which uses null to indicate a state of no quota.
A trigger, to fulfill part of Requirement 7, is described in Table 6.2 on page 29.
A number of indexes are also needed to enable fast search and sorting. They are
described in Table 6.3 on page 29. Some of them are implicitly defined as part of the
entity integrity constraints.
In order to abstract some functionality from the application layer to the database layer,
a number of functions are created. These, together with the trigger function from Table
6.2, are described in Table 6.4 on page 29.
Two database users are needed, one for the accounting application and one for the
accounting administration application. Each of these users will only have the privileges
that are necessary. These users are shown in Table 6.5 on page 30 and their database
privileges are shown in Table 6.6 on page 30.
688 impressions left before this job
Printing History
Timestamp
Impressions
Printer
Job
2003−08−09 16:20:23
3
lp4
dbtech−exam−04.ps
2003−08−09 16:19:56
3
lp4
standard input
2003−08−09 16:13:47
3
lp4
dbtech−exam−08.ps
2003−08−09 16:13:16
1
lp4
dbtech−labs.pdf
2003−08−09 16:13:10
1
lp4
dbtech−info.pdf
2003−08−09 16:13:05
1
lp4
cc−info.ps.gz
2003−08−09 16:09:17
10
lp4
report.ps
2003−08−09 16:09:13
3
lp4
index.html
2003−08−09 16:09:09
1
lp4
registration.fm
2003−08−09 16:09:05
5
lp4
email.txt
Monthly Summary
Year Month
Impressions
Jobs
2003 August
421
78
2003 July
181
90
2003 June
166
121
Transactions History (Quota)
Timestamp
Before
Change
After
Comment
2003−08−09 16:20:45
691
−3
688
Job "dbtech−exam−04.ps" on printer "lp4"
2003−08−09 16:20:10
694
−3
691
Job "standard input" on printer "lp4"
2003−08−09 16:14:48
697
−3
694
Job "dbtech−exam−08.ps" on printer "lp4"
2003−08−09 16:14:20
698
−1
697
Job "dbtech−labs.pdf" on printer "lp4"
2003−08−09 16:13:48
699
−1
698
Job "dbtech−info.pdf" on printer "lp4"
2003−08−09 16:13:17
700
−1
699
Job "cc−info.ps.gz" on printer "lp4"
2003−08−09 16:12:54
550
150
700
Database Technology
2003−08−09 16:12:53
450
100
550
Compiler Construction
2003−08−09 16:11:06
460
−10
450
Job "report.ps" on printer "lp4"
2003−08−09 16:10:27
463
−3
460
Job "index.html" on printer "lp4"
Figure 6.1.: Rendered example of accounting status in PostScript format
Name
Assertions
Description
users
Users and their quota
jobs
Active print jobs
printing
(impressions >= 0)
Printing history
transactions
Transactions history
Table 6.1.: Database tables
Name
Table
Triggers
Granularity
Executes function
delete transactions
users
After update
Row
delete transactions
Table 6.2.: Database triggers
Name
Table
Columns
Definition
users pkey
users
user
Implicit (primary key)
jobs pkey
jobs
printer
Implicit (primary key)
printing pkey
printing
id
Implicit (primary key)
printing user
printing
user
Explicit
printing timestamp
printing
timestamp
Explicit
transactions pkey
transactions
id
Implicit (primary key)
transactions user
transactions
user
Explicit
transactions timestamp
transactions
timestamp
Explicit
Table 6.3.: Database indexes
Name
Result data type
Argument data types
Language
delete transactions
trigger
plpgsql
end job
integer
text, integer, integer
plpgsql
start job
boolean
text, integer, text,
timestamp with time
zone, text
plpgsql
change balance
boolean
text, integer
plpgsql
change balance
boolean
text, integer, text
plpgsql
create user
boolean
text
plpgsql
drop user
boolean
text
plpgsql
rename user
boolean
text, text
plpgsql
Name
Purpose
accounting
Access from the accounting application
accounting admin
Access from the accounting administration application
Table 6.5.: Database users
User
Object type
Object name
Privileges
accounting
table
users
select
accounting admin
table
users
select
accounting
function
delete transactions
execute
accounting admin
function
delete transactions
execute
accounting
table
printing
select
accounting admin
table
printing
select
accounting
table
transactions
select
accounting admin
table
transactions
select
accounting
function
end job
execute
accounting
function
start job
execute
accounting admin
function
change balance
execute
accounting admin
function
create user
execute
accounting admin
function
drop user
execute
accounting admin
function
rename user
execute
Table 6.6.: Database privileges
7. Results
The aim of this thesis was to design and implement an accounting and status
notifica-tion system for the LPRng print spooler. The implementanotifica-tion and the design behind it
constitute the result and accomplishment. The implementation fulfills all the
require-ments laid out in Chapter 3. The full source code can be found in Appendix B and
Appendix C, and the entire database data definition can be found in Appendix D.
During the testing and deployment of the system, it should be noted that it worked
flawlessly with the Xerox DocuPrint N32 and the Xerox Phaser 4400 printer models,
both of which the department use.
While the system works with the listed printers, it must be noted that it may or may
not work with other printers. A prerequisite is of course that the printer correctly
supports SNMP and the mentioned MIBs.
8. Conclusion and Future Work
8.1.
Conclusion
After nearly six months of operation, the system is stable and still operating fast. It
handles the production environment of about 20 printers with over 4,000 active users
without problems. More than 230,000 jobs, consisting of nearly 1.8 million impressions
(not including banner pages), have been printed.
Hopefully this system can inspire the authors of similar systems, give them new ideas
and solutions to common problems. The implementation is clean and simple, bringing
several fields together into a complete solution. The full use of the capabilities of
PostgreSQL abstracts complexity from the applications. Using existing libraries when
available limits the amount of new code that needs to be written. Focus was put on
using one accounting solution, instead of attempting to use several in combination and
consequently making the system complex and failure prone.
Four formal languages are used. SQL to perform database queries, PL/pgSQL for
the functions in the database and PostScript to display the accounting status. Perl
ties together database access, SNMP communication with printers, communication
with LPRng and output of accounting status. This shows that it is possible to select
different languages for different purposes and tie them together in a complete software
solution. This will hopefully serve as further inspiration for future work.
8.2.
Future Work
The implementation presented in this thesis built upon the requirements presented.
On the way there, precaution was taken to ensure that the system would be easy to
extend in the future. One can think of several interesting extensions to this system,
described in the following sections.
8.2.1.
Credit System
Instead of having impressions as the currency for credits, one could add an option to
specify a cost in a number of credits. Then credits could be awarded to a user whenever,
but the cost of printing could change over time. The only change needed here is to add
a multiplication to the database function end job (see Table 6.4 on page 29).
8.2.2.
Individual Cost Per Printer
An even further extension to the credit system would be to have a cost per impression
per printer. This might be useful in a mixed environment of printers with varying
toner costs. The changes needed for this, on top of the one described in Section 8.2.1,
would be to add a new table to the database for this cost and a table lookup to get the
variable used in the multiplication.
8.2.3.
Individual Cost Per User
Different users could be charged a different number of credits. One could multiply
the cost per printer (as described in Section 8.2.2) with a variable fetched from a new
column in the database table users (see Figure 4.2 on page 19).
8.2.4.
And More
As seen in the three given examples, it is easy to extend the system by only small
changes. More eccentric extensions could be cost depending on time of day (one new
table and one new lookup) or cost depending on the number of jobs in the last hour
(one new lookup). Only imagination sets the limits.
Part IV.
References
[1] Microsoft Corporation. Microsoft Windows 2000 Professional Resource Kit.
Red-mond, Washington, USA. Microsoft Press. 2000. Chapter 14. Printing. ISBN
1-57231-808-2.
[2] Hewlett-Packard Development Company, LP. Printer Job Language Technical
Ref-erence Manual [online]. 12th edition. June 2003 [cited 29 October 2003].
Publica-tion number 5021-0380. Available from <http://h20000.www2.hp.com/bc/docs/
support/SupportManual/bpl13208/bpl13208.pdf>.
[3] Adobe Systems Incorporated. PostScript Language Reference [online]. 3rd edition.
Addison-Wesley Publishing Company. 1999 [cited 29 October 2003]. Available from
<http://www.adobe.com/products/postscript/pdfs/PLRM.pdf>. ISBN
0-201-37922-8.
[4] Case, J & Fedor, M & Schoffstall, M & Davin, J. Simple Network Management
Protocol (SNMP) [online]. RFC 1157. Network Working Group. May 1990 [cited 30
October 2003]. Available from <http://www.ietf.org/rfc/rfc1157.txt>.
[5] Waldbusser, S & Grillo, P. Host Resources MIB [online]. RFC 2790. Network
Working Group. March 2000 [cited 27 October 2003]. Available from <http:
//www.ietf.org/rfc/rfc2790.txt>.
[6] Smith, R & Wright, F & Hastings, T & Zilles, S & Gyllenskog, J. Printer MIB
[online]. RFC 1759. Network Working Group. March 1995 [cited 27 October 2003].
Available from <http://www.ietf.org/rfc/rfc1759.txt>.
[7] Powell, Patrick A. LPRng Reference Manual [online]. San Diego, California,
USA. AStArt Technologies. 5 September 2003 [cited 31 October 2003].
Chap-ter 18. Accounting. Available from <http://www.lprng.com/LPRng-Reference/
LPRng-Reference.html>.
[8] The PostgreSQL Global Development Group. PostgreSQL 7.3 Reference Manual
[online]. 2002 [cited 1 November 2003]. Part I. SQL Commands. Available from
<http://www.postgresql.org/docs/7.3/static/reference.html>.
[9] The PostgreSQL Global Development Group. PostgreSQL 7.3 Programmer’s Guide
[online]. 2002 [cited 1 November 2003]. Chapter 19. PL/pgSQL - SQL
Procedu-ral Language. Available from <http://www.postgresql.org/docs/7.3/static/
programmer.html>.
A. LPRng Integration
LPRng can be configured so that an accounting application is executed before and
after the job is sent to the printer [7]. This can be accomplished by using the variables
af, as and ae. Add to lpd.conf the following directives, adjusting the path when
appropriate:
af=|/usr/local/libexec/filters/accounting.pl
as=start
ae=end
In order to be able to trust the name of the user, the workstations that submit jobs
have to be trusted. One way of trusting them is to only allow the administrator to have
superuser access and to let the application that submit print requests (lp or lpr) to have
its user id set to superuser on execution. Then tell LPRng to only allow connections
from ports 1 to 1023, ports that only the superuser can open on the workstations. In
lpd.perms, one could write:
REJECT SERVICE=X NOT PORT=1-1023
Use the accounting namefixup variable in lpd.conf to tell from which workstations
the user name is trusted. This will set the accounting name parameter that LPRng
then passes to the accounting application. As an example, to trust the workstations
on a local subnet and make sure that any other workstation will always get a
non-existing user name ( untrusted), and hence fail due to that it does not exist in the
user database, one could write:
accounting_namefixup=10.0.0.0/24=${L},_unknown;0.0.0.0/0=_untrusted
Of course, one should be more restrictive than this and only allow connections (in
lpd.perms) from the trusted workstations in the first place.
B. Source Code of the Accounting and
Status Notification Application
#!/ usr / l o c a l / bin / p e r l use s t r i c t ; use w a r n i n g s ; use DBI ; use M I M E :: L i t e ; use Net :: S N M P ; use T e x t :: W r a p ; my $ d a t e ; my $ t i m e ; my $ t i m e _ m i l l i s e c o n d s ; my $ j o b ; my $ m a i l ; my $ p r i n t e r ; my $ a c c o u n t i n g _ n a m e ; my $ u s e r ; my $ e x i t _ j o b _ f a i l = 1 ; my $ w r a p _ c o l u m n s = 8 0 ; my $ m a i l _ n o _ e n d _ j o b _ s u b j e c t ; my $ m a i l _ n o _ e n d _ j o b _ b o d y ; my $ m a i l _ d o w n _ a t _ s t a r t _ s u b j e c t ; my $ m a i l _ d o w n _ a t _ s t a r t _ b o d y ; my $ m a i l _ d o w n _ a t _ e n d _ s u b j e c t ; my $ m a i l _ d o w n _ a t _ e n d _ b o d y ; my $ m a i l _ e n d _ j o b _ s u b j e c t ; my $ m a i l _ e n d _ j o b _ b o d y ; my $ m a i l _ n o _ a c c e s s _ s u b j e c t ; my $ m a i l _ n o _ a c c e s s _ b o d y ; my $ m a i l _ n o _ q u o t a _ s u b j e c t ; my $ m a i l _ n o _ q u o t a _ b o d y ; my $ a c t i o n ; my $ d b h ; my $ s t h ; my $ s q l _ s e l e c t _ b a l a n c e = q / S E L E C T " b a l a n c e " F R O M " a c c o u n t i n g " . " u s e r s " / . q / W H E R E " u s e r " = ? / ; my $ a r y _ r e f ; my $ b a l a n c e ; my $ s t r i n g ; my $ s q l _ s e l e c t _ p r i n t i n g = q / S E L E C T / . q / t o _ c h a r ( " t i m e s t a m p " , ’ Y Y Y Y - MM - DD H H 2 4 : MI : SS ’ ), " i m p r e s s i o n s " , / . q / " p r i n t e r " , " job " F R O M " a c c o u n t i n g " . " p r i n t i n g " W H E R E " u s e r " =? O R D E R / . q / BY " t i m e s t a m p " D E S C L I M I T 1 0 / ; my $ t b l _ a r y _ r e f ; my $ r o w ; my $ s q l _ s e l e c t _ s u m m a r y = q / S E L E C T t o _ c h a r ( " t i m e s t a m p " , ’ Y Y Y Y ’ ) AS " y e a r " , / . q / t o _ c h a r ( " t i m e s t a m p " , ’ M o n t h ’ ) AS " m o n t h " , sum ( " i m p r e s s i o n s " ) AS / . q / " i m p r e s s i o n s " , c o u n t ( * ) AS " j o b s " , d a t e _ t r u n c ( ’ m o n t h ’ , " t i m e s t a m p " ) / . q / AS " t i m e s t a m p _ t r u n c " F R O M " a c c o u n t i n g " . " p r i n t i n g " W H E R E " u s e r " =? AND / .
q / age ( d a t e _ t r u n c ( ’ m o n t h ’ , " t i m e s t a m p " ) ) < i n t e r v a l ’ 1 2 m o n t h s ’ G R O U P / . q / BY " y e a r " , " m o n t h " , " t i m e s t a m p _ t r u n c " O R D E R BY t i m e s t a m p _ t r u n c D E S C /; my $ s q l _ s e l e c t _ t r a n s a c t i o n s = q / S E L E C T / . q / t o _ c h a r ( " t i m e s t a m p " , ’ Y Y Y Y - MM - DD H H 2 4 : MI : SS ’ ), " c h a n g e " , " c o m m e n t " / . q / F R O M " a c c o u n t i n g " . " t r a n s a c t i o n s " W H E R E " u s e r " =? O R D E R BY " t i m e s t a m p " / . q / D E S C L I M I T ? / ; my $ t r a n s a c t i o n s _ b a n n e r = 1 0 ; my $ e x i t _ j o b _ s u c c e s s = 0 ; my $ s n m p _ s e s s i o n ; my $ s n m p _ e r r o r ; my $ s n m p _ t i m e o u t = 3 ; # s e c o n d s my $ s n m p _ c o u n t e r _ a v a i l a b l e ; my $ s n m p _ c o u n t e r _ l o o p ; my $ s n m p _ c o u n t e r _ f a i l u r e ; my $ m a i l e d _ a b o u t _ d o w n _ s t a t u s ; my $ s n m p _ c o u n t e r _ d o w n ; my $ s n m p _ c o u n t e r _ l a s t ; my $ s n m p _ m i n _ a v a i l a b l e = 5 ; # t i m e s my $ s n m p _ w a i t = 1 ; # s e c o n d s my $ s n m p _ r e s u l t ; my $ s n m p _ h r _ d e v i c e _ s t a t u s = ’ 1 . 3 . 6 . 1 . 2 . 1 . 2 5 . 3 . 2 . 1 . 5 . 1 ’ ; my $ s n m p _ h r _ p r i n t e r _ s t a t u s = ’ 1 . 3 . 6 . 1 . 2 . 1 . 2 5 . 3 . 5 . 1 . 1 . 1 ’ ; my $ s n m p _ p r t _ m a r k e r _ l i f e _ c o u n t = ’ 1 . 3 . 6 . 1 . 2 . 1 . 4 3 . 1 0 . 2 . 1 . 4 . 1 . 1 ’ ; my $ s n m p _ m a x _ l o o p s = 3 6 0 0 ; # t i m e s my $ s n m p _ m a x _ f a i l u r e s _ a t _ s t a r t = 6 ; # t i m e s in a row my $ s n m p _ m a x _ d o w n s = 1 2 0 ; # t i m e s in a row my $ p r i n t c a p _ e n t r y = $ E N V { ’ P R I N T C A P _ E N T R Y ’ }; my $ j o b _ c r e d i t = 1 ; my $ s q l _ e n d _ j o b = q / S E L E C T " a c c o u n t i n g " . " e n d _ j o b " ( ? , ? , ? ) / ; my $ i m p r e s s i o n s ; my $ t r a n s a c t i o n s _ m a i l = 3 0 ; my $ m s g ; my $ m a i l _ f r o m = ’ P r i n t S p o o l e r < n o b o d y @ e x a m p l e . com >’ ; my $ m a i l _ h o s t = ’ m a i l h o s t ’ ; my $ s m t p _ t i m e o u t = 1 5 ; # s e c o n d s my $ e x i t _ j o b _ r e m o v e = 3 ; my $ t r a n s a c t i o n s ; my $ t r a n s a c t i o n s _ l i n e ; my $ f i l l ; my $ s q l _ s t a r t _ j o b = q / S E L E C T " a c c o u n t i n g " . " s t a r t _ j o b " ( ? , ? , ? , ? , ? ) / ; # get p a r a m e t e r s f o r e a c h ( @ A R G V ) { if ( / ^ - D (\ d { 4 } - \ d { 2 } - \ d { 2 } ) - ( \ d { 2 } : \ d { 2 } : \ d { 2 } ) \ . ( \ d + ) / ) { $ d a t e = $1 ; $ t i m e = $2 ; $ t i m e _ m i l l i s e c o n d s = $3 ; n e x t (); } if ( / ^ - J ( . + ) $ / ) { $ j o b = $1 ; n e x t ( ) ; } if ( / ^ - M ( . + ) $ / ) { $ m a i l = $1 ; n e x t ( ) ; } if ( / ^ - P ( . + ) $ / ) { $ p r i n t e r = $1 ; n e x t ( ) ; } if ( / ^ - R ( . + ) $ / ) { $ a c c o u n t i n g _ n a m e = $1 ; n e x t ( ) ; } if ( / ^ - n ( . + ) $ / ) { $ u s e r = $1 ; n e x t ( ) ; } } # v e r i f y p a r a m e t e r s if ( not ( d e f i n e d ( $ d a t e ) ) ) { w a r n ( ’ Bad or m i s s i n g - D p a r a m e t e r ’ ); e x i t ( $ e x i t _ j o b _ f a i l ); } if ( not ( d e f i n e d ( $ j o b ) ) ) { w a r n ( ’ Bad or m i s s i n g - J p a r a m e t e r ’ ); $ j o b = ’( Job n a m e not a v a i l a b l e )’ ; } if ( not ( d e f i n e d ( $ p r i n t e r ) ) ) { w a r n ( ’ Bad or m i s s i n g - P p a r a m e t e r ’ );
42
e x i t ( $ e x i t _ j o b _ f a i l ); } if ( not ( d e f i n e d ( $ a c c o u n t i n g _ n a m e ) ) ) { w a r n ( ’ Bad or m i s s i n g - R p a r a m e t e r ’ ); e x i t ( $ e x i t _ j o b _ f a i l ); } if ( not ( d e f i n e d ( $ u s e r ) ) ) { w a r n ( ’ Bad or m i s s i n g - n p a r a m e t e r ’ ); e x i t ( $ e x i t _ j o b _ f a i l ); } # set d e f a u l t s for t e x t w r a p p i n g l o c a l ( $ T e x t :: W r a p :: c o l u m n s ) = $ w r a p _ c o l u m n s ; l o c a l ( $ T e x t :: W r a p :: h u g e ) = ’ o v e r f l o w ’ ;
# s e n t w h e n no m e s s a g e can be s e n t w h e n job has c o m p l e t e d $ m a i l _ n o _ e n d _ j o b _ s u b j e c t =
qq / Job " $ j o b " has not yet c o m p l e t e d on p r i n t e r " $ p r i n t e r " /; $ m a i l _ n o _ e n d _ j o b _ b o d y =
w r a p ( ’’ , ’’ ,
qq / T h i s is an a u t o m a t i c m e s s a g e f r o m the p r i n t s p o o l e r c o n c e r n i n g / . qq / p r i n t e r " $ p r i n t e r " .\ n / .
qq /\ n / .
qq / Job " $ j o b " , f r o m $ d a t e at $ t i m e , has not yet c o m p l e t e d . G i v e n / . qq / up w a i t i n g for job to c o m p l e t e . You w i l l not r e c e i v e a m e s s a g e / . qq / w h e n " $ j o b " has c o m p l e t e d .\ n / .
qq /\ n / .
qq / Do not r e p l y to t h i s m e s s a g e ; all r e p l i e s w i l l b o u n c e .\ n /); # s e n t w h e n d o w n s t a t u s at s t a r t of job
$ m a i l _ d o w n _ a t _ s t a r t _ s u b j e c t =
qq / Job " $ j o b " has not s t a r t e d on p r i n t e r " $ p r i n t e r " /; $ m a i l _ d o w n _ a t _ s t a r t _ b o d y =
w r a p ( ’’ , ’’ ,
qq / T h i s is an a u t o m a t i c m e s s a g e f r o m the p r i n t s p o o l e r c o n c e r n i n g / . qq / p r i n t e r " $ p r i n t e r " .\ n / .
qq /\ n / .
qq / Job " $ j o b " , f r o m $ d a t e at $ t i m e , has not s t a r t e d as p r i n t e r is / . qq / s i g n a l i n g a d o w n s t a t u s . T h i s c o u l d m e a n no p a p e r , p a p e r jam / . qq / or t o n e r out . W i l l k e e p t r y i n g for a w h i l e to s u b m i t " $ j o b " to / . qq / p r i n t e r " $ p r i n t e r " but w i l l e v e n t u a l l y g i v e up .\ n / . qq /\ n / . qq / P l e a s e v e r i f y p r i n t e r c o n d i t i o n and t a k e a p p r o p r i a t e / . qq / m e a s u r e s .\ n / . qq /\ n / . qq / Do not r e p l y to t h i s m e s s a g e ; all r e p l i e s w i l l b o u n c e .\ n /); # s e n t w h e n d o w n s t a t u s at end of job $ m a i l _ d o w n _ a t _ e n d _ s u b j e c t =
qq / Job " $ j o b " has not c o m p l e t e d on p r i n t e r " $ p r i n t e r " /; $ m a i l _ d o w n _ a t _ e n d _ b o d y =
w r a p ( ’’ , ’’ ,
qq / T h i s is an a u t o m a t i c m e s s a g e f r o m the p r i n t s p o o l e r c o n c e r n i n g / . qq / p r i n t e r " $ p r i n t e r " .\ n / .
qq /\ n / .
qq / Job " $ j o b " , f r o m $ d a t e at $ t i m e , has not c o m p l e t e d as p r i n t e r / . qq / is s i g n a l i n g a d o w n s t a t u s . T h i s c o u l d m e a n no p a p e r , p a p e r / . qq / jam or t o n e r out . Y o u r job w i l l not c o m p l e t e u n t i l t h i s is / . qq / t a k e n c a r e of .\ n / . qq /\ n / . qq / P l e a s e v e r i f y p r i n t e r c o n d i t i o n and t a k e a p p r o p r i a t e / . qq / m e a s u r e s .\ n / . qq /\ n / . qq / Do not r e p l y to t h i s m e s s a g e ; all r e p l i e s w i l l b o u n c e .\ n /); # s e n t w h e n job has c o m p l e t e d and u s e r w a n t e d to k n o w a b o u t t h a t