前回 は、システムに存在する共有ライブラリのデータベースを作り、そのデータベースからライブラリの依存関係を検索するget_depends.pyとquery_depends.pyの基本的な使い方を紹介しました。今回はこのツールを使って、ディストリビューションにとって厄介な"library not found"のエラーを検出する方法と、それに関わるいくつかの話題を紹介しましょう。
"library not found"問題
インストールしたシステムを、パッケージ更新や自前でソフトウェアをインストールしたりしながら使い込んでいると、こんなエラーメッセージに遭遇することがよくあります。
$ qdoc
qdoc: error while loading shared libraries: libclang.so.10: cannot open shared object file: No such file or directory
前回までに紹介してきたように、このエラーは「指定されたバイナリ(qdoc)に記録されている必要な共有ライブラリ(libclang.so.10)が見つからない」という意味で、原因も対策もはっきりしているものの、実際にそのバイナリを実行するまでトラブルの存在に気づけないので、結構厄介です。
このエラーを出しているバイナリファイルをldd で調べると、必要なライブラリが見つからなかった場合、"not found"が返ります。
$ ldd /usr/bin/qdoc
linux-vdso.so.1 (0x00007ffe6ac7e000)
libclang.so.10 => not found
libQt5Core.so.5 => /usr/lib/libQt5Core.so.5 (0x00007f530f6b8000)
libpthread.so.0 => /lib/libpthread.so.0 (0x00007f530f697000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f530f48b000)
...
一方、前回紹介したget_depends.pyスクリプトでは、lddの結果を単なる文字列としてそのまま依存関係DBに書き込むので、ライブラリのフルパス名ではない"not found"という結果でも、そのままDBに書き込んでしまいます。
$ ./query_depends.py -p /usr/bin/qdoc
/usr/bin/qdoc needs these libraries
linux-vdso.so.1 (0x00007ffd639d5000)()
libclang.so.10(not found)
libQt5Core.so.5(/usr/lib/libQt5Core.so.5)
libpthread.so.0(/lib/libpthread.so.0)
...
一見、これは問題になりそうな仕様なものの、逆に見ると、通常はそのバイナリを実行するまで気づかない"library not found"なエラーを、"not found"を手掛かりにDBで調べればあらかじめチェックできる、ということになります。
この"not found"な情報は、DBのrealpathフィールドに記録されるので、query_depends.pyでは"-r"オプションで指定します。さて、それではPlamo-7.3をインストールした直後の環境で、"library not found"なエラーが潜在しているかチェックしてみましょう。
$ ./query_depends.py -r 'not found' | cat -n
1 not found used by these binaries
2 appstream-builder(/usr/bin/appstream-builder)
3 appstream-builder(/usr/bin/appstream-builder)
...
7 libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
8 libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
...
26 libxul.so(/usr/lib/firefox/libxul.so)
27 libxul.so(/usr/lib/firefox/libxul.so)
....
139 libpostgresql-sdbc-impllo.so(/opt/libreoffice7.1/program/libpostgresql-sdbc-impllo.so)
140 libpostgresql-sdbc-impllo.so(/opt/libreoffice7.1/program/libpostgresql-sdbc-impllo.so)
おやおや、思ったよりも大量のバイナリから「ライブラリが見つからない」旨のエラーが報告されました。また、該当するバイナリは"samba"や"firefox"、"libreoffice"といった特定のジャンルに集中しているようです。これは一体どうしたことでしょう?
共有ライブラリのサーチパス
実はこの問題は共有ライブラリのサーチパス に由来します。というのも、lddは必要な共有ライブラリを探す際、/etc/ld.so.conf に指定されたディレクトリのみを対象とし、それ以外の場所に置かれた共有ライブラリは検索しません。
正確に言うと、lddが参照するのは/etc/ld.so.confを元にldconfig が生成した共有ライブラリの場所情報キャッシュファイル(/etc/ld.so.cache)で、あるディレクトリを/etc/ld.so.confに追加しても、ldconfigを再実行してキャッシュファイルを更新するまでは検索の対象になりません。
一方、sambaやfirefox,libreofficeといった大規模なプロジェクトでは、自前で提供する共有ライブラリは/usr/lib/samba/ や/usr/lib/firefox/ といった専用のディレクトリに収めてシステムの汎用的な共有ライブラリとは区別しておき、コマンドを実行する際に$LD_LIBRARY_PATH 環境変数に自前の共有ライブラリの置き場を設定して必要なライブラリを見つける、という方法を取っています。
$LD_LIBRARY_PATHは、文字通り共有ライブラリの読み込み先ディレクトリを指定するための環境変数で、この変数に指定されたディレクトリは/etc/ld.so.cacheよりも先に調べられ、そこにある共有ライブラリが優先的に読み込まれます。
そのため、これらのプロジェクトの提供するバイナリを単独でlddで調べてみると"library not found"となるものの、実際にそのバイナリを動作させる際には必要な共有ライブラリはちゃんと見つかり、問題なく実行できる、ということになります。
ざっと調べたところ、Plamoのデフォルト環境では、"samba"、"firefox"、"thunderbird"、"java(openjdk)"、"tetex"、"libreoffice"といったパッケージが、このスタイルで自前の共有ライブラリを提供しているので、とりあえず、これらのパッケージに由来するバイナリの"not found"は無視することにします。すると残りは6つくらいのバイナリに絞られてきました。
$ ./query_depends.py -r 'not found' | grep -v samba | grep -v firefox | grep -v thunderbird | \
grep -v java | grep -v libreoffice | cat -n
1 not found used by these binaries
2 appstream-builder(/usr/bin/appstream-builder)
3 appstream-builder(/usr/bin/appstream-builder)
4 appstream-builder(/usr/bin/appstream-builder)
5 appstream-util(/usr/bin/appstream-util)
6 appstream-compose(/usr/bin/appstream-compose)
7 libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
8 libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
9 libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
10 libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
11 libsmbldap.so.2(/usr/lib/libsmbldap.so.2)
12 libappstream-glib.so.8.0.10(/usr/lib/libappstream-glib.so.8.0.10)
13 Libproxy.so(/usr/lib/perl5/site_perl/auto/Net/Libproxy/Libproxy.so)
「入れるべきか、入れざるべきか……」
これくらいまで絞り込んだ"library not found"なバイナリを改めてlddで確認してみると、libsmbldap.so.2 はsambaパッケージが提供する/usr/lib/samba/以下の共有ライブラリを要求し、Perl5のモジュールであるLibproxy.so は同じ"perl"パッケージに含まれている/usr/lib/perl5/CORE/libperl.soを要求しているので、どちらもfirefox等の場合と同じく、必要なライブラリは存在するものの、標準的な場所にはないために見つからなかったようです。
一方、"appstream-builder"や"appstream-util"は"librpm.so.9"等を必要とするものの、この共有ライブラリを提供する"rpm"パッケージは意図的にインストールしていません。
$ ldd /usr/bin/appstream-builder | grep found
librpm.so.9 => not found
librpmio.so.9 => not found
librpmio.so.9 => not found
もちろん、appstream-builderやappstream-utilをビルドした環境には、rpmパッケージも入っていて、/usr/lib/librpm.so.9も参照できていました。しかし確認してみると、これらのライブラリはGNOME方面で開発されているメディアプレイヤーlollypop がappstream-glib を必要とするためインストールされたものの、実のところ、lollypopの動作にはappstream-glibは必要ではなく、ビルドの最終段階でこのパッケージに含まれるappstream-util コマンドを使い、Appstream用のメタ情報を作っているだけでした。
Appstream は、いわゆる「ソフトウェア・センター」から各種パッケージをインストールする際に利用される、パッケージの名前や種類、機能、開発元、各言語による紹介、スクリーンショット等の情報を管理するパッケージ用メタ情報の仕様 です。このファイルはXML形式になっており、lollypopはGNOME系のソフトウェアということもあってappstream-glibに含まれるappstream-utilを使ってこのファイルを作るものの、たいていのソフトウェアはよりシンプルなmsgfmt で作成しているようです。この機能に対応しているソフトウェアは/usr/share/metainfo/に設定ファイルを置いているので、興味ある人は確認してみてください。
それならばわざわざappstream-glibのためだけに、かなり規模が大きいrpmパッケージをインストールして、不要な依存関係を増やす必要も無いだろう、と判断し、rpmパッケージはお好みでインストールするcontrib/utis/以下に置くことにしたのでした。
もっとも、こういう風に整理してみると「そもそもappstream-glibも不要では?」という気もしてきましたが、そのあたりの判断は次の更新の際に先送りすることにしましょう(苦笑)
先に、標準的な場所にインストールされていないライブラリを"not found"の対象から外すために、"| grep -v firefox | grep -v java |..." みたいな操作で出力結果を掃除しました。一方、これらのライブラリがインストールされている場所を/etc/ld.so.confに追加して、改めてldconfigした上で依存情報データベースを作り直す、という方法もあります。
最近では、ldconfigの検索場所を追加する場合、直接/etc/ld.so.confに書き足すのではなく、/etc/ld.so.conf.d/ディレクトリに"xxxx.conf"の名前で追加するようになっているので、たとえばこのようにしておけば、query_depends.pyの出力を操作する必要は無くなるでしょう。
$ sudo cat << EOF > /etc/ld.so.conf.d/append_libs.conf
/usr/lib/firefox
/usr/lib/thunderbird
/usr/lib/samba/
/usr/java/jdk/lib
/usr/java/jdk/lib/server
/opt/texlive/2019/lib
/opt/libreoffice/program
EOF
$ sudo ldconfig -v
$ rm -f depends.sql3
$ sudo ./get_depends.py
もっとも上述のように、これらのディレクトリをlddの検索対象に追加しなくても運用上は問題ないので、依存関係DBのためだけに設定を変更するのも何だかな…… というところです。