免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 58146 | 回复: 65
打印 上一主题 下一主题

Linux平台软件管理系统设计与规划-中级篇(3)-深入理解和使用yum来管理RPM包 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-12-29 13:36 |只看该作者 |倒序浏览
本帖最后由 duanjigang 于 2013-01-05 11:23 编辑

本篇涉及的内容有:
(1): RPM 在 linux系统上如何运行浅解与要点总结
(2): YUM 服务出现和要素分析
(3): 怎样搭建和使用 yum 服务
(4): createrepo 和 RPM 的索引文件分析
(5): yum 本地cache
(6): yum clean 源码浅析
(7): yum 配置文件详解
(: yum插件剖析与开发


========================================
在写完 “RPM认知” 和 ”RPM制作与SPEC文件“ 两篇后,本来计划再整理学习下一篇 ”RPM如何在linux系统上工作“的,但是考虑了下,因为我对 RPM 的源码还没有阅读,只能说出
大概的动作/流程等,细节言不明,难以从开发人员的角度来分析,只好就粗糙点从运维人员的角度来总结下。

关于 ”RPM如何在linux系统上工作“的内容,就简单总结了几点常用的知识,在本篇开头来讲下。

很庆幸有这个空闲的周末,蜗居西溪旁,小雨漫天下,卧室微冷,聆听听着《夜的钢琴曲》,慢慢总结一下这篇,废话不多,开始。

评分

参与人数 2可用积分 +18 收起 理由
Godbach + 6 很给力!
send_linux + 12 很给力!

查看全部评分

论坛徽章:
0
2 [报告]
发表于 2012-12-29 14:02 |只看该作者
本帖最后由 duanjigang 于 2012-12-29 14:10 编辑

从宏观上来说,linux操作系统(只看OS层面,不谈硬件)就是由静态的文件系统和动态的进程组成的(不知道这样说合适不合适)。文件系统记录了 ”我是什么样的,我有什么“,而进程则能够用文件系统的这些功能动态的改变OS的状态或者对外提供服务。
  其实这样的OS也就能够运行了,为什么还要出现软件包呢?大概是人们为了方便的管理操作系统吧,就跟一个国家都是零散的百姓而没有组织机构一样,那样会很难于管理,为此才把地方划为郡县,省市,县,乡,村,甚至大队~~。理想状况下,这样任意一个人需要服务,都可以直接找自己归属的组织去寻求帮助。工作起来效率提高。
   操作系统当然不能够只是零散的文件组成,也需要对这些文件/程序进行管理,因此,就有了软件包的概念。某些文件属于某个软件包,某个软件包有某些文件。这样的有序性
是提供了管理效率,但是也是有付出的,那就是存储,需要有额外的空间来记录文件和包的对应关系。当然,在一个包内部,这是不需要的,因为任何咚咚都属于所在的这个包,当环境转移到OS层面之后,多个软件包要在OS上共存,有序管理,我们不仅要记录文件到包的关系,有时也要记录包和包之间的关系。
    逐渐的,当这些信息被梳理并且记录时,就形成了软件包数据库的概念。对 RPM 而言就是 RPM数据库。

以前没有包的概念时,是人类直接通过工具操作文件,现在介入了数据库的操作。简单画了个图,表达此改变,如下:


自然而然,我们大概也就猜到了 RPM 是如何在OS上工作的: 用户通过工具(rpm命令)管理软件包,也就是对文件系统进行增删查改,在增删查改的同时(也可能先于或者晚于),也对RPM的数据库进行了增删查改操作,总之,RPM数据库是为了方便用户通过工具来管理系统上的文件系统或者说软件包。

下面我们列举下比较重要的几个方面,都是跟RPM在OS上的管理相关的。

论坛徽章:
0
3 [报告]
发表于 2012-12-29 14:11 |只看该作者
本帖最后由 duanjigang 于 2012-12-29 14:17 编辑

(1)关于 linux 主机上的 RPM 数据库

RPM 在 linux 机器上工作原来是比较明确的,首先有实际的rpm解压缩并且安装后的文件;另外有Rpm 的DB文件,
存储这些安装了的 rpm 的信息,这些库中信息在 RPM进行安装,升级和卸载时进行更新。

RPM命令对两者的操作有: (1):对DB库进行增删查该 (2):对文件系统进行增加和删除和读取。
结合起来看:

(1): RPM的安装/升级,写入DB库,写文件系统,生成安装列表和服务,配置等,执行安装时脚本。
(2): RPM卸载,删除DB库,删除文件系统中归属于该软件包的文件,执行卸载时脚本。
(3): RPM查询, 读取DB库/文件并且显示。



RPM 的在主机上的数据库有好多个,根据不同的应用类型,采用不同的访问方法,可以看下:

  1. file /var/lib/rpm/* | grep Berkeley
  2. /var/lib/rpm/Basenames:      Berkeley DB (Hash, version 8, native byte-order)
  3. /var/lib/rpm/Conflictname:   Berkeley DB (Hash, version 8, native byte-order)
  4. /var/lib/rpm/Dirnames:       Berkeley DB (Btree, version 9, native byte-order)
  5. /var/lib/rpm/Filemd5s:       Berkeley DB (Hash, version 8, native byte-order)
  6. /var/lib/rpm/Group:          Berkeley DB (Hash, version 8, native byte-order)
  7. /var/lib/rpm/Installtid:     Berkeley DB (Btree, version 9, native byte-order)
  8. /var/lib/rpm/Name:           Berkeley DB (Hash, version 8, native byte-order)
  9. /var/lib/rpm/Packages:       Berkeley DB (Hash, version 8, native byte-order)
  10. /var/lib/rpm/Providename:    Berkeley DB (Hash, version 8, native byte-order)
  11. /var/lib/rpm/Provideversion: Berkeley DB (Btree, version 9, native byte-order)
  12. /var/lib/rpm/Pubkeys:        Berkeley DB (Hash, version 8, native byte-order)
  13. /var/lib/rpm/Requirename:    Berkeley DB (Hash, version 8, native byte-order)
  14. /var/lib/rpm/Requireversion: Berkeley DB (Btree, version 9, native byte-order)
  15. /var/lib/rpm/Sha1header:     Berkeley DB (Hash, version 8, native byte-order)
  16. /var/lib/rpm/Sigmd5:         Berkeley DB (Hash, version 8, native byte-order)
  17. /var/lib/rpm/Triggername:    Berkeley DB (Hash, version 8, native byte-order)
复制代码
能够看到,主要是 HASH 和 BTREE 这两种访问方式。(很奇怪,version 8 的是 HASH, version 9的是BTREE ?是应用相关,还纯粹是作者爱好呢?)

RPM 的所有DB,都是从 /var/lib/rpm/Packages 生成的。可以做个实验,把除了 Packages 以外的其它文件全部删掉。
然后重新 rpm --rebuilddb -vv
这些文件都会一一生成,因为构成这几个新的db文件的信息都在Packages中包含了。(如果你把这个文件干掉了,那就悲催了,
曾经遇到有人把这个文件给删掉了,只好从同组配置接近的机器给他拷贝了一个os和arch一样的机器的Packages,重新生成了下db,还好,能继续用,呵呵)。因为这个文件中记录了你的机器安装的所有RPM和文件列表,一旦这个文件丢失了,rpm就不知道你的机器上有哪些rpm了,当你通过
rpm -qf path 查询某个二进制文件属于哪个rpm时,也不会有结果输出。


关于 /var/lib/rpm/Packages 这个 DB 文件的最初来源,我分析(结合猜测)是这样的:

首先,在安装(或者OS安装时)每一个 rpm 文件的时候,rpm 命令都会把要安装的 rpm 包的信息分析出来,包括我们在第一篇看到的所有TAG还有文件列表等信息;
然后会把这些信息写入 /var/lib/rpm/Packages 文件中,如果这个文件不存在,就创建;当删除包时,就会从 /var/lib/rpm/Packages 中
删除与该包相关的所有信息。该文件中记录了所有 rpm 的全量信息,因此,我们通过 rpm 命令查询时,才能够看到那么多灿烂和丰富的信息。

以rpm -qf 和 rpm -ql 为例:
比如,为了能从 xshell 拷贝文件到windows机器,我经常会用 sz/rz命令,但是有些机器上就是没这个命令,
要安装,又不知道它在哪个包,怎么办,这时就需要用 rpm -qf 来查询了:

找一台安装了 sz/rz 命令的机器:

  1. # which sz
  2. /usr/bin/sz
  3. # rpm -qf /usr/bin/sz
  4. lrzsz-0.12.20-22.1
复制代码
喔,我们发现sz和rz是在 lrzsz 这个包中的,然后在需要的机器上 rpm/yum 安装 lrzsz 包即可。

那么,rpm -qf 命令路径 就能查到这个命令属于那个包,这是怎么做到的呢?

首先我们知道安装 lrzsz 时 rpm 会把 这个包的所有信息写入 /var/lib/rpm/Packages 文件,这个包包含的文件列表也会写入
到 这个文件中,然后才会更新生成 /var/lib/rpm 中的其它DB文件(个人理解,其它DB文件就是为了查询加速而用生成的cache.)

然后,当执行

  1. rpm -qf /usr/bin/sz
复制代码
时,rpm 就会去 /var/lib/rpm/Packages 中(或者其它cache文件中)查询包含 /usr/bin/sz 这个路径的
包,当然就查到了是 lrzsz.

有人说,可能查到多个包么?这个是不可能的,因为 rpm 在安装 rpm 文件的时候,也就是往 /var/lib/rpm/Packages
中写数据的时候,会检查这个rpm 中包含的文件列表是否已经在本机安装的其它包中出现了,如果找到了,就会警告: 这个文件和其它某个包中的
某某文件冲突了的错误信息,这个现象做SA的同学应该都见过。

实验:

  1. ls -alt /var/lib/rpm/Packages
  2. -rw-r--r-- 1 rpm rpm 19234816 Dec 27 09:30 /var/lib/rpm/Packages
复制代码
我们能够看到,这个文件只对root 可以写,我们尝试改下它的属性:

  1. chattr +i /var/lib/rpm/Packages
复制代码
设置不可改变属性,然后尝试安装包:

  1. rpm -ivh RPMS/i386/test-baby-1.1-1.i386.rpm
  2. error: cannot open Packages index using db3 - Permission denied (13)
  3. error: cannot open Packages database in /var/lib/rpm
复制代码
因为rpm 没有权限去写 Packages  这个数据库文件。

  1. # chattr -i /var/lib/rpm/Packages
  2. rpm -ivh RPMS/i386/test-baby-1.1-1.i386.rpm
  3. Preparing...                ########################################### [100%]
  4.    1:test-baby              ########################################### [100%]
复制代码
这下就成功了,再次验证了 rpm 在包安装时对 /var/lib/rpm/Packages 的写操作确实是存在的。
同样,你也可以同样方法测试下卸载包时对 /var/lib/rpm/Packages  文件的写操作是否存

论坛徽章:
0
4 [报告]
发表于 2012-12-29 14:20 |只看该作者
本帖最后由 duanjigang 于 2012-12-29 14:25 编辑

(2)关于 /var/log/rpmpkgs 和 /etc/cron.daily/rpm

在linux系统上能够看到

  1. # rpm -qf /etc/cron.daily/rpm
  2. rpm-4.4.2-37.el5
复制代码
/etc/cron.daily/rpm 是 rpm 这个包带的一个每日执行的crontab任务。
/etc/crontab 中配置每天4:02执行 cron.daily 里面的计划任务。
看下这个脚本的内容:

  1. cat  /etc/cron.daily/rpm
  2. #!/bin/sh

  3. /bin/rpm -qa --qf '%{name}-%{version}-%{release}.%{arch}.rpm\n' 2>&1 \
  4. | /bin/sort > /var/log/rpmpkgs
复制代码
就是把 系统上所有 rpm 的列表导出下,排序存储在  /var/log/rpmpkgs 这个文件中。
这个文件有什么作用呢?个人分析应该有以下两个作用:

(1): 可以作为rpm -qa 的 cache文件,如果这个文件在很短时间内刚更新,rpm -qa 直接打印该文件内容即可。
(2): 有人可能已经想到了,它可以帮助恢复你的rpm 数据库文件,如果你不小心把 /var/lib/rpm/Packages  弄坏或者丢了,
但是很幸运,var/log/rpmpkgs 这个文件还在,而且是比较新的,然后你就可以如下恢复你的rpm 数据库了。
   
第一: 找到 系统的安装镜像包,也就是包含了所有OS的RPM列表。
第二:写个脚本,读取 var/log/rpmpkgs 文件,每行是一个rpm 文件,进行安装,这里的安装可不是实际安装。而是
        要采用一个特殊的功能,看下 rpm 的 man 手册:

  1.         --justdb
  2.               Update only the database, not the filesystem.
复制代码
只是更新rpm 的数据库,并不更新系统文件(好像这个安装过程也是 --noscripts的,如果你真的走到这一步,可以检验下,然后告诉我,呵呵)。

论坛徽章:
0
5 [报告]
发表于 2012-12-29 14:27 |只看该作者
本帖最后由 duanjigang 于 2012-12-29 14:30 编辑

(3) rpm/rpmq/yum/rpmDB hang 住,死锁的情况

在实际工作中,有时会碰到执行rpm/yum 等命令时,没有任何反应,进程 hang 住的现象,用 strace -p pid 会发现进程 hang 在对 对futex 的操作上,
据有些文章说这个是berkeley db 支持多进程操作不好导致DB死锁的结果。曾经尝试寻找原因,但是未能跟踪到,只总结了下解决办法,希望对碰到的同学
有帮助。

首先查看是哪些进程,一般无非是 rpm rpmq yum 这三个其中的若干个,直接

  1. sudo killall -9 yum rpm rpmq;
复制代码
然后清除临时库文件:

  1. sudo rm -fr /var/lib/rpm/__db.00*
复制代码
这样就可以了。我一般给同事提供的三板斧就是:

  1. (1): sudo killall -9 rpm rpmq yum  
  2. (2): sudo rm -fr /var/lib/rpm/__db.00*
  3. (3): sudo yum clean all
复制代码
当然,别的原因导致的 hang 住情况,就要另当别论了。视具体情况处理。

论坛徽章:
0
6 [报告]
发表于 2012-12-30 09:33 |只看该作者
本帖最后由 duanjigang 于 2012-12-30 17:54 编辑

开始 yum 的介绍
用 yum 服务 对 RPM 进行管理

类似于 RPM 的出现一样,yum 的出现也是为了方便管理而生,对于软件包的管理,采用 rpm 文件来做载体; 安装,部署等操作时,用 rpm 命令,基本上也够了。
好多人现在可能还处于这个状态,我一年之前也是如此:只知道 rpm 能够查看 rpm 包信息,安装,卸载等操作,小日子得过且过也就够了。后来,接触的环境稍微大点,就遇到一些问题了,大概总结了下,面临的问题已以下几点最为典型:

(1):作为开发人员,我只在32位的 rhel5.4 系统上开发,测试。每次修改代码后都会重新 rpmbuild包, 然后把rpm 交给同事去安装,而且同事的操作步骤大概会是:先卸载原来的rpm, 清空安装目录,然后安装新的rpm,这期间,他还可能还要备份自己的配置文件。忽然有一天,同事报告:这台机器不能安装你的rpm,仔细一看,是x86_64平台的系统,或者是4u8的系统,也可能是6u3的系统。显然,这种不同 arch 和 os release version 的环境,是单用 rpm 面临一类问题.

(2):某人开发的 XXOO.rpm 使用了接近十个第三方库,运行时依赖于这些库的so,因此安装时,也就需要依赖于包含这些库的rpm包, 严格按照规范,他在编写 spec 文件时,写了N 个 require 依赖,结果每次部署时,只要运行命令:

  1. rpm -ivh XXOO.rpm
复制代码
,就会报告找不到依赖的若干个包,面对这样的问题,有的聪明一点的部署人员就会把依赖的包安装的语句写在一个脚本中,先执行这个脚本安装依赖包,然后再安装 XXOO 这个包,这样做即便能够解决问题,还是很麻烦,因为我们拿到rpm后还要分析它的require list, 人为的标明,然后先替他安装。有木有一种机制能够自动检测XXOO 依赖的 rpm 并且尝试进行安装呢?

(3):我开发的软件生成的 RPM 已经有很多版本了,每次为了升级到最新版,都需要记住最新的 RPM 文件的名字,而且是完整的名字,比如:test-daddy-1.1-1.x86_64.rpm,然后再执行

  1. rpm -Uvh http://URL路径/test-daddy-1.1-1.x86_64.rpm
复制代码
才能够安装成功,有没有一种机制,能够用很简单的方式:我只给它 RPM 的名字,就能够自动安装对应包的最新版 rpm 文件呢,而且还能安装指定版本的 rpm 呢?

(4):几台机器需要安装或者管理 rpm 时,直接 rpm -i 安装对应OS的包即可,如果成千上万台机器都需要部署该软件,而且有不同 os 版本,平台不一样,那该怎么办?有同学可能会自己写个脚本拷贝到目标机器去执行,自己检查os和arch,然后安装对应的rpm,这也是一种办法,但是有木有更简洁的方式呢,通用而且灵活,简易操作?


(5):有时候我们并不确定要装哪个版本的包,只想看看已经有哪些 rpm 了,然后再选择安装哪个。。你会怎么做?登录rpm服务器去 find,ls ? 还是高级点,安装个lynx,用

  1. lynx --dump http://URL地址
复制代码
浏览文件列表,然后grep选择,再 RPM 安装? 这样岂不是有些太费事?


对于上面列出的五个问题(也可能更多),yum 给提供了很好的解决方案,或者说功能接口。我想 yum 的出现也是由于最早的rpm使用者遇到了不止上面的五个问题等诸多难题后,总结问题特征,归纳,才设计开发出来的。

下面我们会以庖丁解牛的方式来给您阐述 yum 的强大功能和涉及到的知识点,或者详细或者粗糙,由于我个人能力有限,就只就自己知道和能够搜索到的范围的来说吧,有不对的地方请尽管指出。

论坛徽章:
0
7 [报告]
发表于 2012-12-30 12:00 |只看该作者
本帖最后由 duanjigang 于 2012-12-30 18:01 编辑

yum 服务构成的要素

构成一个完整的 yum 服务,需要以下及部分:

(1):  yum 服务器上的服务仓库 (存储rpm文件和索引文件)
(2):  提供rpm和索引下载的网络服务(http或者ftp服务)
(3):  安装客户端的 yum 命令集 (接收用户输入,从服务器检索/下载文件)
(4):  客户端配置文件以及扩展功能模块。


用架构师大会的PPT <<2012系统架构师大会-Linux平台软件管理系统设计与规划.pptx>> 中的一幅图,来表示下这四个要素的部署关系图:



简单描述一下一个 rpm 从发布进 yum, 到客户端可用的过程:

yum 中 rpm 从发布到可用的过程

1. 开发完成,RPM 已经通过 rpmbuild 或者其它工具制作好,把 rpm 放到 yum 服务器的 rpm 仓库目录下 。
2. 在 yum服务器的 仓库目录 (具体目录位置后面会讲)执行 createrepo 命令,该命令会扫描当前目录(包括子目录)里面的rpm 文件,记录rpm 文件的信息,写入repodata 目录中的索引文件中。这样 yum 中就有了新发布的 rpm 的信息了。
3. 使用者在客户端安装了 yum 命令,通过配置 yum 的客户端配置文件,指定了一个或者若干个安装源, yum 安装/检索包时会去这些安装源上检索索引文件。
4. 使用者在客户端使用 yum install pkgname 方式安装包,yum命令 会把配文件中配置的 yum服务器的地址(安装源)作为目标服务器,通过http/ftp/file服务去 yum 服务器上下载索引文件,然后在索引文件中查找有无指定的rpm(适合在本系统安装),找到的话,就按照配置中的路径组成url地址,下载rpm, 并且在本地安装。
5. 安装后,索引文件和rpm包会cache到本地的cache目录,方便下次使用,而且把安装rpm 的信息记录进本地 rpm 的数据库文件中



为了读者能够实际操作一把,我们给出一个实际的例子:

论坛徽章:
0
8 [报告]
发表于 2012-12-30 12:23 |只看该作者
本帖最后由 duanjigang 于 2013-01-03 13:21 编辑

搭建yum服务的简单实例

(1): yum 服务器为 192.168.1.102, 部署网络服务为 httpd, web 根目录是 /usr/local/cme/web, 监听端口是TCP的81, 把制作好的 rpm test-baby-1.1-1.i386.rpm 放进
/usr/local/cme/web/yum/ 目录下。

(2): 创建索引文件,在/usr/local/cme/web/yum/  目录下执行命令  createrepo, 看到结果如下:

  1. createrepo --update -d -p -o . .
  2. 1/1 - test-baby-1.1-1.i386.rpm                                                  
  3. Saving Primary metadata
  4. Saving file lists metadata
  5. Saving other metadata
复制代码
然后可以看到多了个 repodata 目录。内容如下:

  1. ll repodata/
  2. total 20
  3. -rw-r--r-- 1 root root  271 Dec 29 23:03 filelists.xml.gz
  4. -rw-r--r-- 1 root root  236 Dec 29 23:03 other.xml.gz
  5. -rw-r--r-- 1 root root  755 Dec 29 23:03 primary.xml.gz
  6. -rw-r--r-- 1 root root  951 Dec 29 23:03 repomd.xml
  7. drwxr-xr-x 2 root root 4096 Dec 29 23:03 update-info
复制代码
这些就是 createrepo 命令检索并且读取 rpm 列表后生成的索引文件。

(3): 配置客户端的 yum 配置文件,为了为后面的利用智能 DNS 做 yum 优化章节做铺垫,我们从一开始在网络访问时就采用域名而不是IP地址的方式,
首先在 客户端的 /etc/hosts 中配置

  1. 192.168.1.102  yum.test.com
复制代码
然后,进入 /etc/yum.repos.d 目录,清空该目录下的文件(或者备份起来),创建 test.repo 文件内容如下:

  1. # cat /etc/yum.repos.d/test.repo
  2. [test]
  3. name=just a test reposity
  4. baseurl=http://yum.test.com:81/yum
  5. enabled=1
  6. gpgcheck=0
  7. gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release
复制代码
其中配置的 baseurl 就是告诉 yum 客户端,检索 rpm 的路径是什么,然后 yum 在 list 或者 install 时,就会尝试用这个路径 + repodata 构造一个索引目录,然后就能构造索引文件路径,接着下载索引文件,检索等等,在该例子中索引目录路径为:

  1. http://yum.test.com:81/yum/repodata
复制代码
(4): 在客户端执行命令

  1. # yum clean all
  2. # yum info test-baby
复制代码
能看到输出如下:

  1. Available Packages
  2. Name   : test-baby
  3. Arch   : i386
  4. Version: 1.1
  5. Release: 1
  6. Size   : 4.4 k
  7. Repo   : test
  8. Summary: GNU test-baby
复制代码
显示了这个包的信息,注意到: Repo: test 这个输出,表明这个包是从我们配置的 test.repo 中的源上找到的。当然你也可以配置多个源,而且多个源之间可以有优先级关系,这些具体配置后面再说。这样,我们就能检索到 rpm 了。

(5): 通过命令来安装:

  1. #sudo yum install test-baby
复制代码
输出如下:

  1. Dependencies Resolved

  2. =============================================================================
  3. Package                 Arch       Version          Repository        Size
  4. =============================================================================
  5. Installing:
  6. test-baby               i386       1.1-1            test              4.4 k

  7. Transaction Summary
  8. =============================================================================
  9. Install      1 Package(s)         
  10. Update       0 Package(s)         
  11. Remove       0 Package(s)         

  12. Total download size: 4.4 k
  13. Is this ok [y/N]: y
  14. Downloading Packages:
  15. (1/1): test-baby-1.1-1.i3 100% |=========================| 4.4 kB    00:00     
  16. Running Transaction Test
  17. Finished Transaction Test
  18. Transaction Test Succeeded
  19. Running Transaction
  20.   Installing: test-baby                    ######################### [1/1]
  21. posttrans_hook----------------------------

  22. Installed: test-baby.i386 0:1.1-1
  23. Complete!
复制代码
然后再检查下:

  1. # rpm -qi test-baby
  2. Name        : test-baby                    Relocations: /usr
  3. Version     : 1.1                               Vendor: (none)
  4. Release     : 1                             Build Date: Thu Dec 27 09:11:07 2012
  5. Install Date: Sat Dec 29 23:23:35 2012      Build Host: localhost.localdomain
  6. Group       : Development/Tools             Source RPM: test-baby-1.1-1.src.rpm
  7. Size        : 9395                             License: GPL
  8. Signature   : (none)
  9. Summary     : GNU test-baby
  10. Description :
  11. The GNU wget program downloads files from the Internet using the command-line.

  12. # tail -1 /var/log/yum.log
  13. Dec 29 23:23:35 Installed: test-baby.i386 1.1-1
复制代码
说明 test-baby 确实安装成功了。

有几点要注意下:
(1): 实验环境的 yum 服务器和 客户端都是 rhel5u4 和 i386 系统,你可以试下 服务器和 客户端 为不同平台或者版本的OS时情况如何。
(2): test.repo 中配置的网络下载服务是 httpd,你可以试下ftp服务和本地file 服务。
(3): 我们在该例子中只用到了一个test.repo文件,和 yum.test.com 一个源,你可以尝试配置多个repo 配置和源进行测试看看效果,而且 test.repo 中的其他项也没进行说明。
(4): 在客户端配置中我们只看到了.repo配置文件,其它的 yum.conf 和 插件配置并没有提及,后面会详述。
(5): 我们在安装的时候并没有指定包的版本,而是只指定了包名,多个版本时,如果要安装指定版本,yum install 时要写明版本。

这样,一个基本的yum 系统就搭建起来了。

由浅入深,后面我们会逐一详细的讲述上面yum系统中的各个要素,包括:


(1): createrepo 和 索引文件
(2): yum 的本地 cache
(3): yum clean 源码小解
(4): yum客户端配置文件分析: yum.conf 和 *.repo
(5): yum 的插件功能


论坛徽章:
0
9 [报告]
发表于 2013-01-03 07:51 |只看该作者
本帖最后由 duanjigang 于 2013-01-03 08:51 编辑

之一: createrepo 和 索引文件 探秘

首先重复下 createrepo 出现的原因: yum 服务器要提供rpm包的下载服务,服务器的rpm更新对于客户端是未知的,客户端安装更新包时需要找到符合条件的rpm包,而客户端能够访问服务器的唯一途径就是ftp或者http服务,怎样才能高效的检索到服务器上的rpm最新信息呢?前辈们想出了一个办法,就是对服务器上的所有 rpm 包进行索引化,说白了,就是把所有rpm 的信息收集起来,存储到若干个小文件中,如果客户端需要访问 yum 服务的话,先把索引文件拿下去在索引文件中检索,检索到了,再从服务器下载文件,安装。(直接下载所有rpm 文件去检索的方法肯定是不靠谱的,因为 rpm 的大小差异很大,但是每个 rpm 的描述信息却不会很大).

这样, createrepo 命令便应运而生: 检索指定 rpm 文件的信息生成索引文件。

在前面的例子中,我们都看到了,索引文件会存储在一个叫做 repodata 的目录中。我们来看个实际的例子。

  1. [root@localhost yum]# pwd
  2. /var/www/html/yum
  3. [root@localhost yum]# ls *.rpm
  4. cmeguard-1.1.2-20.i386.rpm  test-cmeadmin-1.1.1-21.i386.rpm
  5. [root@localhost yum]# createrepo --update -d -p .                           
  6. Saving Primary metadata
  7. Saving file lists metadata
  8. Saving other metadata
  9. [root@localhost yum]# ls repodata/
  10. filelists.sqlite.bz2  filelists.xml.gz  other.sqlite.bz2  other.xml.gz  primary.sqlite.bz2  primary.xml.gz  repomd.xml
复制代码
看到有 7 个文件,其实也就是四个文件

  1. repomd.xml
  2. primary.xml
  3. other.xml
  4. filelists.xml
复制代码
因为 .xml.gz 文件是对 xml 文件的压缩,为了减少下载数据量(因为索引文件的下载会占用很大流量的),.sqlite.bz2 文件是对应于 xml 文件的 sqlite 文件压缩后的文件。
因为 createrepo 有个选项是可以把信息存储成sqlite 数据库格式的(默认是xml格式):

  1. -d, --database = generate the sqlite databases.
复制代码
每个 xml.gz 和 sqlite.bz2 文件中存储的原始数据是一样的。

我们先来看看 这几个文件都是作甚用的。

repomd.xml:
this is the file that describes the other metadata files. It is like an index file to point to the other files. It contains timestamps and checksums for the other files. This lets a client download this one, small file and know if anything else has changed. This also means that cryptographically (ex: gpg) signing this one file can ensure repository integrity.

说的通俗点,这个文件就是索引文件的索引文件,回到 createrepo 出现的必要性那段: 我想最初的索引文件很可能就是一个文件,其中记录了所有包的信息,后来前辈们发现:存在一个文件中还是有些臃肿,那不如分开吧,把不同类的信息存储到不同文件中,这样,高级一点的索引文件就成了N个文件,因为它代表了N类信息,再后来,前辈们又想:到底有那几个索引文件,N个到底是几个,这些文件变化木有,谁知道,都要挨个去检查么?于是乎,他们又设计了一个文件,用这个文件来存储这N个索引文件的信息,于是乎,repomd.xml 这个文件
就诞生了~~~ 以后每次检查索引更新时,只需要先下载服务器上的这个文件,从中解析 其它 N 个文件是否改变的特征,如果有改变(这个是需要和本地cache索引做对比的),就下载对应的索引文件,更新本地索引,重新下载包,如果没改变,就用本地索引或者cache的包。
   这样做的目的是什么呢?个人分析最终一个目标:就是为了减小从 yum 服务器 下载到客户端的网络流量,因为每次 yum 查询都可能需要检索索引的,因此,软件设计时,尽量会从节省资源使用考虑,去把下载量降低到最少。

大家一起来看看庐山真面目:

  1. # cat repomd.xml
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <repomd xmlns="http://linux.duke.edu/metadata/repo">
  4.   <data type="other_db">
  5.     <location href="repodata/other.sqlite.bz2"/>
  6.     <checksum type="sha">2976204b0a2782c6646a0ae60f196292e380d314</checksum>
  7.     <timestamp>1357776838</timestamp>
  8.     <open-checksum type="sha">678b1af45a0cd57d2a040a4332cf867f0c1b9288</open-checksum>
  9.     <database_version>10</database_version>
  10.   </data>
  11.   <data type="other">
  12.     <location href="repodata/other.xml.gz"/>
  13.     <checksum type="sha">714bc82c1dab6cb0ec4a4b7ca57f61efeb02f7ad</checksum>
  14.     <timestamp>1357776838</timestamp>
  15.     <open-checksum type="sha">66810c751bbf93fd44548b7c458ad9ac21e73638</open-checksum>
  16.   </data>
  17.   <data type="filelists_db">
  18.     <location href="repodata/filelists.sqlite.bz2"/>
  19.     <checksum type="sha">7cee73ebffec1c8dcc23ffc9dbab3e4919fbeac0</checksum>
  20.     <timestamp>1357776838</timestamp>
  21.     <open-checksum type="sha">a2a0e5fb9c68b5d0137428bab2a19ebdedf83588</open-checksum>
  22.     <database_version>10</database_version>
  23.   </data>
  24.   <data type="filelists">
  25.     <location href="repodata/filelists.xml.gz"/>
  26.     <checksum type="sha">99c9c6bd6f056a95b164db408b75c4e024173718</checksum>
  27.     <timestamp>1357776838</timestamp>
  28.     <open-checksum type="sha">d4d100a8d52e833579954cf901f845e6bf0e7375</open-checksum>
  29.   </data>
  30.   <data type="primary_db">
  31.     <location href="repodata/primary.sqlite.bz2"/>
  32.     <checksum type="sha">41059e8ff1b76b2cf06dba4f05db6a20fe72056f</checksum>
  33.     <timestamp>1357776838</timestamp>
  34.     <open-checksum type="sha">8baee8d8a213efa1c3cccfefa5a4c16de964704a</open-checksum>
  35.     <database_version>10</database_version>
  36.   </data>
  37.   <data type="primary">
  38.     <location href="repodata/primary.xml.gz"/>
  39.     <checksum type="sha">1f816e4886b0c2bdd378f0d0f876c2666b3bcf86</checksum>
  40.     <timestamp>1357776838</timestamp>
  41.     <open-checksum type="sha">79183262b5e9f945f912a41e23e1555c0f3a71af</open-checksum>
  42.   </data>
  43. </repomd>
复制代码
首先,我们能看到它是从 xml 的树形格式存储的,每一个 section 定义为一个 data,如本例,包含了六个 data ,也就是六个文件:

  1. # cat repomd.xml | grep -w "<data"
  2.   <data type="other_db">
  3.   <data type="other">
  4.   <data type="filelists_db">
  5.   <data type="filelists">
  6.   <data type="primary_db">
  7.   <data type="primary">
复制代码
可以看到,每一个 section 就是对一个 索引文件的描述,我们来深入每一个 data 段来看下:

  1. <data type="primary">
  2.     <location href="repodata/primary.xml.gz"/>
  3.     <checksum type="sha">1f816e4886b0c2bdd378f0d0f876c2666b3bcf86</checksum>
  4.     <timestamp>1357776838</timestamp>
  5.     <open-checksum type="sha">79183262b5e9f945f912a41e23e1555c0f3a71af</open-checksum>
  6.   </data>
复制代码
四行数据,都很容易理解:
href: 该文件的路径
checksum: 该文件的校验和,采用 sha 的类型校验和,我们都知道文件校验和有crc32,md5sum,sha1,sha2 等方式,这几种类型校验和的差异可以查阅相关文档,还是蛮有意思的,
之前在研究 ICEPATCH2 的时候,发现 ice 也是用 sha1 值给文件打校验和的。
timestamp 修改时间戳
open-checksum 跟 checksum 类似吧,我没有深究。

好了,repomd.xml 文件介绍完了,我们看看其它几个文件都是干啥的:

论坛徽章:
0
10 [报告]
发表于 2013-01-03 11:48 |只看该作者
本帖最后由 duanjigang 于 2013-01-03 12:09 编辑

primary.xml.[gz]

     this file stores the primary metadata information. This includes information such as:

    name, epoch, version, release, architecture
    file size, file location, description, summary, format, checksums header byte-ranges, etc.
    dependencies, provides, conflicts, obsoletes, suggests, recommends
    file lists for the package for CERTAIN files - specifically files matching: /etc*, *bin/*, /usr/lib/sendmail [1]


可以看到 primary.xml 主要存储包的名字,epoch,version.release等信息。
我们来看下它的内容:

  1. $ zcat primary.xml.gz
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <metadata xmlns="http://linux.duke.edu/metadata/common" xmlns:rpm="http://linux.duke.edu/metadata/rpm" packages="2">
  4. <package type="rpm">
  5.   <name>test-daddy</name>
  6.   <arch>i386</arch>
  7.   <version epoch="0" ver="1.1" rel="1"/>
  8.   <checksum type="sha" pkgid="YES">a9f4ad3086dcfcced210361bc72e4d418144cfcc</checksum>
  9.   <summary>GNU test-daddy</summary>
  10.   <description>The GNU wget program downloads files from the Internet using the command-line.</description>
  11.   <packager/>
  12.   <url/>
  13.   <time file="1357185424" build="1357185424"/>
  14.   <size package="4235" installed="9354" archive="4936"/>
  15.   <location href="test-daddy-1.1-1.i386.rpm"/>
  16.   <format>
  17.     <rpm:license>GPL</rpm:license>
  18.     <rpm:vendor/>
  19.     <rpm:group>Development/Tools</rpm:group>
  20.     <rpm:buildhost>yum.test.com</rpm:buildhost>
  21.     <rpm:sourcerpm>test-daddy-1.1-1.src.rpm</rpm:sourcerpm>
  22.     <rpm:header-range start="280" end="2127"/>
  23.     <rpm:provides>
  24.       <rpm:entry name="test-daddy" flags="EQ" epoch="0" ver="1.1" rel="1"/>
  25.     </rpm:provides>
  26.     <rpm:requires>
  27.       <rpm:entry name="rpmlib(PayloadFilesHavePrefix)" flags="LE" epoch="0" ver="4.0" rel="1" pre="1"/>
  28.       <rpm:entry name="/bin/sh" pre="1"/>
  29.       <rpm:entry name="libc.so.6"/>
  30.       <rpm:entry name="rtld(GNU_HASH)"/>
  31.       <rpm:entry name="baby-is-provided"/>
  32.       <rpm:entry name="libc.so.6(GLIBC_2.0)"/>
  33.       <rpm:entry name="rpmlib(CompressedFileNames)" flags="LE" epoch="0" ver="3.0.4" rel="1" pre="1"/>
  34.     </rpm:requires>
  35.     <file>/usr/bin/test_daddy</file>
  36.   </format>
  37. </package>
  38. <package type="rpm">
  39.   <name>test-girl</name>
  40.   <arch>i386</arch>
  41.   <version epoch="0" ver="1.1" rel="1"/>
  42.   <checksum type="sha" pkgid="YES">82828b38c988399f597cceb14d896de6d36cd1c8</checksum>
  43.   <summary>GNU test-girl</summary>
  44.   <description>The GNU wget program downloads files from the Internet using the command-line.</description>
  45.   <packager/>
  46.   <url/>
  47.   <time file="1357185262" build="1357185262"/>
  48.   <size package="4227" installed="9354" archive="4936"/>
  49.   <location href="test-girl-1.1-1.i386.rpm"/>
  50.   <format>
  51.     <rpm:license>GPL</rpm:license>
  52.     <rpm:vendor/>
  53.     <rpm:group>Development/Tools</rpm:group>
  54.     <rpm:buildhost>yum.test.com</rpm:buildhost>
  55.     <rpm:sourcerpm>test-girl-1.1-1.src.rpm</rpm:sourcerpm>
  56.     <rpm:header-range start="280" end="2119"/>
  57.     <rpm:provides>
  58.       <rpm:entry name="test-girl" flags="EQ" epoch="0" ver="1.1" rel="1"/>
  59.       <rpm:entry name="baby-is-provided"/>
  60.     </rpm:provides>
  61.     <rpm:requires>
  62.       <rpm:entry name="rpmlib(PayloadFilesHavePrefix)" flags="LE" epoch="0" ver="4.0" rel="1" pre="1"/>
  63.       <rpm:entry name="/bin/sh" pre="1"/>
  64.       <rpm:entry name="libc.so.6"/>
  65.       <rpm:entry name="rtld(GNU_HASH)"/>
  66.       <rpm:entry name="libc.so.6(GLIBC_2.0)"/>
  67.       <rpm:entry name="rpmlib(CompressedFileNames)" flags="LE" epoch="0" ver="3.0.4" rel="1" pre="1"/>
  68.     </rpm:requires>
  69.     <file>/usr/bin/test_girl</file>
  70.   </format>
  71. </package>
  72. </metadata>
复制代码
不用多言,看到 xml 的内容,就已经明白了它存储了 rpm 包的哪些信息。

其它三个文件
filelists.xml.[gz]
this file stores the complete file and directory listings for the packages. The package is identified by: name, epoch, version, release, architecture and package checksum id.
看下内容:

  1. $ zcat filelists.xml.gz
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <filelists xmlns="http://linux.duke.edu/metadata/filelists" packages="2">
  4. <package pkgid="a9f4ad3086dcfcced210361bc72e4d418144cfcc" name="test-daddy" arch="i386">
  5.   <version epoch="0" ver="1.1" rel="1"/>
  6.   <file>/usr/bin/test_daddy</file>
  7. </package>
  8. <package pkgid="82828b38c988399f597cceb14d896de6d36cd1c8" name="test-girl" arch="i386">
  9.   <version epoch="0" ver="1.1" rel="1"/>
  10.   <file>/usr/bin/test_girl</file>
  11. </package>
  12. <package pkgid="6a6fbc7e58160ffa71b1f91912dc6eaf1cd6e0ae" name="wget" arch="i386">
  13.   <version epoch="0" ver="1.14" rel="1"/>
  14.   <file>/usr/bin/wget</file>
  15.   <file>/usr/etc/wgetrc</file>
  16.   <file>/usr/share/info/wget.info.gz</file>
  17.   <file>/usr/share/locale/be/LC_MESSAGES/wget.mo</file>
  18.   <file>/usr/share/locale/bg/LC_MESSAGES/wget.mo</file>
  19.   <file>/usr/share/locale/ca/LC_MESSAGES/wget.mo</file>
  20.   <file>/usr/share/locale/cs/LC_MESSAGES/wget.mo</file>
  21.   <file>/usr/share/locale/da/LC_MESSAGES/wget.mo</file>
  22.   <file>/usr/share/locale/de/LC_MESSAGES/wget.mo</file>
  23.   <file>/usr/share/locale/el/LC_MESSAGES/wget.mo</file>
  24.   <file>/usr/share/locale/en_GB/LC_MESSAGES/wget.mo</file>
  25.   <file>/usr/share/locale/eo/LC_MESSAGES/wget.mo</file>
  26.   <file>/usr/share/locale/es/LC_MESSAGES/wget.mo</file>
  27.   <file>/usr/share/locale/et/LC_MESSAGES/wget.mo</file>
  28.   <file>/usr/share/locale/eu/LC_MESSAGES/wget.mo</file>
  29.   <file>/usr/share/locale/fi/LC_MESSAGES/wget.mo</file>
  30.   <file>/usr/share/locale/fr/LC_MESSAGES/wget.mo</file>
  31.   <file>/usr/share/locale/ga/LC_MESSAGES/wget.mo</file>
  32.   <file>/usr/share/locale/gl/LC_MESSAGES/wget.mo</file>
  33.   <file>/usr/share/locale/he/LC_MESSAGES/wget.mo</file>
  34.   <file>/usr/share/locale/hr/LC_MESSAGES/wget.mo</file>
  35.   <file>/usr/share/locale/hu/LC_MESSAGES/wget.mo</file>
  36.   <file>/usr/share/locale/id/LC_MESSAGES/wget.mo</file>
  37.   <file>/usr/share/locale/it/LC_MESSAGES/wget.mo</file>
  38.   <file>/usr/share/locale/ja/LC_MESSAGES/wget.mo</file>
  39.   <file>/usr/share/locale/lt/LC_MESSAGES/wget.mo</file>
  40.   <file>/usr/share/locale/nb/LC_MESSAGES/wget.mo</file>
  41.   <file>/usr/share/locale/nl/LC_MESSAGES/wget.mo</file>
  42.   <file>/usr/share/locale/pl/LC_MESSAGES/wget.mo</file>
  43.   <file>/usr/share/locale/pt/LC_MESSAGES/wget.mo</file>
  44.   <file>/usr/share/locale/pt_BR/LC_MESSAGES/wget.mo</file>
  45.   <file>/usr/share/locale/ro/LC_MESSAGES/wget.mo</file>
  46.   <file>/usr/share/locale/ru/LC_MESSAGES/wget.mo</file>
  47.   <file>/usr/share/locale/sk/LC_MESSAGES/wget.mo</file>
  48.   <file>/usr/share/locale/sl/LC_MESSAGES/wget.mo</file>
  49.   <file>/usr/share/locale/sr/LC_MESSAGES/wget.mo</file>
  50.   <file>/usr/share/locale/sv/LC_MESSAGES/wget.mo</file>
  51.   <file>/usr/share/locale/tr/LC_MESSAGES/wget.mo</file>
  52.   <file>/usr/share/locale/uk/LC_MESSAGES/wget.mo</file>
  53.   <file>/usr/share/locale/vi/LC_MESSAGES/wget.mo</file>
  54.   <file>/usr/share/locale/zh_CN/LC_MESSAGES/wget.mo</file>
  55.   <file>/usr/share/locale/zh_TW/LC_MESSAGES/wget.mo</file>
  56.   <file>/usr/share/man/man1/wget.1.gz</file>
  57. </package>
  58. </filelists>
复制代码
可以跟 rpm 中的实际文件列表进行对比下:

  1. $ rpm -qpl ../test-daddy-1.1-1.i386.rpm
  2. /usr/bin/test_daddy
  3. $ rpm -qpl ../test-girl-1.1-1.i386.rpm
  4. /usr/bin/test_girl
  5. $ rpm -qpl ../wget-1.14-1.i386.rpm
  6. /usr/bin/wget
  7. /usr/etc/wgetrc
  8. /usr/share/info/wget.info.gz
  9. /usr/share/locale/be/LC_MESSAGES/wget.mo
  10. /usr/share/locale/bg/LC_MESSAGES/wget.mo
  11. /usr/share/locale/ca/LC_MESSAGES/wget.mo
  12. /usr/share/locale/cs/LC_MESSAGES/wget.mo
  13. /usr/share/locale/da/LC_MESSAGES/wget.mo
  14. /usr/share/locale/de/LC_MESSAGES/wget.mo
  15. /usr/share/locale/el/LC_MESSAGES/wget.mo
  16. /usr/share/locale/en_GB/LC_MESSAGES/wget.mo
  17. /usr/share/locale/eo/LC_MESSAGES/wget.mo
  18. /usr/share/locale/es/LC_MESSAGES/wget.mo
  19. /usr/share/locale/et/LC_MESSAGES/wget.mo
  20. /usr/share/locale/eu/LC_MESSAGES/wget.mo
  21. /usr/share/locale/fi/LC_MESSAGES/wget.mo
  22. /usr/share/locale/fr/LC_MESSAGES/wget.mo
  23. /usr/share/locale/ga/LC_MESSAGES/wget.mo
  24. /usr/share/locale/gl/LC_MESSAGES/wget.mo
  25. /usr/share/locale/he/LC_MESSAGES/wget.mo
  26. /usr/share/locale/hr/LC_MESSAGES/wget.mo
  27. /usr/share/locale/hu/LC_MESSAGES/wget.mo
  28. /usr/share/locale/id/LC_MESSAGES/wget.mo
  29. /usr/share/locale/it/LC_MESSAGES/wget.mo
  30. /usr/share/locale/ja/LC_MESSAGES/wget.mo
  31. /usr/share/locale/lt/LC_MESSAGES/wget.mo
  32. /usr/share/locale/nb/LC_MESSAGES/wget.mo
  33. /usr/share/locale/nl/LC_MESSAGES/wget.mo
  34. /usr/share/locale/pl/LC_MESSAGES/wget.mo
  35. /usr/share/locale/pt/LC_MESSAGES/wget.mo
  36. /usr/share/locale/pt_BR/LC_MESSAGES/wget.mo
  37. /usr/share/locale/ro/LC_MESSAGES/wget.mo
  38. /usr/share/locale/ru/LC_MESSAGES/wget.mo
  39. /usr/share/locale/sk/LC_MESSAGES/wget.mo
  40. /usr/share/locale/sl/LC_MESSAGES/wget.mo
  41. /usr/share/locale/sr/LC_MESSAGES/wget.mo
  42. /usr/share/locale/sv/LC_MESSAGES/wget.mo
  43. /usr/share/locale/tr/LC_MESSAGES/wget.mo
  44. /usr/share/locale/uk/LC_MESSAGES/wget.mo
  45. /usr/share/locale/vi/LC_MESSAGES/wget.mo
  46. /usr/share/locale/zh_CN/LC_MESSAGES/wget.mo
  47. /usr/share/locale/zh_TW/LC_MESSAGES/wget.mo
  48. /usr/share/man/man1/wget.1.gz
复制代码
每个 <file> 就是一个文件。

other.xml.[gz]

this file currently only stores the changelog data from packages. However, this file could be used for any other additional metadata that could be useful for clients.
看下内容:

  1. $ zcat other.xml.gz
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <otherdata xmlns="http://linux.duke.edu/metadata/other" packages="4">
  4. <package pkgid="6a6fbc7e58160ffa71b1f91912dc6eaf1cd6e0ae" name="wget" arch="i386">
  5.   <version epoch="0" ver="1.14" rel="1"/>
  6. </package>
  7. <package pkgid="a9f4ad3086dcfcced210361bc72e4d418144cfcc" name="test-daddy" arch="i386">
  8.   <version epoch="0" ver="1.1" rel="1"/>
  9. </package>
  10. <package pkgid="82828b38c988399f597cceb14d896de6d36cd1c8" name="test-girl" arch="i386">
  11.   <version epoch="0" ver="1.1" rel="1"/>
  12. </package>
  13. <package pkgid="6ce87802e577736bb8b481c157cb9fb8822f8b88" name="wget-debuginfo" arch="i386">
  14.   <version epoch="0" ver="1.14" rel="1"/>
  15. </package>

  16. </otherdata>
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP