ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

老RPM包在KeyarchOS上的适配实战:编译修复与spec重写

老RPM包在KeyarchOS上的适配实战:编译修复与spec重写 上个月在整理内部服务器清单时发现一台新入库的KeyarchOS机器上有个任务始终没跑起来——cron里每天凌晨都要执行一次calendar输出当日日程到审计日志但日志里连续几天都是command not found。查了一圈根子是包管理器里根本没有calendar而运维拿来的安装包还是calendar-1.28-1.20140613cvs这种2014年风格的RPM。说白了这就是一个典型的国产操作系统适配老软件包的问题二进制包装不上、源码得重新编译、spec要改还得保证行为不变。把整个适配过程走完之后我最大的感受是这种老包的适配难度不在代码本身而在破案——顺着版本号、依赖关系和构建日志一步一步往回倒搞清楚十年前这个包是怎么生产出来的然后在今天的系统上把这个过程重演一遍。这篇文章就把我这次在KeyarchOS上适配calendar-1.28-1.20140613cvs的完整链路写出来包括怎么分析RPM、怎么修编译错误、怎么重写spec、怎么验证功能。如果你也在国内Linux发行版上碰到类似的老RPM包这篇的排查思路可以直接复用。1. 为什么要在KeyarchOS上折腾一个十年前的老日历包1.1 calendar这个工具到底是干嘛的先说清楚对象。这里的calendar不是图形界面的日历应用也不是安卓上的日历App而是一个源自BSD的命令行日程工具。它的工作方式非常朴素系统在/usr/share/calendar目录下放一批日期定义文件比如calendar.usholiday、calendar.christian用户也可以在自己的目录下放一个~/.calendar文件里面按行写月份/日期 日程描述。运行calendar命令时它会读取今天和接下来几天的日程然后打到标准输出。这个工具的典型用法是配cron。比如我这边实际部署的场景0 7 * * * /usr/bin/calendar -d | mail -s Today Schedule opsinternal每天早上7点把当天日程发到邮箱服务器端不需要任何图形环境几十KB的二进制就够了。对自动化运维和内部审计来说这种极简工具比安装一个完整的日历套件实用得多——没有数据库、没有后台服务、不占端口输出是纯文本方便进日志系统。1.2 谁还会在2025年用到这么老的包很多人第一反应是2014年的包还有人用。但现实就是企业内部遗留系统的粘性远比想象中大。我在这次适配前梳理了一下使用场景审计留痕某些内部流程要求每天记录排班、维护窗口等日程calendar配合cron输出纯文本日志格式固定便于grep。脚本依赖个别老脚本直接调用了/usr/bin/calendar处理日期判断比如判断今天是否为季度末这类逻辑依赖calendar的日期库。迁移合规原来的CentOS环境跑得好好的现在服务器换成了KeyarchOS应用层不能变否则下游对接方要跟着改。所以这个适配的目标很明确不是把代码现代化而是让一个在旧系统上正常工作、新系统上装不上的RPM包经过重新构建后能在KeyarchOS上以同样的方式安装、运行、输出。行为必须和旧环境一致。1.3 适配完成后的交付物是什么我在接这个任务时给自己列了一个交付清单不只是能跑了这么简单一个可在KeyarchOS上安装的RPM包calendar-1.28-1.20140613cvs.keyarchos.x86_64.rpm一套可复用的spec文件以后如果KeyarchOS升级内核或工具链可以用rpmbuild -ba重新产出包。一份功能验证记录证明日历输出、日期计算、cron集成这些核心行为和旧环境一致。这个清单帮我框定了工作量也避免了装上了但没测透的情况。后面所有步骤都是在为这三项交付服务。2. 动手之前把RPM包的底细摸透2.1 从版本号读出关键线索拿到calendar-1.28-1.20140613cvs这个包名第一件事不是急着装而是拆解它的版本号命名规则。这里有个容易忽略的信息量1.28是上游版本号对应calendar工具自身的版本。1.20140613cvs是RPM的release号其中20140613是构建日期cvs表示这份代码是从CVS版本控制系统的某个快照拉出来的。这种release里带日期和cvs的命名风格在2014年前后的Linux发行版里非常常见尤其是Fedora边源和EPEL里一些缺乏正式发版节奏的小工具。它暗示了一件事这个包的构建方式很可能是从CVS checkout代码 - 打补丁 - 用rpmbuild打包。也就是说原包不一定是纯tar包而可能是版本库里某个时间点的快照外加若干补丁。搞清楚这个命名规则后我开始判断这个包属于哪个上游家族。语法上月份/日期的行格式、默认读取/usr/share/calendar目录下的calendar.*文件、支持-t参数指定日期、-A参数指定天数——这些都是BSD calendar的经典行为所以它的上游源码基本可以确定是BSD系或者基于BSD移植的版本。这一点在后面的源码编译环节很关键因为BSD代码对于glibc和GCC标准库的依赖习惯和GNU项目不太一样。2.2 用rpm命令做一次尸体解剖老规矩先把RPM包的元数据、文件清单、依赖关系全拆出来看一眼。我习惯用一条命令分别拿三组信息rpm -qip calendar-1.28-1.20140613cvs.x86_64.rpm rpm -qlp calendar-1.28-1.20140613cvs.x86_64.rpm rpm -qpR calendar-1.28-1.20140613cvs.x86_64.rpm执行后关键信息如下项目解析结果NamecalendarVersion1.28Release1.20140613cvsArchitecturex86_64依赖/bin/sh,libc.so.6(GLIBC_2.14),rtld(GNU_HASH)文件列表/usr/bin/calendar、/usr/share/calendar/calendar.usholiday、/usr/share/man/man1/calendar.1.gz等这里有两个点值得注意。第一它对glibc的依赖是GLIBC_2.14这个要求很古老。理论上2025年的KeyarchOS上glibc版本至少在2.28以上这种版本符号要求是向下兼容的所以直接安装时一般不会卡在glibc版本上。第二它只依赖/bin/sh没有任何额外的动态库依赖。这得益于calendar本来就是独立的小工具编译时只链接了libc。这意味着如果这个二进制能被加载运行基本不会遇到缺库的经典问题。2.3 实际安装时的报错才是真正的缺口理论上依赖很干净那为什么装不上直接在KeyarchOS上试一次rpm -ivh calendar-1.28-1.20140613cvs.x86_64.rpm报错内容大概是error: Failed dependencies: /bin/sh is needed by calendar-1.28-1.20140613cvs.x86_64看到这个报错我愣了一下。KeyarchOS上/bin/sh明明存在而且指向的是bash。查了一圈问题出在/bin在KeyarchOS上的默认文件系统布局新版系统里/bin是usr/bin的符号链接而老版RHEL/CentOS的rpm会在事务中单独校验/bin/sh这个文件路径是否存在符号链接场景下某些rpm版本会判定依赖不满足。这个坑非常典型。它不是缺依赖而是打包时间太早用的依赖写法太死。解决办法不是强行--nodeps装进去——一旦强制装后续rpm -e、rpm -V校验都会出问题。正确路线是重新构建一个适配KeyarchOS的RPM在spec里把/bin/sh的依赖改成/bin/sh或直接写成bash同时让构建环境生成新的依赖标记。这一步也确认了我的判断这个包必须走源码重新构建路线而不是二进制迁移路线。3. 依赖断点与源码构建环境准备3.1 先把构建工具链补齐全KeyarchOS本身装了gcc、make这些基础工具但rpmbuild不一定在默认安装里。我这边缺失的是rpm-build包。补齐的命令dnf install -y rpm-build dnf groupinstall -y Development Tools补完后验证一下rpmbuild可用rpmbuild --showrc | grep -E topdir|buildarch拿到源码包之后rpmbuild才真正开始起作用。这里提醒一句如果在内网离线环境做适配最好提前准备一份rpm-build的离线仓库或者把构建机和配有网环境的机器放在同一个网络区域否则临时装工具会非常痛苦。3.2 源码树里藏着第一个编译断点找到了配套的SRPM包calendar-1.28-1.20140613cvs.src.rpm在KeyarchOS上解包rpm -ivh calendar-1.28-1.20140613cvs.src.rpm解包后源码在~/rpmbuild/SOURCES/calendar-1.28目录下spec文件在~/rpmbuild/SPECS/calendar.spec。然后执行普通的构建流程rpmbuild -ba ~/rpmbuild/SPECS/calendar.spec第一次构建不出意外地挂了。报错关键行是calendar.c: In function getdate: calendar.c:123:15: error: implicit declaration of function stpcpy这个错误在2025年看到其实一点不意外。老代码写于2014年甚至更早当时GCC对函数隐式声明只给警告不给错误。但GCC的默认标准在十几年间升级了好几轮从-stdgnu89到默认-stdgnu11乃至更高把隐式声明从警告抬高成了错误。这里值得展开说一下原理。stpcpy这个函数在POSIX 2008标准里才被正式纳入旧代码默认认为调用一个未声明的函数也能编译过。新版glibc里stpcpy的声明是有条件暴露的通常需要_GNU_SOURCE或_POSIX_C_SOURCE 200809L才会在头文件里出现。老代码没有设置这些宏于是编译器在string.h里看不到stpcpy的声明就报隐式声明错误。修复方案很朴素在源码里加一个宏定义或者编译时加-D_GNU_SOURCE。我选择了修改Makefile在CFLAGS里追加CFLAGS -D_GNU_SOURCE -O2 -g这一处改完stpcpy的声明问题消失但紧接着又冒出来第二个编译断点。3.3 链接阶段的隐藏断点bsd兼容函数第二个报错出在链接阶段/tmp/ccXXXX.o: undefined reference to strlcpy collect2: error: ld returned 1 exit statusstrlcpy同样是BSD系的常用函数但glibc直到比较晚的版本才在string.h里暴露声明而且链接时它属于libbsd的范畴不像stpcpy那样直接从glibc的库里就能找到符号。这个断点的本质是calendar的上游代码默认自己运行在类BSD环境里它直接使用了BSD的字符串函数。而KeyarchOS是标准的glibc/Linux环境没有默认提供这些函数的实现。我查了一下系统里有没有libbsddnf search libbsd dnf install -y libbsd-develKeyarchOS的仓库里是有libbsd-devel的装上之后在Makefile里把-lbsd加到LDFLAGSLDFLAGS -lbsd这个做法在Linux发行版上构建BSD系工具时非常常见不改源码里的函数调用只用libbsd提供的兼容实现保持上游代码尽量少动。3.4 环境细节locale和时区对calendar输出的影响编译一过就以为万事大吉是大忌。calendar这种工具输出内容完全依赖日期和本地化规则而构建机和运行机的locale、timezone配置稍有不同输出就会跟着变。比如calendar内部判断节假日时可能会读取/usr/share/calendar下带locale后缀的文件calendar.zh_CN之类如果构建时没有安装对应的locale数据运行时就会静默降级成空白日程。我这边虽然不需要中文节假日但为了行为一致还是在spec的%post脚本里显式设置了软链ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime以及在%build阶段强制指定LANGen_US.UTF-8、LC_TIMEC保证构建期间生成的man page和默认日历文件不因为locale不同而内容漂移。这种细节如果不在构建时锁死后面功能回归阶段会浪费大量时间。4. 重写spec文件与rpmbuild全过程4.1 先理清原spec的结构把原spec打开看了一遍整体框架没问题毕竟是2014年从官方渠道流出来的包打包规范是标准的。核心段落有Source0: calendar-1.28.tar.gzPatch0: calendar-1.28-cvs.patch%build里执行make%install里执行make install DESTDIR%{buildroot}我保留了Source0和Patch0因为这些是上游代码的正确基线。改动点集中在以下三处。4.2 三个必须改的地方Release、依赖写法、构建参数第一处是Release号。为了和原包区分避免在同一个系统里出现两个release号相同的RPM我把Release改成了1.20140613cvs.keyarchos%{?dist}。这样产出的包是calendar-1.28-1.20140613cvs.keyarchos.x86_64.rpm一眼就能看出是适配版本也符合KeyarchOS对第三方适配包的命名习惯。第二处是依赖写法。原spec里写死Requires: /bin/sh在KeyarchOS上引发了本篇第2节说的路径依赖坑。我把这里的依赖改成Requires: /bin/sh Requires: bash虽然KeyarchOS的/bin/sh实际就是指向bash的符号链接但显式加上bash依赖更稳妥而且rpm做依赖解析时不会再纠结/bin/sh的路径类型。第三处是构建参数。把我在3.2节和3.3节验证过的-D_GNU_SOURCE和-lbsd写进spec的%build段确保后续任何人在这个环境里执行rpmbuild -ba都能稳定复现成功结果make %{?_smp_mflags} CFLAGS-D_GNU_SOURCE -O2 -g LDFLAGS-lbsd4.3 完整的rpmbuild执行过程spec改完后执行rpmbuild -ba ~/rpmbuild/SPECS/calendar.spec这一步会把整个流程重跑一遍%prep解压源码、打补丁%build编译%install安装到临时root%files列表校验最后生成二进制RPM和SRPM。这里有个经验rpmbuild的输出信息里如果%files阶段出现File not found错误通常不是文件真的不存在而是%install阶段的安装路径没对齐。我这次就遇到了/usr/share/man/man1/calendar.1.gz找不到的报错原因是源码里的Makefile安装man page时用的目录是/usr/man而不是/usr/share/man。修复方式是在spec的%install段加一句mkdir -p %{buildroot}%{_mandir}/man1 install -m 0644 calendar.1 %{buildroot}%{_mandir}/man1/calendar.1这种源码自带的安装路径旧的问题在2014年的包中不少见处理思路就是不要在%files里硬凑而是统一在%install阶段把文件放回标准的FHS路径上。4.4 安装新包并做一次完整性校验构建完拿到的产物~/rpmbuild/RPMS/x86_64/calendar-1.28-1.20140613cvs.keyarchos.x86_64.rpm安装并校验rpm -Uvh calendar-1.28-1.20140613cvs.keyarchos.x86_64.rpm rpm -V calendarrpm -V输出为空时说明文件属性和校验和与打包时一致安装是干净的。接着确认核心文件确实落在预期位置ls -l /usr/bin/calendar /usr/share/calendar/calendar.usholiday到这里RPM包本身已经能在KeyarchOS上正常安装了。5. 回归验证与想清楚老日历在现代场景的位置5.1 功能回归用例不能只验证能跑一个命令行工具适配完成后至少要跑一遍核心功能。calendar的价值在于日期计算和日程输出我设计了下面这组回归用例用例命令预期结果基础输出calendar输出今日及未来若干天的日程指定日期calendar -t 2025/12/25输出2025年12月25日的日程扩展天数calendar -A 7输出未来7天的日程用户自定义在~/.calendar写一行自定义日程后运行用户日程出现在输出中系统日历数据cat /usr/share/calendar/calendar.usholiday节假日定义文件完整man pageman calendarhead -5这组用例全部通过基本可以确认适配没有破坏原有行为。特别推荐大家把用户自定义日程这个用例放在最后跑因为只有它真正测到了用户态读写路径比单纯看系统节假日文件要深一层。5.2 同一个日历关键词下的另一个世界fossify calendar搭这套命令行日历的过程中我顺便留意到现在搜索calendar热词时经常看到fossify calendar这个项目。它是安卓端的一个开源日历应用强调的是无广告、离线可用、隐私友好支持事件提醒、农历视图、ICS导入导出这些功能。我把两者放到一起看不是为了分高下而是想说明一个事同样是日历服务端命令行工具和个人移动端应用是两条完全不同的产品线。calendar-1.28-1.20140613cvs解决的是无人值守环境里如何自动生成、投递日程文本的问题它的竞品是cron、邮件和脚本fossify calendar解决的是个人如何在手机上管理事件、获取提醒的问题它的竞品是Google Calendar和系统自带日历。理解这个边界很有实际意义。很多人在服务器上调不出图形日历就认为日历适配没用其实是要区分使用场景。如果哪天KeyarchOS上需要跑安卓容器、做移动端日历应用的交叉编译那fossify calendar的开源代码反而是更好的研究对象——它那套用Kotlin写的日程存储、事件提醒模块可以迁移到桌面端复用。5.3 这次适配沉淀下来的通用方法把整个流程回头看一遍适配老RPM包的关键点可以收敛成六步排查法先拆版本号1.28-1.20140613cvs里的上游版本、release、日期、版本系统代号都要拆出来判断它的上游代码来自哪里、有多老。再拆二进制包rpm -qip、rpm -qlp、rpm -qpR三连把依赖关系、文件清单、架构信息全部拿到手。直接安装做验证用一次真实的rpm -ivh试错拿到具体的依赖报错判断是路径问题、glibc问题还是缺包。从SRPM重构建能拿到源码包就优先走源码重打包路径不要用--nodeps硬装二进制包。逐个击破编译错误隐式声明、缺库、安装路径这类问题每一个都要搞清楚背后的标准差异再用补丁或spec参数修复。回归验证与打包命名验证功能完整后一定要做release重命名、依赖改写、完整性校验确保成品符合新系统的安装规范和命名预期。这次适配calendar-1.28-1.20140613cvs本身不是一个高深项目但恰恰是这样不起眼的老包最能考验一个工程师对RPM生态、编译工具链演进和系统兼容性的理解深度。我在操作过程中一个比较深的体会是不要急着改代码先把这个包十年前是怎么构建出来的这个问题回答清楚后面每一步都能少走弯路。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进