玩式草子─ソフトウェアとたわむれる日々

第32回 SQLiteでRDB再入門[その2]

この記事を読むのに必要な時間:およそ 6 分

前回紹介したように,しばらくPythonからSQLiteをイジってみたところ,本格的なRDBの機能を思ったよりも簡単に使えることがわかりました。そこで,実際にバイナリファイルの依存性情報を扱うためのコードを書いてみることにしました。

ざっと考えて,必要な機能は以下の3つになるでしょう。

  • ①システム上の全てバイナリファイルの依存性情報を調べる
  • ②その情報をデータベースに登録する
  • ③バイナリファイルや共有ライブラリの名前からデータベースを検索する

このうち,①と②は一連の処理なので一つのスクリプト,③は別のスクリプトにしておくのが便利そうです。

もっとも,最終的には2本のスクリプトにするにしても,筆者がPythonのオンラインドキュメントを首っぴきでないとコードを書けないレベルなので,もう少し小さな部分から書き出すことにしました。

依存性情報データベースの設計

前回紹介したように,あるバイナリファイルが参照している共有ライブラリはlddコマンドで調べることができます。

$ ldd /usr/bin/sed 
    linux-vdso.so.1 =>  (0x00007fff3a35b000)
    libacl.so.1 => /usr/lib64/libacl.so.1 (0x00007f526a0c6000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f5269d5c000)
    libattr.so.1 => /usr/lib64/libattr.so.1 (0x00007f5269b58000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f526a2cd000)

一方,今回利用しようとしているSQLiteはRDBタイプのデータベースなので,データは(テーブル)の形に記録します。

さて,上記のような依存関係の情報は,どのような表に記録するのが便利でしょう? 単純に「バイナリファイルごとに参照しているライブラリを記録する」なら,以下のような形になりそうです。

バイナリファイル共有ライブラリ1共有ライブラリ2共有ライブラリ3共有ライブラリ4 ...
/usr/bin/sedlinux-vdso.so.1libacl.so.1libc.so.6libattr.so.1....

こういう風に記録するとバイナリファイルと共有ライブラリの対応関係は明確になりますが,参照する共有ライブラリの数はバイナリファイルごとに違うので,行ごとに欄の数がまちまちになりそうです。

行ごとに欄の数が異なっていると,共有ライブラリからそれを参照しているバイナリファイルを調べる際などに厄介でしょう。そこで行数はだいぶ大きくなるものの,それぞれの共有ライブラリごとに1行にまとめることにしました。

バイナリファイル共有ライブラリ
/usr/bin/sedlinux-vdso.so.1
/usr/bin/sedlibacl.so.1
/usr/bin/sedlibc.so.6
/usr/bin/sedlibattr.so.1
........

こういう形で整理するならば,共有ライブラリ名だけでなく実際のパス名も加えた方が便利でしょう。また,バイナリファイルも日常使うコマンド名だけでも引けるようにしておく方が便利そうです。そこでデータベースは各行が4つの欄を持つ表の形で記録することにしました。

コマンド名ファイル名共有ライブラリ名ライブラリへのパス
sed/usr/bin/sedlinux-vdso.so.1
sed/usr/bin/sedlibacl.so.1/usr/lib64/libacl.so.1
sed/usr/bin/sedlibc.so.6/lib64/lib.so.6
sed/usr/bin/sedlibattr.so.1/usr/lib64/libattr.so.1
sed/usr/bin/sed/lib64/ld-linux-x86-64.so.2

この例のうち,linux-vdso.so.1はカーネルが提供する仮想的な共有ライブラリなのでライブラリへのパスは存在しません。また,ld-linux-x86-64.so.2はバイナリファイルと共有ライブラリをメモリ上にロードして実行可能な状態にするローダで,正確に言えば共有ライブラリとは異なります。

データベース作成スクリプト

前節で示した表を実現するために,こんなPythonスクリプトを書いてみました。このスクリプトでは,SQLiteのデータベースをdepends.sql3とし,先に紹介した依存情報を収める表をdependsとしています。

 1: #! /usr/bin/python
 2: 
 3: import sqlite3
 4: 
 5: def init_db(dbname):
 6:     conn = sqlite3.connect(dbname)
 7:     conn.execute('''CREATE TABLE depends
 8:        (base TEXT, path TEXT, soname TEXT, realname TEXT)''')
 9:     conn.close
10: 
11: def insert_db(dbname, t):
12:     conn = sqlite3.connect(dbname)
13:     try:
14:         print "inserting ", t
15:         conn.execute('INSERT INTO depends VALUES(?, ?, ?, ?)', t)
16:         conn.commit()
17:     except sqlite3.Error, e:
18:         print "An error occurred:", e.args[0]
19:         conn.rollback()
20: 
21: def main():
22:     dbname = './depends.sql3'
23:     init_db(dbname)
24:     data = (('sed', '/usr/bin/sed', 'linux-vdso.so.1', ''),
25:             ('sed', '/usr/bin/sed', 'libacl.so.1', '/usr/lib64/libacl.so.1'),
26:             ('sed', '/usr/bin/sed', 'libc.so.6', '/lib64/libc.so.6'),
27:             ('sed', '/usr/bin/sed', '', '/lib64/ld-linux-x86-64.so.2'))
28: 
29:     for i in data:
30:         insert_db(dbname, i)
31: 
32: if __name__ == "__main__":
33:     main()

1行目はこのスクリプトを解釈するためのコマンドで,このスクリプトに実行属性を付けて,実行可能にした際に起動すべきコマンド(/usr/bin/python)を指定しています。3行目はPythonに標準添付されているsqlite3モジュールを読み込みます。

5行目から9行目がSQLiteのデータベースにdependsという表を作る処理で,この手順をinit_db()という関数にしています。init_db()では,指定したSQLiteのデータベース(この例では22行目で指定しているdepends.sql3)にSQLコマンドを発行してdependsという表を作ります。この表は,先に示した共有ライブラリごとに1つの行で,各行が4つの欄を持ちます。

各欄の名称を先の例と対応させるとこういう形になります。

コマンド名ファイル名共有ライブラリ名ライブラリへのパス
basepathsonamerealname

7行目と8行目で使っている'''(トリプルクォート)は行の途中の改行を無視させるための指定です。Pythonでは,'''で括った文字列中では,改行コードをエスケープせずに書くことができます。

11行目から19行目が,このdependsという表にデータを登録するための関数(insert_db())です。この関数は,引数としてSQLiteのデータベース名と登録すべきデータのタプルを受け取って,そのデータを表に登録します。

「タプル(tuple)⁠というのはPythonのデータ型の1つで,いくつかの要素を1つにまとめた形式のデータです。このようなデータ形式は一般に「リスト」と呼ばれますが,Pythonでは内容が変更可能なリスト型と,変更不可能なタプル型を区別して利用しています。

この関数では,12行目でSQLiteのデータベースと接続し,15行目でデータをdependsという表に登録しようとします。13行目のtry:は,登録処理を試してみて,うまく行かなかった場合には17行目からのエラー処理部を実行するという指定です。

21行目から30行目までがこのスクリプトのmain()部で,SQLiteのデータベース名(depends.sql3)init_db()に渡して初期化(表を作成)し,24から27行目までに作ったサンプルデータのタプルを29行目からのループでinsert_db()に渡してデータベースに登録しています。

32行目と33行目はPython的なお約束ですが,このスクリプトを起動した際にmain()関数を実行するための指定です。

このスクリプトを script01.py いう名前で保存して実行すると,同じディレクトリにdepends.sql3というSQLiteのデータベースファイルが作成され,そこに24行目から27行目に登録したサンプルデータが登録できました。

$ python script01.py
inserting  ('sed', '/usr/bin/sed', 'linux-vdso.so.1', '')
inserting  ('sed', '/usr/bin/sed', 'libacl.so.1', '/usr/lib64/libacl.so.1')
inserting  ('sed', '/usr/bin/sed', 'libc.so.6', '/lib64/libc.so.6')
inserting  ('sed', '/usr/bin/sed', '', '/lib64/ld-linux-x86-64.so.2')

depends.sql3をsqlite3コマンドで調べてみると,このスクリプトで登録したデータを確認できます。

$ sqlite3 depends.sql3 
SQLite version 3.7.10 2012-01-16 13:28:40
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> select * from depends;
sed|/usr/bin/sed|linux-vdso.so.1|
sed|/usr/bin/sed|libacl.so.1|/usr/lib64/libacl.so.1
sed|/usr/bin/sed|libc.so.6|/lib64/libc.so.6
sed|/usr/bin/sed||/lib64/ld-linux-x86-64.so.2
sqlite> select * from depends where soname like "%acl%";
sed|/usr/bin/sed|libacl.so.1|/usr/lib64/libacl.so.1

これでデータベースに登録する部分ができました。

著者プロフィール

こじまみつひろ

Plamo Linuxとりまとめ役。もともとは人類学的にハッカー文化を研究しようとしていたものの,いつの間にかミイラ取りがミイラになってOSSの世界にどっぷりと漬かってしまいました。最近は田舎に隠棲して半農半自営な生活をしながらソフトウェアと戯れています。

URLhttp://www.linet.gr.jp/~kojima/Plamo/index.html

コメント

コメントの記入