概要
この記事は、以下を目標としています。
- RPM ファイルには何が書かれているのかを調べます。バイナリエディタで
*.rpm
を開いて、どの辺りに何が書かれているのかを探ります - 簡単な Python スクリプトでヘッダをダンプしてみます
- RPM パッケージの依存関係で provides name が使われているのを見ます
とりあえず、以下のダンプを見るだけで雰囲気はわかると思います。
参照したドキュメント
自分が見つけられた中では、Linux Standard Base の以下のドキュメントが一番参考になりました。
- Linux Standard Base Core Specification, Generic Part - 25.2. Package File Format (2015 年)
RPM 公式の以下のドキュメントは図が乗っていてわかりやすいのですが、書きかけで止まっている感じです。
- Description of RPM file format
古いドキュメントのようですが、上よりは以下のほうが詳しそうでした。
RPM ファイル形式の解説記事です。
- Argh-P-M! – Dissecting the RPM file format
使用するサンプルファイル
Remi リポジトリより、以下の rpm ファイルを例として使用します。
このパッケージを選んだのはたまたまです。たまたま、このパッケージの依存関係を調べていた時に、とったメモをベースにして、本記事を作成しました。
以下のコマンドでパッケージの情報が得られます。
$ rpm -qip php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm
Name : php70-php-pecl-imagick
Version : 3.4.4
Release : 17.el7.remi
Architecture: x86_64
Install Date: (not installed)
Group : Unspecified
Size : 522499
License : PHP
Signature : DSA/SHA1, Mon 22 Feb 2021 06:07:07 PM JST, Key ID 004e6f4700f97f56
Source RPM : php70-php-pecl-imagick-3.4.4-17.el7.remi.src.rpm
Build Date : Mon 22 Feb 2021 06:06:05 PM JST
Build Host : builder.remirepo.net
Relocations : (not relocatable)
Packager : Remi Collet
Vendor : Remi's RPM repository <https://rpms.remirepo.net/>
URL : https://pecl.php.net/package/imagick
Bug URL : https://forum.remirepo.net/
Summary : Extension to create and modify images using ImageMagick
Description :
Imagick is a native php extension to create and modify images
using the ImageMagick API.
Package built for PHP 7.0 as Software Collection (php70 by remi).
4つのセクション
rpm ファイルは、4つのセクションに分かれています。
- Lead セクション - 基本情報
- Signature セクション - 署名
- Header セクション - パッケージの詳細情報
- Payload セクション - パッケージに含まれるファイル
2番めの Signature セクションと 3番目の Header セクションは、同じ形式 ( Header Structure
) で書かれています。
最初の Lead Section から見ていきます。
Lead Section - 基本情報
+---+---+---+---+---+---+---+---+---+---+
|M1 |M2 |M3 |M4 |MAJ|MIN| TYPE | ARCH | (more ->)
+---+---+---+---+---+---+---+---+---+---+
+~~~~~~~~~~~~~~~~~~+---+---+
| 66 bytes of NAME |OS |SIG| (more ->)
+~~~~~~~~~~~~~~~~~~+---+---+
+~~~~~~~~~~~~~~~~~~~~~~+
| 16 bytes of RESERVED |
+~~~~~~~~~~~~~~~~~~~~~~+
例:
00000000: edab eedb 0300 0000 0001 7068 7037 302d ..........php70-
00000010: 7068 702d 7065 636c 2d69 6d61 6769 636b php-pecl-imagick
00000020: 2d33 2e34 2e34 2d31 372e 656c 372e 7265 -3.4.4-17.el7.re
00000030: 6d69 0000 0000 0000 0000 0000 0000 0000 mi..............
00000040: 0000 0000 0000 0000 0000 0000 0001 0005 ................ # name ここまで + 0001 + 0005
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060:
- Magic Number
ed ab ee db
で始まる - Version
0300
- RPM ファイルフォーマットのバージョン 3.0 - TYPE
0000
- バイナリが 0、ソースが 1 - ARCH
0001
- x86 architecture は 1 - NAME (66bytes):
php70-php-pecl-imagick-3.4.4-17.el7.remi
+00*
- OS
0001
- Linux は 1 - SIG
0005
- signature type 5 (現在使われているのは 5) - RESERVED
00
* 16 バイト
OS と SIG は図だと 1 バイトのように書かれていますが、2バイトずつあります。
Signature セクション - 署名
この Signature セクションと、次の Header セクションは、両方とも Header Structure という形式で書かれています。
Header Structure 形式は、
- ヘッダ - インデックスの個数とデータのサイズが書かれています
- インデックス (* N個) - データストア内の位置、内容(タグ名)、形式、サイズが書かれています
- データ (* N個) - インデックスから参照されるデータが格納されています
という3部構成になっています (ヘッダがいくつも出てきてわかりにくいですね……)。
Signature セクションのヘッダ
+---+---+---+---+---+---+---+---+---+---+---+---+
|HM1|HM2|HM3|VER| RESERVED | INDEXCOUNT | (more ->)
+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+===============+============+
| STORESIZE | Index Entries | Data Store |
+---+---+---+---+===============+============+
例:
00000060: 8ead e801 0000 0000 0000 0007 0000 0140 ...............@
- Magic Number
8e ad e8
で始まる - VER
1
- バージョン1 - RESERVED 4バイト - 0 で埋める
- INDEXCOUNT 4 バイト -
0000 0007
Index エントリが 7 個ある - STORESIZE 4 バイト -
0000 0140
Data Store の長さは 320 バイト
Signature セクションのインデックスとデータ
1つのインデックスは固定長で 16 バイトあります。上の INDEXCOUNT
にある通り、7 個のインデックスが含まれています。
+---+---+---+---+---+---+---+---+---+---+---+---+
| TAG | TYPE | OFFSET | (more ->)
+---+---+---+---+---+---+---+---+---+---+---+---+
+---+---+---+---+
| COUNT |
+---+---+---+---+
例:
00000070: 0000 003e 0000 0007 0000 0130 0000 0010 ...>.......0.... # 1つ目のインデックス
00000080: 0000 010b 0000 0007 0000 0000 0000 0077 ...............w # 2つ目のインデックス
00000090: 0000 010d 0000 0006 0000 0077 0000 0001 ...........w.... # 3つ目のインデックス
000000a0: 0000 03e8 0000 0004 0000 00a0 0000 0001 ................ # 4つ目のインデックス
000000b0: 0000 03ec 0000 0007 0000 00a4 0000 0010 ................ # 5つ目のインデックス
000000c0: 0000 03ed 0000 0007 0000 00b4 0000 0077 ...............w # 6つ目のインデックス
000000d0: 0000 03ef 0000 0004 0000 012c 0000 0001 ...........,.... # 7つ目のインデックス
000000e0:
それぞれのタグの定義は、このドキュメント にある程度リストアップされています。(全てではありません)
1 つ目のインデックスとデータ
00000070: 0000 003e 0000 0007 0000 0130 0000 0010 ...>.......0.... # 1つ目のインデックス
1つ目のインデックスの内容は以下の通り。
- TAG
3e
- 62:RPMTAG_HEADERSIGNATURES
- TYPE
7
-RPM_BIN_TYPE
バイナリ - OFFSET
130
- 304 バイト目 (データは一番最後の位置にある) - COUNT
10
- 16 バイト
各インデックスは、後続のデータストア内のデータを指しています。
インデックスとデータは、同じ順序で並んでいるとは限りません。この 1 つ目のインデックスは、データストアの最後の項目を指しています。
OFFSET
304 バイト + COUNT
16 バイトで、合計 320 バイト。ヘッダに書かれていたデータストアのサイズ STORESIZE
320 バイトと一致しています。
TAG
と TYPE
により、データストアの中身が RPMTAG_HEADERSIGNATURES
というバイナリであることがわかります。
インデックスが指しているデータの中身は以下の通り。
# 開始位置 e0 + オフセット 130 = 210
00000210: 0000 003e 0000 0007 ffff ff90 0000 0010 ...>............
RPMTAG_HEADERSIGNATURES
の内容は、ドキュメントによると、
The signature tag differentiates a signature header from a metadata header, and identifies the original contents of the signature header.
とのことですが、いまいちよくわかりませんでした。。
ffff ff90
の部分以外はインデックスと一致するので、Signature セクションの終わりを示しているのかもしれません。
2 つ目のインデックスとデータ
00000080: 0000 010b 0000 0007 0000 0000 0000 0077 ...............w # 2つ目のインデックス
- TAG
10b
- 267:RPMSIGTAG_DSA
- TYPE
7
-RPM_BIN_TYPE
バイナリ - OFFSET
0
- 0 バイト目 - COUNT
77
- 119 バイト
2つ目のインデックスに相当するデータは以下の通り。これがデータストアの最初のデータです。
# 開始位置 e0 + オフセット 0 = e0
000000e0: 8875 0400 1102 0035 1621 041e e04c ce88 .u.....5.!...L..
000000f0: a4ae 4aa2 9a5d f500 4e6f 4700 f97f 5605 ..J..]..NoG...V.
00000100: 0260 3374 3b17 1c72 706d 7340 6661 6d69 .`3t;..rpms@fami
00000110: 6c6c 6563 6f6c 6c65 742e 636f 6d00 0a09 llecollet.com...
00000120: 1000 4e6f 4700 f97f 5609 f300 9f63 1f04 ..NoG...V....c..
00000130: 49ca bfee 1ff7 2281 a07c b90b 342a c8c0 I....."..|..4*..
00000140: bf00 9f4d 91aa e114 dc79 6ba9 8650 e7f4 ...M.....yk..P..
00000150: 8854 89e1 cdfb 4163 3530 3863 3966 6364 .T....Ac508c9fcd # 41 まで
中身は、Header セクションの DSA 署名とのこと。
3 つ目のインデックスとデータ
00000090: 0000 010d 0000 0006 0000 0077 0000 0001 ...........w.... # 3つ目のインデックス
- TAG
10d
- 269:RPMSIGTAG_SHA1
- TYPE
6
-RPM_STRING_TYPE
文字列 - OFFSET
77
- 119 バイト目 - COUNT
1
- 文字列はNULL
で終端する可変長の値
COUNT が 1 となっています。文字列 (RPM_STRING_TYPE
) は NULL で終端し、サイズは 1
とだけ書かれるようです。データを読まないと、文字列の長さはわかりません。
# 開始位置 e0 + オフセット 77 = 157
00000150: 8854 89e1 cdfb 4163 3530 3863 3966 6364 .T....Ac508c9fcd
00000160: 3439 6530 6332 3735 6334 3532 6163 3531 49e0c275c452ac51
00000170: 3132 6334 3932 3735 6537 3832 3839 3600 12c49275e782896. # SHA1 文字列 + NULL
中身は Header セクションの SHA1 チェックサムです。値は文字列で c508c9fcd49e0c275c452ac5112c49275e782896
となっています。
4、5 つ目のインデックスとデータ
000000a0: 0000 03e8 0000 0004 0000 00a0 0000 0001 ................ # 4つ目のインデックス
000000b0: 0000 03ec 0000 0007 0000 00a4 0000 0010 ................ # 5つ目のインデックス
- 4つ目
- TAG
3e8
- 1000:RPMSIGTAG_SIZE
- TYPE
4
-RPM_INT32_TYPE
- OFFSET
a0
- 160 バイト目 - COUNT
1
- 1 個
- TAG
- 5つ目
- TAG
3ec
- 1004:RPMSIGTAG_MD5
- TYPE
7
-RPM_BIN_TYPE
バイナリ - OFFSET
a4
- 164 バイト目 - COUNT
10
- 16 バイト
- TAG
RPMSIGTAG_SIZE
は、この後に続く、Header セクションと Payload セクションの合計サイズで、RPMSIGTAG_MD5
は、その MD5 チェックサムです。
4つ目、5つ目のインデックスに相当するデータは以下の通り。
# 開始位置 e0 + オフセット a0 = 180
00000180: 0001 fe90 64f3 99f8 9d9d 524c 34f0 bca8 ....d.....RL4... # SIZE \x1fe90
00000190: 27f0 99df # MD5 64f399f89d9d524c34f0bca827f099df
Header + Payload の合計サイズは \x1fe90
(130,704 バイト) あることがわかります。
これに \x220
(544 バイト) を足すと、rpm ファイルのサイズ 131,248 バイトに一致することが確認できます。544 バイトは Lead + Signature セクションのサイズです。\x220
から Header セクションが開始します。
Header + Payload セクションの MD5 は以下のコマンドで確認できます。冒頭の 544 バイトを飛ばして、以降の md5 を計算します。
# Mac で実行
$ dd if=php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm bs=1 skip=544 | md5
130704+0 records in
130704+0 records out
130704 bytes transferred in 0.235784 secs (554338 bytes/sec)
64f399f89d9d524c34f0bca827f099df
6 つ目のインデックスとデータ
- TAG
3ed
- 1005:RPMSIGTAG_GPG
- TYPE
7
-RPM_BIN_TYPE
バイナリ - OFFSET
b4
- 180 バイト目 - COUNT
77
- 119 バイト
データは以下の通り。
# 開始位置 e0 + オフセット b4 = 194
00000190: 27f0 99df 8875 0400 1102 0035 1621 041e '....u.....5.!.. # \x88 から
000001a0: e04c ce88 a4ae 4aa2 9a5d f500 4e6f 4700 .L....J..]..NoG.
000001b0: f97f 5605 0260 3374 3b17 1c72 706d 7340 ..V..`3t;..rpms@
000001c0: 6661 6d69 6c6c 6563 6f6c 6c65 742e 636f famillecollet.co
000001d0: 6d00 0a09 1000 4e6f 4700 f97f 56c7 3500 m.....NoG...V.5.
000001e0: 9f65 7edd 2d28 d66f e9ec bbb7 d11f e72e .e~.-(.o........
000001f0: 808e 0e6c cd00 9e2f ad0e d80d fd50 e2d0 ...l.../.....P..
00000200: 656b a17f 0c58 acc8 d86a 5800 0008 025c ek...X...jX....\ #\x58 まで
中身は Header + Payload セクションの DSA 署名とのこと。
7 つ目のインデックスとデータ
000000d0: 0000 03ef 0000 0004 0000 012c 0000 0001 ...........,.... # 7つ目のインデックス
- TAG
3ef
- 1007:RPMSIGTAG_PAYLOADSIZE
- TYPE
4
-RPM_INT32_TYPE
- OFFSET
12c
- 300 バイト目 (上の OFFSET + COUNT は 299。アライメントで1バイト空いてる?) - COUNT
1
- 1 個
内容は Payload セクションの、非圧縮状態のサイズです。
# 開始位置 e0 + オフセット 12c = 20c
00000200: 656b a17f 0c58 acc8 d86a 5800 0008 025c ek...X...jX....\ # \x0008025c
Payload を展開したサイズ \x0008025c
(524,892 バイト) は、rpm2cpio
コマンドで確認できます。
$ rpm2cpio php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm > tmp.cpio
$ ls -l tmp.cpio
-rw-r--r-- 1 koseki koseki 524892 May 3 22:16 tmp.cpio
Header セクション - パッケージの詳細情報
Signature セクションと同様、Header セクションも Header Structure 形式で記述されます。
8e ad e8
で始まるヘッダがあり、インデックスが複数個あり、インデックスから参照されるデータストアが続きます。
Header セクションには、パッケージの各種情報が書かれています。
- パッケージ名
- バージョン
- ライセンス
- 依存関係
スクリプトでダンプする
例で使用している rpm パッケージには、Header セクションに 70 個のデータが含まれていました。さすがに手動で読むには多すぎたので、Python スクリプトを書いてダンプしました。
-
dump-rpm-headers.py
- ダンプ結果
Python の struct モジュール を使うことで、固定長のヘッダやインデックスは簡単に読むことができました。可変長のデータストアを読むには、もう少し面倒なコーディングが必要でした。
依存関係
Header セクションの中で、依存関係がどのように表現されているかを見てみます。
この RPM パッケージが提供する機能 (capability) が、
RPMTAG_PROVIDENAME
RPMTAG_PROVIDEFLAGS
RPMTAG_PROVIDEVERSION
に書かれています。
[ 29] RPMTAG_PROVIDENAME
b'config(php70-php-pecl-imagick)'
b'php70-php-imagick'
b'php70-php-imagick(x86-64)'
b'php70-php-pecl(imagick)'
b'php70-php-pecl(imagick)(x86-64)'
b'php70-php-pecl-imagick'
b'php70-php-pecl-imagick(x86-64)'
b'scl-package(php70)'
[ 50] RPMTAG_PROVIDEFLAGS
268435464
8
8
8
8
8
8
32768
[ 51] RPMTAG_PROVIDEVERSION
b'3.4.4-17.el7.remi'
b'3.4.4'
b'3.4.4'
b'3.4.4'
b'3.4.4'
b'3.4.4-17.el7.remi'
b'3.4.4-17.el7.remi'
b''
この RPM パッケージが何に依存しているかが、
RPMTAG_REQUIRENAME
RPMTAG_REQUIREFLAGS
RPMTAG_REQUIREVERSION
に書かれています。
[ 31] RPMTAG_REQUIRENAME
b'/bin/sh'
b'/bin/sh'
b'/bin/sh'
b'config(php70-php-pecl-imagick)'
b'libMagickCore-6.Q16.so.7()(64bit)'
b'libMagickWand-6.Q16.so.7()(64bit)'
b'libc.so.6()(64bit)'
b'libc.so.6(GLIBC_2.14)(64bit)'
b'libc.so.6(GLIBC_2.2.5)(64bit)'
b'libc.so.6(GLIBC_2.3.4)(64bit)'
b'libc.so.6(GLIBC_2.4)(64bit)'
b'php70-php(api)'
b'php70-php(zend-abi)'
b'php70-runtime'
b'php70-runtime(remi)(x86-64)'
b'rpmlib(CompressedFileNames)'
b'rpmlib(FileDigests)'
b'rpmlib(PayloadFilesHavePrefix)'
b'rtld(GNU_HASH)'
b'rpmlib(PayloadIsXz)'
[ 30] RPMTAG_REQUIREFLAGS
256
288
4352
268435464
16384
16384
16384
16384
16384
16384
16384
8
8
16384
0
16777226
16777226
16777226
16384
16777226
[ 32] RPMTAG_REQUIREVERSION
b''
b''
b''
b'3.4.4-17.el7.remi'
b''
b''
b''
b''
b''
b''
b''
b'20151012-64'
b'20151012-64'
b''
b''
b'3.0.4-1'
b'4.6.0-1'
b'4.0-1'
b''
b'5.2-1'
フラグはバージョンの大小比較 (>=
, <=
, ==
, etc.) を表しています。 (25.2.4.4.2. Package Dependencies Attributes)
RPM には、「ファイル名」や「パッケージ名」の他に、Provides Name と呼ばれる名前があることがわかります。これは、そのパッケージが提供する機能 (capability) に名前を付けた「仮想パッケージ名」です。
- ファイル名:
php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm
- パッケージ名:
-
php70-php-pecl-imagick-3.4.4-17.el7.remi
(Lead セクションname
) -
php70-php-pecl-imagick
(RPMTAG_NAME
)
-
- 仮想パッケージ名 (Provides Name):
config(php70-php-pecl-imagick)
php70-php-imagick
php70-php-imagick(x86-64)
php70-php-pecl(imagick)
php70-php-pecl(imagick)(x86-64)
php70-php-pecl-imagick
php70-php-pecl-imagick(x86-64)
scl-package(php70)
Provides Name は /bin/sh
のような名前がついている場合があります。
Requires Name に、パッケージ名や Provides Name を列挙することで、RPM の依存関係が成り立っています。
依存関係を調べるコマンド
RPM ファイルに含まれる Provides Name は以下のコマンドで調べることができます。
$ rpm -q --provides -p php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm
config(php70-php-pecl-imagick) = 3.4.4-17.el7.remi
php70-php-imagick = 3.4.4
php70-php-imagick(x86-64) = 3.4.4
php70-php-pecl(imagick) = 3.4.4
php70-php-pecl(imagick)(x86-64) = 3.4.4
php70-php-pecl-imagick = 3.4.4-17.el7.remi
php70-php-pecl-imagick(x86-64) = 3.4.4-17.el7.remi
scl-package(php70)
Requires Name は、以下のコマンドで調べられます。
$ rpm -q --requires -p php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm
/bin/sh
/bin/sh
/bin/sh
config(php70-php-pecl-imagick) = 3.4.4-17.el7.remi
libMagickCore-6.Q16.so.7()(64bit)
libMagickWand-6.Q16.so.7()(64bit)
libc.so.6()(64bit)
libc.so.6(GLIBC_2.14)(64bit)
libc.so.6(GLIBC_2.2.5)(64bit)
libc.so.6(GLIBC_2.3.4)(64bit)
libc.so.6(GLIBC_2.4)(64bit)
php70-php(api) = 20151012-64
php70-php(zend-abi) = 20151012-64
php70-runtime
php70-runtime(remi)(x86-64)
rpmlib(CompressedFileNames) <= 3.0.4-1
rpmlib(FileDigests) <= 4.6.0-1
rpmlib(PayloadFilesHavePrefix) <= 4.0-1
rtld(GNU_HASH)
rpmlib(PayloadIsXz) <= 5.2-1
yum search
コマンドは Provides Name にはヒットしないようです。代わりに、yum provides
コマンドを使います。
$ yum provides 'config(php70-php-pecl-imagick)'
:
php70-php-pecl-imagick-3.4.4-10.el7.remi.x86_64 : Extension to create and modify images using ImageMagick
Repo : remi-safe
Matched from:
Provides : config(php70-php-pecl-imagick) = 3.4.4-10.el7.remi
:
RPM ファイルの URL は以下のようにして検索できます。
$ yumdownloader --urls 'config(php70-php-pecl-imagick)'
:
http://rpms.remirepo.net/enterprise/7/safe/x86_64/php70-php-pecl-imagick-3.4.4-17.el7.remi.x86_64.rpm
Payload セクション - パッケージに含まれるファイル
最後の Payload セクションには、パッケージに含まれるファイルが、cpio アーカイブ形式 gzip 圧縮で格納されています。