SDB:Using Your Own Backends to Print with CUPS

Jump to: navigation, search


Tested on openSUSE Recommended articles Related articles
Icon-checked.png

Icon-manual.png Icon-help.png

Situation

You want to use a custom backend to print with CUPS.

Requirements:

You should have a basic knowledge of the printing system and be well acquainted with CUPS (see SDB:CUPS in a Nutshell), bash scripts, and common command-line tools.

CUPS Backends Background Information

Usually, the backend gets printer-specific data as input and sends it to the printer device. See the section "The Backends" in SDB:CUPS in a Nutshell.

The backend gets its input data either via stdin or it must read its input data from a file when the backend was called with a filename argument as argv[6].

The backend must not send its output data via stdout. Instead the backend must implement whatever is needed to send it directly to the printer device or to whatever other recipient up to any external destination (compare the section about "Allow printer admin tasks for a normal user" in SDB:CUPS in a Nutshell).

When the backend is called without an argument, it must output on stdout "device discovery" information. For example when a backend /usr/lib/cups/backend/mybackend is called without arguments it would have to output at least:

direct mybackend "Unknown" "no device info"

Usually the second field (here only "mybackend") is a specific device URI, see "The Backends" at SDB:CUPS in a Nutshell.

When the backend is called as usual by the cupsd, the normal stderr output of the backend is forwarded to /var/log/cups/error_log if there is "LogLevel debug" set in /etc/cups/cupsd.conf (see the section how to "Get CUPS debug messages if it does not work" in SDB:CUPS in a Nutshell). The stderr output of the backend can be prefixed e.g. with "INFO:" or "ERROR:" to distinguish how it is logged.

See the man pages "man 7 backend" for what is specific for CUPS backends and "man 7 filter" for general information regarding CUPS filters (backends are a special type of filters for CUPS).

Custom Backend Examples

The CUPS default backends are usually the best choice to send printing data to its recipient.

In particular when the data is sent via network the CUPS backends provide some possibilities for tuning how the data is sent to meet usual requirements. You may have a look at "Network Protocols Supported by CUPS" on http://www.cups.org/documentation.php/doc-1.4/network.html

However, custom backends might be needed in some exceptional cases.

The following examples implement the same basic functionality as the CUPS backends "usb" and "socket" to show the very basic ideas how to implement a backend for CUPS.

A careless backend for a single USB printer

The following bash script sends data to /dev/usb/lp0 which is the device file for the first USB printer regardless which USB printer model might be actually connected there.

Such a careless backend could be useful when only one USB printer will be used and a print queue should be set up but the printer is not connected when the print queue is set up. For example an admin may have to set up a print queue on a workstation for a printer which is not available at that time. In this case the CUPS backend "usb" cannot detect the printer so that it cannot report the right device URI as "device discovery" information (see above). But without the right device URI it is not possible to set up a queue with the CUPS backend "usb". In this case a careless backend could help because its device URI is always the same "usblp0:/dev/usb/lp0" regardless which USB printer might become connected later. The drawback is that the careless backend sends any data to any connected USB printer regardless if the data matches the printer model which may cause that tons of sheets get printed with nonsense.

See SDB:Installing a Printer how you can test if the first USB printer is accessible via /dev/usb/lp0.

 #! /bin/bash
 # Have debug info in /var/log/cups/error_log:
 set -x
 # Output "device discovery" information on stdout:
 if test "$#" = "0"
 then echo 'direct usblp0:/dev/usb/lp0 "Unknown" "any USB printer"'
      exit 0
 fi
 # Have the input at fd0 (stdin) in any case:
 if test -n "$6"
 then exec <"$6"
 fi
 # Infinite retries to access the device file:
 until cat /dev/null >/dev/usb/lp0
 do echo 'INFO: cannot access /dev/usb/lp0 - retry in 30 seconds' 1>&2
    sleep 30
 done
 echo 'INFO: sending data to /dev/usb/lp0' 1>&2
 # Forward the data from stdin to the device file:
 if cat - >/dev/usb/lp0
 then echo 'INFO:' 1>&2
      exit 0
 else echo 'ERROR: failed to send data to /dev/usb/lp0' 1>&2
      exit 1
 fi
 

Install this script as /usr/lib/cups/backend/usblp0 with the same owner, group and permissions as the other backends (usually "rwxr-xr-x root root").

Run as root "lpinfo -v" and verify that it reports "direct usblp0:/dev/usb/lp0", otherwise the device URI "usblp0:/dev/usb/lp0" of the new backend could not be used to set up a print queue.

See the section "How to set up a print queue in full compliance with CUPS" in SDB:CUPS in a Nutshell.

A hardcoded backend for a network printer

The following bash script sends data to the hardcoded IP address 192.168.1.2 to TCP port 9100 which is the usual port for network printers which support data transfer via a plain TCP socket, see SDB:Printing via TCP/IP network.

 #! /bin/bash
 # Have debug info in /var/log/cups/error_log:
 set -x
 # Output "device discovery" information on stdout:
 if test "$#" = "0"
 then echo 'network mysocket://192.168.1.2:9100 "Unknown" "192.168.1.2:9100"'
      exit 0
 fi
 # Set INPUTFILE to where the input comes from:
 INPUTFILE="-"
 if test -n "$6"
 then INPUTFILE="$6"
 fi
 # 5 retries with 60 seconds delay to access the remote port:
 for I in first second third fourth last
 do if netcat -z 192.168.1.2 9100
    then break
    fi   
    echo "INFO: 192.168.1.2:9100 busy - $I of 5 retries" 1>&2
    sleep 60
 done
 sleep 1
 if netcat -z 192.168.1.2 9100
 then echo 'INFO: sending data to 192.168.1.2:9100' 1>&2
      sleep 1
 else echo 'ERROR: failed to access 192.168.1.2:9100' 1>&2
      exit 1
 fi
 # Send the data to the remote port:
 if cat $INPUTFILE | netcat -w 1 192.168.1.2 9100
 then echo 'INFO:' 1>&2
      exit 0
 else echo 'ERROR: failed to send data to 192.168.1.2:9100' 1>&2
      exit 1
 fi
 

Adapt "192.168.1.2" to the IP address of yout network printer. Additionally you may have to adapt the port "9100" according to what your network printer uses. You may have a look at "Common Network Printer URIs" on http://www.cups.org/documentation.php/doc-1.4/network.html

Install this script as /usr/lib/cups/backend/mysocket with the same owner, group and permissions as the other backends (usually "rwxr-xr-x root root").

Run as root "lpinfo -v" and verify that it reports "network mysocket://192.168.1.2:9100", otherwise the device URI "mysocket://192.168.1.2:9100" of the new backend could not be used to set up a print queue.

See the section "How to set up a print queue in full compliance with CUPS" in SDB:CUPS in a Nutshell.

You may wonder why there are "sleep" delays between consecutive netcat calls. This avoids failures in the backend when the receiver in the printer's network interface is too slow to accept consecutive connections.

You may try to speed it up by using the following optimistic backend for a network printer which does nothing else than to send the data:

 #! /bin/bash
 # Have debug info in /var/log/cups/error_log:
 set -x
 # Output "device discovery" information on stdout:
 if test "$#" = "0"
 then echo 'network mysocket://192.168.1.2:9100 "Unknown" "192.168.1.2:9100"'
      exit 0
 fi
 # Set INPUTFILE to where the input comes from:
 INPUTFILE="-"
 if test -n "$6"
 then INPUTFILE="$6"
 fi
 # Send the data to the remote port:
 echo 'INFO: sending data to 192.168.1.2:9100' 1>&2
 if cat $INPUTFILE | netcat -w 1 192.168.1.2 9100
 then echo 'INFO:' 1>&2
      exit 0
 else echo 'ERROR: failed to send data to 192.168.1.2:9100' 1>&2
      exit 1
 fi
 

Probably under higher load when several print jobs have to be sent continuously to the printer i.e. when speed does actually matter, then such kind of optimistic backend may fail depending on whether or not the printer device accepts consecutive connections. If it fails in this case you need "sleep" delays to get it sufficiently fail-safe so that there is a dilemma between speed and reliability - at least for such simple custom-made backends cf. RFC 1925 item (7a): "Good, Fast, Cheap: Pick any two (you can't have all three)."

A backend that sends its input into a file for debugging

For debugging so that one can see what a backend would send to its destination a selfmade backend /usr/lib/cups/backend/tofile can be used that sends its input into a file (e.g. temporarily instead of the originally used backend to debug issues of a particular print queue) for example as follows:

 #! /bin/bash
 # Have debug info in /var/log/cups/error_log:
 set -x
 # Output "device discovery" information on stdout:
 if test "$#" = "0"
 then echo 'direct tofile:/tmp/tofile.out "Unknown" "/tmp/tofile.out"'
      exit 0
 fi
 # Have the input at fd0 (stdin) in any case:
 test -n "$6" && exec <"$6"
 # Infinite retries to access the file:
 until cat /dev/null >/tmp/tofile.out
 do echo 'INFO: cannot access /tmp/tofile.out - retry in 30 seconds' 1>&2
    sleep 30
 done
 echo 'INFO: sending data to /tmp/tofile.out' 1>&2
 # Forward the data from stdin to the file:
 if cat - >/tmp/tofile.out
 then echo 'INFO:' 1>&2
      exit 0
 else echo 'ERROR: failed to send data to /tmp/tofile.out' 1>&2
      exit 1
 fi
 

In contrast to enabling "FileDevice Yes" with a DeviceURI like file:///tmp/output.prn that does not work for 'raw' queues (cf. https://github.com/apple/cups/issues/5117) a backend like the above /usr/lib/cups/backend/tofile works in any case and one can add further debugging comands into that backend as needed to debug a specific issue.

Of course "interesting effects" happen when for several queues one same /usr/lib/cups/backend/tofile is used that outputs to one same file /tmp/tofile.out (i.e. different simultaneously printing queues must output to different files) but for testing and debugging purposes such a simple backend should be sufficient.

There is a difference between using a DeviceURI file:///path/to/file and a backend that outputs into a static file /path/to/file. Using a DeviceURI file:///path/to/file requires a filtered queue (i.e. a queue where at least one filtering program is run) because there is no "file" backend. When a DeviceURI file:///path/to/file is used cupsd redirects stdout of the last filter to the file. When a raw queue is used there are no filters so nothing will get written to the file, see https://lists.cups.org/pipermail/cups/2006-February/035598.html that reads

>> Something like "cat test.txt | lp -d fakeprinter" and the out put
>> would be equal to "cat test.txt >> filename"
> 
> If a fixed file name is sufficient and if you don't need to
> care much about security:
> Set up a "raw" queue using the "file" backend and enable
> "FileDevice" in cupsd.conf.

That won't work and is one of the reasons we don't advertise or
support the use of the file pseudo-device in CUPS for anything but
testing or pointing at /dev/null.

Basically, there is no "file" backend, it just maps internally to
point the stdout of the last filter to the file.  When you configure
a raw queue there are no filters so nothing will ever get written to
the file!

Backends to send data to other recipients

The openSUSE RPM package cups-backends provides the bash script /usr/lib/cups/backend/pipe which is a wrapper backend for printing to any program. It forwards the print job data like a pipe into another command. You may have a look at its code for an example that CUPS backends are not restricted to send data only to printer devices.

The RPM package cups-pdf which is available via the openSUSE build service project "Printing" provides /usr/lib/cups/backend/cups-pdf which saves the data as a PDF file, see SDB:Printing to PDF.

See also

Portal:Printing