PulseAudioの32bit版(i386 build)をDockerで動かそうとするも、うまく動かなくて苦労したので書き残しておく。
TL;DR
dokcer run
のオプションに--security-opt seccomp=unconfined
をつけたら解決する- ただし、セキュリティ理由の制限を外すことになるので注意
気づいたきっかけはWine
今どきなぜ32bit版のPulseAudioを使っているのかというと、Wineで32bitのWindowsアプリを起動したことがきっかけだった。
アプリは起動するが、音声が鳴らない。 Wineの出力を見てみると次のようにPulseAudioの内部でエラーが発生していた。
Assertion 'clock_gettime(CLOCK_REALTIME, &ts) == 0' failed at pulsecore/core-rtclock.c:93, function pa_rtclock_get(). Aborting.
WINEARCH=win32
を設定後、winecfg
で "Audio" のタブを開くと次のような画面になり、アプリごと落ちてしまった。
Wine上でサウンドドライバの認識ができてないようだった。
困ったことに、64bitのWineは正常なのに32bitの場合のみエラーが出てしまうという状況だった。
PulseAudioが落ちる原因を調査する
まだ救いなのは、PulseAudioのエラーメッセージがわかりやすいことだった。
文字通りclock_getres
関数が0を返すことを期待したが、それ以外の値を返したので強制終了したということらしい。
Assertion 'clock_gettime(CLOCK_REALTIME, &ts) == 0' failed at pulsecore/core-rtclock.c:93, function pa_rtclock_get(). Aborting.
再現方法
再現させるためには64bit LinuxがベースのDocker containerで32bit版のPulseAudioを起動すればよい。 筆者は以下の環境と手順で再現をさせることができた。
- ホストマシン: iMac 5K (2017)
- Docker: Docker for Mac 2.3.0.5
Dockerfile
FROM ubuntu:20.04 ENV DEBIAN_FRONTEND noninteractive RUN dpkg --add-architecture i386 \ && apt update -y \ && apt install -y pulseaudio:i386 ENTRYPOINT ["pulseaudio", "-v"]
Output
docker build -t pulse32 . docker run --rm -it pulse32 ... I: [pulseaudio] main.c: This is PulseAudio 13.99.1 I: [pulseaudio] main.c: Page size is 4096 bytes I: [pulseaudio] main.c: Machine ID is a9a6ee927f5b4b64b33bf688bd29d4e2. I: [pulseaudio] main.c: Using runtime directory /root/.config/pulse/a9a6ee927f5b4b64b33bf688bd29d4e2-runtime. I: [pulseaudio] main.c: Using state directory /root/.config/pulse. I: [pulseaudio] main.c: Using modules directory /usr/lib/pulse-13.99.1/modules. I: [pulseaudio] main.c: Running in system mode: no E: [pulseaudio] core-rtclock.c: Assertion 'clock_getres(CLOCK_REALTIME, &ts) == 0' failed at pulsecore/core-rtclock.c:139, function pa_rtclock_hrtimer(). Aborting.
clock_getres の失敗原因を探る
clock_getresのMan pageを見ると、失敗時のエラーコードはerrnoに格納されるとのこと。
RETURN VALUE top clock_gettime(), clock_settime(), and clock_getres() return 0 for success, or -1 for failure (in which case errno is set appropriately).
というわけで、このエラーが出た直後にerrnoの変数の値を取得すればエラーコードがわかりそう。
gdbを使ってエラーコードを取得してみる。 取得のやり方は https://stackoverflow.com/a/18518122 を参考にした。
apt install -y gdb gdb --args pulseaudio -v (gdb) run ... E: [pulseaudio] core-rtclock.c: Assertion 'clock_getres(CLOCK_REALTIME, &ts) == 0' failed at pulsecore/core-rtclock.c:139, function pa_rtclock_hrtimer(). Aborting. Program received signal SIGABRT, Aborted. 0xf7eefc49 in __kernel_vsyscall () (gdb) print *((int*(*)())__errno_location)() $3 = 1
errnoの値は 1
だった。moreutilsに含まれてるerrnoコマンドを使えば、1
がどのエラーに対応するか表示できる。
1番は EPERM Operation not permitted
らしい。
apt install -y moreutils errno 1 EPERM 1 Operation not permitted
clock_getresと x86, x64 システムコール
上の調査からclock_getresの呼び出しをしたが、権限が足りずにエラーになっているとわかった。 しかし、この問題で不思議なのは、64bitの場合は普通に成功していることだ。そこで、clock_getresに32bitと64bit(x86とx64)に違いがあるのか調べた。
x86 Linux の 32bit と 64bit のシステムコールの違い
このページによると、clock_getresはシステムコールの一種で、x86とx64ではシステムコール番号が異なっているらしい。なるほど。
Dockerとシステムコールの制限
また、clock_getres permission denied
などでWeb検索すると、次のページがヒットした。
c - Linux syscall clock_settime(...) fails with EPERM in Docker container - Stack Overflow
Dockerではセキュリティ上の理由でいくつかのシステムコールはデフォルトで制限されている。よって特殊なオプションを与えない限り permission denied になるとのこと。
この記事では --privileged
オプションを勧めているが、このオプションではなくてもシステムコールの制限を外す方法はある。それがseccomp オプション。
というわけで、seccomp=unconfined
をつけて実行したらエラーが出なくなり解決した!
$ docker run --rm -it --security-opt seccomp=unconfined pulse32 ... I: [pulseaudio] main.c: Daemon startup complete. I: [pulseaudio] module-device-restore.c: Storing port for device sink:auto_null. I: [pulseaudio] module-device-restore.c: Storing volume/mute for device+port sink:auto_null:null. I: [pulseaudio] module-device-restore.c: Storing port for device source:auto_null.monitor. I: [pulseaudio] module-device-restore.c: Storing volume/mute for device+port source:auto_null.monitor:null. I: [snapd-glib] module-snap-policy.c: Starting GLib main loop
しかし、もちろんこのオプションによりセキュリティ上の制限を外すのでリスクは上がってしまうことは考慮しておく必要がある。
まとめ
clock_getresというシステムコールはx86とx64で番号が異なっており、筆者の環境のDockerではx86のほうのシステムコールのみ制限されている状況となっていたようだった。
一見どうしたらいいのかわからないようなエラーでも、1ステップごとにじっくり調べていくと勉強にもなって解決につながるのはよかった。