SDB:Using Your Own Backends to Print with CUPS
情况
你想使用自定义后端与 CUPS 打印。
本文面向有经验的 Linux 用户。
要求
你应该对打印系统有基本的了解,并且熟悉 CUPS(参见 SDB:CUPS in a Nutshell)、bash 脚本和常用的命令行工具。
此处的一些细节可能略有陈旧(具体取决于 CUPS 版本),但总体而言,这些内容仍然有效。
本文档描述了如何在 Linux 下使用自己的后端与 CUPS 打印,最高版本为 2.x,使用传统的过滤系统和那里的后端。
如今的无驱动程序打印工作流程则大不相同。
CUPS 后端背景信息
通常,后端将特定于打印机的数据作为输入接收,并将其发送到打印机设备。请参阅 SDB:CUPS in a Nutshell 中的“后端”部分。
后端通过 stdin 接收其输入数据,或者当后端使用文件名参数作为 argv[6] 调用时,必须从文件读取其输入数据。
后端不得通过 stdout 发送其输出数据。相反,后端必须实现将数据直接发送到打印机设备或任何其他外部目标(比较 SDB:CUPS in a Nutshell 中的“允许普通用户执行打印机管理任务”部分)所需的任何内容。
当后端没有参数被调用时,它必须在 stdout 上输出“设备发现”信息。例如,当后端 /usr/lib/cups/backend/mybackend 没有参数被调用时,它必须至少输出
direct mybackend "Unknown" "no device info"
通常,第二个字段(这里只有“mybackend”)是一个特定的设备 URI,请参阅 SDB:CUPS in a Nutshell 中的“后端”。
当后端像往常一样被 cupsd 调用时,后端的正常 stderr 输出将被转发到 /var/log/cups/error_log,如果 /etc/cups/cupsd.conf 中设置了“LogLevel debug”(请参阅 SDB:CUPS in a Nutshell 中的“如果不起作用,如何获取 CUPS 调试消息”部分)。后端的 stderr 输出可以用例如“INFO:”或“ERROR:”前缀来区分其记录方式。
请参阅手册页“man 7 backend”以获取 CUPS 后端特定的内容,以及“man 7 filter”以获取有关 CUPS 过滤器的常规信息(后端是 CUPS 的一种特殊类型的过滤器)。
自定义后端示例
CUPS 默认后端通常是将打印数据发送到其接收方的最佳选择。
特别是当通过网络发送数据时,CUPS 后端提供了一些可能性来调整数据发送方式,以满足通常的要求,请参阅 https://openprinting.github.io/cups/doc/network.html
但是,在某些特殊情况下,可能需要自定义后端。
以下示例实现了与 CUPS 后端“usb”和“socket”相同的基础功能,以展示如何为 CUPS 实现后端的基本思想。
一个用于单个 USB 打印机的粗心后端
以下 bash 脚本将数据发送到 /dev/usb/lp0,这是第一个 USB 打印机的设备文件,无论实际连接到那里的 USB 打印机型号如何。
这种粗心的后端在只使用一台 USB 打印机并且应该设置打印队列,但打印机在设置打印队列时未连接时可能很有用。例如,管理员可能需要在打印机不可用的情况下,在工作站上设置打印队列。在这种情况下,CUPS 后端“usb”无法检测到打印机,因此无法报告正确的设备 URI 作为“设备发现”信息(参见上文)。但是,如果没有正确的设备 URI,就无法使用 CUPS 后端“usb”设置队列。在这种情况下,粗心的后端可能会有所帮助,因为其设备 URI 始终是“usblp0:/dev/usb/lp0”,无论将来连接哪个 USB 打印机。缺点是,粗心的后端会将任何数据发送到任何连接的 USB 打印机,无论数据是否与打印机型号匹配,这可能会导致大量纸张被打印出无意义的内容。
请参阅 SDB:Installing a Printer,了解如何测试第一个 USB 打印机是否可以通过 /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
将此脚本安装为 /usr/lib/cups/backend/usblp0,并具有与其它后端相同的拥有者、组和权限(通常为“rwxr-xr-x root root”)。
以 root 身份运行“lpinfo -v”,并验证它是否报告“direct usblp0:/dev/usb/lp0”,否则无法使用新后端的设备 URI 设置打印队列。
请参阅 SDB:CUPS in a Nutshell 中的“如何完全符合 CUPS 设置打印队列”部分。
一个用于网络打印机的硬编码后端
以下 bash 脚本将数据发送到硬编码的 IP 地址 192.168.1.2 到 TCP 端口 9100,这是支持通过纯 TCP 套接字传输数据的网络打印机的常用端口,请参阅 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
将“192.168.1.2”调整为你的网络打印机的 IP 地址。此外,你可能需要根据你的网络打印机使用的端口调整端口“9100”。你可以查看 https://openprinting.github.io/cups/doc/network.html
将此脚本安装为 /usr/lib/cups/backend/mysocket,并具有与其它后端相同的拥有者、组和权限(通常为“rwxr-xr-x root root”)。
以 root 身份运行“lpinfo -v”,并验证它是否报告“network mysocket://192.168.1.2:9100”,否则无法使用新后端的设备 URI 设置打印队列。
请参阅 SDB:CUPS in a Nutshell 中的“如何完全符合 CUPS 设置打印队列”部分。
你可能会想知道为什么在连续的 netcat 调用之间有“sleep”延迟。这避免了后端在打印机网络接口中的接收器太慢以接受连续连接时的故障。
你可以尝试通过以下乐观的网络打印机后端来加速它,该后端除了发送数据外什么也不做
#! /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
可能在高负载下,当需要连续向打印机发送多个打印作业时,即速度确实很重要时,这种乐观的后端可能会失败,具体取决于打印机设备是否接受连续连接。如果它在这种情况下失败,你需要“sleep”延迟才能使其足够安全,从而导致速度和可靠性之间的两难境地——至少对于这种简单的自定义后端而言,正如 RFC 1925 项目 (7a) 所述:“好、快、便宜:选择其中两个(你不可能同时拥有全部三个)”。
一个将输入发送到文件以进行调试的后端
为了调试,以便可以看到后端会将其发送到目标的位置,可以使用自制的后端 /usr/lib/cups/backend/tofile,将输入发送到文件(例如,临时代替最初使用的后端来调试特定打印队列的问题)如下所示
#! /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
将此脚本安装为 /usr/lib/cups/backend/tofile,并具有与其它后端相同的拥有者、组和权限(通常为“rwxr-xr-x root root”)。
以 root 身份运行“lpinfo -v”,并验证它是否报告“direct tofile:/tmp/tofile.out”,否则无法使用新后端的设备 URI 设置打印队列。
请参阅 SDB:CUPS in a Nutshell 中的“如何完全符合 CUPS 设置打印队列”部分。
与使用 DeviceURI file:///path/to/file 启用“FileDevice Yes”不同,后者对于“raw”队列不起作用(参见 https://github.com/apple/cups/issues/5117),上述 /usr/lib/cups/backend/tofile 这样的后端在任何情况下都有效,并且可以根据需要将进一步的调试命令添加到该后端中以调试特定问题。
当然,当使用一个相同的 /usr/lib/cups/backend/tofile 输出到同一个文件 /tmp/tofile.out 时,会发生“有趣的效果”(即,不同的同时打印队列必须输出到不同的文件),但对于测试和调试目的,这种简单的后端应该足够了。
使用 DeviceURI file:///path/to/file 和后端输出到静态文件 /path/to/file 之间存在差异。使用 DeviceURI file:///path/to/file 需要过滤队列(即,至少运行一个过滤程序的队列),因为没有“file”后端。当使用 DeviceURI file:///path/to/file 时,cupsd 将最后一个过滤器的 stdout 重定向到该文件。当使用原始队列时,没有过滤器,因此不会将任何内容写入文件,请参阅 https://lists.cups.org/pipermail/cups/2006-February/035598.html,其中指出
>> 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!
一个监视实际后端的后端
请参阅 https://github.com/jsmeix/cups-backends 中的 /usr/lib/cups/backend/monitor
将数据发送到其他接收方的后端
openSUSE RPM 包 cups-backends 提供了 bash 脚本 /usr/lib/cups/backend/pipe,它是一个用于打印到任何程序的包装后端。它像管道一样将打印作业数据转发到另一个命令。你可以查看其代码,了解 CUPS 后端不仅限于将数据发送到打印机设备,请参阅 https://github.com/jsmeix/cups-backends 中的 /usr/lib/cups/backend/pipe。
通过 openSUSE 构建服务项目“Printing”提供的 RPM 包 cups-pdf 提供了 /usr/lib/cups/backend/cups-pdf,它将数据保存为 PDF 文件,请参阅 SDB:Printing to PDF。