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

第42回Plamo Linuxのビルドスクリプトとパッケージ管理ツールその4]

前回はニュース速報的にPlamo Linux 5.0リリースの話題を取りあげましたが、今回はPlamo Linuxのパッケージ管理の話題に戻り、前々回に紹介し残したinitpkgの機能について紹介しましょう。

パッケージの初期化問題

以前、Plamo Linuxがパッケージ形式として採用しているtgz/txz形式は、パッケージ用の機能を考慮した形式ではなく、インストールすべきファイルをtarでまとめて圧縮しただけだ、という話をしました。

一方、インストール時に、必要なファイルを適切な場所に置く以上の処理を必要とするソフトウェアもあります。たとえば、デーモンとして常駐するタイプのソフトウェアでは、セキュリティの観点から、専用のユーザアカウントを作って、その権限でしか動作しないように設計されていることがよくあります。

そのようなソフトウェアをパッケージとしてインストールするには、必要なユーザアカウントが既に存在するかを確認し、無ければ作成しなければなりません。パッケージ用として考慮されていないtgz/txz形式の場合、このような処理にも工夫が必要です。

先に紹介したように、tgz/txz形式の場合、tarで固めたパッケージを展開後、そのパッケージにinstall/doinst.shというスクリプトが含まれていれば、シェルスクリプトとしてそれを実行する、という機能があります。そこで、必要な処理をこのスクリプトに組み込む方法が考えられますが、ユーザアカウントを追加する操作を単に組み込むだけではうまく行きません。

というのも、Plamo Linuxも含め、たいていのLinuxディストリビューションでは、DVD等から起動されたインストーラ環境は、RAMディスクをルートディレクトリとする独立したLinux環境として動作しているので、インストール先の環境(HDD上のパーティション)からuseradd等のコマンドを発行しても、それは動作中のインストーラ環境に反映されてしまい、/mnt等にマウントされたインストール先の環境には反映されません。

また、コマンドやライブラリのパスはインストーラ環境を向いているので、そこには発行されたコマンドや必要なライブラリが存在しないこともあります。通常、インストーラ環境にはインストールに必要な最小限のコマンドしか用意されていないので、専用のコマンドを必要とする初期化処理は実行できないでしょう。

この種の問題を解決するには、インストール先の環境にchrootする方法が考えられます。chrootは、ファイルシステムの基点となるルートディレクトリを一時的に切り替えるコマンドで、インストール先の環境にchrootすれば、そこをルートディレクトリとして必要なコマンドやライブラリが検索されます。この機能を使えば、インストール先のバイナリファイルを起動して、インストール先の設定ファイルを書き変えることが可能です。

rpmやdeb形式のように、パッケージ自体が依存関係情報を持っていて、あるパッケージをインストールしようとすると、そのパッケージが依存しているパッケージも自動的にインストールされるような環境では、起動しようとするコマンドが必要とするライブラリもあらかじめインストールされているのでこの方法で十分でしょう。

ところが、tgz/txz形式ではパッケージ自体に依存関係情報を記録できないので、それぞれのパッケージは依存関係の有無に関わらずにインストールされます。その結果、あるパッケージのコマンドを動かそうとしても、それに必要なライブラリがまだインストールされていないことがあります。

このような問題を回避するために、Plamo Linuxではパッケージをカテゴリという単位に分けてインストールする順番を大まかに制御しているものの、GTKやQtを使うGUIツールの複雑な依存関係までは制御しきれていません。

initpkgの仕組み

そのためPlamo Linuxでは、パッケージごとにchrootしてコマンドを実行する方法は取らず、その種の配慮が必要なコマンドは、インストールした環境を最初に起動した時に実行することにしました。

具体的には、インストールに使う/sbin/installpkgコマンドを改造し、install/doinst.shスクリプト中に"%% begin"で始まる行があれば、そこから"%% end"までの部分を切り出して/var/initpkg/以下に保存し、installpkgではdoinst.shの残りの部分のみを実行するようにしました。

一方、/var/initpkg/以下に切り出した部分を実行するのは/etc/rc.d/rc.initpkgというスクリプトです。このスクリプトは、システムの起動時に/etc/rc.d/rc.Mから呼び出されるので、/var/initpkg/以下に切り出した部分の処理が実行されるのは、インストールが終了して初めて起動する時になります。

この処理の具体的な例として、gtk+_2-2.24.13-x86_64-P1.txzのinstall/doinst.shを見てみましょう。


 1  ( cd usr/lib64 ; rm -rf libgdk-x11-2.0.so.0 )
 2  ( cd usr/lib64 ; ln -sf libgdk-x11-2.0.so.0.2400.13 libgdk-x11-2.0.so.0 )
 3  ( cd usr/lib64 ; rm -rf libgdk-x11-2.0.so )
 4  ( cd usr/lib64 ; ln -sf libgdk-x11-2.0.so.0.2400.13 libgdk-x11-2.0.so )
 5  ( cd usr/lib64 ; rm -rf libgtk-x11-2.0.so.0 )
 6  ( cd usr/lib64 ; ln -sf libgtk-x11-2.0.so.0.2400.13 libgtk-x11-2.0.so.0 )
 7  ( cd usr/lib64 ; rm -rf libgtk-x11-2.0.so )
 8  ( cd usr/lib64 ; ln -sf libgtk-x11-2.0.so.0.2400.13 libgtk-x11-2.0.so )
 9  ( cd usr/lib64 ; rm -rf libgailutil.so.18 )
10  ( cd usr/lib64 ; ln -sf libgailutil.so.18.0.1 libgailutil.so.18 )
11  ( cd usr/lib64 ; rm -rf libgailutil.so )
12  ( cd usr/lib64 ; ln -sf libgailutil.so.18.0.1 libgailutil.so )
13  #%% begin initialize 
14  gtk-query-immodules-2.0 > /etc/gtk-2.0/gtk.immodules
15  #%% end

installpkgは、パッケージを展開後、このファイルを調べ、13行目から15行目の部分を/var/log/initpkg/gtk+_2というファイルに切り出してから、残りの1行目から12行目までのシンボリックリンク作成処理を実行します。

DVD等から起動した新規インストールの場合、切り出された部分はそのまま放置されます。そして、新しくインストールした環境が初めて起動した際、システムの起動スクリプトの一部である/etc/rc.d/rc.initpkgから実行されて、そこで必要な初期化処理が実行されるわけです。

Plamo Linuxでは、install/doinst.shをインストールスクリプトと呼んでおり、それと区別する必要がある場合、この"#%% begin"から"#%% end"で括られた部分をinitpkgスクリプトと呼んでいます。

rc.initpkgから実行されたinitpkgスクリプトは、正常終了すれば/var/log/initpkg.log/へ移されます。正常に終了しなかった場合はそのまま/var/log/initpkg/に残され再度rc.initpkgが起動した際に実行が試みられます。

追加インストールの場合、パッケージをインストールする順番によってはinitpkgスクリプト実行時に必要なライブラリが揃っていないことがあります。そのため、必要なライブラリが無い等の理由で失敗したスクリプトは、他のパッケージをインストールする度に繰り返し実行され、必要なライブラリが揃って実行が無事完了すれば、その時点で/var/log/initpkg.log/へ移されるわけです。

このような仕様のため、手動でパッケージをインストールする場合、足りないライブラリがあったりすると、initpkgスクリプトが残ったままになって、新しいパッケージのインストールやシステムの起動のたびにエラーメッセージが表示される、という状態になることがあります。このような場合は/var/log/initpkg/に残っているファイルをチェックして、必要なライブラリ等を追加するようにしてください。

実行が終了したinitpkgスクリプトは/var/log/initpkg.log/に移されているので、それぞれのパッケージがどのような初期化処理を行ったのかも簡単に確認できます。

たとえば、各種スキャナを操作するsane_backendsパッケージの初期化処理は、/var/log/initpkg.log/sane_backendsに記録されており、以下のような、⁠scannerグループがあるかをチェックして、無ければ新しく作る⁠といった内容になっています。

1 #%% begin initialize 
2 grp_chk=`grep scanner /etc/group`
3 if [ "$grp_chk.x" = ".x" ]; then
4    echo "adding scanner group"
5    groupadd -g 46 scanner
6 fi
7 #%% end

このディレクトリには、他にもさまざまなパッケージの初期化処理が記録されているので、興味ある人は調べてみてください。

makepkgとinitpkg

initpkgの処理は,パッケージ作成時にinstallディレクトリに用意するdoinst.shに直接書き込んでしまってもいいのですが、その際は処理の部分を"#%% begin"と"#%% end"で括る必要があります。

多数のビルドスクリプトを作っていると、ついこの手の指定は忘れがちになるので、最近のmakepkgコマンドではinstallディレクトリにinitpkgというファイルがあれば、自動的にそれを"#%% begin"と"#%% end"で括って、doinst.shに取り込むような処理を追加しています。この機能を使えば、ビルドスクリプトはinstall/initpkgというファイルに初期化時に行いたい処理を書き込むだけで済みます。たとえばgtk+_2のビルドスクリプトは、このような形でinitpkgを作っています。

195 ######################################################################
196 # * make install でコピーされないファイルがある場合はここに記述します。
197 ######################################################################
198   mkdir -p $docdir/$src
199   mkdir -p $P/install
200   cat <<"EOF" > $P/install/initpkg
201 gtk-query-immodules-2.0 > /etc/gtk-2.0/gtk.immodules
202 EOF

最終的にはマージされてinstall/doinst.shというファイルの一部になるものの、独立したinstall/initpkgを使えば、シンボリックリンクの再生成処理とパッケージの初期化処理を区別しやすくなるので、ビルドスクリプトのミスなどもチェックしやすくなります。

initpkgを導入する以前は、この手の初期化処理が必要なパッケージの場合、install/doinst.shの中で直接インストール先の環境にchrootして処理を行うような記述をしていました。そのためには、初期化に使うコマンドに必要なライブラリなどを事前に揃えておく必要があり、01_...や02_...といったプレフィックスをパッケージ名に付けて、インストールの順番を制御したりしていました。initpkgを使うことでそのような小細工は不要になり、ビルドスクリプトも初期化処理の部分を明示できるようになりました。

このように紹介するとinitpkgはずいぶん複雑な処理をしているように聞こえそうですが、実際は、/sbin/installpkgではsedでdoinst.shからinitpkgの部分を切り出し/etc/rc.d/rc.initpkgでは/var/log/initpkg/にファイルがあれば実行する/sbin/makepkgではinstall/initpkgをdoinst.shに取り込むといった単純な処理を組み合わせているに過ぎません。

自分のニーズを自力で解決できるのがOSSの魅力のひとつです。initpkgのように、パッケージ管理上の問題が、簡単、かつシンプルな形で解決できた時は、難しいパズルが解けたような爽快感を感じます。筆者にとっては、この感覚がOSSを使い続ける原動力になっているようです。

パッケージ管理の裏技

さて、今回でビルドスクリプトとパッケージ管理ツールに関する一連の話題は終了するので、最後に少し裏技的な機能を紹介しておきましょう。

Plamo Linuxの場合、紹介してきたように、パッケージの有無は/var/log/packages/にあるインストール済みパッケージのベース名でチェックしています。そのため、このファイルを別の名前に変えれば、インストール済みのパッケージを削除しなくても、同名のパッケージをインストールすることが可能です。

たとえばカーネルパッケージの場合、パッケージ名はkernel-3.7.1_plamo64-x86_64-P1.txzなので、/var/log/packages/kernelがインストール済みのパッケージ名になります。これを別の名前に変えてやると、このパッケージからインストールしたカーネルやモジュールを削除しなくても、異なるバージョンのカーネルやモジュールをインストールすることができます。

手元では、Plamo-5.0デフォルトの3.7.1カーネルはサスペンド回りが不安定な気がしたので、ほぼ同じ設定で3.6.11カーネルをパッケージ化して、比べてみることにしました。カーネルを更新する際、単純にupdatepkgでパッケージを更新すると、インストール済みのパッケージは削除されてしまうので両者の比較ができません。そこで/var/log/packages/kernelを/var/log/packages/kernel_3.7.1にリネームして、2つのカーネルを同時に使えるようにしてみました。

# mv /var/log/packages/kernel{,_3.7.1}
# installpkg kernel-3.6.11_plamo64-x86_64-P1.txz
...
# ls /boot
System.map@                config@                diag1.img  inside.bmp    vmlinuz@
System.map-3.6.11-plamo64  config-3.6.11-plamo64  diag2.img  onlyblue.bmp  vmlinuz-3.6.11-plamo64
System.map-3.7.1-plamo64   config-3.7.1-plamo64   grub/      tuxlogo.bmp   vmlinuz-3.7.1-plamo64
# ls /lib/modules/
3.6.11-plamo64/  3.7.1-plamo64/  fglrx/

こうしておいて、/boot/grub/grub.cfgに3.6.11カーネル用のエントリを追加すれば、2種のカーネルを必要に応じて切り替えることができます。

カーネルとそのモジュールは、カーネルのソースコードからmake modules_install等で直接インストールすることも可能です。しかし、make install等で直接インストールしてしまうと、不要になった際のアンインストール作業に苦労することがよくあります。それに対し、パッケージ化してからインストールすれば、アンインストールはremovepkgコマンドだけで済みます。

この裏技は、インストールするファイルがバージョン番号等で区別されていないと使えないので、利用する機会は多くないものの、古いバージョンの共有ライブラリを残しつつ、新しい共有ライブラリもインストールしたい、といった際にも応用できるでしょう。

Plamo Linuxのパッケージ管理は、専用のデータベースなどは使わず、平文のテキストファイルを組み合わせて実現した、よく言えば必要最低限でシンプル、悪く言えば低機能でルーズな仕組みになっています。面白い使い方を見つけた人は、ぜひ紹介してください。

おすすめ記事

記事・ニュース一覧