rpm プログラミング with python を読み砕く : 3

引用元ドキュメントから自分勝手に要点のみを抜粋

パッケージをインストール/アップグレードする

RPM システムではパッケージをインストールしたり、アップグレードしたりする方法がたくさんあります。rpm コマンドや C 言語 APIPython API を使用したりすることができます。もし、インストールやアップグレードのために特別なプログラムを書くならば、Python API はとても簡単です。C 言語 API も同様ですが、労力の大半はトランザクションセットの部分です。

インストールやアップグレードをするために、依存性のチェックや依存性に基づくトランザクションの順番など、トランザクション要素を持つトランザクションセットを作成して実行します。ここでは、それらの方法を紹介します。

トランザクションセットを組み立てる

パッケージのインストールやアップグレードは、トランザクションセットのコンテキスト内で処理される必要があります。パッケージのセットをインストール/アップグレードするには addInstall() 関数を呼び出す必要があります。基本的な文法は以下の通りです。

ts.addInstall(header, key_data, mode)

addInstall() 関数を呼び出す際、任意のコールバックキーデータやモードフラグに加えて、ヘッダオブジェクトを渡します。モードフラグは、インストールなら'i'、アップグレードなら'u'を、インストールやアップグレードではなく、トランザクションチェックを有効にさせるために特殊なコードとして'a'があります。'a'フラグは滅多に使用されません。多くのケースでは、"rpm -i" の代わりに "rpm -U" を使ってインストールするのと同じように'u'を使用するべきです。

key_data パラメータは、コールバックを実行するトランザクションセットに引き継がれます。後で「トランザクションを実行する」の項目で説明します。

  • 注意

インストールやアップグレードの代わりにパッケージを削除する場合は addErase() 関数を呼びます。

ts.addErase(package_name)

インストールやアップグレードを行う場合、以下のようなコードを使用します。

h = readRpmHeader( ts, sys.argv[1] )
ts.addInstall(h, sys.argv[1], 'u') 

この例は、コマンドライン上からパッケージのファイル名を受け取り、この後で紹介する readRpmHeader() 関数で使用するヘッダを読み込みます。addInstall() 関数を呼ぶには、ヘッダオブジェクトを追加して、アップグレードのために'u'モードフラグをセットします。sys.argv[1] から取得したパッケージファイルの名前は、コールバック関数を実行する際にトランザクションセットのための任意のデータとして渡されます。

トランザクション要素

トランザクションセットはトランザクション要素から成り立ちます。トランザクション要素は、トランザクションの一部を作成して、個々のトランザクションセット内で、オペレーション(インストールや削除)毎に1つのパッケージを保持します。つまり、トランザクションセット内の操作とパッケージ毎に1つのトランザクション要素があります。トランザクション要素を取得すると、パッケージの依存性セットを取得するのと同様に、ヘッダ内のエントリのチェックするために各々の要素のメソッドを呼び出す事ができます。

以下は、トランザクション要素を呼び出す事ができるインフォメーションメソッドです。大半のメソッドは1つの値を返却します。

メソッド 返却値
A アーキテクチャ
E エポック
O オペレーティングシステム
R リリース番号
V バージョン番号
N パッケージ名
NEVR 名前-エポック-バージョン-リリース
DS 与えられたタグの依存性セット
FI ファイル情報セット

さらに複雑なチェックのために、DS メソッドは与えられたタグの依存性セットを返却します。

ds = te.DS(tag_name)

タグ名に'Providename', 'Requirename', 'Obsoletename', 'Conflictname' の1つを渡します。例えば、

ds = te.DS('Requirename')

FI メソッドはファイル情報を返却します。

fi = te.FI(tag_name)

FI メソッドには 'Basenames' の1つのタグ名を渡さなければいけません。

トランザクション要素を取得するために、トランザクションセットをイテレートする方法を以下に示します。

#!/bin/env python
import rpm, os, sys

def readRpmHeader(ts, filename):
    """ Read an rpm header. """
    fd = os.open(filename, os.O_RDONLY)
    h = ts.hdrFromFdno(fd)
    os.close(fd)
    return h

ts = rpm.TransactionSet()

# Set to not verify DSA signatures.
ts.setVSFlags(rpm._RPMVSF_NOSIGNATURES)

for filename in sys.argv[1:]:
    h = readRpmHeader(ts, filename)
    print "Installing %s-%s-%s" % (h['name'], h['version'], h['release'])
    ts.addInstall(h, filename, 'i')

print "This will install:"
for te in ts:
    print "%s-%s-%s" % (te.N(), te.V(), te.R())
    ts.check()
    ts.order()

print "This will install:"
for te in ts:
    print "%s-%s-%s" % (te.N(), te.V(), te.R())

実行結果。

$ python te.py amarok-xine-1.4.10-1m.mo4.i686.rpm amarok-yauap-1.4.10-1m.mo4.i686.rpm amarok-helix-1.4.10-1m.mo4.i686.rpm amarok-1.4.10-1m.mo4.i686.rpm amarok-libvisual-1.4.10-1m.mo4.i686.rpm
Installing amarok-xine-1.4.10-1m.mo4
Installing amarok-yauap-1.4.10-1m.mo4
Installing amarok-helix-1.4.10-1m.mo4
Installing amarok-1.4.10-1m.mo4
Installing amarok-libvisual-1.4.10-1m.mo4
This will install:
amarok-xine-1.4.10-1m.mo4
amarok-xine-1.4.10-1m.mo4
amarok-yauap-1.4.10-1m.mo4
amarok-helix-1.4.10-1m.mo4
amarok-libvisual-1.4.10-1m.mo4
This will install:
amarok-1.4.10-1m.mo4
amarok-xine-1.4.10-1m.mo4
amarok-yauap-1.4.10-1m.mo4
amarok-helix-1.4.10-1m.mo4
amarok-libvisual-1.4.10-1m.mo4

このスクリプトトランザクションをセットアップして、その要素を出力しますが、決してトランザクションを完了しません。その目的は、ここでは、ただトランザクション内に何があるのかを示すだけです。次の節で説明します。

トランザクション要素をチェックして並び替える

addInstall/addErase() 関数を呼び出した後、全ての要素のトランザクションセットと順番を検証するために2つのメソッドを呼び出す必要があります。

  • 依存性をチェックする

トランザクションセットの依存性をチェックするメソッド

unresolved_dependencies = ts.check() 

全ての依存性が解決されれば None が返却されます。そうでなければ、個々の解決できない依存性の複雑なタプルが返却されます。大抵は、チェックメソッドは None ではなく何かを返却するので、トランザクションを実行する事ができません。

依存性の解決が失敗すると、以下のようなフォーマットの複雑なタプルを依存性情報として返却します。

((N,V,R), (reqN, reqV), needsFlags, suggestedPackage, sense) 

最初のタプルの要素は、インストールしようとしているパッケージの名前、バージョン、リリースのタプルです。次のタプルは、要求(require)された名前とバージョン、又は競合(conflict)した名前とバージョンを返却します。他のファイルや共有ライブラリの依存性の場合、バージョンは None を返します。

needsFlags は、要求(require)又は競合(conflict)について伝えます。含められる bit セッティングは、rpm.RPMSENSE_EQUAL, rpm.RPMSENSE_GREATER と rpm.RPMSENSE_LESS のビットマスクです。例えば、これはその依存性が 4.1 以上のパッケージバージョンのためのものです。

suggestedPackage は依存性を解決します。依存性を解決するパッケージ名が分からなければ、この値は None です。

sense の値に基づいて、依存性が競合(conflict)又は要求(require)を伝える事ができます、rpm.RPMSENSE_CONFLICTS 又は rpm.RPMSENSE_REQUIRES のどちらかです。

例えば、次のタプルは、要求(require)されたパッケージを示します。
(('eruby-devel', '0.9.8', '2'), ('eruby-libs', '0.9.8'), 8, None, 0)

次のタプルは、共有ライブラリを要求(require)している事を示します。
(('jpilot', '0.97', '1'), ('libpisock.so.3', None), 0, None, 0)

  • 注意

このタプルフォーマットは、RPM の将来のバージョンで変更されると思います。この例は RPM 4.1 のフォーマット例で、Python API のオンラインドキュメントで変更点を確認してください。

チェック用に呼び出されるオプションのコールバック関数を渡すことができます。このコールバックは、トランザクションセットの個々の未解決の依存性を取得します。自動的に要求されるパッケージを取り込むようにこのコールバックを使用する事ができます。例えば、基本的な文法は次の通りです。

def checkCallback(ts, TagN, N, EVR, Flags): 

全てのパッケージのデータベースを含む Red Hat RPM データベースパッケージから rpmdb-redhat パッケージを取得します。そのトリックを使用する事で同時に1つ以上のトランザクションをオープンできます。シンプルに "/usr/lib/rpmdb/i386-redhat-linux/redhat" への _dbpath マクロ、又は rpmdb-redhat データベースの場所をセットして、トランザクションセットを作成します。あなたのチェックコールバックは、そのデータベースから現実の RPM データベースの中へ、この余分なパッケージと追加パッケージを検索します。

チェックコールバックはまた、例のようにネットワークアーカイブやディスク上のディレクトリから、依存性を解決するためにパッケージファイルを検索しようとします。以下のコードは、依存性を解決しようとするチェックコールバックのスタブを示します。このコールバックは、他の RPM データベース内に未解決のパッケージを検索しようとするフォーマットをセットします。実際、スケルトンに依存性を解決したいアルゴリズムを書き込む必要があります。

def checkCallback(ts, TagN, N, EVR, Flags):
    if TagN == rpm.RPMTAG_REQUIRENAME:
    prev = ""
    Nh = None

    if N[0] == '/':
        dbitag = 'basenames'
    else:
        dbitag = 'providename'

    # What do you need to do.
    if EVR:
        print "Must find package [", N, "-", EVR, "]"
    else:
        print "Must find file [", N, "]"

    if resolved:
        # ts.addIntall(h, h, 'i')
        return -1

    return 1

コールバックに渡される値に依存すると、あなたのコードは、与えられたファイルや依存性を解決できる領域で提供されたパッケージ、又はそれ自身でパッケージを検索しなければなりません。もし、rpmdb-redhat データベースのような、探すための他の RPM データベースを持っていれば、そのデータベースで必要なパッケージを探すために dbMatch を使用する事ができます。しかしながら、RPM ファイルのディレクトリ上で動作させなければ、パッケージ名とバージョン、リリースからファイル名を組み立てる必要があります。

いかなる順番でもトランザクションセットへパッケージを追加する事ができます。order() メソッドは、正しい順番でパッケージのインストールや削除を保証するためにトランザクションセットを並び替えます。order() メソッドは、依存性の比較によるオブジェクト間の依存性を使用する位相的なソートによって並び替えます。

  • 注意

並び替える前に check() を呼び出さなければいけません

トランザクションを実行する

トランザクションセットの設定後、run() を実行することでトランザクションを実行します。2つのパラメータが必要です。

ts.run(callback, client_data)

callback パラメータは Python の関数でなければいけません。client_data はコールバックへ渡したい任意のデータです。トランザクションセットは1つ以上のパッケージになる可能性があります、つまり、このデータは特定パッケージを指定したものにすべきではありません。

  • 警告

client_data として None を渡す必要はありません、そうすると Python エラーを受け取ります。

トランザクションセットでメソッドを実行するために渡すコールバックは重要です。コールバックは適切に動作させなければいけません、そうしないと、トランザクションは失敗します。トランザクションセットが実行するコールバックの基本的な文法は以下の通りです。

def runCallback(reason, amount, total, key, client_data)

key は addInstall() メソッドを呼び出すために提供したデータです。client_data はメソッドを実行するために渡したデータです。コールバックが呼ばれる毎に、トランザクションセットは reason フラグを提供します。reason パラメータの値は以下の通りです。

reason
rpm.RPMCALLBACK_UNKNOWN 特定の問題
rpm.RPMCALLBACK_INST_PROGRESS インストール進捗中
rpm.RPMCALLBACK_INST_START インストール開始
rpm.RPMCALLBACK_INST_OPEN_FILE パッケージファイルオープン
rpm.RPMCALLBACK_INST_CLOSE_FILE パッケージファイルクローズ
rpm.RPMCALLBACK_TRANS_PROGRESS トランザクション進捗中
rpm.RPMCALLBACK_TRANS_START トランザクション開始
rpm.RPMCALLBACK_TRANS_STOP トランザクション停止
rpm.RPMCALLBACK_UNINST_PROGRESS アンインストール進捗中
rpm.RPMCALLBACK_UNINST_START アンインストール開始
rpm.RPMCALLBACK_UNINST_STOP アンインストール停止
rpm.RPMCALLBACK_REPACKAGE_PROGRESS リパッケージング進捗中
rpm.RPMCALLBACK_REPACKAGE_START リパッケージング開始
rpm.RPMCALLBACK_REPACKAGE_STOP リパッケージング停止
rpm.RPMCALLBACK_UNPACK_ERROR パッケージファイルのアンパックエラー
rpm.RPMCALLBACK_CPIO_ERROR パッケージペイロード取得の cpio エラー

コールバックは、少なくとも2つのケース(rpm.RPMCALLBACK_INST_OPEN_FILE と rpm.RPMCALLBACK_INST_CLOSE_FILE)を操作する必要があります。

rpm.RPMCALLBACK_INST_OPEN_FILE は RPM パッケージファイルをオープンし、ファイルディスクリプタを返却しなければなりません。このファイルディスクリプタはグローバルスコープ、又は他の方法でアクセスできる変数として保持する必要があります。その理由は rpm.RPMCALLBACK_INST_CLOSE_FILE でこのファイルをクローズしなければならないからです。

パッケージのインストール/アップグレードするために有効なコールバックのサンプルを示します。

# Global file descriptor for the callback.
rpmtsCallback_fd = None

def runCallback(reason, amount, total, key, client_data):
    global rpmtsCallback_fd
    if reason == rpm.RPMCALLBACK_INST_OPEN_FILE:
        print "Opening file. ", reason, amount, total, key, client_data
        rpmtsCallback_fd = os.open(client_data, os.O_RDONLY)
        return rpmtsCallback_fd
    elif reason == rpm.RPMCALLBACK_INST_START:
        print "Closing file. ", reason, amount, total, key, client_data
        os.close(rpmtsCallback_fd)

このコールバックは、addInstall() の呼び出すために、パッケージファイル名として client_data が渡されているかどうかを検証しています。このコールバックは、run() メソッドへ渡された client_data を無視します、しかし、これは1つのオブジェクトを渡すのに完璧なスロットではありません。例えば、ファイルディスクリプタグローバル変数として持つのを避けるためにこれを使用する事ができます。

  • パッケージをアップグレードする
#!/usr/bin/env python

import rpm, os, sys

# Global file descriptor for the callback.
rpmtsCallback_fd = None

def runCallback(reason, amount, total, key, client_data):
    global rpmtsCallback_fd
    if reason == rpm.RPMCALLBACK_INST_OPEN_FILE:
        print "Opening file. ", reason, amount, total, key, client_data
        rpmtsCallback_fd = os.open(key, os.O_RDONLY)
        return rpmtsCallback_fd
    elif reason == rpm.RPMCALLBACK_INST_START:
        print "Closing file. ", reason, amount, total, key, client_data
        os.close(rpmtsCallback_fd)
    elif reason == rpm.RPMCALLBACK_TRANS_START:
        print "Transaction start. ", reason, amount, total, key, client_data
    elif reason == rpm.RPMCALLBACK_TRANS_PROGRESS:
        print "Transaction progress...", reason, amount, total, key, client_data
    elif reason == rpm.RPMCALLBACK_TRANS_STOP:
        print "Transaction stop.", reason, amount, total, key, client_data
    elif reason == rpm.RPMCALLBACK_INST_PROGRESS:
        print "Install progress...", amount*100/total, key, client_data
    elif reason == rpm.RPMCALLBACK_UNKNOWN:
        print "Unknown Error.", reason, amount, total, key, client_data
    elif reason == rpm.RPMCALLBACK_UNPACK_ERROR:
        print "Unpack Error.", reason, amount, total, key, client_data
    elif reason == rpm.RPMCALLBACK_CPIO_ERROR:
        print "cpio Error.", reason, amount, total, key, client_data

def checkCallback(ts, TagN, N, EVR, Flags):
    if TagN == rpm.RPMTAG_REQUIRENAME:
        prev = ""
        Nh = None

    if N[0] == '/':
        dbitag = 'basenames'
    else:
        dbitag = 'providename'

    # What do you need to do.
    if EVR:
        print "Must find package [", N, "-", EVR, "]"
    else:
        print "Must find file [", N, "]"

    #if resolved:
    #    # ts.addIntall(h, h, 'i')
    #    return -1

    return 1

def readRpmHeader(ts, filename):
    """ Read an rpm header. """
    fd = os.open(filename, os.O_RDONLY)
    h = ts.hdrFromFdno(fd)
    os.close(fd)
    return h

ts = rpm.TransactionSet()
# Set to not verify DSA signatures.
ts.setVSFlags(-1)
for filename in sys.argv[1:]:
    h = readRpmHeader(ts, filename)
    print "Upgrading %s-%s-%s" % (h['name'], h['version'], h['release'])
    ts.addInstall(h, filename, 'u')
    unresolved_dependencies = ts.check(checkCallback)

if not unresolved_dependencies:
    ts.order()
    print "This upgrade will install:"
    for te in ts:
        print "%s-%s-%s" % (te.N(), te.V(), te.R())
    print "Running transaction (final step)..."
    ts.run(runCallback, 1)
else:
    print "Error: Unresolved dependencies, transaction failed."
    print unresolved_dependencies

実行結果。

$ sudo python rpm_upgrade.py amarok-*1.4.10-1m.mo4.i686.rpm
Upgrading amarok-1.4.10-1m.mo4
Upgrading amarok-helix-1.4.10-1m.mo4
Upgrading amarok-libvisual-1.4.10-1m.mo4
Upgrading amarok-xine-1.4.10-1m.mo4
Upgrading amarok-yauap-1.4.10-1m.mo4
This upgrade will install:
amarok-1.4.10-1m.mo4
amarok-helix-1.4.10-1m.mo4
amarok-libvisual-1.4.10-1m.mo4
amarok-xine-1.4.10-1m.mo4
amarok-yauap-1.4.10-1m.mo4
amarok-1.4.8-1m.mo4
amarok-helix-1.4.8-1m.mo4
amarok-libvisual-1.4.8-1m.mo4
amarok-xine-1.4.8-1m.mo4
amarok-yauap-1.4.8-1m.mo4
Running transaction (final step)...
Transaction start.  32 6 10 None 1
Transaction progress... 16 0 10 None 1
Transaction progress... 16 1 10 None 1
Transaction progress... 16 2 10 None 1
Transaction progress... 16 3 10 None 1
Transaction progress... 16 4 10 None 1
Transaction progress... 16 5 10 None 1
Transaction progress... 16 6 10 None 1
Transaction progress... 16 7 10 None 1
Transaction progress... 16 8 10 None 1
Transaction progress... 16 9 10 None 1
Transaction stop. 64 6 10 None 1
Opening file.  4 0 0 amarok-1.4.10-1m.mo4.i686.rpm 1
Closing file.  2 0 28518084 amarok-1.4.10-1m.mo4.i686.rpm 1
Install progress... 0 amarok-1.4.10-1m.mo4.i686.rpm 1
... ...
Install progress... 100 amarok-1.4.10-1m.mo4.i686.rpm 1
Opening file.  4 0 0 amarok-helix-1.4.10-1m.mo4.i686.rpm 1
Closing file.  2 0 302932 amarok-helix-1.4.10-1m.mo4.i686.rpm 1
Install progress... 0 amarok-helix-1.4.10-1m.mo4.i686.rpm 1
Install progress... 22 amarok-helix-1.4.10-1m.mo4.i686.rpm 1
Install progress... 100 amarok-helix-1.4.10-1m.mo4.i686.rpm 1
Opening file.  4 0 0 amarok-libvisual-1.4.10-1m.mo4.i686.rpm 1
Closing file.  2 0 14396 amarok-libvisual-1.4.10-1m.mo4.i686.rpm 1
Install progress... 99 amarok-libvisual-1.4.10-1m.mo4.i686.rpm 1
Install progress... 100 amarok-libvisual-1.4.10-1m.mo4.i686.rpm 1
Opening file.  4 0 0 amarok-xine-1.4.10-1m.mo4.i686.rpm 1
Closing file.  2 0 149632 amarok-xine-1.4.10-1m.mo4.i686.rpm 1
Install progress... 1 amarok-xine-1.4.10-1m.mo4.i686.rpm 1
Install progress... 44 amarok-xine-1.4.10-1m.mo4.i686.rpm 1
Install progress... 88 amarok-xine-1.4.10-1m.mo4.i686.rpm 1
Install progress... 100 amarok-xine-1.4.10-1m.mo4.i686.rpm 1
Opening file.  4 0 0 amarok-yauap-1.4.10-1m.mo4.i686.rpm 1
Closing file.  2 0 49416 amarok-yauap-1.4.10-1m.mo4.i686.rpm 1
Install progress... 3 amarok-yauap-1.4.10-1m.mo4.i686.rpm 1
Install progress... 92 amarok-yauap-1.4.10-1m.mo4.i686.rpm 1
Install progress... 100 amarok-yauap-1.4.10-1m.mo4.i686.rpm 1