免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: duanjigang
打印 上一主题 下一主题

Linux平台软件管理系统设计与规划-基础篇(1)-RPM文件认识与格式剖析 [复制链接]

论坛徽章:
0
11 [报告]
发表于 2012-11-15 13:48 |只看该作者

多谢茂哥,歇歇继续。。

论坛徽章:
154
2022北京冬奥会纪念版徽章
日期:2015-08-07 17:10:5720周年集字徽章-年
日期:2022-10-26 16:44:2015-16赛季CBA联赛之深圳
日期:2022-11-02 14:02:4515-16赛季CBA联赛之八一
日期:2022-11-28 12:07:4820周年集字徽章-20	
日期:2023-07-19 08:49:4515-16赛季CBA联赛之八一
日期:2023-11-04 19:23:5115-16赛季CBA联赛之广夏
日期:2023-12-13 18:09:34
12 [报告]
发表于 2012-11-15 13:51 |只看该作者
9月的那个大会参加过,感觉兰兰在台上气场不足,

如果练习不起来,就学习网易的哪位先暴数据,在公司多少年,因为做了个什么系统,从此不用加班
会场反映,很管用lol

论坛徽章:
0
13 [报告]
发表于 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.





论坛徽章:
0
14 [报告]
发表于 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): 所有数据都是网络字节序存储的。

论坛徽章:
0
15 [报告]
发表于 2012-11-16 11:32 |只看该作者
很不错啊,学习了。

论坛徽章:
0
16 [报告]
发表于 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 类似。

论坛徽章:
48
15-16赛季CBA联赛之青岛
日期:2021-01-07 13:41:2315-16赛季CBA联赛之上海
日期:2020-12-01 18:02:0720周年集字徽章-20	
日期:2020-10-28 14:14:2620周年集字徽章-20	
日期:2020-10-28 14:04:3015-16赛季CBA联赛之天津
日期:2020-10-18 22:51:412016猴年福章徽章
日期:2016-02-18 15:30:3415-16赛季CBA联赛之北控
日期:2015-12-22 13:30:48操作系统版块每日发帖之星
日期:2015-12-07 06:20:00操作系统版块每日发帖之星
日期:2015-09-04 06:20:002015亚冠之德黑兰石油
日期:2015-08-05 18:46:082015年亚洲杯之巴勒斯坦
日期:2015-04-19 10:42:502015年亚洲杯之巴林
日期:2015-04-09 08:03:23
17 [报告]
发表于 2012-11-16 15:58 |只看该作者
支持分享!谢谢LZ,占座听讲:wink:

论坛徽章:
0
18 [报告]
发表于 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 的源代码。

论坛徽章:
0
19 [报告]
发表于 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  

论坛徽章:
5
摩羯座
日期:2014-07-22 09:03:552015元宵节徽章
日期:2015-03-06 15:50:392015亚冠之大阪钢巴
日期:2015-06-12 16:01:352015年中国系统架构师大会
日期:2015-06-29 16:11:2815-16赛季CBA联赛之四川
日期:2018-12-17 14:10:21
20 [报告]
发表于 2012-11-16 16:57 |只看该作者
终于写完了,看过以后,学到了不少啊
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP