简介:CUPS 2.3.3 源码包面向类 Unix 系统的开发者、系统管理员与打印服务学习者,适合在本地部署、定制或研究开源打印系统,涵盖打印作业管理、过滤器驱动、网页管理界面、加密传输与跨平台兼容等核心能力。压缩包共 1294 个文件、约 7.76MB,以 C 与 C++ 源码、头文件、网页文档、测试脚本、模板和构建文件为主,并含配置脚本、手册、打印描述示例、国际化文件及多种后端与过滤器资源,目录覆盖服务端、库、工具链、策略和文档等模块。已有 928 人学习下载。完整源码结构便于读者在本地编译定制,也可据此理解网络打印协议支持,研究自定义过滤器与后端扩展方式,或排查打印队列、驱动适配与配置管理问题,对中高级运维与开发人员查漏补缺、二次开发均有参考价值。
1. 从发行版包换成 cups-2.3.3-source.tar.gz:一次「全都对,就是打不出来」
在一台要接三台网络打印机的机器上,我遇到过很别扭的情况:发行版仓库里装的打印服务能把队列建起来,管理页面也能打开,可一提交任务就卡在Filter failed,日志翻半天只有一行看不出所以然的话。后来我把 cups-2.3.3-source.tar.gz 解开自己编,问题没有立刻消失,但至少每一步都在自己手里:依赖装没装、运行目录权限对不对、过滤器在哪个目录、服务单元是谁写的,全都能翻到。源码装 CUPS 最难的地方在于「编译成功」和「打印成功」是两件事——configure和make install一路绿灯,只证明调度器能起来,不证明你的纸能出来。
2. 编译前清点:工具链、依赖库与 lp 用户组的分配
动手之前先花十分钟确认环境,比编译完再回头补依赖省事得多。CUPS 2.3.3 属于 2.3 分支的收尾版本,代码不算新,对编译器要求不高,但它对「系统里有没有 lp 用户」「/run 下有没有对应目录」这类事情的假设很强,缺一样就在运行时报怪错。
2.1 编译器与构建工具的最低要求
CUPS 2.3.3 的代码大量使用 C99 特性,gcc 4.8 以上、clang 3.4 以上都能编。make 需要 3.8 以上,主要是为了让并行构建和install目标行为正常。另外记得装pkg-config,configure 探测依赖库时靠它拿编译参数,缺了不会直接报错,但会静默判定「某个库不存在」,最后你编出来的东西少一块功能。
# 三行确认构建工具是否能满足 2.3.3 的最低要求 gcc --version | head -n1 # 期望 4.8 以上 make --version | head -n1 # 期望 3.8 以上 pkg-config --version # 期望 0.20 以上如果机器上同时装了多个 gcc,别让 configure 抓到老的那个。我一般显式指定CC=/usr/bin/gcc,并在后面看config.log里实际用的是不是同一个。另外,源码包里是带了configure的发布包,不需要再跑autoreconf或autoconf。很多人习惯性先autoreconf -fi,结果因为本机 autoconf 版本太新,生成的脚本跟源码里的 m4 宏对不上,直接在第一关失败。
提示:编译时不要往
CFLAGS里加-Werror。CUPS 2.3.3 的代码在新版编译器下会产生不少新告警,-Werror会把告警变成错误,编到一半中断。
2.2 依赖库清单:必装、可选、能砍掉
CUPS 的依赖分三层:调度器自己需要的、过滤器需要的、外部功能需要的。先把这张表过一遍再决定装什么,能少装一半东西。
| 依赖 | 对应功能 | 不装的后果 | 能否关掉 |
|---|---|---|---|
| zlib | 压缩、PPD 与 IPP 数据处理 | configure 直接报错退出 | 不能 |
| libpng / libjpeg | 图像相关的内建转换 | configure 输出里这两项显示 no,图像类任务受影响 | 能,但建议装 |
| openssl 开发头文件 | 连接加密、摘要计算 | 探测不到就退回明文,基本打印不受影响 | 能 |
| avahi / libavahi-client | 网段内自动发现打印机 | 广播发现功能不可用,手动加队列不受影响 | 能,加--with-dnssd=no |
| libusb | USB 直连打印机 | USB 后端不可用,网络打印机不受影响 | 能 |
| cups-filters | PDF/PS 转设备格式的过滤器 | 队列能建,一打就Filter failed | 不能,但它是独立包 |
表格里最后一行是最容易忽略的:从很早的版本开始,CUPS 就把大部分过滤器拆到了独立项目里,源码包装完之后/usr/lib/cups/filter下只有寥寥几个文件。你如果只用高端网络打印机自带的 IPP 处理能力,问题不大;一旦要驱动一台只懂 PCL 的老设备,就必须另装过滤器包,否则调度器会把任务接下来,然后在中途找不到转换程序,报一句非常笼统的错误。
图像库里,libpng和libjpeg这两个不用纠结,装就完了。有些最小化系统只有运行时库没有开发头文件,pkg-config --exists libpng返回 false,configure 就把这一项判成 no。装的时候记得带-devel或-dev后缀的包名。
至于 avahi,如果你的机器在机房里、打印机 IP 固定、队列全部手工建,那就直接关掉。它带来的是一整条依赖链,编译时间翻倍,还容易在启动时因为守护进程没起而卡一下。我一般的写法是--with-dnssd=no。
2.3 用户组与安装前缀的提前规划
CUPS 运行时要有两个身份:一个非特权用户跑过滤器(默认lp),一个管理组决定谁能通过本地套接字做管理操作(默认lpadmin)。这两个东西不搞好,后面lpadmin建队列会莫名其妙地提示认证失败。
# 确认 lp 用户和 lpadmin 组是否存在,缺了就补 getent passwd lp || useradd -r -s /sbin/nologin -d /var/spool/lpd lp getent group lpadmin || groupadd -r lpadmin # 把日常运维账号加进 lpadmin,避免每次都用 root 做管理操作 usermod -aG lpadmin opsusermod之后要重新登录才生效,别在这个 shell 里接着测,会以为加组失败。管理组的作用是:配置文件里写@SYSTEM的地方,实际展开就是这个组。Web 管理页要求输入账号密码时,走的也是这套逻辑。
安装前缀也要提前定。我固定用--prefix=/usr、--sysconfdir=/etc、--localstatedir=/var。为什么不装/usr/local?因为 CUPS 的部分组件有自己写死的默认路径查找逻辑,装到/usr/local后 man 页、服务单元、以及某些按/usr/lib/cups硬编码的第三方脚本会对不上,出问题时排查成本远大于省下的那点隔离收益。如果你的系统是 64 位且库目录分了 lib 和 lib64,还要额外加--libdir=/usr/lib64,否则后端和过滤器会落到/usr/lib/cups,而某些按 lib64 找东西的组件找不到它们,表现就是「队列正常,打印无声失败」。
3. configure 到 make install:CUPS 2.3.3 的参数逐条拆
这一章是整个安装过程里最需要看清楚的一段。参数写错几个,编译照样成功,问题留到运行时才暴露,那时候你就得靠日志猜了。
3.1 解包与源码树自检
# 先看包内结构,确认是一级目录还是裸文件 tar -tzf cups-2.3.3-source.tar.gz | head -n 5 # 解开并进入源码目录 tar -xzf cups-2.3.3-source.tar.gz cd cups-2.3.3 # 发布包一定带这两个文件,缺一个就说明拿到的不是发布包 ls -l configure INSTALLconfigure文件存在且可执行,说明这是打包好的发布版本,直接跑就行。如果只有configure.ac和configure.in,那是开发树,得先autoconf,编译门槛和踩坑概率都会上一个台阶,能换发布包就换。
解包之后顺手看一眼CHANGES.txt里 2.3.3 这一节的修复条目,能提前知道这个版本改过哪些路径相关的行为。长期维护的机器,这一步值得做。
3.2 configure 参数取舍
这是我在这类环境里用得最多的一套组合:
./configure \ --prefix=/usr \ --sysconfdir=/etc \ --localstatedir=/var \ --libdir=/usr/lib64 \ --with-rundir=/run/cups \ --with-system-groups=lpadmin \ --with-printcap=/etc/printcap \ --with-dnssd=no \ --enable-shared \ --disable-static \ CFLAGS="-O2 -g" CXXFLAGS="-O2 -g"每个参数的理由:
--prefix=/usr:二进制落在/usr/sbin,和系统里已有的工具路径一致,PATH 不用改。--sysconfdir=/etc:配置文件最终进/etc/cups,这是 CUPS 自己也在找的位置。--localstatedir=/var:spool 在/var/spool/cups,日志在/var/log/cups,缓存进/var/cache/cups。--libdir=/usr/lib64:决定后端、过滤器、通知器的安装位置。64 位系统上不写这个,容易和按 lib64 找文件的组件错位。--with-rundir=/run/cups:PID 文件、本地域套接字都在这里。用/run而不是/var/run,是因为现在多数系统/var/run只是软链,写死旧路径在某些场景下会失效。--with-system-groups=lpadmin:决定@SYSTEM展开成哪个组,直接影响谁能管理队列。--with-printcap=/etc/printcap:有些老脚本和老工具还在读这个文件,留着能省事。--with-dnssd=no:关掉网段广播发现,砍掉 avahi 依赖链。--enable-shared --disable-static:只编动态库,省体积也省编译时间。静态库在这类场景里没有使用价值。
CFLAGS="-O2 -g"保留调试符号,是为了将来真出问题时能用调试器看栈。生产机器上多几十兆磁盘换一个可排查的现场,我觉得划算。
跑完 configure,别急着 make,把输出末尾那段构建配置摘要存下来:
# 把 configure 的关键探测结果留下来,将来对比版本差异时非常有用 ./configure ... 2>&1 | tee /tmp/cups-configure.log grep -iE "png|jpeg|openssl|avahi|dnssd|pam|usb" /tmp/cups-configure.log摘要里如果某项显示 no 而你以为装了,回去翻config.log,搜索checking for对应的那一行,能看到具体的编译或链接失败原因。这一步是后面所有排查的基础,我习惯把这个日志留在机器上,命名里带日期。
3.3 make 并发与安装后的目录核验
# 并行编译,日志留一份,编译告警不要忽略 make -j"$(nproc)" 2>&1 | tee /tmp/cups-build.log # 安装,注意这一步会往 /etc、/usr、/var 下写文件 make installmake -j的并发数按 CPU 核数给就行,CUPS 的构建脚本没有并行依赖问题,但把日志 tee 出来很重要。CUPS 编译过程中的告警里,有一类是「某个可选组件未找到,跳过」,这类信息不会让编译失败,但直接决定你装完以后功能是否完整。
安装完成后按下面的清单核验一遍:
# 主程序是否就位、版本是否是 2.3.3 ls -l /usr/sbin/cupsd /usr/sbin/cupsd -v # 三个关键目录是否都有内容 ls /usr/lib64/cups/backend ls /usr/lib64/cups/filter ls /usr/lib64/cups/notifier # 配置目录下应该是 cupsd.conf、cups-files.conf 和对应的 .default ls -l /etc/cups # 检查 cupsd 链接了哪些外部库,确认没有意外的缺库 ldd /usr/sbin/cupsd | grep -i "not found"关于/etc/cups,有一点要提前知道:make install遇到已存在的配置文件时,不会直接覆盖,新的那份会以.default结尾留下来。如果你是从旧版本升级或之前装过发行版包,cupsd.conf很可能还是旧内容。第一次装可以不管,但只要机器上原本就有配置,就得手动对比一次,否则你会以为参数生效了,实际跑的还是一年前的配置。
/usr/lib64/cups/filter目录里如果只有三四个文件,不要慌,这是正常的,剩下的大量过滤器在独立的过滤器包里。这一点在第五章的排错里会详细说。
4. 配置与接管:cupsd.conf、自写 systemd 单元与第一个打印队列
编译安装和「服务能自动拉起」之间还差好几步。源码包不会替你装服务单元,也不会替你建运行目录,更不会替你写好配置文件里那些必须显式声明的授权段落。这一章做完,才算把这套东西接管过来。
4.1 运行目录与权限:cupsd 启动前必须存在的三处
cupsd 启动时会检查几个目录,缺了会自己建,但权限不对时它会拒绝服务,报出来的话通常很含糊,只说某个目录不安全。手工先建好,能少走一段弯路。
# 运行目录:放 PID 和本地套接字,属主 root,属组 lp,0755 install -d -m 0755 -o root -g lp /run/cups # spool 目录:放待打印任务,权限必须收紧到 0710 install -d -m 0710 -o root -g lp /var/spool/cups # 日志与缓存目录 install -d -m 0755 -o root -g lp /var/log/cups install -d -m 0755 -o root -g lp /var/cache/cups/var/spool/cups的0710不是随手写的。打印任务里可能包含敏感文档,这个目录如果对其他用户可读,cupsd 启动时会打印一条警告,某些编译选项下直接拒绝启动。0755是常见误填,一定要改。
/run目录还有个额外问题:它基于内存文件系统,重启就清空。所以要么用 systemd 的RuntimeDirectory,要么写一条tmpfiles.d规则。我倾向后者,因为调试时手动起停更可控:
# /etc/tmpfiles.d/cups.conf d /run/cups 0755 root lp -这条规则的意思是:目录不存在就创建,权限 0755,属主 root,属组 lp。tmpfiles在开机早期运行,比服务启动早,顺序上刚好。
4.2 cupsd.conf 最小可用配置
配置文件分两个:cupsd.conf放服务行为,cups-files.conf放路径和身份。这两者分开是有原因的,从 1.6 之后,User、Group、SystemGroup、ServerBin这类涉及文件系统访问的指令全部搬到了cups-files.conf,写回cupsd.conf不会生效,某些版本还会直接报「未知指令」。这是很典型的一个坑。
先看cups-files.conf里要确认的几行:
# /etc/cups/cups-files.conf # 过滤器运行身份,必须是一个真实存在的非特权用户 User lp Group lp # 管理组,对应 @SYSTEM 的展开结果 SystemGroup lpadmin # 本地域套接字位置,lpadmin 通过它做认证 DomainSocket /run/cups/cups.sock再看cupsd.conf的最小可用版本:
# /etc/cups/cupsd.conf # 日志级别,排查期用 debug2,稳定后改回 warn LogLevel warn MaxLogSize 0 # 只监听本机和内网,本地套接字必开 Listen localhost:631 Listen /run/cups/cups.sock # 允许 Web 管理页 WebInterface Yes # 默认认证方式 DefaultAuthType Basic <Location /> Order allow,deny Allow localhost Allow 192.168.10.0/24 </Location> <Location /admin> Order allow,deny Allow localhost Allow 192.168.10.0/24 </Location> <Location /admin/conf> AuthType Default Require user @SYSTEM Order allow,deny Allow localhost </Location>几个要点:Listen localhost:631和Listen /run/cups/cups.sock这两行是并列关系,前者给 Web 页面和远程 IPP 用,后者给本机的lpadmin、lpstat等命令用。少写第二行,命令行工具会去连默认套接字然后失败,报的错是「无法连接到服务器」,很容易被误判成服务没起。
192.168.10.0/24换成你自己网段。这个网段只是示例,实际要比你这台机器能访问的打印机的网段匹配。<Location /admin/conf>里加了Require user @SYSTEM,意思是只有管理组的人能改配置,其他授权网段的人只能看队列和提交任务。这个分层的意义在于:Web 界面谁都能打开,但改配置需要身份。
配置写完先做语法检查,别急着启动:
# 只检查配置语法,不真正启动服务 /usr/sbin/cupsd -t # 想看更细的解析过程,加 -c 指定配置文件再配合 -t /usr/sbin/cupsd -t -c /etc/cups/cupsd.conf-t的返回值是 0 才继续,非 0 就去/var/log/cups/error_log看最后几行,通常会明确指出行号。
4.3 自写 systemd 单元,让服务开机自起
源码安装不会给你装服务单元。自己写一份放在/etc/systemd/system下,比改源码树里的模板更清楚来龙去脉。
# /etc/systemd/system/cups.service [Unit] Description=CUPS Scheduler After=network.target Wants=network.target [Service] Type=simple # -f 表示前台运行,交给 systemd 托管进程 ExecStart=/usr/sbin/cupsd -f # 让 systemd 管理 /run/cups,重启后自动重建 RuntimeDirectory=cups RuntimeDirectoryMode=0755 Restart=on-failure RestartSec=5s LimitNOFILE=65536 [Install] WantedBy=multi-user.targetType=simple配-f是最省心的组合:cupsd 在前台跑,标准输出和错误都进 journal,systemctl status能直接看到。不要用Type=forking,那样进程一退出父进程,日志归属就乱了,排查时体验很差。
RuntimeDirectory=cups会让 systemd 在启动前建好/run/cups并在停止后清理,和前面写的 tmpfiles 规则功能重叠但不冲突。两处都留着,是为了在手动cupsd -f调试时目录也在。
启用并启动:
systemctl daemon-reload systemctl enable --now cups # 三件事一起看:服务状态、端口监听、启动日志 systemctl status cups --no-pager ss -ltnp | grep 631 journalctl -u cups -n 30 --no-pager如果 631 端口没起来而服务显示 active,多半是配置文件里的Listen段落写错,或者端口被上一次没退干净的进程占着。ss -ltnp的输出里能看到占用者的 PID,直接处理。
4.4 用 lpadmin 建第一个队列
服务起来之后,先看看这台机器能识别到哪些设备:
# 列出所有可用的后端和设备 URI lpinfo -v输出里通常会有network ipp、network socket、direct usb这几类。网络打印机优先用 IPP,直接写设备的 IPP 地址:
# 建一个走 IPP Everywhere 的队列,由服务端自动协商能力 lpadmin -p office-a \ -E \ -v ipp://192.168.10.20/ipp/print \ -m everywhere \ -D "Office A" \ -L "3F" \ -o printer-is-shared=false # 设为默认队列 lpadmin -d office-a参数拆开说:-p是队列名,只能用字母数字和连字符;-E表示建完立刻启用并接受任务,漏了它队列是禁用状态,lpstat -t里会显示disabled;-v是设备 URI;-m everywhere让调度器跟设备协商生成 PPD,不需要你预先准备驱动文件;-D和-L是描述和位置,人多的时候很有用;-o printer-is-shared=false表示不通过广播共享出去。
如果设备比较老、只认 9100 端口的原始数据流,可以用 raw 队列跳过格式转换:
# 老设备兜底方案:不打任何过滤,直接把字节流送过去 lpadmin -p legacy-1 -E -v socket://192.168.10.31:9100 -m rawraw 队列的好处是不依赖过滤器,坏处是格式转换全交给客户端。两种方式按设备情况选,不要为了统一全用 raw,那样 PDF 直接被推给打印机,出来一堆乱码。
5. 排查清单:cupsd 起不来和打不出纸的五个现场
这一章是我自己攒的问题记录。CUPS 的日志写得不算差,但很多错误信息是「结果」不是「原因」,得顺着往下摸。下面五条按现象、原因、解决的顺序写,遇到对应症状可以直接对照。
5.1 服务起不来的三个现场
现象一:systemctl start cups失败,日志只有一行Unable to open Listen socket - Address already in use。
原因:上一次的 cupsd 没退干净,或者系统里还有另一个打印服务单元在抢 631 端口。RPM 或 deb 包安装的版本和自己编的版本共存时特别容易出现,两个单元名字不同但都监听同一端口。
解决:ss -ltnp | grep 631找到占用的 PID,先确认是不是打印相关进程,是就kill掉,再systemctl disable掉不用的那个单元。别直接pkill cupsd完事,端口释放有延迟,隔一两秒再启动。
现象二:服务启动后立刻退出,journalctl里只有cupsd: Child exited with status 1,没有任何具体原因。
原因:cupsd 在守护化之前把真正的错误写到了标准错误,但被 systemd 吞掉了,或者配置里的LogLevel设得太高,关键信息被过滤。另一个常见原因是cups-files.conf里User指定的用户不存在,cupsd 在降权那一步失败后静默退出。
解决:手动前台跑一遍,直接看输出:
# 前台运行,标准错误直接打到终端,最能暴露真实原因 /usr/sbin/cupsd -f -c /etc/cups/cupsd.conf顺手确认一下getent passwd lp有输出。这个「守护进程起不来就前台跑一遍」的习惯,帮我省过太多次瞎猜。
现象三:服务起来了,631 端口也在听,但lpstat -t报Unable to connect to server。
原因:本地域套接字没建出来,或者建在别的位置。cups-files.conf里的DomainSocket和cupsd.conf里的Listen没对齐时就会这样,客户端按编译时的默认路径找套接字,服务端在另一个路径上监听。
解决:ls -l /run/cups/cups.sock看文件在不在。不在的话检查两处路径是否一致,改完重启。顺手看一眼semanage或强制访问控制的审计日志,某些默认开启强制访问控制的系统上,套接字会被策略挡住,表现同样是「
本文还有配套的精品资源,点击获取