パッケージ管理についての説明を LFS ブックに加えて欲しいとの要望をよく頂きます。 パッケージ管理ツールが優れていれば、パッケージを再インストールしたりアップグレードしたりするときでも、ユーザーによる設定を保持しつつ、設定ファイルを適切に取り扱ってくれます。 パッケージ管理ツールでは、バイナリファイルやライブラリファイルだけでなく、設定ファイル類のインストールも取り扱います。 パッケージ管理ツールをどうしたら・・・ いえいえ本節は特定のパッケージ管理ツールを説明するわけでなく、その利用を勧めるものでもありません。 もっと広い意味で、管理手法にはどういったものがあり、どのように動作するかを説明します。 あなたにとって最適なパッケージ管理がこの中にあるかもしれません。 あるいはそれらをいくつか組み合わせて実施することになるかもしれません。 本節ではパッケージのアップグレードを行う際に発生する問題についても触れます。
LFS や BLFS においてパッケージ管理ツールに触れていない理由には以下のものがあります。
本書の目的は Linux システムがいかに構築されているかを学ぶことです。 パッケージ管理はその目的からはずれてしまいます。
パッケージ管理についてはいくつもの方法があり、それらには一長一短があります。 ユーザーに対して満足のいくものを選び出すのは困難です。
ヒントプロジェクト (Hints Project) ページにパッケージ管理についての情報が示されています。 望むものがあるかどうか確認してみてください。
パッケージ管理ツールがあれば、各種ソフトウェアの最新版がリリースされた際に容易にアップグレードができます。 全般に LFS ブックや BLFS ブックに示されている作業手順に従えば、新しいバージョンへのアップグレードを行っていくことはできます。 以下ではパッケージをアップグレードする際に注意すべき点、特に稼動中のシステムに対して実施するポイントについて説明します。
カーネルをアップグレードする必要がある場合 (たとえば 5.10.17 から 5.10.18 や 5.11.1 へ、など)、これ以外に再ビルドを必要とするものはありません。 カーネルとユーザー空間のインターフェースが適切に定義されているため、システムは動作し続けるはずです。 特に Linux API ヘッダーは、カーネルに伴ってアップグレードする必要もありません。 アップグレードしたカーネルは、システムを再起動しさえすれば利用できるようになります。
Glibc を新しいバージョン (たとえば Glibc-2.31 から Glibc-2.39) にアップグレードする必要が発生した場合は、システムが壊れないようにすることが必要です。 詳しくは 「Glibc-2.39」 を参照してください。
共有ライブラリを提供しているパッケージをアップデートする場合で、そのライブラリ名が変更になったとします。
この場合は、このライブラリに動的リンクを行っていたパッケージは、新たなライブラリに向けてのリンクとなるように再コンパイルすることが必要になります。
(なおパッケージバージョンとライブラリ名には関連性はありません。) たとえば foo-1.2.3
というパッケージがあって、これが共有ライブラリ libfoo.so.1
をインストールしているとします。 そして新バージョン
foo-1.2.4 が共有ライブラリ libfoo.so.2
を持っていて、これにアップグレードするものとします。 この場合 libfoo.so.1
に動的リンクを行っていたパッケージは、すべて新ライブラリバージョン libfoo.so.2
へのリンクを行うように再コンパイルしなければなりません。
そのように依存していたパッケージをすべて再コンパイルしてからでないと、古いバージョンのライブラリは削除するべきではありません。
あるパッケージが(直接的か間接的に)一つの共有ライブラリにリンクしていて、しかも古いライブラリ名と新しいライブラリ名にリンクしているとします。
(たとえばそのパッケージが libfoo.so.2
と
libbar.so.1
にリンクしていて、さらに後者のライブラリは libfoo.so.3
にリンクしているとします。)
その場合にはパッケージが誤動作する可能性があります。
なぜなら共有ライブラリのリビジョンが異なると、一部のシンボル名に対する定義の互換性が失われる可能性があるからです。
こういった状況が起こりうるのは、共有ライブラリを提供するパッケージがアップグレードされた際に、古い共有ライブラリ名にリンクしているパッケージを(すべてではなく)一部だけ再ビルドしたような場合です。
この問題を回避するため、共有ライブラリにリンクするパッケージをすべて、(たとえば libfoo.so.2 から
libfoo.so.3 のように)アップグレードされたリビジョンを使ってできるだけ、早くに再ビルドすることです。
共有ライブラリを提供しているパッケージをアップデートする場合で、そのライブラリ名には変更がなかったとします。
ただしライブラリ名の変更はなくても、ライブラリファイルのバージョン番号が減らされたとします。
(たとえばライブラリ libfoo.so.1
はそのままの名前であったとして、ライブラリファイル名が libfoo.so.1.25
から libfoo.so.1.24
に変更となった場合です。)
この場合、それまでインストールされていたバージョン(例では libfoo.so.1.25
)のライブラリファイルは削除すべきです。
そうしておかないと、ldconfig
を実行したときに(自分でコマンドライン実行したり、別のパッケージをインストールする際に実施されたりしたときに)、シンボリックリンク
libfoo.so.1
がリセットされますが、それが指し示す先が古いライブラリファイルとなってしまいます。
なぜならバージョン番号がより大きい方なので、そのバージョンの方が「より新しい」と解釈されるためです。
こういった状況は、パッケージをダウングレードした場合や、パッケージの作者がバージョン番号づけの取り決めを変更してしまった場合に起こり得るものです。
共有ライブラリを提供しているパッケージをアップデートする場合で、そのライブラリ名に変更はなかったとします。
ただしそこでは重大な問題(特にセキュリティぜい弱性)が解消されているような場合は、この共有ライブラリにリンクしている実行中プログラムは、すべて再起動してください。
アップグレードした後に、以下のコマンドを root
で実行すると、どういったプロセスが古いバージョンのライブラリを利用しているかの一覧が表示されます。 (libfoo
の部分は、目的のライブラリ名に置き換えてください。)
grep -l 'libfoo
.*deleted' /proc/*/maps | tr -cd 0-9\\n | xargs -r ps u
OpenSSH を利用してシステムにアクセスしている場合であって、これがリンクするライブラリがアップデートされたとします。 その場合は sshd サービスの再起動が必要です。 またシステムからはいったんログアウトしてログインし直し、前に示したコマンドをもう一度実行して、削除されたライブラリを利用していないかどうかの確認を行ってください。
systemd
デーモンが(PID 1
として実行されていて)、アップデートしたライブラリにリンクされていた場合は、リブートするのではなく、root
ユーザーになって systemctl
daemon-reexec を実行すれば再起動できます。
実行プログラムや共有ライブラリが上書きされると、その実行プログラムや共有バイナリ内のコードやデータを利用するプロセスがクラッシュすることがあります。 プロセスがクラッシュしないように、プログラムや共有ライブラリを正しく更新する方法は、まず初めに削除を行ってから、新たなものをインストールすることです。 coreutils が提供する install コマンドは、すでにこの処理が実装されているため、たいていのパッケージにおいて、バイナリファイルやライブラリをインストールするコマンドとして利用しています。 したがってそのような問題に悩まされることは、これまでほとんどなかったはずです。 しかしパッケージの中には (特に BLFS にある SpiderMonkey など)、すでにあるファイルを上書きする方式をとっているため、クラッシュするものがあります。 そこでパッケージ更新の前には、それまでの作業を保存して、不要な起動プロセスは停止することが安全です。
以下に一般的なパッケージ管理手法について示します。 パッケージ管理マネージャーを用いる前に、さまざまな方法を検討し特にそれぞれの欠点も確認してください。
そうです。 これもパッケージ管理のやり方の一つです。 いろいろなパッケージに精通していて、どんなファイルがインストールされるか分かっている人もいます。 そんな人はパッケージ管理ツールを必要としません。 あるいはパッケージが更新された際には、いつでもシステム全体を再構築しようと考える人なら、やはりパッケージ管理ツールを必要としません。
これは最も単純なパッケージ管理のテクニックであり、パッケージ管理のための特別なプログラムを必要としません。
個々のパッケージを個別のディレクトリにインストールする方法です。 例えば foo-1.1 というパッケージを
/opt/foo-1.1
ディレクトリにインストールし、この
/opt/foo-1.1
に対するシンボリックリンク
/opt/foo
を作成します。 このパッケージの新しいバージョン
foo-1.2 がリリースされた際には /opt/foo-1.2
ディレクトリにインストールした上で、先ほどのシンボリックリンクをこのディレクトリを指し示すように置き換えます。
PATH
、MANPATH
、INFOPATH
、PKG_CONFIG_PATH
、CPPFLAGS
、LDFLAGS
といった環境変数、あるいは設定ファイル /etc/ld.so.conf
に対しては、/opt/foo
ディレクトリを加えることで、対応する
/opt/foo-x.y
ディレクトリを含める必要があるかもしれません。
このやり方は BLFS ブックが採用するものであり、大規模のパッケージを用意にアップグレードできるようにします。 ただし多数のパッケージをインストールするとなると、このやり方では管理がしにくくなってきます。 また(たとえば Linux API ヘッダーや Glibc などのように)パッケージの中には、このやり方ではうまく動作しないものも出てきてしまいます。 このやり方は、システム全体に渡るものについては用いないでください。
これは一つ前に示したパッケージ管理テクニックの応用です。 各パッケージは、上で説明した方法と同じようにインストールします。
ただし先ほどのように、汎用的なパッケージ名によるシンボリックリンクを生成するのではなく /usr
ディレクトリ階層の中に各ファイルのシンボリックリンクを生成します。
この方法であれば環境変数を追加設定する必要がなくなります。
シンボリックリンクはユーザーが生成することもできますが、パッケージ管理者の多くは、この手法を使っています。
よく知られているものとして Stow、Epkg、Graft、Depot があります。
インストールスクリプトは、意図的にダマす指示が必要です。 パッケージにとっては /usr
にインストールすることが指定されたものとなりますが、実際には
/usr/pkg
配下にインストールされるわけです。
このインストール方法は単純なものではありません。 例えば今 libfoo-1.1 というパッケージをインストールするものとします。
以下のようなコマンドでは、このパッケージを正しくインストールできません。
./configure --prefix=/usr/pkg/libfoo/1.1 make make install
インストール自体は動作しますが、このパッケージに依存している他のパッケージは期待どおりには libfoo を正しくリンクしません。
例えば libfoo をリンクするパッケージをコンパイルする際には /usr/lib/libfoo.so.1
がリンクされると思うかもしれませんが、実際には
/usr/pkg/libfoo/1.1/lib/libfoo.so.1
がリンクされることになります。 結局、正しい方法は DESTDIR
変数を使って、パッケージを直接インストールすることです。 この方法は以下のようにして行います。
./configure --prefix=/usr make make DESTDIR=/usr/pkg/libfoo/1.1 install
この手法をサポートするパッケージは数多く存在しますが、そうでないものもあります。
この手法を取り入れていないパッケージに対しては、手作業でインストールすることが必要になります。
またはそういった問題を抱えるパッケージであれば /opt
ディレクトリにインストールする方が簡単かもしれません。
この方法ではパッケージをインストールするにあたって、あるファイルにタイムスタンプが記されます。 インストールの直後に find コマンドを適当なオプション指定により用いることで、インストールされるすべてのファイルのログが生成されます。 これはタイムスタンプファイルの生成の後に行われます。 この方法を用いたパッケージ管理ツールとして install-log があります。
この方法はシンプルであるという利点がありますが、以下の二つの欠点があります。 インストールの際に、いずれかのファイルのタイムスタンプが現在時刻でなかった場合、そういったファイルはパッケージ管理ツールが正しく制御できません。 またこの方法は、インストールされるパッケージが、その時には一つだけであることを前提とします。 例えば二つのパッケージが二つの異なる端末から同時にインストールされるような場合は、ログファイルが適切に生成されません。
この方法はインストールスクリプトが実行するコマンドを記録するものです。 これには以下の二種類の手法があります。
インストールされるライブラリを事前にロードする場所を環境変数 LD_PRELOAD
に定めておいてそれからインストールを行う方法です。
パッケージのインストール中には cp、install、mv
など、さまざまな実行モジュールにそのライブラリをリンクさせ、ファイルシステムを変更するようなシステムコールを監視することで、そのライブラリがパッケージを追跡管理できるようにします。
この方法を実現するためには、動的リンクする実行モジュールはすべて suid ビット、sgid ビットがオフでなければなりません。
事前にライブラリをロードしておくと、インストール中に予期しない副作用が発生するかもしれません。
したがって、ある程度のテスト確認を行って、パッケージ管理ツールが不具合を引き起こさないこと、しかるべきファイルの記録が取られていることが良いとされます。
別の方法として strace を用いるものがあります。 これはインストールスクリプトの実行中に発生するシステムコールを記録するものです。
この方法では、シンボリックリンク方式によるパッケージ管理にて説明したのと同じように、パッケージが個別のディレクトリにインストールされます。 インストールの後は、インストールされたファイルのアーカイブが生成されます。 このアーカイブはローカル PC へのインストールに用いられたり、他の PC へのインストールにも利用されたりします。
商用ディストリビューションが採用しているパッケージ管理ツールは、ほとんどがこの方法によるものです。 この方法に従ったパッケージ管理ツールの例に RPM があります。 (これは Linux Standard Base Specification が規定しています。) また pkg-utils、Debian の apt、Gentoo の Portage システムがあります。 このパッケージ管理手法を LFS システムに適用するヒント情報が https://www.linuxfromscratch.org/hints/downloads/files/fakeroot.txt にあります。
パッケージファイルにその依存パッケージ情報まで含めてアーカイブ生成することは、非常に複雑となり LFS の範疇を超えるものです。
Slackware は、パッケージアーカイブに対して tar ベースのシステムを利用しています。 他のパッケージ管理ツールはパッケージの依存性を取り扱いますが、このシステムは意図的にこれを行っていません。 Slackware のパッケージ管理に関する詳細は https://www.slackbook.org/html/package-management.html を参照してください。
この手法は LFS に固有のものであり Matthias Benkmann により考案されました。 ヒントプロジェクト (Hints Project) から入手することが出来ます。 考え方としては、各パッケージを個々のユーザーが共有ディレクトリにインストールします。 パッケージに属するファイル類は、ユーザーIDを確認することで容易に特定出来るようになります。 この手法の特徴や短所については、複雑な話となるため本節では説明しません。 詳しくは https://www.linuxfromscratch.org/hints/downloads/files/more_control_and_pkg_man.txt に示されているヒントを参照してください。
LFS システムの利点の一つとして、どのファイルもディスク上のどこに位置していても構わないことです。
他のコンピューターに対してビルドした LFS の複製を作ろうとするなら、それが同等のアーキテクチャーであれば容易に実現できます。
つまり tar コマンドを使って LFS
のルートディレクトリを含むパーティション (LFS の基本的なビルドの場合、非圧縮で 900MB 程度)
をまとめ、これをネットワーク転送か、あるいは CD-ROM や USB スティックを通じて新しいシステムにコピーし、伸張 (解凍)
するだけです。 その後は、設定ファイルにいくらかの変更を行うことが必要です。 変更が必要となる設定ファイルは以下のとおりです。
/etc/hosts
, /etc/fstab
, /etc/passwd
, /etc/group
, /etc/shadow
, /etc/ld.so.conf
新しいシステムのハードウェアと元のカーネルに差異があるかもしれないため、カーネルを新しいシステム向けに再ビルドする必要があるでしょう。
類似するアーキテクチャーのシステム間にてコピーを行う際には問題が生じるとの報告があります。 例えばインテルアーキテクチャーに対する命令セットは AMD プロセッサーに対するものと完全に一致しているわけではないため、一方の命令セットが後に他方で動作しなくなることも考えられます。
最後に新システムを起動可能とするために 「GRUB を用いたブートプロセスの設定」を設定する必要があります。