Chinaunix

标题: Linux平台软件管理系统设计与规划-基础篇(1)-RPM文件认识与格式剖析 [打印本页]

作者: duanjigang    时间: 2012-11-14 23:03
标题: Linux平台软件管理系统设计与规划-基础篇(1)-RPM文件认识与格式剖析
本帖最后由 duanjigang 于 2012-11-17 23:03 编辑

转载请保留作者信息和来自CU的原站
2012-11-17: 更新至27楼
################

在论坛有大半年时间没写文章了,今天“发表帖子”都不知道怎么写了。看看这个荒芜的板块,顿时觉得有些内疚,就跟自家的自留地好久没有经管一样,呵呵,忙完了一阵子,终于有空来
总结下了,旨在作为自己工作总结,也为需要的同学提供点参考,

今年年初到现在这断断续续七八个月都在负责维护公司yum系统,进行RPM/YUM的日常支持,浑浑噩噩也学了不少东西,虽然不能对yum/rpm这些做到精通,但是也算是有所了解,于是在9月份,厚着脸皮去CU的架构师大会分享了一把,演讲标题是《Linux平台软件管理系统设计与规划》(可以从此下载PPT:http://bbs.chinaunix.net/thread-3771155-1-1.html)。其中洒洒扬扬对rpm/yum进行了介绍,高可用性/稳定的yum系统搭建进行了较为深入的分析,都是结合着在公司的工作内容讲的,最后第三部分还就软件包制作与命名管理规范就行了一些分享。总觉得架构师大会上一小时的时间太短,说不清好多东西,于是希望在线下进行一个系列的帖子总结和讨论,算是对演讲PPT的深入吧。废话不多,开始了。
作者: duanjigang    时间: 2012-11-14 23:14
本帖最后由 duanjigang 于 2012-11-14 23:20 编辑

看文章的标题和PPT的内容,很容易理解,要说的对象是RPM和YUM系统,鄙人不太喜欢那种太教材方式的介绍,喜欢从个人的最直观理解去阐述,叙说。呵呵,希望都能适应。

RPM文件

首先说说 RPM 文件,百度下RPM,可以看到英文名字是:RedHat Package Manager, 应该说是一个包管理方式,或者一个包管理工具。提到包管理,就赘述几句,什么是软件包呢?我的理解,最原始的包就是一堆需要运行的程序的集合,可能还需要加上一些配置文件,动态库之类,就构成了软件包。
你把自己写好的脚本或者C代码编译生成的二进制文件,加上依赖的某些so文件和conf文件,扔进一个目录,叫做execute,那么,我们就可以说excute是一个软件包了。。
有人可能说这怎么能叫软件包呢?仔细想想,它确实应该是一个软件包,它包含了要运行的应用需要的基本东西,执行程序,配置,依赖库等,只不过这个包看起来有些原始和过于简单而已。
    有了基本的软件包-一个目录中存放文件的集合,我们就会想着更高级别的软件包,比如对excute进行压缩,也是一个包,通过tar或者gzip得到tar.gz,rar,zip格式的文件,你就获得了一个较为高级的软件包了,它确实比较高级,因为它把程序和配置变成了一个单一的文件,这样就方便拷贝了,另外压缩文件的包方式,也节省了磁盘空间占用和网络数据传输的量。在我之前从事3年的单位,我们所在项目组的程序基本都是通过这种方式发布的,每次发布程序,都是把动态库,二进制程序和配置文件压缩成一个tgz文件,拷贝到需要运行的2000多台机器上,解压缩到指定目录下,然后直接运行即可。看的出,这种方式已经比较方便了。
作者: duanjigang    时间: 2012-11-14 23:35
本帖最后由 duanjigang 于 2012-11-15 07:06 编辑

有了tgz/rar/zip方式的包管理和存储方式就够了吗?可能对于某些小型应用确实够了,但是,日常工作中你应该会碰到如下一些问题:
(1):  想看这个软件包的信息,比如谁制作的,什么时候制作的,描述信息呢?
(2):  给软件包带上一些特殊功能,除了文件拷贝功能外,还要有配置文件生成,安装服务,执行命令等操作,压缩文件怎么用?
(3):  软件包版本升级时,通过压缩文件怎么做?
除了以上这3点,应该还有其他方面的问题,你应该都会遇到或者思考到。再思考下。。。压缩文件格式的软件包确实存在这些功能上的不足,能不能在文件压缩存储之外在能进行更多功能的实现和更多信息的存储呢?把这些都写入最终的包文件中?
   答案是肯定的,呵呵。
RPM 就是具有上面提到功能需求的一个包存储或者管理方式。当然还有其他方式的高级软件包格式,不过本文和后续的文章中只对RPM这种方式进行讨论。先来看看这中高级软件包的特点或者说功能吧:

(1): 压缩存储数据。(和tgz方式一样)
(2):   文件安装到指定路径(基本功能)。
(3):  配置文件产生。
(4):  系统服务注册
(5):  软件依赖检查。


除了(1)和(2),后面这三个功能,大概是压缩方式软件包都不具有的特性吧。

   压缩存储,这个是基本功能,比如我这里有个目录是5.2M,用它制作好的RPM只有2.4M.
   文件安装,这个同样是基本功能,运行命令:

  1.   rpm -qpl ./cmeguard-1.1.2-34.i386.rpm
  2. /etc/init.d/cmeguard
  3. /usr/local/cmeguard/bin/auto_update
  4. /usr/local/cmeguard/bin/cmeguard
  5. /usr/local/cmeguard/bin/cmesync
  6. /usr/local/cmeguard/bin/daemon
  7. /usr/local/cmeguard/bin/genfinger
  8. /usr/local/cmeguard/bin/run
  9. /usr/local/cmeguard/bin/sync_plug
  10. /usr/local/cmeguard/bin/sync_plug_back
  11. /usr/local/cmeguard/conf/cmeguard.conf
  12. /usr/local/cmeguard/conf/cmeproxy.conf
  13. /usr/local/cmeguard/conf/cmesync.conf
  14. /usr/local/cmeguard/conf/error.html
  15. /usr/local/cmeguard/conf/mime.types
  16. /usr/local/cmeguard/data
  17. /usr/local/cmeguard/db
  18. /usr/local/cmeguard/finger
  19. /usr/local/cmeguard/lib
  20. /usr/local/cmeguard/log
  21. /usr/local/cmeguard/pid
  22. /usr/local/cmeguard/tmp
复制代码
能够看到这么一大坨文件,这里看到的文件就是将来会安装的文件列表,如果你用默认安装命令
  1. rpm -ivh cmeguard-1.1.2-34.i386.rpm
复制代码
安装这个包的话,就会在你的系统上找到上面列表中对应的所有文件,而且最少是这些文件(因为安装时可能会产生新的文件,这个后面会说)。

然后是配置文件产生:配置文件即可以通过安装列表中的文件来生成,也可以通过安装过程中的脚本来生成。

服务注册:如果你用rpm安装过apache,mysql-server等常见软件的话,应该会知道,安装完,会对应在/etc/init.d/底下创建一个httpd或者mysqld的文件,这个文件按照
标准的自启动脚本格式书写的(参考chkconfig),当系统以对应的模式启动时,你安装的服务程序就会被运行起来。
举个例子:

  1. rpm --nosignature -qpl ./vsftpd-2.0.5-10.el5.i386.rpm  | grep init
  2. /etc/rc.d/init.d/vsftpd
复制代码
能够看到

  1. /etc/rc.d/init.d/vsftpd
复制代码
这个文件会被安装,然后你就可以通过

  1. service vsftpd start/stop/restart/status
复制代码
等方式来控制这个服务的起停了。

软件依赖检查: 我们开发的程序很少是单独运行的,大多数都会依赖其它软件,比如你开发的数据库处理程序可能需要 libmysql,网络报文处理程序需要libpcap这个包的支持,这时,为了保证你的软件安装后能正常运行,而且在安装时能够检查环境是否就绪,就可以通过软件依赖的方式来实现(当然,有人可能会问怎么实现这些呢,这个在后面的spec语法中会详细说明)。
还是举个例子,比如有个rpm叫:

  1. test_rpm-1.1.1-21.i386.rpm
复制代码
首先我们看下它require哪些咚咚:

  1. rpm -qp test_rpm-1.1.1-21.i386.rpm --requires
  2. test__require_pkg  
  3. ruby-libs  
  4. /bin/sh  
  5. /bin/sh  
  6. rpmlib(PayloadFilesHavePrefix) <= 4.0-1
  7. rpmlib(CompressedFileNames) <= 3.0.4-1
复制代码
能够看到它依赖了这些组件或者包。

然后尝试安装下:

  1. rpm -ivh test_rpm-1.1.1-21.i386.rpm           
  2. error: Failed dependencies:
  3.         test__require_pkg is needed by test_rpm-1.1.1-21.i386
复制代码
能够看到,系统缺少 test__requre_pkg 这个包,因此 test_rpm-1.1.1-21.i386.rpm
这个包是不能安装成功的,这样的逻辑确实是合理和应该的.虽然我们能够看到

  1. rpm -qa ruby-libs
  2. ruby-libs-1.8.5-5.el5_3.7
复制代码
这个包已经安装了,但是在依赖列表中,缺一不可,它们是与的关系。
好了,RPM 这五个特性,我们就说到这里。
作者: duanjigang    时间: 2012-11-15 07:21
本帖最后由 duanjigang 于 2012-11-15 07:29 编辑

再来少许赘言对上面起初提的问题进行回答: RPM 是怎样在压缩存储之外又能做到其它功能的呢,比如安装服务,执行某些命令,打印信息,发邮件,检查依赖包,还有升级时做版本检查。这些都怎么实现的?
   在此不做太细致说明,还是留待spec文件那节再说吧。
RPM有个功能就是对 scripts 的支持功能,除了文件压缩存储,它支持在安装软件或者卸载软件的过程中(确切点说,是这个过程的开始,进行和结束后这些不同时间点),执行一些命令,常用的有

  1. post install
  2. pre install
  3. post uninstall
  4. pre uninstall
复制代码
从字面意思就能看到其作用,也就是说能够在安装前,安装完,卸载前,卸载完执行某些脚本,这就为扩展包功能提供了极大的空间。
我们看一个例子:

  1. rpm -qp test_rpm-1.1.1-21.i386.rpm --scripts
  2. preinstall scriptlet (using /bin/sh):
  3. echo "pre install scripts by duanjigang"
  4. postinstall scriptlet (using /bin/sh):
  5. echo "post install scripts by duanjigang"
  6. preuninstall scriptlet (using /bin/sh):
  7. #!/bin/bash
  8. echo "pre uninstall by duanjigang"
  9. postuninstall scriptlet (using /bin/sh):
  10. #!/bin/bash
  11. echo "post uninstall by duanjigang"
复制代码
能够看到这个软件包有 5 个 scripts 在 RPM 中带着,分别在安装前后,卸载前后,build 这五个点执行,这样,你就可以通过这些 Scripts 来实现想要的功能了。
关于 scripts 在那些不同点执行,有什么效果,后面我会详述。

   还有就是 RPM 怎么做到版本的控制和变化?
通过这个信息

  1. rpm -qpi test_rpm-1.1.1-21.i386.rpm
  2. Name        : test_rpm                    
  3. Version     : 1.1.1                             
  4. Release     : 21                        
复制代码
能够看到 版本时1.1.1, Release 号是21,因此它的包名默认就是:
test_rpm-1.1.1-21
当version进行升级,或者 Release 号进行升级后,新版本的 RPM 就可以在老的版本上做版本升级了,而切 RPM 安装器会检查版本号,然后确定是否能够升级。
有时候,制作包时也会用 RPM 的 “Epoch” 这个字段来控制版本,可以称这个字段是一个神秘数字吧,他会加在版本的前面,用冒号隔开,比如:

  1. 99:1.1.1-21
复制代码
这样的一个版本字符串,和

  1. 1.2.3-xx
复制代码
来比较,前者始终比后者版本高,后者版本的包就没法升级前者了,当然这么做并不全是为了控制版本升级,主要目的还是做特殊标识的。
RPM 的基础介绍部分先到这里。:wink:
作者: chenyx    时间: 2012-11-15 08:29
感谢分享,坐板凳学习
作者: happy_fish100    时间: 2012-11-15 09:28
兰兰V5,感谢分享!
作者: duanjigang    时间: 2012-11-15 09:40
本帖最后由 duanjigang 于 2012-11-15 10:56 编辑

RPM 文件格式

做过网络程序开发或者数序wireshark (or tcpdump)的同学肯定都对报文文件比较熟悉,我们都知道,网络报文文件都有它固定的文件格式,或者我觉得叫协议更容易理解。
这些协议是人为定下来的,告知大家:我们这个文件前面多少比特写什么内容,中间哪个数据结构表示什么意思,后面那个字段又代表什么。。总之,通过这么一种通告的方式向众生告知,如果你要参与到这种格式的数据(或者文件)中来,就必须按照我说的格式(协议)去读写,只有这样,你才能够读取到正确的数据,或者写成一个正确格式的文件。

报文文件为大家熟知,我们就从报文文件的例子开始。下面是我用wireshark抓取的一个数据包,用wireshark 查看该文件,能够清晰看到按照报文文件协议解析后的数据包文件的各个字段:
链路层
IP层
TCP层
FTP层
每层的各个字段都能看到。


之所以 wireshark 能够展示出来报文的每个字段,因为它是去按照报文文件的协议进行解析的,对于 RPM 文件,原理也是一样的,知晓了文件的数据格式,然后去逐个解析,就能拿到你想要的信息,RPM 命令其中的大部分功能,也都是这样实现的。


下面,我们看看 RPM 的文件格式。
就像报文文件由 MAC层,IP层,TCP/UDP/ICMP层,HTTP/SNMP层这些信息块组成一样,一个 RPM 文件由一下几种数据块组成:

lead

signature

header

archive

在 一个 RPM 文件中,上面这四种数据元,会被包含一个或者多个,其中每个数据元中又有自己的数据格式,这样,一层层的存储协议,就构成了一个RPM文件。
作者: T-Bagwell    时间: 2012-11-15 09:52
获益良多呀
作者: duanjigang    时间: 2012-11-15 10:59
本帖最后由 duanjigang 于 2012-11-15 11:50 编辑

接着。

首先看 Lead 信息。

rpm-devel 这个包中 的文件 /usr/include/rpm/rpmlib.h  中 对 rpmlead
是如是定义的:

  1. unsigned char magic[4];
  2.     unsigned char major;
  3.     unsigned char minor;
  4.     short type;
  5.     short archnum;
  6.     char name[66];
  7.     short osnum;
  8.     short signature_type;       /*!< Signature header type (RPMSIG_HEADERSIG) */
  9. /*@unused@*/ char reserved[16]; /*!< Pad to 96 bytes -- 8 byte aligned! */
复制代码
通过字面意思大概能看到几分意思,最起码这个结构体包含了包的名字,os 类型, arch数,包类型(二进制包还是源码包)。

前四个字节,magic,是表示这个文件是否是RPM文件的标识,file 命令和 rpm 命令都是靠这前四个字节来判断的
比如:

  1. file test-rpm-1.1.1-15.x86_64.rpm
  2. test-rpm-1.1.1-15.x86_64.rpm: RPM v3 bin i386 test-rpm-1.1.1-15
复制代码
基本上就是从 lead 中获取了主要信息输出的。
目前,这个magic 数组是 “edab eedb”,可以通过 ultraedit 打开一个RPM 文件 查看前四个字节,如图示:


接下来两个字节 major 和 minor 是标识 RPM 文件格式的版本的,这个和 TCP/IP 协议的版本一样,就像网络报文中的 version 大多为 4 和 6 一样。
RPM文件中能看到的大多数值都是  major = 3 minor = 0, 也就是 3.0 版本的 RPM 文件。
在上图中同样能看到。

下来是 RPM 文件的类型 type,0 是 二进制 RPM 文件,1 是源码二进制文件,标识了RPM中存的是二进制程序还是源码包。



下来的archnum 是用标识 包将要安装的架构信息的,1 标识i368,在最新的 rpm version 3.0 中看到的 这个字段在 x86_64, noarch 和 i386 中都是0,可能已经不用这个字段了,而是采用header来存储,这个后面再说。

66个字符的 name 是包的名字。
osnum 是标识 操作系统的, 1 标识是 Linux,2 是 IRIX,这些对应的常量定义能在文件 /usr/lib/rpm/rpmrc
中看到:

  1. os_canon:       Linux:  Linux   1   
  2. os_canon:       IRIX:   Irix    2   
  3. # This is wrong
  4. os_canon:       SunOS5: solaris 3
  5. os_canon:       SunOS4: SunOS   4   

  6. os_canon:      AmigaOS: AmigaOS 5
  7. os_canon:          AIX: AIX     5   
  8. os_canon:        HP-UX: hpux10  6
  9. os_canon:         OSF1: osf1    7   
  10. os_canon:       osf4.0: osf1    7   
  11. os_canon:       osf3.2: osf1    7   
  12. os_canon:      FreeBSD: FreeBSD 8
  13. os_canon:       SCO_SV: SCO_SV3.2v5.0.2  9
  14. os_canon:       IRIX64: Irix64  10
  15. os_canon:     NEXTSTEP: NextStep 11
  16. os_canon:       BSD_OS: bsdi    12
  17. os_canon:      machten: machten 13
  18. os_canon:  CYGWIN32_NT: cygwin32 14
  19. os_canon:  CYGWIN32_95: cygwin32 15
  20. os_canon:      UNIX_SV: MP_RAS: 16
  21. os_canon:         MiNT: FreeMiNT 17
  22. os_canon:       OS/390: OS/390  18
  23. os_canon:       VM/ESA: VM/ESA  19
复制代码
signature_type 字段标识了下一个 数据块 signature 的类型,在 RPM version 3.0 中,这个变量的值是 5.
作者: send_linux    时间: 2012-11-15 13:43
感谢这么精彩的原创分享啊!
作者: duanjigang    时间: 2012-11-15 13:48

多谢茂哥,歇歇继续。。
作者: shang2010    时间: 2012-11-15 13:51
9月的那个大会参加过,感觉兰兰在台上气场不足,

如果练习不起来,就学习网易的哪位先暴数据,在公司多少年,因为做了个什么系统,从此不用加班
会场反映,很管用lol
作者: duanjigang    时间: 2012-11-15 13:53
本帖最后由 duanjigang 于 2012-11-16 10:43 编辑

回复 12# shang2010


头结构体 header structure

接着说 RPM 文件格式的其它部分,这部分文字大多是根据官方的说法翻译而来,加些自己的理解。

从 上面 讲到的 lead 结构体可以看到,lead 这个结构体从编程角度来来讲,很好用,要读取成员,你只需要用

  1. xx->name
复制代码
即可获取到 rpm 的名字,但是,很容易发现,name 这个数组的长度只能容纳 66 个字符,如果包的名字长度超过66个字符,怎么办呢?
有的开发人员可能会想到,说我把 name 长度改成 100,256,    这样是能处理大多数包,但是会带来两个问题:

(1): name 长度修改了,重新生成的 rpm 命令,会去按照新的格式读取rpm文件,这样,新版的rpm命令就不能读取老的格式的 rpm 文件。
(2): 老版本的rpm 命令,不能正确读取新版本的rpm文件。

因此,要解决此类问题,就需要讲数据的存储和读取规范化,或者说,需要把跟业务相关的数据在协议中弱化,在协议中只见协议,不见跟业务相关的数据。
我想,这是好多软件设计的原则吧,工具和业务分开,才能灵活扩展,特别是通讯程序之类的应用。

RPM 文件中为了解决数据统一读取和存储的需求,引入了 头结构体 (header structure),在一个文件中,可以有一个或者多个 header structure, 当然,在RPM文件中
只有两个header structure,一个是 signature 数据块,一个是header 数据块。

  每一个 header structure 有 三部分:
    第一部分叫做:header structure header, 用来标识一个header structrure 的起始位置,header structure 的大小,以及它包含的数据条目数。
    第二部分紧跟 header structure header,叫索引 index ,index 包含了多个index条目,其中每个 index 条目都是对一块数据的描述,每个index告诉你它指向的数据是什么样子的,在哪存着。。基本山根据index你就能获取到这个index对应的数据。
  第三部分是存储字段,叫 store, 存储了 index 描述的数据。

好了,之所以引入这个header structure, 是因为在新版本中,为了补充lead的不足,header structure 已经在新版rpm中使用了,但是能将这个历史讲下最好,方便我们学习。
下面继续回到 RPM 文件后面的部分,signature 和 header 部分,看看这两个 header structure.






作者: duanjigang    时间: 2012-11-16 10:46
本帖最后由 duanjigang 于 2012-11-16 14:06 编辑

接着看 RPM 的两个 header structure.:wink:

在 RPM 文件中,每个 header structure 开始都是用三个字节的神秘数字开始 "8E AD E8",

而且前文,我已经得瑟了
,每个RPM有两个 header structure-- signature 和 header,我们不妨打开一个rpm文件看看,我手头的这个为例,如图:



能够看到,确实找到了这两个神秘数字,也就是我们的两个 header structure 找到了。
接着往下分析。

在 三字节的什么数字之后,是 1 个字节的版本号(为1),然后是四个字节的保留字,在保留字之后,是四个字节的整数, 表示在该header structure 中有多少个索引项,也就是说
有多少个index, 接下来的四个字节整数,标识在该header structure 中有多少数据。 这些,在上图都能看到。

header structure 已经很清楚了,但是还有两个概念没清楚,一个是 index, 一个是 store,接下来,我们来剖析这两个咚咚。

header structure 之一 ----------- index

每个 index 有 16个 字节长度,前四个字节整数是一个 Tag,表明该 index 指向的数据是什么类型的,关于这段描述,原文是:

The first four bytes contain a tag — a numeric value that identifies what type of data is pointed to by the entry. The tag values change according to the header structure's position in the RPM file

这个很容易让人误解为是,这个类型是 整形,字符串,或者数组,其实不是,这里的 “TYPE” 其实就是我们通常说的变量名称,参考附录才知道。
其实就是RPM中的信息要素(我们通过 rpm -qpi xxoo.rpm 看到的咚咚)。
Tag 的值定义列表如下:

  1. #define RPMTAG_NAME                     1000
  2. #define RPMTAG_VERSION                  1001
  3. #define RPMTAG_RELEASE                  1002
  4. #define RPMTAG_SERIAL                   1003
  5. #define RPMTAG_SUMMARY                  1004
  6. #define RPMTAG_DESCRIPTION              1005
  7. #define RPMTAG_BUILDTIME                1006
  8. #define RPMTAG_BUILDHOST                1007
  9. #define RPMTAG_INSTALLTIME              1008
  10. #define RPMTAG_SIZE                     1009
复制代码
好了,接下来的四个字节整数(4-7),才是前四个字节的变量的类型,整数啊,字符串啊。。
定义如下(在文件 /usr/include/rpm/header.h 中能看到):

  1. typedef enum rpmTagType_e {
  2. #define RPM_MIN_TYPE            0
  3.     RPM_NULL_TYPE               =  0,
  4.     RPM_CHAR_TYPE               =  1,
  5.     RPM_INT8_TYPE               =  2,
  6.     RPM_INT16_TYPE              =  3,
  7.     RPM_INT32_TYPE              =  4,
  8. /*    RPM_INT64_TYPE    = 5,   ---- These aren't supported (yet) */
  9.     RPM_STRING_TYPE             =  6,
  10.     RPM_BIN_TYPE                =  7,
  11.     RPM_STRING_ARRAY_TYPE       =  8,
  12.     RPM_I18NSTRING_TYPE         =  9
  13. #define RPM_MAX_TYPE            9
  14. } rpmTagType;
复制代码
STRNG_TYPE 是空字符结束的字符串
STRING_ARRAY_TYPE 是 STRING_TYPE 的集合组成。

接下来的 8 到 11字节的整数,是该 index 对应的数据在 store 段的偏移,当然从哪里开始算偏移,现在还不清楚,后面再说。

最后12-15字节的整数,表明了该index指向的数据,有多个数据据条目,主要是给STRING 和 STRING_ARRY用的,STRING 的话,是1,STRING_ARRY就是STRING的个数。

header structure 之二 ----------- store

store 就是 该 header structure 中数据存储的地方,有几个点要注意:

(1): 每个 STRING 类型,都以 空字符结尾。
(2): 整数存储都是按照它的自然边界存储的,翻译的实在太拗口了。也就是说,64位的用8个字节存储,16位的用2个字节存储,32位的用4个字节存储等等。
(3): 所有数据都是网络字节序存储的。


作者: 0711101021    时间: 2012-11-16 11:32
很不错啊,学习了。
作者: duanjigang    时间: 2012-11-16 11:45
本帖最后由 duanjigang 于 2012-11-16 16:05 编辑

接着来看由 header structure 组成的 RPM 文件中的两个要素:
signature 和 Header

signature 和 header 都是 header structure
看一段我从RPM中取出的元数据:


  1. 8E AD E8 01 00 00 00 00  00 00 00 05 00 00 00 54 ----第一个heder,signature 开始  5 个 index,0X54 = 84 个字节的数据存储在本header
  2. 00 00 00 3E 00 00 00 07  00 00 00 44 00 00 00 10                                  TAG 是 62  PGPTAG_PRIVATE_62         = 62, \/*!< Private or Experimental Values *
  3. 00 00 01 0D 00 00 00 06  00 00 00 00 00 00 00 01                        TAG 是 RPMTAG_R        rpmlib.h        269;"
  4. 00 00 03 E8 00 00 00 04  00 00 00 2C 00 00 00 01             TAG是 1000 RPMSIGTAG_SIZE
  5. 00 00 03 EC 00 00 00 07  00 00 00 30 00 00 00 10         TAG是1004  RPMTAG_SUMMARY
  6. 00 00 03 EF 00 00 00 04  00 00 00 40 00 00 00 01          TAG是RPMTAG_BUILDHOST
  7. 35 62 65 36 33 31 65 38  34 36 31 30 33 63 33 36  
  8. 38 66 34 36 62 30 32 66  64 65 36 34 33 61 32 30  
  9. 63 36 39 62 63 64 38 65  00 00 00 00 00 00 06 DB  
  10. 2A DB 93 17 6D BE D3 9D  4B BE AA 2A 19 88 36 35  
  11. 00 00 01 C0 00 00 00 3E  00 00 00 07 FF FF FF B0  
  12. 00 00 00 10 00 00 00 00  8E AD E8 01 00 00 00 00   ---signature结束,第二个header,header开始
复制代码
第一行是 标准头,第二行开始就是 5 个 index,每行一个index,每个 index 项都是四个32为的整数,分别是:

  1. TAG (0-3)         TYPE (4-7)             OFFSET (8-11)           COUNT(12-15)
复制代码
有两个地方需要注意,我还不是很清楚
(1) 在 lead 结束时,为什么插入了16个字节的空数据?
(2) signature 结束时,84 个 字节结束时,紧接着应该是header的开头,可是却插入了四个字节的空数据,不知道为什么,还是我计算错了? 还在查找中


然后 signature 字段就此结束了。

第二个 header 是 Header 字段。

Header字段中包含了 RPM 文件的主要信息,名称,版本,文件列表等信息。
因此,在Header的index将会很多,而且你能看到很多类型的 TAG 出现 (SPEC文件中的很多元素都会出现)。

贴一段看看:

  1. 00 00 00 10 00 00 00 00  8E AD E8 01 00 00 00 00   ---signature结束,第二个header,header开始
  2. 00 00 00 31 00 00 02 F6  00 00 00 3F 00 00 00 07  49 个 index 758个字符的数据存储
  3. 00 00 02 E6 00 00 00 10  00 00 00 64 00 00 00 08  1
  4. 00 00 00 00 00 00 00 01  00 00 03 E8 00 00 00 06  2 -------TAG 1000 RPMTAG_NAME
  5. 00 00 00 02 00 00 00 01  00 00 03 E9 00 00 00 06  3 ------ TAG 1001 RPMTAG_VERSION
  6. 00 00 00 0B 00 00 00 01  00 00 03 EA 00 00 00 06  4  ---- TAG 1002 RPMTAG_RELEASE
  7. 00 00 00 11 00 00 00 01  00 00 03 EB 00 00 00 04  5  ---- TAG 1003 RPMTAG_EPOCH
  8. 00 00 00 14 00 00 00 01  00 00 03 EC 00 00 00 09  6      --- TAG 1004 RPMTAG_SUMMARY
  9. 00 00 00 18 00 00 00 01  00 00 03 ED 00 00 00 09  7
  10. 00 00 00 2F 00 00 00 01  00 00 03 EE 00 00 00 04  8
  11. 00 00 00 48 00 00 00 01  00 00 03 EF 00 00 00 06  9
  12. 00 00 00 4C 00 00 00 01  00 00 03 F1 00 00 00 04  10
  13. 00 00 00 68 00 00 00 01  00 00 03 F3 00 00 00 06  11
  14. 00 00 00 6C 00 00 00 01  00 00 03 F6 00 00 00 06  12
  15. 00 00 00 9A 00 00 00 01  00 00 03 F7 00 00 00 06  13
  16. 00 00 00 C6 00 00 00 01  00 00 03 F8 00 00 00 09  14
  17. 00 00 00 FE 00 00 00 01  00 00 03 FD 00 00 00 06  15
  18. 00 00 01 03 00 00 00 01  00 00 03 FE 00 00 00 06  16
  19. 00 00 01 09 00 00 00 01  00 00 04 04 00 00 00 04  17
  20. 00 00 01 10 00 00 00 02  00 00 04 06 00 00 00 03  18
  21. 00 00 01 18 00 00 00 02  00 00 04 09 00 00 00 03  19
  22. 00 00 01 1C 00 00 00 02  00 00 04 0A 00 00 00 04  20
  23. 00 00 01 20 00 00 00 02  00 00 04 0B 00 00 00 08  21
  24. 00 00 01 28 00 00 00 02  00 00 04 0C 00 00 00 08  22
  25. 00 00 01 6A 00 00 00 02  00 00 04 0D 00 00 00 04  23
  26. 00 00 01 6C 00 00 00 02  00 00 04 0F 00 00 00 08  24
  27. 00 00 01 74 00 00 00 02  00 00 04 10 00 00 00 08  25
  28. 00 00 01 7E 00 00 00 02  00 00 04 14 00 00 00 06  
  29. 00 00 01 88 00 00 00 01  00 00 04 15 00 00 00 04  
复制代码
Header 的分析和 signature 类似。
作者: lbseraph    时间: 2012-11-16 15:58
支持分享!谢谢LZ,占座听讲:wink:
作者: duanjigang    时间: 2012-11-16 16:07
本帖最后由 duanjigang 于 2012-11-16 16:16 编辑

关于 RPM_TAG 的所有定义,可以去文件
/usr/include/rpm/rpmlib.h
中查找。

Archive 字段

Header 之后就是 archive 字段,存储了组成 RPM 的所有文件内容,能够通过标志位 “1F 8B” 看到,archive 部分是通过 gzip
压缩后存储的。

  1. 00 3F 00 00 00 07 FF FF  FC F0 00 00 00 10 1F 8B
  2. 08 00 00 00 00 00 00 03  8D 8E BD 0E 82 30 14 46
  3. 3B F3 14 14 F7 72 6F 5B  04 07 07 07 07 13 27 E2
  4. 0B 40 29 C2 20 90 B6 9A  F0 F6 A2 40 34 46 12 4E
  5. 6E 72 FF BE E1 40 0C 31  20 20 AA 04 77 09 0C 24
  6. 58 16 F0 1F 94 25 64 B2  10 D3 5F C8 B1 2F E6 67
  7. F4 3C B0 30 AF 9B D0 69  EB 58 D7 93 0D 0D EF D6
  8. BC 4F 5D EF AA B6 E1 6C  EB 75 A6 6E 9C 1F B8 AA
  9. B6 FE 50 99 FF 4A FB 37  6D 6D 76 D5 81 07 DF BE
  10. D9 E8 9B CB 25 DF 68 D0  8D F8 56 4D AB 58 E7 8B
  11. F0 F1 D5 4E 8D BE AA 6D  4A 42 88 D5 E6 A1 CD 1E
  12. 81 09 CE 90 23 E3 1E 99  9C D6 B1 36 F7 4B 3E 0F
  13. 97 F4 70 3A 1F 53 4A E9  A0 43 9E 3D 9F 06 07 C0
  14. 01 00 00 43 00 74 65 73 74 2D 72  49
复制代码
接着的 “08” 表明数据是用 gzip 的  “deflation” 方法压缩的。
如果你想获取更多的细节,可以参考 rpmbuild 或者 RPM 的源代码。


作者: duanjigang    时间: 2012-11-16 16:47
本帖最后由 duanjigang 于 2012-11-16 16:56 编辑

test.zip (1.06 KB, 下载次数: 248)



附件的 test.c 是基于 rpm-devel 这个库开发的一个 rpm 信息读取功能的 C程序,读取RPM基本信息并且打印,如果你了解了RPM的格式,看这段代码就比较容易了。
核心代码片段如下:

  1. char * readHeaderString (Header header, int_32 tag_id)
  2. {
  3.           int_32 type;
  4.           void *pointer;
  5.           int_32 data_size;
  6.           int header_status = headerGetEntry (header, tag_id, &type, &pointer, &data_size);
  7.           if (header_status)
  8.             {
  9.                       if (type == RPM_STRING_TYPE)
  10.                 {
  11.                           return pointer;
  12.                 }
  13.             }
  14.           return NULL;
  15. }

  16. int samplerpm (const char *szrpm)
  17. {
  18.         char g_szname[1024] = {0};
  19.         FD_t fd = Fopen (szrpm, "r");
  20.         memset (g_szname, 0, 1024);
  21.         sprintf (g_szname, "%s", szrpm);
  22.         fflush (stdin);
  23.         fflush (stdout);
  24.           if (!fd)
  25.             {
  26.                       printf ("open file '%s' failed\n", szrpm);
  27.                       return 0;
  28.             }
  29.            //else
  30.             // printf ("open '%s' success\n", szrpm);
  31.    
  32.           struct rpmlead plead;
  33.           int lead = readLead (fd, &plead);

  34.           if (lead)
  35.             {
  36.                       printf ("readLead of '%s' failed\n", szrpm);
  37.                       Fclose (fd);
  38.                       return 0;
  39.             }
  40.           else
  41.             {
  42.                               //printf ("name=%s,may=%d,min=%d\n",plead.name,plead.major,plead.minor);
  43.             }

  44.           //sigType sig_type = plead.signature_type;
  45.           Header header;
  46.           rpmRC ret = rpmReadSignature (fd, &header, plead.signature_type);

  47.           if (ret != RPMRC_OK)
  48.             {
  49.                       printf ("rpmReadSignature of '%s' failed\n", szrpm);
  50.                       Fclose (fd);
  51.                       return 0;
  52.             }
  53.           //else
  54.           //printf ("rpmReadSignature success:%s\n", szrpm);
  55.           //read header
  56.           Header newheader =
  57.             headerRead (fd, (plead.major >= 3) ? HEADER_MAGIC_YES : HEADER_MAGIC_NO);
  58.           if (!newheader)
  59.             {
  60.                       printf ("headerRead of '%s' failed\n", szrpm);
  61.                       Fclose (fd);
  62.                       return 0;
  63.             }
  64.         const char *name = readHeaderString (newheader, RPMTAG_NAME);
  65.           const char *version = readHeaderString (newheader, RPMTAG_VERSION);
  66.           const char *release = readHeaderString (newheader, RPMTAG_RELEASE);
  67.           const char *group = readHeaderString (newheader, RPMTAG_GROUP);
  68.           const char *packager = readHeaderString (newheader, RPMTAG_PACKAGER);

  69.         if (!group) group = "NONE_GROUP";
  70.         if (!packager) packager = "NONE_PACKAGER";
  71.         printf ("name:%s\nversion:%s\nrelease:%s\ngroup:%s\npackager:%s\n\n",
  72.         name, version, release, group, packager);
  73.           Fclose (fd);
  74.           return 1;
  75. }
  76. [code]

  77. 执行编译命令:

  78. [code]
  79. gcc test.c -I/usr/include/rpm -lrpm -lrpmdb -lrpmio -lpopt -o test_rpm
复制代码
然后可以测试其功能:

  1. ./test_rpm ./mysql-server-5.0.77-4.el5_6.6.x86_64.rpm
  2. name:mysql-server
  3. version:5.0.77
  4. release:4.el5_6.6
  5. group:Applications/Databases
  6. packager:Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla>
复制代码
本人没有读 rpm 的源码,不过其实现应该也类似,参考 RPM 的协议文档,逐个做解析吧。

到此为止, RPM 文件的介绍和格式剖析基本上结束。关于 RPM 的操作工具,可以参考 man 手册。
或者有空时,我再来补充,等对 spec 文件做整理后,我会把 Spec 的部分总结出来。


:wink:  over  
作者: T-Bagwell    时间: 2012-11-16 16:57
终于写完了,看过以后,学到了不少啊
作者: zooyo    时间: 2012-11-16 17:00
提示: 作者被禁止或删除 内容自动屏蔽
作者: duanjigang    时间: 2012-11-16 17:00
回复 20# T-Bagwell

多多实践啊
下周出 spec 文件剖析


   
作者: duanjigang    时间: 2012-11-16 17:02
回复 21# zooyo

感谢邹总支持。


   
作者: ecjtubaowp    时间: 2012-11-17 14:18
学习了。。。。。。
作者: eliry    时间: 2012-11-17 14:50
之前搞过一段时间RPM包制作,不过没楼主研究的深入。楼主写的很精彩!
作者: duanjigang    时间: 2012-11-17 21:16
本帖最后由 duanjigang 于 2012-11-17 22:56 编辑

我再整理下,想把 RPM 文件的协议图画出来,像 TCP/IP一样。呵呵。

下面是我再次分析的一个 RPM 的格式。
这次比较清晰了。
1,2,3 附图 基本上是RPM所有格式的集合。








作者: duanjigang    时间: 2012-11-17 22:58
本帖最后由 duanjigang 于 2012-11-17 22:59 编辑

赫赫,最后一幅图,是整个 RPM 文件的格式表示图::wink:

作者: duanjigang    时间: 2012-11-17 23:01
回复 25# eliry

一起交流学习:wink:

   
作者: duanjigang    时间: 2012-11-17 23:07
回复 10# send_linux
感谢茂哥加分!


   
作者: X19851012251    时间: 2012-11-18 11:44
怎么没有更新了/
作者: duanjigang    时间: 2012-11-18 12:01
回复 30# X19851012251
RPM 部分到此就基本结束了,再想想有什么要补充的就补上来。
准备后面的spec file 篇了。:wink:


   
作者: pitonas    时间: 2012-11-20 09:38
觉得有些最起码的内疚
标识了下这么精彩的原创啊!
作者: duanjigang    时间: 2012-11-20 10:01
回复 32# pitonas


   
作者: yizhengming    时间: 2012-11-22 22:35
学习了  感觉rpm比apt-get慢
作者: X19851012251    时间: 2012-11-22 22:36
xuexi .小白求学,
作者: 346196247    时间: 2012-11-23 22:24
我正在学习liunx非常好的资料
作者: daniel_11    时间: 2012-11-26 18:01
收藏了,多谢lz分享!
作者: crazyhadoop    时间: 2012-11-28 13:13
段兄出品,必是精品
作者: mingming_song    时间: 2012-11-29 14:41
RPM也看过一段时间,但是真心没楼主研究的深入,学习了
作者: duanjigang    时间: 2012-12-03 15:47
apt-get 没研究过。
不知道这位兄弟觉得rpm慢在什么地方?一般软件安装对于速度的要求不会太苛刻吧?
我是发现如果RPM过多并且文件列表太大,会导致yum的索引文件很大,从而降低检索的速度,这个我们在后面再说。
yizhengming 发表于 2012-11-22 22:35
学习了  感觉rpm比apt-get慢

作者: 兜一圈    时间: 2012-12-05 08:58
新手上路,学习!!
作者: pitonas    时间: 2012-12-08 14:33
获益呀
作者: dgww    时间: 2012-12-11 13:34
mark~mark~mark~mark~mark~mark~mark~mark~mark~mark~
作者: wahu0315210    时间: 2012-12-29 10:54
学习了,感谢分享~
作者: 609854    时间: 2013-03-18 20:15
回复 22# duanjigang


   期待 spec,  锁定你了
作者: duanjigang    时间: 2013-03-19 16:01
回复 45# 609854
谢谢支持,第二篇SPEC在这里:
http://bbs.chinaunix.net/thread-4059136-1-2.html


   
作者: loveyuqing    时间: 2013-03-22 12:37
谢谢分享!!!!:wink:
作者: wonghoifung    时间: 2013-03-28 16:04
先mark再看。。。
作者: yahoon    时间: 2013-04-02 11:16
非常不错的分享
作者: defcon    时间: 2013-08-21 13:22
不要插队,让人家说完在留言吗
作者: forgaoqiang    时间: 2013-10-05 00:45
分析好深入
作者: hiyachen    时间: 2014-09-21 20:31
很棒的文章,已下载。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2