Einer für alle - Linux-Printserver für (fast) treiberfreie Windows-Clients

Vortrag zum 4. Chemnitzer Linuxtag vom 09. bis 10.03.2002

Vortragsfolien als [html][pdf][StarOffice Präsentation]

PDF statt Papier
Nacharbeiten am Postscriptfile
Drucken per CUPS
Drucken per LPRng
Filter
Priorisieren von Druckaufträgen
Lastverteilung
Chooser
Routing
printcap-Includes
Druckbenachrichtigung

Man ist immer geneigt, die Schuld auf die Technik zu schieben, wenn etwas gründlich schief geht. Im Falle meines Vortrages zum 4. Chemnitzer Linuxtag gebe ich aber offen zu: Die mißglückte Vorführung war schlicht eigene Dummheit ;-). Ich meine daher, daß ich Ihnen als Zuhörer, der Sie mir immerhin eine Stunde Ihres Wochenendes geschenkt haben, einen Nachtrag zur Vorführung schuldig bin. Auf den folgenden Seiten finden Sie einige Anmerkungen, die ich aus Zeitgründen nicht mehr machen konnte sowie, was noch viel wichtiger ist, die Konfigurationsfiles zu den einzelnen kurz angesprochenen LPRng-Features.

PDF statt Papier - Drucken auf den ps2pdf-Filter (Folie)(Top)

Filter benutzen in letzter Konsequenz meist Ghostscript, um die Wandlung von Postscript in das Zielformat vorzunehmen. Ghostscript selbst kennt dafür diverse "Devices", die im übertragenen Sinne als Treiber angesehen werden können. Eines dieser Devices ist pdfwrite, das vom Umwandlungsskript ps2pdf  (im ghostscript-rpm mit weiteren Formatwandlern enthalten) benutzt wird. Die Implementierung eines "PDF-Druckers" geschieht nun folgendermaßen:

In der smb.conf wird folgende Printershare eingerichtet
...
[pdf_drucker]
   browseable = yes
   path = /var/spool/pdf_drucker
   read only = yes
   printable = yes
   # Wichtig! Dem Umwandlungsskript muß der Windows-Benutzer mitgeteilt
   # werden, der den Druckauftrag abgesetzt hat (%U)
   print command = /home/samba/bin/pdfprint %s %U
   guest ok = yes

/home/samba/bin/pdfprint sieht folgendermaßen aus

#!/bin/sh

SPOOLFILE=/var/spool/pdf_drucker/$1
USERNAME=$2

# Ein interessantes Problem stellt die Benennung des Ausgabefiles dar, denn beim
# Drucken kann man ja nicht ohne weiteres eine solche Information mitgeben.
# Prinzipiell sind zwei Workarounds denkbar:

# 1. Wir erzeugen einen "zufälligen" Namen.
# 2. Wir senden im Postscriptfile einen "unsichtbaren" Namen mit, indem wir
#    dort den Namen zwischen zwei selbstdefinierten Tags einschließen, die
#    mit einer Wahrscheinlichkeit nahe Null im eigentlichen Text vorkommen,
#    dieses ganze Konstrukt mit einer simplen Schriftart formatieren
#    (z.B. Helvetica), die Postscript kennt (und damit nicht in Bitmap
#    wandelt) und das Ganze dann weiß einfärben, um es optisch zu verbergen.
#    Wir entscheiden uns für die Tags <pdffilename> und </pdffilename>.

# Finden wir in dem Postscriptfile Tags?
if grep '<pdffilename>' $SPOOLFILE >/dev/null && grep '</pdffilename>' $SPOOLFILE >/dev/null; then
        # der Filename steht zwischen den Tags <pdffilename> und </pdffilename>
        FILENAME=`cat $SPOOLFILE|sed -e 's/.*<pdffilename>//g'|grep pdffilename|sed -e 's/<\/pdffilename>.*//g'`
        # Achtung! Das File kommt von Windows. Wetten daß im Filename "verbotene" Zeichen stehen?
        # Die müssen wir wandeln/löschen, sonst können wir das PDF-File nicht erzeugen.
        FILENAME=`echo "$FILENAME"|tr -d '\\\'|tr '<>=:*/() ' '??????\[\]#'`
        ZIELNAME=/tmp/$FILENAME.pdf
# falls wir keine Tags gefunden haben, dann erfinden wir einen Filenamen
# username.titel_des_druckauftrages.druckdatum.pdf
else
        ZIELNAME=/tmp/$USERNAME.`grep %%Title $SPOOLFILE|dos2unix|head -1|awk '{print $2}'`.`date +%y%m%d.%H%M%S`.pdf
fi

# Das Postscriptfile wird nun aus dem Spoolverzeichnis gelesen, gewandelt und gelöscht
# (falls File kein Postscript ist, einfach ungewandelt weiterreichen, dann aber ohne Endung .pdf)
if grep '%!PS-Adobe-' $SPOOLFILE >/dev/null; then
        ps2pdf $SPOOLFILE $ZIELNAME
else
        ZIELNAME=`basename $ZIELNAME .pdf`
        ln -s $SPOOLFILE $ZIELNAME
fi

# Nun schicken wir das erzeugte PDF-File wieder zurück
# Eine Variante ist die Nutzung des smbclient, denkbar wäre auch eMail
# (der Windowsnutzer pdfwriter muß natürlich Schreibrecht in allen Homedirs haben)
smbclient //homeserver/homeshare -U pdfwriter%pdfpass <<SMBPUT
cd $USERNAME
put $ZIELNAME
exit
SMBPUT

# Am Ende räumen wir auf dem Printserver noch auf
rm -f $ZIELNAME $SPOOLFILE

Nacharbeiten am Postscriptfile (Folie)(Top)

Das gute an Standards ist, daß es so viele davon gibt. Konsequenterweise verhält sich auch Postscript so. Aus diesem Grunde ist das, was am Printserver ankommt, nicht unbedingt das, was eine Linuxapplikation erzeugt hätte. Deshalb muß das entgegengenommene Postscriptfile vor der Umwandlung in das Druckerformat häufig noch etwas "zurechtgeschnitten" werden. Dabei kann man leider keine allgemeine Empfehlung geben, da sich vermutlich jeder Windows-PS-Treiber etwas anders verhält. Hier hilft leider nur beobachten, tracen, probieren. Die beiden von mir getesteten Treiber haben folgende Eigenarten.

Adobe Generic Postscript:

Beispielausschnitt:
%!PS-Adobe-3.0^M
%%Title: Intranet Sparkasse Chemnitz^M
%%Creator: AdobePS5.dll Version 5.2.1^M
%%CreationDate: 3/12/2002 14:9:42^M
%%BoundingBox: (atend)^M
...
statusdict begin (%%[ ProductName: ) print product print ( ]%%)= flush end^M
...
(%%[Page: 1]%%) = ^M
...
(%%[LastPage]%%) = ^M
%%EOF^M
^D

Die Zeilen mit (%%[ ProductName: ..., (%%[Page: ..., (%%[LastPage werden bei der Umwandlung sauber ausgefüllt und auf jeweils einer eigenen Seite ausgedruckt. Sie sollten also im print command der smb.conf ausgefiltert werden:
print command = (grep -v '(%%\[ ProductName' %s|grep -v '(%%\[Page'|grep -v '(%%\[LastPage'|/usr/bin/lpr -P%p;rm -f %s)

HP LaserJet 40x0:

Beispielausschnitt:
^[%-12345X@PJL SET JOBATTR="SrcIO=Windows LPT3:"^M
@PJL SET JOBATTR="SrcServerName=\\O9991678"^M
@PJL SET JOBATTR="SrcQ=HP LaserJet 4050 Series PS"^M
@PJL SET JOBATTR="TimeSubmit=1015969007"^M
@PJL SET JOBATTR="JobID=17"^M
@PJL SET JOBATTR="JobDesc=ERROR: Cache Access Denied"^M
@PJL SET JOBATTR="JobFName="^M
@PJL SET JOBATTR="DocOwner=Administrator"^M
@PJL SET JOBATTR="DocOwnerID="^M
@PJL SET JOBATTR="DocSize=4593"^M
@PJL JOB
@PJL SET RESOLUTION = 600
@PJL SET BITSPERPIXEL = 2
@PJL SET ECONOMODE = OFF
@PJL SET HOLDKEY = "0000"
@PJL ENTER LANGUAGE = POSTSCRIPT
%!PS-Adobe-3.0
...
%%Pages: 1
%%EOF
^[%-12345X@PJL EOJ

Dieser Treiber sendet PCL-Steuercodes mit, mit denen die Filter nicht umgehen können. Sie erkennen statt Postscript Text. Falls Sie diesen Treiber verwenden, filtern Sie in der smb.conf folgendermaßen:
print command = (grep -v "^@PJL" %s|grep -v "%-12345X"|/usr/bin/lpr -P%p;rm -f %s)

Drucken per CUPS (Folie)(Top)

Wie bereits im Vortrag erwähnt, genügen die auf der Folie beschriebenen Anpassungen der smb.conf. Ein Hinweis wäre noch zu präzisieren (Danke an Matthias Ehrig vom URZ der TU Chemnitz für den Hinweis). Es genügt nicht, den Printcap-Eintrag in der cupsd.conf zu ändern, um eine bestehende /etc/printcap zu retten. CUPS löscht sie auch dann beim ersten Start, weil das hart codiert ist. Achten Sie also auf ein Backup der /etc/printcap, bevor Sie CUPS installieren.

Drucken per LPRng (Folie)(Top)

Filter (Folie)(Top)

Eine Standardkonfiguration der /etc/printcap könnte folgendermaßen aussehen. Wir definieren hier zwei Druckerqueues, die ljet4 für einen HP LaserJet und die stylus für einen Epson Stylus Color. Als Aliase wurden die ausführlichen Beschreibungen verwendet. Sie erscheinen an der Windows Konsole bei "net view". Beide Spoolqueues haben folgende Eigenschaften:
 
Spooldirectory (:sd)  /var/spool/lpd/queuename
Filter (:filter) /usr/libexec/filters/lpdomatic (Download von www.linuxprinting.org, zusammen mit foomatic-gswrapper in /usr/bin)
lpdomatic-Optionen(:filter_options) übernimmt die Z-Options (%Z) von lpr und nutzt die entsprechenden Description-Files /usr/libexec/filters/*.lpdomatic, die ebenfalls von www.linuxprinting.org bezogen wurden
Druckeranschluß (:lp) LPT1

ljet4|HP LaserJet 4000
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :filter_options=--lprng %Z /usr/libexec/filters/ljet4.lpdomatic
        :lp=/dev/lp0

stylus|Epson Stylus Color
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :filter_options=--lprng %Z /usr/libexec/filters/stcolor.lpdomatic
        :lp=/dev/lp0

Die smb.conf muß analog der CUPS-smb.conf angepaßt werden mit dem Unterschied, daß jetzt
    printing = lprng
anzugeben ist.

Priorisieren von Druckaufträgen (Folie)(Top)

Eine Priorisierung von Druckaufträgen kann man durch die lpr-Option -C angeben. Der hinter dem C anzugebende Großbuchstabe bestimmt die Priorität dieses Druckauftrages von A (geringste) bis Z (höchste).  Da lpr in der smb.conf aufgerufen wird, müßte auch dort die C-Option gesetzt werden. Zur Demonstration sei hier einmal die Regelung angenommen, daß sich die Priorität umgekehrt proportional zur Größe verhält, große Aufträge also immer gegenüber kleineren zurückgestellt werden. Der Einfachheit halber wird für die Größenbestimmung nur die Stellenzahl, nicht die Größe selbst verwendet und folgendes print command in der smb.conf eingetragen:

print command = (grep -v "^@PJL" %s|grep -v "%-12345X"|/usr/bin/lpr -P%p -C`echo \`ls -l %s|awk '{print($5)}'\`|awk '{printf "%c", 90-length($1)}'`; rm -f %s)

Neben dem obligatorischen Ausfiltern der Steuerzeichen sieht man hier auch das Setzen der C-Option mittels `echo \`ls -l %s|awk '{print($5)}'\`|awk '{printf "%c", 90-length($1)}'`. Die Priorität wird hier in folgenden Schritten ermittelt:
 
ls -l %s ls-Listing des Spoolfiles mit Größenangabe
awk '{print($5)}' selektieren der Größenangabe, Rückgabe an echo
awk '{printf "%c", 90-length($1)}' Entgegennahme der Filegröße von Stdin, Subtrahieren der Länge der Filegröße (=Stellenanzahl) von 90 (ASCII-Wert für Z), Ausgabe des Zeichens, dessen ASCII-Wert der ermittelten Differenz entspricht 

In der Praxis sähe eine derart definierte Queue beispielsweise so aus:

Printer: ljet4@o999h601 'HP LaserJet 4000' (printing disabled)
 Queue: 3 printable jobs
 Server: no server active
 Rank   Owner/ID                  Class Job Files                 Size Time
1      lp@o999h601+826              S   826 (STDIN)            1599550 22:16:01
2      lp@o999h601+870              S   870 (STDIN)           7311206 22:18:49
3      lp@o999h601+853              R   853 (STDIN)          18873432 22:18:30

Obwohl Job 870 später angenommen wurde als Job 853, steht er an Stelle 2 in der Queue. Seine Größe ist geringer als die von Job 853, deshalb hat er eine andere Class und damit eine höhere Priorität zugewiesen bekommen als 853.

Lastverteilung (Folie)(Top)

Load-Balance-Queues werden implementiert, indem der ursprünglichen Queue sogenannte Subserver, auch Load-Balancing-Server, zugeordnet werden. Ein typischer Anwendungsfall wäre neben dem auf der Folie angegebenen die Einrichtung von Druckerpools, d.h. die automatische Verteilung von Druckaufträgen zwischen mehreren gleichartigen Druckern. Eine Load-Balance-Variante für unsere Queue ljet4 könnte so aussehen:

ljet4|HP LaserJet 4000
        :sd=/var/spool/lpd/%P
        :sv=ljet4_lb1,ljet4_lb2
ljet4_lb1
        :ss=ljet4
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :filter_options=--lprng %Z /usr/libexec/filters/ljet4.lpdomatic
        :lp=/dev/lp0
ljet4_lb2
        :ss=ljet4
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :filter_options=--lprng %Z /usr/libexec/filters/ljet4.lpdomatic
        :lp=/dev/lp0

Zu beachten sind der Verweis LB-Queue auf LB-Server (:sv=) sowie die jeweiligen Rückverweise (:ss=). Load-Balancing stellt sich in der Praxis folgendermaßen dar:

Printer: ljet4@o999h601 'HP LaserJet 4000' (subservers ljet4_lb1, ljet4_lb2)
 Queue: 5 printable jobs
 Server: pid 9087 active
 Status: waiting for server queue process to exit at 22:34:56.226
 Rank   Owner/ID                  Class Job Files                 Size Time
1      lp@o999h601+132              S   132 (STDIN)            1599550 22:34:31
2      lp@o999h601+149              S   149 (STDIN)            1599550 22:34:36
3      lp@o999h601+168              S   168 (STDIN)            1599550 22:34:42
4      lp@o999h601+186              S   186 (STDIN)            1599550 22:34:48
5      lp@o999h601+206              S   206 (STDIN)            1599550 22:34:56
Server Printer: ljet4_lb1@o999h601 (serving ljet4)
 Queue: 1 printable job
 Server: pid 9088 active
 Unspooler: pid 9090 active
 Status: IF filter 'lpdomatic' filter msg - 'Loading NimbusSanL-Bold font from
 Rank   Owner/ID                  Class Job Files                 Size Time
active lp@o999h601+80               S    80 (STDIN)            1599550 22:34:18
Server Printer: ljet4_lb2@o999h601 (serving ljet4)
 Queue: 1 printable job
 Server: pid 9114 active
 Unspooler: pid 9115 active
 Status: IF filter 'lpdomatic' filter msg - 'Loading NimbusSanL-Bold font from
 Rank   Owner/ID                  Class Job Files                 Size Time
active lp@o999h601+104              S   104 (STDIN)            1599550 22:34:24

Am obigen Snapshot der Druckerqueue wird die Verfahrensweise recht gut deutlich. Die LB-Queue ljet4 nimmt die Druckaufträge entgegen und puffert sie, jede der beiden LB-Server-Queues bearbeitet je einen Auftrag mit lpdomatic. Beobachten Sie derartige Konfigurationen von Zeit zu Zeit. In unserem Beispiel sind die beiden LB-Server klare Flaschenhälse, denn in der LB-Queue stauen sich Druckaufträge an. Mögliche Auswege wären das Hinzufügen weiterer LB-Server (wenn genügend Systemresourcen für die Druckaufbereitung mit lpdomatic zur Verfügung stehen, denn die lpdomatic-Instanzen aller LB-Server laufen parallel) oder das Verlagern des Filters auf die LB-Queue ljet4 (was allerdings in einigen Fällen den Sinn des Load-Balancing in Frage stellen dürfte).

Chooser (Folie)(Top)

Ein sogenanntes chooser-Programm kann die zyklische Zuweisung von Druckaufträgen beeinflussen, indem es der LB-Queue explizit den zu benutzenden LB-Server mitteilt. In einem einfachen Beispiel wollen wir den Chooser so implementieren, daß er jeden Druckauftrag auf den LB-Server ljet4_lb1 leitet. So eine Konfiguration könnte als schneller Workaround denkbar sein, wenn die Spoolplatte oder der von ljet4_lb2 angesprochene Drucker ausgefallen ist. Die /etc/printcap ist dazu wie folgt abzuändern:

ljet4|HP LaserJet 4000
        :sd=/var/spool/lpd/%P
        :sv=ljet4_lb1,ljet4_lb2
        :chooser=(echo ljet4_lb1)
ljet4_lb1
        :ss=ljet4
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :filter_options=--lprng %Z /usr/libexec/filters/ljet4.lpdomatic
        :lp=/dev/lp0
ljet4_lb2
        :ss=ljet4
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :filter_options=--lprng %Z /usr/libexec/filters/ljet4.lpdomatic
        :lp=/dev/lp0

Schauen wir uns jetzt die Queues an, sehen wir, daß der LB-Server ljet4_lb2 keine Aufträge zugewiesen bekommt, obwohl er frei wäre. Die Aufträge stauen sich in ljet4 und werden ausschließlich von ljet4_lb1 abgearbeitet.

Printer: ljet4@o999h601 'HP LaserJet 4000' (subservers ljet4_lb1, ljet4_lb2)
 Queue: 4 printable jobs
 Server: pid 9529 active
 Status: waiting for server queue process to exit at 22:54:28.547
 Rank   Owner/ID                  Class Job Files                 Size Time
1      lp@o999h601+549              S   549 (STDIN)            1599550 22:54:09
2      lp@o999h601+571              S   571 (STDIN)            1599550 22:54:17
3      lp@o999h601+594              S   594 (STDIN)            1599550 22:54:23
4      lp@o999h601+619              S   619 (STDIN)            1599550 22:54:28
Server Printer: ljet4_lb1@o999h601 (serving ljet4)
 Queue: 1 printable job
 Server: pid 9533 active
 Unspooler: pid 9534 active
 Status: IF filter 'lpdomatic' filter msg - 'Loading NimbusSanL-Bold font from
 Rank   Owner/ID                  Class Job Files                 Size Time
active lp@o999h601+522              S   522 (STDIN)            1599550 22:54:02
Server Printer: ljet4_lb2@o999h601 (serving ljet4)
 Queue: no printable jobs in queue
 Server: no server active
 Status: job '<NULL>' attempt 1, trying 3 times at 22:53:46.207
 Rank   Owner/ID                  Class Job Files                 Size Time
 

Routing (Folie)(Top)

Routing findet bereits beim Einreihen des Druckauftrages in die Queue statt. Deshalb ist es hier noch möglich, Joboptionen zu verändern. Im Gegensatz zur Lastverteilung werden die Druckaufträge auch nicht in der Eingangsqueue gepuffert, sondern sofort mit den neuen Optionen an die Subserver weitergegeben. Unser Beispiel definiert ein Routing, das (wozu auch immer) jeden eingehenden Job in zwei Kopien auf den ersten und zusätzlich eine Kopie mit veränderter Priorität auf den zweiten Subserver sendet. Dazu müssen wir die /etc/printcap folgendermaßen gestalten:

ljet4|HP LaserJet 4000
        :sd=/var/spool/lpd/%P
        :sv=ljet4_lb1,ljet4_lb2
        :router=/usr/libexec/filters/myrouter
ljet4_lb1
        :ss=ljet4
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :filter_options=--lprng %Z /usr/libexec/filters/ljet4.lpdomatic
        :lp=/dev/lp0
ljet4_lb2
        :ss=ljet4
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :filter_options=--lprng %Z /usr/libexec/filters/ljet4.lpdomatic
        :lp=/dev/lp0

Das Routerprogramm myrouter hat für die Realisierung der gestellten Aufgabe folgenden Inhalt:

#!/bin/sh

/bin/cat <<EOF
dest ljet4_lb1    # auf den ersten Subserver ...
copies 2          # ... zwei Kopien
end
dest ljet4_lb2    # auf den zweiten Subserver ...
priority Z        # ... eine Kopie mit höchster Priorität
end
EOF
exit 0

Ein Schnappschuß der Printqueues zeigt folgenden Inhalt:

Printer: ljet4@o999h601 'HP LaserJet 4000' (subservers ljet4_lb1, ljet4_lb2)
 Queue: 1 printable job
 Server: pid 10059 active
 Unspooler: pid 10102 active
 Status: sending data file 'dfA087o999h601.spk-chemnitz' to ljet4_lb1@localhost at 23:03:09.685
 Rank   Owner/ID                  Class Job Files                 Size Time
active lp                           S    86 (STDIN)            1599549 23:03:08
-          lp@o999h601+86.1               ->ljet4_lb1 <cpy 1/2>
-          lp@o999h601+86.2               ->ljet4_lb2

ljet4 gibt jeden hereinkommenden Auftrag sofort weiter an die Subserver, hier gerade die erste der zwei Kopien des Auftrags 86 an ljet4_lb1 und eine Kopie an ljet4_lb2. Wir sehen hier keine Staus.

Server Printer: ljet4_lb1@o999h601 (serving ljet4)
 Queue: 3 printable jobs
 Server: pid 10063 active
 Unspooler: pid 10064 active
 Status: IF filter 'lpdomatic' filter msg - 'This software comes with NO WARRANT
 Rank   Owner/ID                  Class Job Files                 Size Time
active =lp@o999h601+41.1            S    41 (STDIN)            1599549 23:03:02
2      =lp@o999h601+41.1            S    42 (STDIN)            1599549 23:03:02
3      =lp@o999h601+86.1            S    86 (STDIN)            1599549 23:03:09
incoming =lp@o999h601+86.1          S    87 (STDIN)                  0 23:03:09

Der Subserver ljet4_lb1 hat von jedem Druckauftrag zwei Kopien in der Queue.

Server Printer: ljet4_lb2@o999h601 (serving ljet4)
 Queue: 1 printable job
 Server: pid 10061 active
 Unspooler: pid 10065 active
 Status: IF filter 'lpdomatic' filter msg - 'foomatic-gswrapper: gs '-dBATCH' '-
 Rank   Owner/ID                  Class Job Files                 Size Time
active =lp@o999h601+41.2            Z    43 (STDIN)            1599549 23:03:02
2      =lp@o999h601+86.2           Z    88 (STDIN)            1599549 23:03:11

Im Subserver ljet4_lb2 finden wir eine weitereKopie des Auftrags 86, die Priorität wurde korrekt von S auf Z geändert.
 

printcap-Includes (Folie)(Top)

In großen Druckumgebungen würde die /etc/printcap schnell unübersichtlich und schlecht wartbar. Das Konzept der Includes erlaubt es, gemeinsame Optionen einmalig aufzuschreiben und dann in die betreffenden Queue-Beschreibungen zu integrieren. Schauen wir uns z.B. die /etc/printcap aus dem "Filter"-Abschnitt an, erkennen wir, daß sich die Einträge eigentlich nur in den :filter_options unterscheiden. Wir könnten daher die angesprochene printcap folgendermaßen abändern:

.common
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :lp=/dev/lp0

ljet4|HP LaserJet 4000
        :tc=.common
        :filter_options=--lprng %Z /usr/libexec/filters/ljet4.lpdomatic

stylus|Epson Stylus Color
        :tc=.common
        :filter_options=--lprng %Z /usr/libexec/filters/stcolor.lpdomatic

Könnten wir uns noch dazu entscheiden, Queuename und Name des Descriptionfiles homogen zu wählen, wäre sogar folgende Vereinfachung denkbar:

.common
        :sd=/var/spool/lpd/%P
        :filter=/usr/libexec/filters/lpdomatic
        :filter_options=--lprng %Z /usr/libexec/filters/%P.lpdomatic
        :lp=/dev/lp0

ljet4|HP LaserJet 4000
        :tc=.common

stylus|Epson Stylus Color
        :tc=.common
 

Druckbenachrichtigung (Folie)(Top)

Die Druckbenachrichtigung muß zurück an Windows-PCs gesendet werden, wofür wir natürlich SMB-Informationen benötigen. Da uns diese nur über Variablen der smb.conf zur Verfügung stehen, müssen wir sie dort abgreifen und für spätere Verwendung abspeichern. Wir ändern deshalb dort das print command zu

print command = (/usr/libexec/filters/mylpr %p %s %U %m %L)

und übergeben dem Skript mylpr neben den Druckinformationen (%p - Name der angesprochenen Druckerfreigabe, %s - Name des Spoolfiles) zum Zurücksenden benötigte SMB-Informationen (%U - Windows-Username des abschickenden Nutzers, %m - NetBIOS-Name des absendenden Hosts, %L - NetBIOS-Name des Printserves). Diese Informationen werden von mylpr geeignet abgespeichert und erst dann wird der eigentliche Druckvorgang initiiert. mylpr hat folgenden Aufbau:

#!/bin/sh

# Die SMB-Informationen werden entgegengenommen ...
WUSER=$3
NBCLIENT=$4
NBHOST=$5
# ... und in einem File mit eindeutigem Namen abgelegt.
# Wir benutzen zur Bildung des Filenamens den Namen des Spoolfiles, da der später im Printfilter
# wieder ermittelbar ist. Der Printfilter läuft nicht als Subshell von mylpr, deshalb können keine
# Informationen in Variablen übergeben werden.
# Beim Abspeichern wird gleich ein Format verwendet, das beim Wiedereinlesen Variablen belegt
# (VARIABLE=$WERT...)

echo -e "WUSER=$WUSER\nNBCLIENT=$NBCLIENT\nNBHOST=$NBHOST" >/tmp/$2.smbinfo

# Jetzt erst wird wirklich gedruckt
grep -v "^@PJL" $2|grep -v "%-12345X"|/usr/bin/lpr -P%p; rm -f %s
exit 0

Die /etc/printcap nutzt jetzt auch ein anderes Filter-Skript, da dieses letztendlich die Nachricht generieren und versenden soll. Da wir eine möglichst informative Nachricht erzeugen wollen, geben wir auch noch einige Zusatzinformationen in der /etc/printcap an. Theoretisch könnte man diese natürlich auch irgendwo anders im Filesystem hinterlegen und beim Erzeugen der Nachricht auslesen.

ljet4|HP LaserJet 4000
        :sd=/var/spool/lpd/%P
        # unser eigener "Filter"heißt myfilter ...
        :filter=/usr/libexec/filters/myfilter
        # ... und erhält die Optionen Queuename, Jobgröße, lpr-Spoolfile, Datum/Zeit, SMB-Spoolfile
        :filter_options=$P $b $e $j $t $f
        :lp=/dev/null
        # das sind selbst erfundene Optionen:
        # welches Descriptionfile benötigt der Drucker für sein Zielformat
        :desc_name=ljet4.lpdomatic
        # wie heißt der Drucker, wo steht er
        :bezeichnung=HP LaserJet 4000, NSG, Raum 111
        # ist es ein postscriptfähiger Drucker (dann sparen wir uns nämlich die Wandlung)
        :ps_drucker=nein

Das Filterskript myfilter nimmt nun diese Parameter und auf Stdin den Inhalt des Druckauftrages entgegen, wandelt ihn ggf. ins Zielformat und gibt ihn auf Stdout zurück, so wie es jeder "normale" Filter auch tut. Zusätzlich wird noch eine Nachricht generiert und zum Windows-Nutzer geschickt. Wie das funkitoniert, schauen wir uns jetzt an. Hier ist myfilter:

#!/bin/bash

# Zuerst übernehmen wir die Job-Infos, die lpr übergibt
# Achtung, die Optionen werden zusammen mit einem Anstrich und ihrem Optionsnamen (1 Buchstabe)
# geliefert, deshalb interessiert uns der Inhalt erst ab der dritten Stelle
QUEUE=`echo "$1"|tr -d \"|cut -b3-`
JOBGROESSE=`expr \`echo "$2"|tr -d \"|cut -b3-\` / 1024`
SPOOLFILE=`echo "$3"|tr -d \"|cut -b3-`
JOBNR=`echo "$4"|tr -d \"|cut -b3-`
DRUCKDATUM=`echo "$5"|tr -d \"|cut -b3-`
SMBINFO_FILE=/tmp/`echo "$7"|tr -d \"|cut -b3-`.smbinfo

# Weitere Job-Infos, die wir frei eingetragen haben, lesen wir direkt aus der
# Queue-Beschreibung der /etc/printcap. Im Filter wird uns dazu eine Variable PRINTCAP_ENTRY
# bereitgestellt (es gibt übrigens auch noch die sehr interessante Variable CONTROL, die
# den Inhalt des Jobcontrolfiles liefert). Alternativ hätten wir unsere Optionen auch in den
# :filter_options als ${desc_name}, ${bezeichnung} und ${ps_drucker} mitgeben können

DESC_NAME=`echo $PRINTCAP_ENTRY|sed -e "s/.*:desc_name=//g"|sed -e "s/:.*//g"|tr -d "[:blank:]"`
BEZEICHNUNG=`echo $PRINTCAP_ENTRY|sed -e "s/.*:bezeichnung=//g"|sed -e "s/:.*//g"|tr -d "[:blank:]"`
PS_DRUCKER=`echo $PRINTCAP_ENTRY|sed -e "s/.*:postscript=//g"|sed -e "s/:.*//g"|tr -d "[:blank:]"`

# Dann lesen wir noch die von mylpr abgespeicherten SMB-Informationen ein und belegen damit die
# Variablen WUSER, NBCLIENT und NBHOST
eval `cat $SMBINFO_FILE`; rm $SMBINFO_FILE

# Jetzt ermitteln wir noch, wo genau (mit Pfad) das auszudruckende Spoolfile liegt
SPOOLFILE_VOLL=${SPOOL_DIR}/${SPOOLFILE}

# Womit wir an die Stelle kämen, an der nun die eigentliche Formatwandlung erfolgen soll.
# Vorher prüfen wir aber noch zwei Dinge:
# 1. Wollen wir auf einen postscriptfähigen Drucker drucken? Falls ja, sparen wir uns die Umwandlung
#     und schicken den Auftrag per /bin/cat - einfach weiter
# 2. Bekommen wir da überhaupt ein Postscriptfile angeboten (grep -i '!PS-Adobe',
#     head -20|grep -i '!PS-Adobe' müßte auch genügen)? Falls nicht, gehen wir davon aus, daß uns etwas
#     von einem für den Zieldrucker bestimmten Treiber (PCL,ESC/P2 etc.) geschickt wurde. Auch das
#     senden wir einfach unberührt weiter
# Nur wenn wir Postscript bekommen und unser Zieldrucker das nicht verstehen würde, wandeln wir durch
# Benutzung von lpdomatic um.

if [ "${PS_DRUCKER}" = "nein" ] && grep -i '!PS-Adobe' $SPOOLFILE_VOLL >/dev/null 2>&1; then
        /bin/cat -|/usr/libexec/filters/lpdomatic --lprng usr/libexec/filters/${DESC_NAME} 2>/dev/null
else
        /bin/cat -
fi

# Zum Schluß bauen wir noch eine Netzwerknachricht für den Windows-Nutzer zusammen.
# Dazu speichern wir die Nachricht in einer Variable (Variablen kennen keine Sonderzeichen, deshalb
# verwenden wir hier alternativ die Tags <newline> und <tab>)
NACHRICHT="
`echo -n "Druckauftrag entgegengenommen am ${DRUCKDATUM}<newline>"`\
`echo "Druckjobinformationen:<newline>"`\
`echo "<tab>abgeschickt von<tab>: ${WUSER}@${NBCLIENT}<newline>"`\
`echo "<tab>gedruckt auf<tab>: ${BEZEICHNUNG}<newline>"`\
`echo "<tab>Warteschlange<tab>: //${NBHOST}/${QUEUE}<newline>"`\
`echo "<tab>Druckjobgroesse<tab>: ${JOBGROESSE} KByte<newline><newline>"`\
`fortune|awk '{print gensub("\t","<tab>","g",gensub("$","<newline>","g"))}'|tr -d '\012'`\
`echo "<newline><tab>... powered by:"`\
`echo "<newline><tab><tab>- "``cat /etc/redhat-release|sed -e "s/(.*)//g"``echo " Kernel "``uname -r``echo " ("``uptime|awk '{print $2" "$3 gensub(","," ","g",$4)" "gensub(",","min","g",gensub(":","h ","g",$5))")"}'`\
`echo "<newline><tab><tab>- Samba "``/usr/sbin/smbd -V`\
`echo "<newline><tab><tab>- "``gs -v|head -1`\
`echo "<newline><tab><tab>- "``lpstat -V|cut -d, -f1`\
`echo "<newline><tab><tab>- Fortune 1.0-13"`\
"

# Die Nachricht ist fertig, wir senden sie also jetzt per smbclient an den Windows-Nutzer.
# Beim Senden wandeln wir nur noch die Tags <newline> und <tab> wieder in die korrekten
# Escape-Sequenzen \n und \t zurück.
echo "$NACHRICHT"|awk '{print gensub("<tab>","\t","g",gensub("<newline>","\n","g"))}'|smbclient -M ${NBCLIENT} -U ${NBHOST} >/dev/null

exit 0

Zur Verdeutlichung des Unterschiedes zwischen einer originalen Windows-Drucknachricht und unserer selbst erzeugten seien Beispiele beider Varianten im Folgenden gezeigt.

Diese Meldung schickt ein Windows-NT-Printserver. Urteilen Sie bitte selbst, wie wichtig Ihnen die darin enthaltenen Informationen sind. Mir persönlich erschienen sie bisher nie so richtig hilfreich. Sollten Sie sie auch nicht mögen, überlegen Sie kurz welche Alternativen Sie hätten - richtig, den Nachrichtendienst auf Ihrem PC beenden.

Zum Vergleich die Rückmeldung des Linuxprintservers mit dem im Kapitel Druckbenachrichtigung konfigurierten Inhalt:

Neben der Tatsache, daß hier wohl unzweifelhaft wichtigere Informationen dargestellt werden, sollten Sie beachten, daß die gesamte Meldung frei konfigurierbar war (der geübte Linuxnutzer wird sogar ein fortune-Cookie entdecken) und im Rahmen von maximal 1.600 Zeichen beliebig gestaltet werden kann. Im Gegensatz zur Windows-Variante hätten Sie ohne viel Mühe zusätzlich die Möglichkeit, einzeln zu entscheiden, welcher Absender eine Nachricht bekommt. Die Kollegen, die häufig drucken, werden es Ihnen vielleicht danken - aber bestimmt nicht, bevor sie alle fortunes auswendig kennen ;-).