忘记密码   免费注册 查看新帖 | 论坛精华区

ChinaUnix.net

  平台 论坛 博客 认证专区 大话IT 视频 徽章 文库 沙龙 自测 下载 频道自动化运维 虚拟化 储存备份 C/C++ PHP MySQL 嵌入式 Linux系统
最近访问板块 发新帖
楼主: duanjigang

Linux平台软件管理系统设计与规划-进阶篇(2)-rpm生成:rpmbuild 和 spec文件剖析 [复制链接]

论坛徽章:
0
发表于 2012-12-21 13:55 |显示全部楼层
但是/usr/lib/rpm/macro中也有使用%define,这又是怎么回事呢?

论坛徽章:
0
发表于 2012-12-22 11:58 |显示全部楼层
回复 21# lofeng410
这个文件是最早查找的文件,后面的文件中的定义可以把前面文件中的定义覆盖掉。


   

论坛徽章:
0
发表于 2012-12-22 12:02 |显示全部楼层
回复 19# lofeng410

  1. % __debug_package 1
复制代码
这样试下


   

论坛徽章:
0
发表于 2012-12-22 13:43 |显示全部楼层
本帖最后由 duanjigang 于 2012-12-22 14:49 编辑

Macros (宏)

这里是根据自己的理解写点,如果有错误之处请指出。

RPM 不提供那种像C语言一样格式的对过程的宏定义,我们在spec 文件中能看到的都是单个变量的定义,比如:

  1. %define version         1.14
复制代码
但是却很少或者说基本看不到类似于:

  1. #define TR_HLEN         (sizeof(struct trh_hdr)+sizeof(struct trllc))
复制代码
这样定义过程代码的写法。

不过在RPM的 build 过程中有两个动作对于大多数包来说基本上都是一样的操作,因此 RPM 为它们设置了两个宏定义:源代码的解压缩代码的 patch 过程

  1.      The %setup macro, which is used to unpack the original sources.
  2.     The %patch macro, which is used to apply patches to the original sources.
复制代码
%setup 宏在使用时大多数情况都是不加任何选项的,比如:

  1. Source: ftp://ftp.gnomovision.com/pub/cdplayer/cdplayer-1.0.tgz
  2. %prep
  3. %setup
复制代码
这个过程,解释成执行脚本,内容如下:

  1. cd /usr/src/redhat/BUILD
  2. rm -rf cdplayer-1.0
  3. gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
  4. if [ $? -ne 0 ]; then
  5.   exit $?
  6. fi
  7. cd cdplayer-1.0
  8. cd /usr/src/redhat/BUILD/cdplayer-1.0
  9. chown -R root.root .
  10. chmod -R a+rX,g-w,o-w .
复制代码
我们能看到,就是目录切换,代码解压缩,检测解压缩结果,进入目录,修改属主/组和mode.
在这里需要注意几点,在 %setup 不加任何参数时,rpm 命令执行时默认了几个咚咚:
第一个:build 目录是,cdplayer-1.0,并且路径是 /usr/src/redhat/BUILD/cdplayer-1.0
第二:删除了原来的build 目录
第三:对源代码进行了解压缩。
第四:修改属性时需要进入cdplayer-1.0目录。


看到这里你可能会感觉到比较迷惑,难道这些不应该吗?呵呵。是的,不过 rpm 在 %setup 的宏定义中提供了几个执行选项,可以修改默认执行动作中的操作流程。
下来我们一一看下.

-n <name> : 设置build 目录的名字。

因为默认的build 目录名字是 <name>-<version>的方式,但是你也可以通过 -n name 来指定,比如:

  1. %setup -n cd-player
复制代码
执行的脚本过程如下:

  1. cd /usr/src/redhat/BUILD
  2. rm -rf cd-player
  3. gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
  4. if [ $? -ne 0 ]; then
  5.   exit $?
  6. fi
  7. cd cd-player
  8. cd /usr/src/redhat/BUILD/cd-player
  9. chown -R root.root .
  10. chmod -R a+rX,g-w,o-w .
复制代码
我们能看到,默认的build 名称,是被 -n 选项修改了。

-c 创建build目录,并且在解压缩前进入该目录

这一点,个人很有感触,有时候在制作rpm前压制源码文件时,经常会

  1. tar -czf mycode.tar.gz mycode
复制代码
把整个目录打包成一个压缩文件,而有时候则会直接把源代码压缩,而不包括目录

  1. cd mycode
  2. tar -czf mycode.tar.gz *

复制代码
,这时,-c 选项就能帮上忙了,替你建立目录,然后在目录里面解压缩文件。
在该例中,加上 -c 后,解释执行的脚本过程如下:

  1. cd /usr/src/redhat/BUILD
  2. rm -rf cdplayer-1.0
  3. mkdir -p cdplayer-1.0
  4. cd cdplayer-1.0
  5. gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
  6. if [ $? -ne 0 ]; then
  7.   exit $?
  8. fi
  9. cd /usr/src/redhat/BUILD/cdplayer-1.0
  10. chown -R root.root .
  11. chmod -R a+rX,g-w,o-w .
复制代码
如果你的压缩包解压缩后就是白花花的代码,那就应该用上这个选项。

-D: 在解压缩前不删除目录

这种情况的应用场景就是你新解压缩的代码是要被加入到新的目录树中,因此以前的目录不能删除。
对应的解释脚本执行过程如下:

  1. cd /usr/src/redhat/BUILD
  2. gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
  3. if [ $? -ne 0 ]; then
  4.   exit $?
  5. fi
  6. cd cdplayer-1.0
  7. cd /usr/src/redhat/BUILD/cdplayer-1.0
  8. chown -R root.root .
  9. chmod -R a+rX,g-w,o-w .
复制代码
-T: 不进行默认压缩文件的解压缩

有人可能会问:不解压缩代码,那还搞毛啊?先不要着急,我们会接后着后面的 option 一起来看这个 选项的应用场景.
首先看下加上 -T 时 %setup 执行的动作:

  1. cd /usr/src/redhat/BUILD
  2. rm -rf cdplayer-1.0
  3. cd cdplayer-1.0
  4. cd /usr/src/redhat/BUILD/cdplayer-1.0
  5. chown -R root.root .
  6. chmod -R a+rX,g-w,o-w
复制代码
-b <n>: 在进入目录前解压缩第 n 个源码包

看下 openssl 中的source 定义片段:

  1. Source: openssl-fips-%{version}-usa.tar.bz2
  2. Source1: hobble-openssl
  3. Source2: Makefile.certificate
复制代码
RPM 对 Source 的解释是 编号是0的Source,也就是 Source0,然后我们看下在spec 中,如下写的执行结果:

  1. %setup -b 0
复制代码
根据 -b 的解释,它的执行过程如下:

  1. cd /usr/src/redhat/BUILD
  2. rm -rf cdplayer-1.0
  3. gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
  4. if [ $? -ne 0 ]; then
  5.   exit $?
  6. fi
  7. gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
  8. if [ $? -ne 0 ]; then
  9.   exit $?
  10. fi
  11. cd cdplayer-1.0
  12. cd /usr/src/redhat/BUILD/cdplayer-1.0
  13. chown -R root.root .
  14. chmod -R a+rX,g-w,o-w .
复制代码
可以看到对 Source0 的解压缩进行了两次,重复了。
我想这下你应该理解了 -T 的作用了,是为了避免不必要的冗余解压缩,当然这个场景是你直接想用Source0 以后的源码,而不用第一个Source.
然后看下 %setup -T -b 0 结合使用的效果:

  1. cd /usr/src/redhat/BUILD
  2. rm -rf cdplayer-1.0
  3. gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
  4. if [ $? -ne 0 ]; then
  5.   exit $?
  6. fi
  7. cd cdplayer-1.0
  8. cd /usr/src/redhat/BUILD/cdplayer-1.0
  9. chown -R root.root .
  10. chmod -R a+rX,g-w,o-w
复制代码
这样就对了,当然重点是 -b 后面的参数你可以自己设置。

-a n: 在进入目录后解压缩第 n 个源码
这个跟 -b 有些像,不过 -b 是在进入目录前解压缩。反正他们都是跟目录相关的,具体场景自己应该可以去想了,我们只看下执行的动作:

%setup -T -a 0

  1. cd /usr/src/redhat/BUILD
  2. rm -rf cdplayer-1.0
  3. cd cdplayer-1.0
  4. gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
  5. if [ $? -ne 0 ]; then
  6.   exit $?
  7. fi
  8. cd /usr/src/redhat/BUILD/cdplayer-1.0
  9. chown -R root.root .
  10. chmod -R a+rX,g-w,o-w .
复制代码
可以看到没有创建目录直接就 cd 了,因此,它大多与 -c 一起结合使用:
%setup -c -T -a 0

  1. cd /usr/src/redhat/BUILD
  2. rm -rf cdplayer-1.0
  3. mkdir -p cdplayer-1.0
  4. cd cdplayer-1.0
  5. gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
  6. if [ $? -ne 0 ]; then
  7.   exit $?
  8. fi
  9. cd /usr/src/redhat/BUILD/cdplayer-1.0
  10. chown -R root.root .
  11. chmod -R a+rX,g-w,o-w .
复制代码
然后把这些结合起来,看下 %setup 处理多个source 源码包的一个例子:

  1. %setup
  2. %setup -T -D -a 1
  3. mkdir database
  4. cd database
  5. gzip -dc /usr/src/redhat/SOURCES/source-two.tar.gz | tar -xvvf -
复制代码
按照上面的规范,它解释后的动作是:

  1. cd /usr/src/redhat/BUILD
  2. rm -rf cdplayer-1.0
  3. gzip -dc /usr/src/redhat/SOURCES/source-zero.tar.gz | tar -xvvf -
  4. if [ $? -ne 0 ]; then
  5.   exit $?
  6. fi
  7. cd cdplayer-1.0
  8. cd /usr/src/redhat/BUILD/cdplayer-1.0
  9. chown -R root.root .
  10. chmod -R a+rX,g-w,o-w .
  11. cd /usr/src/redhat/BUILD
  12. cd cdplayer-1.0
  13. gzip -dc /usr/src/redhat/SOURCES/source-one.tar.gz | tar -xvvf -
  14. if [ $? -ne 0 ]; then
  15.   exit $?
  16. fi
  17. mkdir database
  18. cd database
  19. gzip -dc /usr/src/redhat/SOURCES/source-two.tar.gz | tar -xvvf -
复制代码
我们能够看到是把 0 代码和 1代码解压缩到同一个目录下,然后在build 目录下建立 database 目录,把 2 代码解压缩到 database 目录下。

上面所有参数结合起来使用使得这个过程以最简洁的方式执行了。

得瑟点个人的感觉:spec 中这样去增加%setup 的多个选项来控制脚本执行的过程有些得不偿失,开发人员有记住这些选项的时间,自己会去研究原始的脚本怎么样写更合适。
不过也算是一种机制,其目的就在于你在使用它时,为用户提供能用实现功能的接口,至于接口的好坏,可能并没做太多考虑,有些烂,呵呵。

论坛徽章:
0
发表于 2012-12-22 14:59 |显示全部楼层
本帖最后由 duanjigang 于 2012-12-22 15:10 编辑

%patch 宏:调用patch 命令来给解压缩后的源码打patch

patch 和 Source 的定义和使用格式基本上一致。这里只看下几个option 和对用的行为.

我们看下 openssl 中的用法:

  1. Patch0: openssl-fips-0.9.8e-redhat.patch
  2. Patch1: openssl-0.9.8a-defaults.patch
  3. Patch2: openssl-0.9.8a-link-krb5.patch
  4. Patch3: openssl-0.9.8b-soversion.patch
  5. Patch4: openssl-0.9.8a-enginesdir.patch
  6. Patch5: openssl-0.9.8a-no-rpath.patch

  7. %patch0 -p1 -b .redhat
  8. %patch1 -p1 -b .defaults
  9. # Fix link line for libssl (bug #111154).
  10. %patch2 -p1 -b .krb5
  11. %patch3 -p1 -b .soversion
  12. %patch4 -p1 -b .enginesdir
  13. %patch5 -p1 -b .no-rpath
复制代码
然后看 patch 的 option.

%patch2 就是调用patch 去应用第二个path: Patch2
等同于

  1. %patch -P 2
复制代码

-p 过滤 #注释行
-b name 设置备份文件的扩展名为 name
-E 删除输出的空文件


当然,对于压缩文件格式的patch文件,默认的动作会进行解压缩,比如
Patch: bother-3.5-hack.patch.gz
执行时的动作是:

  1. echo "Patch #0:"
  2. gzip -dc /usr/src/redhat/SOURCES/bother-3.5-hack.patch.gz | patch -p1  -s

复制代码
到此,spec 中的 宏,介绍结束。

论坛徽章:
0
发表于 2012-12-22 18:04 |显示全部楼层
本帖最后由 duanjigang 于 2012-12-22 19:11 编辑

%filelist

%filelist 这个section 下面列举的所有文件(目录,以及目录内的所有文件)是都会被封装进 rpm 的文件和目录列表。
filelist 段的数据每行表示一个文件或者目录,针对于这个文件/目录,会有一个指令,设置这个文件/目录在被安装机器上将会是什么样子,具有什么属性,最常见的就是
设置文件的数组,属主和模式,还有这个文件是普通文件,还是文档文件,还是配置文件,更新时是否替换等指令。
下面我们一一列举。


文件相关的指令:

文件相关的指令有:

  1. %doc  文档指令
  2. %config 配置文件指令
  3. %attr 文件属性指令
  4. %verify 校验指令
复制代码
%doc 标注一个文件是文档文件(doc),安装后会在rpm的安装机器的db中记录起来。默认的doc 安装目录是 /usr/doc
可以通过修改 rpmrc 文件 中的变量 defaultdocdir  的值来修改此路径。

%config 标注该文件是一个配置文件,如果你设置noreplace 属性的话,包在升级时,不会用新的配置文件覆盖原来包中的配置文件,如果要卸载包的话,配置文件会被保存为 "文件名".save,保留该文件。
看下一个例子
SPEC 文件:

  1. %install
  2. mkdir -p $RPM_BUILD_ROOT/etc
  3. install baby.conf  $RPM_BUILD_ROOT/etc
  4. %files
  5. %defattr(-,root,root)
  6. /usr/bin/test_baby
  7. %config(noreplace) /etc/baby.conf
复制代码
RPM 生成后,我们看下配置文件列表:

  1. rpm -qp RPMS/i386/test-baby-1.1-1.i386.rpm --configfiles
  2. /etc/baby.conf
复制代码
我们进行包安装,查看配置文件:

  1. cat  /etc/baby.conf
  2. this is the origin config file installed
复制代码
然后修改此文件内容为:

  1. this is the origin config file installed
  2. modified by jigang.djg for package 'test-baby-1.1-1'
复制代码
然后,只是修改release 号,重新生成rpm: RPMS/i386/test-baby-1.1-2.i386.rpm
进行软件包升级:

  1. sudo rpm -Uvh RPMS/i386/test-baby-1.1-2.i386.rpm
复制代码
再看下文件内容,发现还是 test-baby-1.1-2.i386.rpm 中自带的配置经过修改后的内容。说明配置文件确实为 "noreplace"状态。
而且有一点注意下:采用 rpm -qf /etc/baby.conf
看到该文件所归属的包,是最新的 rpm .

%attr 指令用来设置文件默认 mode,属主,数组。例如:

  1. %attr(755, root, root) foo.bar
复制代码
%verify 校验指令:

RPM 的 verify 能力能够校验被安装文件的 9 个属性,如下:

   1  Owner (owner) 属主

   2 Group (group) 属组

    3 Mode (mode) 模式

   4  MD5 Checksum (md5) md5和

    5 Size (size) 大小

    6 Major Number (maj) 主设备号

    7 Minor Number (min) 从设备号

    8 Symbolic Link String (symlink) 符号链接

    9 Modification Time (mtime)  修改时间


举一个例子,安装完 test-baby-1.1-2.i386.rpm 后,先 verify 下:

  1. rpm -qvV test-baby
  2. ........ c /etc/baby.conf
  3. ........ d /usr/bin/test_baby
复制代码
然后我们修改下配置文件的mode 为 777,再次 verify

  1. rpm -qvV test-baby
  2. .M...... c /etc/baby.conf
复制代码
是MODE改变了,再次修改下内容,verify

  1. rpm -qvV test-baby
  2. SM5....T c /etc/baby.conf
  3. ........ d /usr/bin/test_baby
复制代码
MODE,SIZE,MD5SUM, mtime 都变了。

论坛徽章:
0
发表于 2012-12-22 19:29 |显示全部楼层
本帖最后由 duanjigang 于 2012-12-22 21:39 编辑

目录相关指令

%docdir  xxx
标注 xxx 为 doc 目录,%docdir 默认包含了系统默认doc 目录 :

  1. /usr/doc, /usr/info, and /usr/man
复制代码
当你的doc 全部放在一个目录下时,可以先安装这个目录,然后用:
%docdir 绝对路径
设置这个目录下的所有文件为doc.

比如:

  1. %docdir /usr/blather
  2. /usr/blather
复制代码
设置 /usr/blather 为 doc 目录。


%dir xxx 指令

之前我们知道,如果在 %files 段的某一行写上一个目录名称,那么该目录底下的所有目录和文件都将会被打包到该 RPM 当中去,但是有时候,你确实只想要这个目录,而不包含该目录下
的其它东西,那么就可以用这个指令。

比如 make 之后, data 目录下有一堆文件,如果你把 data 目录列入 %files 中,那它里面的文件都会打包到rpm中。
但是类似于

  1. %dir /usr/local/data
复制代码
这样的写法,却知会把这个目录打包进去。

-f 文件名: 从文件中读取 %files 的内容


-f myfile
记住两点:
1:myfile 中一行一个文件/目录
2:myfile 中支持%files 的所有指令,在 myfile 中写数据,就当字节码在 %files 下即可。

这个机制能够实现那种动态 file list.因为 myfile 完全可以在 build 的时候生成。

看一个例子,例子中 -f 文件名和 直接写文件,能够混合使用的。

  1. %files latex -f tetex-latex-skel
  2. /usr/bin/latex
  3. /usr/bin/pslatex
复制代码

论坛徽章:
0
发表于 2012-12-22 21:54 |显示全部楼层
本帖最后由 duanjigang 于 2012-12-22 22:03 编辑

%package string 子包创建

这个指令用来在一个spec 文件中来创建除了默认 rpm 包以外的其它rpm包,称为子包。
看下 openssl.spec 中的例子:

  1. package devel
  2. Summary: Files for development of applications which will use OpenSSL
  3. Group: Development/Libraries
  4. Requires: %{name} = %{version}-%{release}, krb5-devel, zlib-devel

  5. %description devel
  6. OpenSSL is a toolkit for supporting cryptography. The openssl-devel
  7. package contains static libraries and include files needed to develop
  8. applications which support various cryptographic algorithms and
  9. protocols.

  10. %package perl
  11. Summary: Perl scripts provided with OpenSSL
  12. Group: Applications/Internet
  13. Requires: perl
  14. Requires: %{name} = %{version}-%{release}
复制代码
表示在openssl.spec 除了定义openssl这个包外,还定义了它的子包 (注意名字是:基础包-子包 构成):

openssl-devel

openssl-perl

看下生成的rpm

  1. /usr/src/redhat/RPMS/x86_64/openssl-0.9.8v-22.1.x86_64.rpm
  2. /usr/src/redhat/RPMS/x86_64/openssl-devel-0.9.8v-22.1.x86_64.rpm
  3. /usr/src/redhat/RPMS/x86_64/openssl-perl-0.9.8v-22.1.x86_64.rpm
复制代码
这下你应该知道了,子包名字的生成规范了。
当然,还可以自己完全重新定义一个子包名称,比如:

  1. %package -n openssl-devel
复制代码
这样也可以。



%package string
后面的所有tag 就都属于 这个子包了,当然其它指令或者hook,只要你加上 string 或者 -n string,都会标识为属于 string 这个子包,比如:

  1. %package perl
  2. Summary: Perl scripts provided with OpenSSL
  3. Group: Applications/Internet
  4. Requires: perl
  5. Requires: %{name} = %{version}-%{release}

  6. %description perl
  7. OpenSSL is a toolkit for supporting cryptography. The openssl-perl
  8. package provides Perl scripts for converting certificates and keys
  9. from other formats to the formats used by the OpenSSL toolkit.

  10. %prep
  11. %setup -q -n %{name}-fips-%{version}

  12. %{SOURCE1} > /dev/null
  13. %patch0 -p1 -b .redhat
  14. %patch1 -p1 -b .defaults
复制代码


  1. %package -n bar

  2. %post -n bar

复制代码

论坛徽章:
0
发表于 2012-12-22 22:24 |显示全部楼层
本帖最后由 duanjigang 于 2012-12-22 22:38 编辑

SPEC 中另外一个组成块就是:
Conditionals (条件)

我们在前面的 TAG 中都知道,通过 excludearch,exclusivearch,excludeos 和 exclusiveos
这些tag 可以标明这个包只能在或者不能在某些平台或者OS上build,但是这个只是规定了能,或者不能,属于比较粗糙的条件性build,我们需要更灵活的条件判断,类似于
C语言的条件判断语句:

  1. if (xxoo)
  2. {

  3. }else{

  4. }

复制代码
还记得你写的多平台代码吗?

  1. if (defined(__WIN32))
  2. {

  3. }else{

  4. }
复制代码
同样,RPM 中也提供了这样的SPEC语法,方便用一个 Spec,一套代码来编译多平台的RPM,而不是采用多个spec 文件(每个平台一个Spec)的方式。
SPEC 中支持的条件语句有:

%ifarch cond1 cond2 ..
%ifnarch cond1 cond2
%ifos os1 os2 ..
$ifnos os1 os2
$else
%endif

比如下面的代码:

  1. %ifarch alpha
  2. make RPM_OPT_FLAGS="$RPM_OPT_FLAGS -I ."
  3. %else
  4. make RPM_OPT_FLAGS="$RPM_OPT_FLAGS"
  5. %endif
复制代码

论坛徽章:
0
发表于 2012-12-22 22:39 |显示全部楼层
本帖最后由 duanjigang 于 2012-12-22 22:45 编辑

到此为止,第二篇告一段落,想起来什么再补上吧,阅读的朋友有什么意见和发现的问题欢迎与我交流。以下是参考的所有文章:
其中不免有参考,拷贝,抄袭之嫌,不管如何,先对原始作者标识敬意,然后希望我们能够学习到别人总结的东西。

1 Taking the Red Hat Package Manager to the Limit
http://www.rpm.org/max-rpm/
2 RPM Guide
http://docs.fedoraproject.org/en ... PM_Guide/index.html
3 Packaging software with RPM, Part 1: Building and distributing packages
http://www.ibm.com/developerworks/library/l-rpm1/
4 Packaging software with RPM, Part 2: Upgrading and uninstalling software
Packaging software with RPM, Part 2: Upgrading and uninstalling software
5 Packaging software with RPM, Part 3: Accommodating software dependencies
https://www.ibm.com/developerworks/library/l-rpm3/
6:http://rpm5.org/docs/ 下载资源。



您需要登录后才可以回帖 登录 | 注册

本版积分规则

SACC2017购票7.8折优惠进行时

2017中国系统架构师大会(SACC2017)将于10月19-21日在北京新云南皇冠假日酒店震撼来袭。今年,大会以“云智未来”为主题,云集国内外顶级专家,围绕云计算、人工智能、大数据、移动互联网、产业应用等热点领域展开技术探讨与交流。本届大会共设置2大主会场,18个技术专场;邀请来自互联网、金融、制造业、电商等多个领域,100余位技术专家及行业领袖来分享他们的经验;并将吸引4000+人次的系统运维、架构师及IT决策人士参会,为他们提供最具价值的交流平台。
----------------------------------------
优惠时间:2017年8月2日前

活动链接>>
  

北京皓辰网域网络信息技术有限公司. 版权所有 京ICP证:060528号 北京市公安局海淀分局网监中心备案编号:1101082001
广播电视节目制作经营许可证(京) 字第1234号 中国互联网协会会员  联系我们:
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP