rpm包管理和安装盘定制
本文是构造Linux的图形化安装程序系列文章的第四部分,内容主要包括RPM基本命令介绍,RPM包的定制过程,RPM SPEC文件的主要内容,RPM函数库简单参考和安装程序中关于RPM包管理部分[url=http://www.pccode.net].net" class="wordstyle"源码的简单介绍。通过这部分的介绍,希望读者能对Linux系统下RPM包的定制过程和RPM包的系统安装过程有一个基本的了解。
在安装程序进行了磁盘分区工作之后,安装程序就可以进行RPM系统包的安装了。这是整个安装过程中很重要的一步。在这一环节,安装程序要读出所有rpm包的描述信息并根据包之间的依赖关系,构造出正确的包安装顺序。这种构造的机制主要是对包依赖关系的树形结构进行深度搜索,对于最基本的系统包(比如Glibc和Bash)一定要最先安装。为了保证在安装了所有的系统包之后,RPM数据库运行良好,还要在安装过程中构造正确的RPM数据库。最后为了调试的方便,也便于用户检查安装的系统包,还需要对包的安装过程建立日志。
1 RPM包的基本概念
RPM(Redhat Package Management)是由RedHat开发的,Linux系统下的系统包管理工具。它的目标是:使包的安装和卸载过程更容易,能够证实一个包是否已经正确安装了,简化包的建立过程,可以从源代码建立整个包,使它能用于不同的体系结构。RPM系统已经成为现在Linux系统下包管理工具事实上的标准,并且它也移植到很多商业的unix系统之下。
RPM包由包标签标识,它包含这样几个部分,软件名,软件版本,包的发行版本。在包的内部还包含如下信息:包的建立时间,包的内容描述,安装包的所有文件的大小,数字签名以证实包的完整性。RMP包还包含包内的文件信息,其中包括:每个文件的文件名,每个文件的权限,文件的属组和拥有者,每个文件的md5校验和,文件的内容。
RPM的包管理系统提供了下列功能:安装新的包,除去旧的包,将一个旧包升级为新的包,获得已经安装包的信息。
常用的RPM命令:
rpm -i
使用此命令可以安装一个rpm包。在安装的过程中,此命令依次要进行包依赖性检测,包冲突检测,完成安装前必须执行的任务,处理相应的配置文件,解开包中的文件并将其拷贝到正确的位置,完成安装后必须执行的任务,对包进行的处理进行跟踪记录。
例如:
rpm -i bzip2-1.0.1-3.i586.rpm
//安装bzip2包。
rpm -ivh bzip2-1.0.1-3.i586.rpm
//安装bzip2包的同时,显示更多的文本提示信息,以及在屏幕上显示连续的#号来表示的安装进度。
有时在安装一个新包时,根据依赖性检查的结果,需要首先安装其他的包。但可能这时系统中并没有安装所需要包的合适版本,这样rpm会终止包的安装。为了直接安装这个包,您需要加入--nodeps选项。下例表示在安装bzip2包时,不进行依赖性检测。
rpm -ivh bzip2-1.0.1-3.i586.rpm --nodeps
rpm -ivh bzip2-1.0.1-3.i586.rpm --force
//强制安装rpm包。这条命令实际上等价于
rpm -ivh bzip2-1.0.1-3.i586.rpm --replacepkgs --replacefiles
rpm -e
使用此命令可以删除一个rpm包。删除rpm包时,此命令要完成如下工作:
检查rpm数据库确保没有其他包依赖将要删除的包。
如果包存在卸载前脚本,执行此脚本。
检测安装包时是否对包配置文件进行了修改。如果进行了修改,则保存备份。
查找rpm数据库中此RPM包所包含的文件。如果这些文件不属于任何其它的包,则删除它。
如果包存在卸载后脚本,执行此脚本。
从rpm数据库除去所有包跟踪记录。
例如:
rpm -e bzip2
//从系统中除去bzip2包。添加--nodeps选项可以在删除包时,禁止包的依赖性检查。
rpm -U
这条命令完成rpm包的升级。它执行的操作包括安装合意的包,删除所有存在的老版本的包。例如:
rpm -U bzip2
//升级包bzip2。
rpm -q
这条命令可以获得rpm包的信息。通过这条命令可以查询包的文件列表,包的版本,包的描述性信息。同样的,你也可以通过这条命令查得一个文件属于哪个rpm包。例如:
rpm -qf `which fdisk`
//检查fdisk文件属于哪个系统包。
rpm -qi bzip2
//获得已安装包bzip2的描述性信息。
rpm -ql bzip2
//获得安装包bzip2的文件列表。
rpm -qa
//获得系统安装的所有rpm包的列表。这条命令和grep命令一起使用,可以快速找到系统中包含的某个rpm包,例如:
rpm -qa | grep bzip2
2 RPM包建立过程
为了完成RPM包的建立过程,需要执行以下步骤:
执行Spec文件prep节的命令和宏。
检查文件列表的内容。
执行Spec文件build节的命令和宏。
执行Spec文件install节的命令和宏,同时也执行文件列表中的宏。
创建二进制包文件。
创建[url=http://www.pccode.net].net" class="wordstyle"源码包。
为了执行打包的工作,RPM需要一系列目录完成建立的工作。正常的目录结构通常由一个顶级目录和五个子目录构成。这五个子目录分别是:
SOURCES------包含原始的源文件、补丁和像标文件。
SPECS--------包含控制建立过程的spec文件。
BUILD--------包含[url=http://www.pccode.net].net" class="wordstyle"源码解包和软件建立的目录。
RPMS---------包含建立过程创建的二进制包文件。
SRPMS--------包含建立过程创建的[url=http://www.pccode.net].net" class="wordstyle"源码包文件。
除了上述这五个主要的目录外,在RPMS或SRPMS目录下通常还会有关于包目标平台的目录。例如,i386、i586、i686等代表与Intel兼容cpu的平台,noarch目录下的包代表可以在任何平台下执行。
2.1 SPEC文件
Spec文件是整个RPM包建立过程的中心,它的作用就如同编译程序时的Makefile文件。Spec文件包含建立一个rpm包必需的信息,包括哪些文件是包的一部分以及它们安装在哪个目录下。这个文件一般分为如下的几节:
Preamle(序言)
序言包含用户请求包的信息时所显示的内容。它可以包含包的功能描述,包的软件版本,版权信息,所属的包组等。
Prep节 Prep节进行实际的打包准备工作,它是使用节前缀%prep表示的。一般而言,这一节的主要工作是检查标签语法是否正确,删除旧的软件源程序,对包含源程序的tar文件进行解码。如果包含补丁(patch)文件,将补丁文件应用到解开的[url=http://www.pccode.net].net" class="wordstyle"源码中。它一般包含%setup与%patch两个命令。%setup用于将软件包打开,执行%patch可将补丁文件加入解开的源程序中。
%setup
-n newdir---------将压缩的软件源程序在newdir目录下解开。
-c ---------------在解开源程序之前先创建目录。
-b num------------在包含多个源程序时,将第num个源程序解压缩。
-T----------------不使用缺省的解压缩操作。
例如:
%setup -T -b 0
//解开第一个源程序文件。
%setup -c -n newdir
//创建目录newdir,并在此目录之下解开源程序。
%patch
%patchN----------这里N是数字,表示使用第N个补丁文件,等价于%patch -P N
-p0--------------指定使用第一个补丁文件,-p1指定使用第二个补丁文件。
-s---------------在使用补丁时,不显示任何信息。
-b name----------在加入补丁文件之前,将源文件名上加入name。若为指定此参数,则缺省源文件加入.orig。
-T---------------将所有打补丁时产生的输出文件删除。
Build节
这一节主要用于编译[url=http://www.pccode.net].net" class="wordstyle"源码,它是使用节前缀%build表示的。这一节一般由多个make命令组成。
Install节
这一节主要用于完成实际安装软件必须执行的命令,它是使用节前缀%install表示的。这一节一般是由make install指令构成,但是有时也会包含cp、mv、install等指令。
这一节还能指定在用户安装的系统上,包安装时运行的脚本。这样的脚本称为安装(卸载)脚本。它可以指定包安装前、包安装后、包除去前、包除去后的系统必须运行的外壳程序段。在用户安装的系统上,为了验证一个包是否已经成功安装的验证脚本也可由这一节指定。
Clean节
这一节所描述的内容表示在完成包建立的工作之后,自动执行此节下的脚本进行附加的清除工作,它是使用节前缀%clean表示的。一般而言,这一节的内容是简单地使用rm -rf $RPM_BUILD_ROOT命令,不需要指定此节的其它内容。
文件列表
这一节指定构成包的文件的列表,它是使用节前缀%files表示的。此外,它还包含一系列宏控制安装后的文件属性和配置信息。
改动日志
这一节主要描述软件的开发记录,它是使用节前缀%changlog表示的。这个段的内容是为了开发人员能详细的了解该软件的开发过程,对于包的维护极有好处。
2.2 建立rpm包
有时您可能只有一个tar.gz格式的源程序包,为了生成正确的rpm包,您可以使用autospec自动创建spec文件。
举例来说,您有一个源程序文件some.tar.gz。为了定制rpm包,您要进行如下操作:
解压缩源程序包
tar xvzf some.tar.gz
手动编译和安装此源程序包
make; make install
自动生成spec文件
make -n install | autospec -i > some.spec
编译生成rpm包
rpm -ba some.spec
在创建spec文件之前,必须成功编译源程序包。否则autospec生成的spec文件将不会包含%build、%install、%file。对于一般的源程序包,您只需到SPEC目录下,直接执行上面操作的第四步就可以了。
3 定制系统安装盘
定制系统安装盘的过程主要是指对于用户提供的一组RPM包,安装程序能够根据用户提供的描述文件,自动进行包的安装。这个过程包括对包的依赖性进行检查、根据类别选择需要安装的包。
使用HappyLinux发布盘上附带的RPM包管理工具可以定制系统发布盘,这些包管理工具保存在安装盘的/misc目录下。为了保证安装程序能够工作,必须在/HappyLinux/base目录下提供以下的文件:
compss
在HappyLinux系统下,它是由系统命令gendistrib自动生成的。它可以根据包所属的包组对包进行分类。compss描述发布盘上所有包的一个列表。这个文件对于安装过程没有影响,它的存在只是为了与以前的版本兼容。
compssUsers
此文件的内容是对rpmsrate文件所描述的包类别进行进一步的包装,以形成更高层的包类表述。
depslist
depslist.ordered
这两个文件是命令gendistrib生成的,它描述每个包所依赖的系统包。depslist.ordered使安装程序实际使用的文件,它将依赖包的文本描述转换为包的编号,这样可以加快整个安装的进程。
happyinst_stage2.bz2
这个文件保存的是整个安装环境(也就是安装盘上/HappyLinux/happyinst目录下的所有文件)的压缩镜像,它会在启动过程中由系统加载程序调入内存。这个文件是由命令make_happyinst_stage2生成的。
hdlists
这个文件是对安装盘rpm包所在的目录进行描述。当要进行多盘安装时,此文件的每一行表示一张发布介质的RPM包所在的目录。这个文件必须由用户手动生成。
hdlist.cz2
这是由命令gendistrib生成的,包头信息的描述文件。安装程序由此文件读出每个包的大小、版本等信息。它的名字是由hdlists文件的对应行表示的。
provides
这个文件也是命令gendistrib生成的,它将包冲突检测的信息保存到此文件中。
rpmsrate
这个文件是由用户自己定制的。它主要描述同类别包的编组。同时,对包组的文件指定了优先级,使得不同的场景可以自动安装不同的包组。这些包类别将要写入compssUsers文件中,构成更大的包逻辑分组。
在发布盘上的任何包的发生改变时,为了使此改变生效,必须运行命令gendistrib,重新生成所有与包描述有关的文件。
gendistrib --distrib /export
根据目录/export下保存的所有包信息生成相应的描述文件。
在对包的安装环境进行了任何修改之后,必须重新运行命令make_happyinst_stage2。
make_happyinst_stage2 /export/HappyLinux/happyinst /export/HappyLinux/base/happyinst_stage2.bz2
生成整个安装程序的压缩镜像。
在进行了上述步骤之后,您就可以使用mkisofs创建iso文件,以生成正确的光盘发布了。
4 rpmlib库函数
rpmlib库函数是包含在librpm.a库中的。在c语言中,使用这些函数必须包含头文件rpmlib.h,对于与header有关的函数还需要包含头文件header.h。这些函数完成的功能包括从底层的rpm数据库记录遍历到高层的包处理。
在安装程序中,使用rpmlib中的函数完成包安装的功能,具有以下的一些优点:
大大减小了整个安装程序占用的内存。这样安装程序就无需提供rpm系统文件了。
直接使用c语言的库函数提高了包的安装速度。
通过安装回调函数,可以在包的安装过程中,实现一些特殊的效果,比如安装过程中更换显示图片,显示安装进度等。
下面包含的函数只是rpmlib中与安装程序有关的部分函数接口。
4.1 错误处理
int rpmErrorCode(void);
这个函数返回最后一个失败的rpmlib函数返回的错误码。此函数仅用于rpmErrorSetCallBack()定义的错误回调函数中。
char *rpmErrorString(void);
这个函数返回最后一个失败的rpmlib函数返回的错误串。此函数仅用于rpmErrorSetCallBack()定义的错误回调函数中。
rpmErrorCallBackType rpmErrorSetCallback(rpmErrorCallBackType);
这个函数设置当前错误回调函数,它返回以前设置的错误回调函数
4.2 获得包信息
下列函数用于获得包文件信息。返回的信息以Header结构的形式保存。
int rpmReadPackageInfo(int fd, Header * signatures, Header * hdr);
使用给定fd(表示对应的rpm包)读入头信息和标记信息。如果指定signatures或hdr,则其对应的信息不返回。
int rpmReadPackageHeader(int fd, Header * hdr, int * isSource, int * major, int * minor);
使用给定fd(表示对应的rpm包)读入头信息,以及此包文件是否是[url=http://www.pccode.net].net" class="wordstyle"源码包,包的主、次版本号。
4.3 RPM数据库处理
这一节的函数完成RPM数据库的基本操作。这包括打开和关闭数据库,以及在数据库损坏时进行重建。每个存取RPM数据库的函数都要使用rpmdb结构,它是RPM数据库的句柄。
int rpmdbOpen(char * root, rpmdb * dbp, int mode, int perms);
此过程打开环境变量RPMVAR_DBPATH指定的RPM数据库,返回rpmdb结构。
void rpmdbClose(rpmdb db);
此过程关闭rpmdb结构指定的RPM数据库。
int rpmdbInit(char * root, int perms);
此过程在环境变量RPMVAR_DBPATH指定的位置创建一个新的RPM数据库。如果数据库存在,此函数不做任何操作。
int rpmd