GentooのGitHubがクラックされてrm -r /入ってたそうだけどやばいの?

GentooGitHub リポジトリが乗っ取られて, 復旧作業のためアクセスできなくなってますね.

infra-status.gentoo.org

どうやら ebuild (パッケージビルド&インストールするためのbashスクリプトファイル)に "rm -rf /*"とかもしこまれていたとか.

なんか見た感じやばそうだけど, 実際のところやばいんでしょうか?

まあ, もちろんのっとられたからにはやばいし, しばらく該当 GitHub リポジトリを避けておくのがいいんですが, 実際どのぐらいなにが起きるぐらいやばいんでしょうか?

ということで, 以下の話をします.

  • やられたリポジトリがやばくない
    • ミラーだから, 開発者は使わないよ
    • いろいろないから, 一般ユーザも普通使わないよ
    • プルリク受けつけと、まあバックアップぐらいのとこだよ
  • 書かれたコマンドがやばくない
    • ebuild の先頭に rm -rf 書いても動かないよ
    • /bin/rm でも sandbox で止まるよ
  • その他
    • せっかくだし git から sync してる人は PGP の verify は有効にするとよさそう

やられたリポジトリがやばくない

f:id:meech:20180701213516p:plain:w2368:h300

Gentoo のパッケージリポジトリは, 3つの場所で公開されています. 1つは開発者が commit を行うリポジトリGentoo 公式の git.gentoo.org 上でホスティングされています.

2つ目は, GitHubgentoo/gentoo にある, このリポジトリのミラーです. 定期的に git.gentoo.org から commit が 上書き されてきます.

3つ目は, GitHubgentoo-mirror/gentooリポジトリです. 一般ユーザがパッケージツリーを git pull したい時に使います.

今回やられたのは, 2番目の GitHubgentoo/gentoo にあるリポジトリです. 開発者はここには push しませんし, そのうちミラーで上書きされる(らしい)ので開発者のコードがやられた状態ではありませんでした.

では, ユーザ側はどうでしょうか? このミラーから pull していることはないのでしょうか?

ここで少し Gentoo のパッケージツリーの話をします. Gentoo のパッケージ情報は"Portageツリー"と呼ばれ, 各パッケージのebuildファイルが, "カテゴリ/パッケージ名"のディレクトリの下に配置されています. Portageツリーの中には膨大な量の ebuild ファイル(いま見たら35,537個)が入っています. パッケージのインストール・更新時には, この Portage ツリーをひたすらなめていく………のはちょっと大変なので ebuild のキャッシュがあればそれを読みます. これによって, パッケージのインストールが高速化されているわけです.

このキャッシュの内容自体は ebuild の内容とかぶっている(キャッシュなので当然)ので, 1番目の開発者のリポジトリにも, そのミラーである2番目のリポジトリにも入っていません. キャッシュは, 3番目のリポジトリにだけ入っています. すなわち, git.gentoo.org にホストされている開発者リポジトリから, ebuildファイルをコピーして, キャッシュを生成・追加したものが3番目のリポジトリ(あと rsync や http でも取得できる)として公開されています.

つまり, ユーザは3番目のリポジトリからgit pull してきて, 今回やられたミラーからは 普通は git pull しないということです.

また, そもそもデフォルトではリポジトリツリーの取得は rsync で行われ, git pull での同期は明示的に設定しなければならないという点もあります(が, Gentooは選択だし奇特な人も多いので…)

余談ですが, ではなぜ2番目のリポジトリがあるのでしょうか? 1つの意味はメインサーバが死んだ時用のミラーです(kernel.org が昔やられたように). そして, もう1つより使いやすいバグレポ・Pull Request の受け付けという意味もあります. それでいうと, PR書こうとしてgit pullしていた人は改変された portage ツリーを入手していたかもしれません.

書かれたコマンドがやばくない

では, 仮に改変されたportageツリーが来ちゃってたらどうでしょうか? rm -rf /* なんて書いてあるんだし, お使いのPCは全滅です, するんでしょうか?

実際のところ, 今回の変更であれば, チェック機構にひっかかって影響はありません.

かなり省略してますが, Gentooebuild ファイルは以下のようになっています. 基本的には bash スクリプトですが, いくつか変数を設定したり, src_compile といった決まった名前の関数内にやりたい処理を書いたりします.

EAPI=6
KEYWORDS="amd64"
SLOT="0"

src_compile() {
    emake
}

今回, これの先頭にrm -rf /*が入っていたということでこうなりますね.

rm -rf /*
EAPI=6
KEYWORDS="amd64"
SLOT="0"

src_compile() {
    emake
}

では動かしてみましょう. みなさんGentooをお持ちでしょうのでそこで・・・・・するのはこわいのでdocker でも使っときましょう. Gentoo の公式イメージがあるんで使っておきましょう. portageツリーを担当するコンテナ(gentoo/portage)と, それを使う Gentoo のシステムコンテナとを立ち上げますと, Gentoo な環境に導かれていきます.

$ docker create -v /usr/portage --name portagesnap gentoo/portage:latest /bin/true
$ docker run --rm -i -t --volumes-from portagesnap gentoo/stage3-amd64 /bin/bash

適当にディレクトリを作ってさっきのebuild を設置します. 今回は"app-misc/test"というパッケージ名になります.

# mkdir -p portage/app-misc/test
# cat > portage/app-misc/test/test-0.ebuild

そして, PORTDIR_OVERLAYでこのebuildの入ったツリーを指定し emerge してみます. (FEATURES=digest は, ebuildチェックサムの確認をスキップするため) なにもかも消えちゃうんでしょうか? ドキドキ

# PORTDIR_OVERLAY=/portage FEATURES=digest emerge -1 app-misc/test
Calculating dependencies - * ERROR: app-misc/test-0::x-portage failed (depend phase):
 *   External commands disallowed while sourcing ebuild: rm -rf /bin /boot /deleteme /dev /etc /home /lib /lib32 /lib64 /media /mnt /opt
 /portage /proc /root /run /sbin /sys /tmp /usr /var
 *
 * Call stack:
 - *       ebuild.sh, line 635:  Called source '/portage/app-misc/test/test-0.ebuild'
 *   test-0.ebuild, line   1:  Called command_not_found_handle 'rm' '-rf' '/bin' '/boot' '/deleteme' '/dev' '/etc' '/home' '/lib' '/lib3
2' '/lib64' '/media' '/mnt' '/opt' '/portage' '/proc' '/root' '/run' '/sbin' '/sys' '/tmp' '/usr' '/var'
 *       ebuild.sh, line  88:  Called die
 * The specific snippet of code:
 *              die "External commands disallowed while sourcing ebuild: ${*}"
 *
 * If you need support, post the output of `emerge --info '=app-misc/test-0::x-portage'`,
 * the complete build log and the output of `emerge -pqv '=app-misc/test-0::x-portage'`.
 * Working directory: '/usr/lib64/python3.5/site-packages'
 * S: '/var/tmp/portage/app-misc/test-0/work/test-0'
 / * ERROR: app-misc/test-0::x-portage failed (depend phase):
 *   External commands disallowed while sourcing ebuild: rm -rf /bin /boot /deleteme /dev /etc /home /lib /lib32 /lib64 /media /mnt /opt
 /portage /proc /root /run /sbin /sys /tmp /usr /var
 *
 * Call stack:
 *       ebuild.sh, line 635:  Called source '/portage/app-misc/test/test-0.ebuild'
 *   test-0.ebuild, line   1:  Called command_not_found_handle 'rm' '-rf' '/bin' '/boot' '/deleteme' '/dev' '/etc' '/home' '/lib' '/lib3
2' '/lib64' '/media' '/mnt' '/opt' '/portage' '/proc' '/root' '/run' '/sbin' '/sys' '/tmp' '/usr' '/var'
 *       ebuild.sh, line  88:  Called die
 * The specific snippet of code:
 - *            die "External commands disallowed while sourcing ebuild: ${*}"
 *
 * If you need support, post the output of `emerge --info '=app-misc/test-0::x-portage'`,
 * the complete build log and the output of `emerge -pqv '=app-misc/test-0::x-portage'`.
 * Working directory: '/usr/lib64/python3.5/site-packages'
 * S: '/var/tmp/portage/app-misc/test-0/work/test-0'
... done!

!!! All ebuilds that could satisfy "app-misc/test" have been masked.
!!! One of the following masked packages is required to complete your request:
- app-misc/test-0::x-portage (masked by: corruption)

For more information, see the MASKED PACKAGES section in the emerge
man page or refer to the Gentoo Handbook.

なんだかたくさんエラーが出てきて失敗したようです. なにも消えてなさそうです…

見てみると, "External commands disallowed while sourcing ebuild" とエラーが出てます. つまり, 今回のように関数の外になる先頭にコマンドを書いても実行されないんですね〜 (これ command_not_found_handle で実装してるのか〜)

では, これが "/bin/rm" だったらどうなるんでしょう?

# PORTDIR_OVERLAY=/portage FEATURES=digest emerge -1 app-misc/test
Calculating dependencies - * ACCESS DENIED:  unlinkat:     /bin/arping
 * ISE:write_logfile: unable to append logfile: app-misc_-_test-0
 * /var/tmp/portage/sys-apps/sandbox-2.13/work/sandbox-2.13/libsandbox/libsandbox.c:check_syscall():968: failure (Bad file descriptor):
 * ISE: unlinkat(/bin/arping)
        abs_path: /bin/arping
        res_path: /bin/arping
/usr/lib64/libsandbox.so(+0xb765)[0x7f1b4aec6765]
/usr/lib64/libsandbox.so(+0xb848)[0x7f1b4aec6848]
/usr/lib64/libsandbox.so(+0x53b5)[0x7f1b4aec03b5]
/usr/lib64/libsandbox.so(unlinkat+0x59)[0x7f1b4aec5999]
/bin/rm(+0x2ab0)[0x558484d5fab0]
/bin/rm(+0x3641)[0x558484d60641]
/bin/rm(+0x23f3)[0x558484d5f3f3]
/lib64/libc.so.6(__libc_start_main+0xea)[0x7f1b4ab17f0a]
/bin/rm(+0x255a)[0x558484d5f55a]
/proc/6090/cmdline \/bin/rm -rf /bin /boot /deleteme /dev /etc /home /lib /lib32 /lib64 /media /mnt /opt /portage /proc /root /run /sbin
 /sys /tmp /usr /var

/portage/app-misc/test/test-0.ebuild: line 1:  6090 Aborted                 /bin/rm -rf /*
 * EAPI assignment in ebuild 'app-misc/test-0::x-portage' does not
 * conform with PMS section 7.3.1 (see bug #402167):
 *      valid EAPI assignment must occur on or before line: 1
 - * ACCESS DENIED:  unlinkat:     /bin/arping
 * ISE:write_logfile: unable to append logfile: app-misc_-_test-0
 * /var/tmp/portage/sys-apps/sandbox-2.13/work/sandbox-2.13/libsandbox/libsandbox.c:check_syscall():968: failure (Bad file descriptor):
 * ISE: unlinkat(/bin/arping)
        abs_path: /bin/arping
        res_path: /bin/arping
/usr/lib64/libsandbox.so(+0xb765)[0x7f798b60f765]
/usr/lib64/libsandbox.so(+0xb848)[0x7f798b60f848]
/usr/lib64/libsandbox.so(+0x53b5)[0x7f798b6093b5]
/usr/lib64/libsandbox.so(unlinkat+0x59)[0x7f798b60e999]
/bin/rm(+0x2ab0)[0x55a60649eab0]
/bin/rm(+0x3641)[0x55a60649f641]
/bin/rm(+0x23f3)[0x55a60649e3f3]
/lib64/libc.so.6(__libc_start_main+0xea)[0x7f798b260f0a]
/bin/rm(+0x255a)[0x55a60649e55a]
/proc/6133/cmdline: /bin/rm -rf /bin /boot /deleteme /dev /etc /home /lib /lib32 /lib64 /media /mnt /opt /portage /proc /root /run /sbin
 /sys /tmp /usr /var

/portage/app-misc/test/test-0.ebuild: line 1:  6133 Aborted                 /bin/rm -rf /*
 * EAPI assignment in ebuild 'app-misc/test-0::x-portage' does not
 * conform with PMS section 7.3.1 (see bug #402167):
 *      valid EAPI assignment must occur on or before line: 1
... done!

!!! All ebuilds that could satisfy "app-misc/test" have been masked.
!!! One of the following masked packages is required to complete your request:
- app-misc/test-0::x-portage (masked by: corruption)

また, いろいろ出てきて失敗しているようです……フルパスにしたので command_not_found_handle にはひっかからないはずなのだが?

今度はsandboxにひっかかっています. Gentoo では ebuild ファイルを sandbox の中で実行しています. 今回は, ISE: unlinkat(/bin/arping) とあるように, rm -rf /*が /bin/arping を削除しようとしたものの, そのパスへの変更はsandboxで許可された動作ではないため止められてしまいました.

実際, この sandbox は結構にべんりで, うっかり変なパッケージが configure やら make の中で "rm -rf /*" などしようとしても, sandbox にひっかかって無事にすむんですね〜

ということで, 関数外部チェックや sandbox のおかげもあり, 今回のコマンドは無力化されていたわけでした (もちろん, うまく動かす関数の場所を選べばアレなんですが)

その他

当然他にも防御朔があります. しれっと書いてた ebuild のハッシュチェックもそうですし, 開発者のgitリポジトリにはPGPサインしてpush しなければいけませんし, rsyncやhttpの場合でもツリー全体でのハッシュチェックが行われるようになっています.

PGPサインのチェックはgitの場合, いまのところデフォルトでは走っていないようなのでこのような設定(sync-git-verify-commit-signature = true)をしておくとよりよいでしょう.

Project:Repository mirror and CI - Gentoo Wiki

Gentooの「依存USEフラグ」をお掃除しよう

Gentooってべんりですよね. たとえば, あるパッケージでpython2系のコードはいれとかなくてもいいな〜と思ったら, そのパッケージのUSEフラグからPYTHON_TARGETS=python2_6を落としておけば, python2.6のコードなしでインストールができちゃう.

ところが, 時にはこれがめんどいことになりますね. 新しくパッケージを入れようとしたら, python2しかまだ対応してなくて, おかげで依存するパッケージ群にPYTHON_TARGETS=python2_6を生やしてまわることになったりしますね. まあ--autounmask-write使えば, だいたい勝手に書かれるんですけども.

実際に問題になるのは, このほしかったパッケージがpython3対応した時で. ここでほしかったパッケージからPYTHON_TARGETS=python2_6を落とせるわけですが, 依存するパッケージからPYTHON_TARGETS=python2_6を落とせるかどうか, それは注意深く依存関係をたどっていくか, 実際に落としてビルドを試すまでわからない. こんなふうに依存で入ってきた「USEフラグ」というのは後片付けがとてもめんどい.

これが依存パッケージであれば, emerge --depcleanでいいんだけれど, 「依存USEフラグ」まではきれいにしてくれない. とてもつらい. しかも, それが何段も依存関係を作ったりもする. かなりつらい.

と, いうことで, そういう「依存USEフラグ」がworldパッケージまでちゃんと依存のチェーンがつながっているかを解析するスクリプトをひとつ書きました. 「依存USE」になるのはこういうの:

TARGET_REGEXP = r"^((?:ruby|python)_targets_|python_single_target_|abi_x86_)"

github.com

実行するだけで, こんな感じで消してもよさそうなUSEフラグを出してきます. あとはこれを眺めてお掃除しちゃるといいわけですね.

$ python3 chain-dep-scanner.py|grep ruby22|head
dev-ruby/ruby-clutter:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-clutter-gstreamer:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-gtk2:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-gdk3:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-vte3:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-clutter-gdk:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-clutter-gtk:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-webkit2-gtk:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-gnome2:0[ruby_targets_ruby22] is not pulled by any world packages

ただ実行してみるとわかるんですが, 現状あちこち問題はあります.

たとえば, "app-accessibility/espeak:0[abi_x86_64] is not pulled by any world packages"のように, グローバルでオンにしてるようなUSEに関しては出てきてもあんまりうれしくないです. また, PYTHON_TARGETS=python3_6としてるけど, python3.5までしかまだ対応してないよというパッケージも, disableにする余地がないので出てきてほしくないわけですね. 多分pythonだったらこれにしたい〜, rubyはこれにしたい〜という願望を引数に渡して, それが実現できそうなpackageだけリストするモードとかあるといいんでしょうね. そのうち実装します(それかだれかPRでも・・・・)

GCE でemerge -e world, インスタンスはどれにする?

今後記事で詳しく書いていく予定なのだけれど, 最近Gentooの開発用のDockerイメージを作っている. Gentoo 公式のDockerイメージの gentoo/stage3-amd64 から ACCEPT_KEYWORDS=~amd64 emerge -uDN --with-bdeps=y world したものだと思えばよい.

https://hub.docker.com/r/naota/gentoo-devel/

毎日, そういうビルドを自分のマシンで回すというのもめんどうなので, GCE (Google Compute Engine)でやっていくことにした. こんな感じの ansible playbook でドーンとVMとsystemdのserviceがデプロイされる.

github.com

 

さて, ここでインスタンスをどうしするのがベストかな〜というのが問題となる. f1-micro や g1-small といった共有コアインスタンスから, 最大96コアのインスタンスまでいろいろありますね.

マシンタイプ  |  Compute Engine ドキュメント  |  Google Cloud Platform


とりあえず Gentooのビルドにどれがいいか調べてみよう. 標準マシンタイプでコア数を1, 2, 4, 8, 16まで変えてみる. (それに応じてメモリ量も増える) ワークロードとしては, emerge -e world (システムに入っている全パッケージのビルドし直し) をやっておく. Dockerのボリュームは, 永続SSD上に載っていて, ビルドもこのSSD上で行われる.

(なお, 16コアまでなのは, デフォルトで24コアまでのクォータがかかってるから. 96コアまで制限外してって言ってみたら先にマネーをある程度チャージしてくれとのこと, なるほど〜)


はい, ではemerge -eの時間はどうなったかなというのが下. 1コアだと4時間ぐらいかかっているのが, 16コアだと80分まで減ってる. すごい!!

f:id:meech:20180222211951p:plain


いや, でもちょっと待って. インスタンスはコア数によって価格が変わってしまいますね. 1コアのプリエンプティブ料金で1時間0.01ドル, 2コアで0.02ドルと, コア数 X $0.01 の料金がかかる (ゾーンはアイオワ, 多分一番安い). ということで, このワークロードを毎日1ケ月, 30日まわしたらおいくらかかる?というのが下

 

f:id:meech:20180222212023p:plain

いやぁ………コア数増やすほどコストは上がってますね…かなしい.

 

さて, ここでもう1つワークロードを見ておきましょう. 上記のように開発用のイメージを作るという場合であっても, 毎日同じパッケージをビルドする必要はありません. 一度ビルドしたものはバイナリパッケージとして保存しておいて, 新しい(アップデートされた)パッケージだけビルドしたらよくない? ということですね.

ということで, 次のワークロードは全部バイナリパッケージがあるという状態で, emerge -e world します. バイナリパッけージを展開して全部いれ直すというやつですね.

結果はこちら, ついでにコストも. コア数が1つで16分, 16個で10分とそれなりに減ってはいるけれどIOの方が多いワークロードだろうし, ビルドと比べるとそこまでは減っていってない. そんなもんだね

f:id:meech:20180222212049p:plain

f:id:meech:20180222212100p:plain


ということで, GCEでGentooの(最初の状態のシステムでの)ビルドをまわすとだいたいこの2つの線の間になる, ということでしょう. 月5ドルぐらいなら16コアでもいいかな〜とかコスパを考えて1コアかな〜とかそこはいろいろですかね.

 

f:id:meech:20180222212128p:plain

 

あとは上の方のインスタンスだとメモリが十分にあるので, /dev/shm でビルドしたりできるかな. そのへんはまた今度.

それと, これはstage3初期からのemerge -e worldだから, コア数によるスケール効果が出にくい小さなパッケージが多い, ということもある. もしこれがKDE全部ビルドするなどなると, きっといろいろ変わってくるでしょうね.

(それにしても, 112パッケージ(圧縮した状態で, 223M)をバイナリでインストールで10分ってちょっと遅いような気もする. 他だとどんなもんなんだろ. Archとかはやそうだが)

GentooのBINPKGの複数インスタンスとか

FEATURES=binpkg-multi-instance の話をもっとと見かけたのでもっと

これまで Gentooのバイナリパッケージを生成すると, /usr/portage/packages/app-shells/bash-4.4_p18.tbz2 のような感じで, /usr/portage/packages の下に "<category>/<package>-<version>.tbz2" というファイル名で保存されてきた. この保存ファイル名は, Gentooにしては異常に窮屈なものだ.

Gentooのパッケージというのは, USEフラグをいろいろ変えたり, ビルド時に依存関係が書きこまれたりする. バイナリパッケージがあっても, もし, これらUSEフラグなどが異なればそのバイナリパッケージを使うことができない. もともとのファイル名だと, 「最後にビルドした時」と同じUSEフラグなどでなければ, そのパッケージは使えない. Gentoo でそんなことできるはずがない. いろんなマシン用にバイナリをホストしだすとますます大きな問題になってしまう. だからGentooでバイナリパッケージがはやらないんだ(ちがう?

さて, では FEATURES=binpkg-multi-instance にするとどうなるの? バイナリパッケージのファイル名が "app-shells/bash/bash-4.4_p18-1.xpak" のように "<category>/<package>/<package>-<version>-<buildID>.xpak" となる. <version>の後に<BuildID>がついた. このIDにより, 同じパッケージ・バージョンでも別のバイナリパッケージを置けるようになるのだ. やったぜ

BuildID はどうやって決まるのか? これはパッケージごとに最初は"1", その次は"2"みたいに単調にインクリメントされる. (なんかびみょうな実装なような気がするぞ, いいけど) そして, いまのところquickpkgは同じconfigかどうかなどチェックしていないようだ. すなわち, FEATURES=binpkg-multi-instance して, 同じパッケージを quickpkg するとどんどん BuildID が増えていく.


BINPKGの複数インスタンスは, こんな話でした


ちなみにだけれど, この新しいファイル配置は昔のファイル配置と互換性があるみたい. なんで, さくっと FEATURES=binpkg-multi-instance しちゃっていいと思う. いろんなマシン用にバイナリパッケージ作っている人には, ほんとにべんりになると思う.

GentooのBINPKGの圧縮とか複数インスタンスとか

ひさしぶりにman make.confなど見ていると, `BINPKG_COMPRESS` という変数があったのに気がついた. Gentooのバイナリパッケージの圧縮方法を選べるやつで, "bzip2, gzip, lz4, lzip, lzop, xz, zstd"に対応している.

話題のzstdもあるじゃん. どんぐらい圧縮時間やらサイズやら変わるんだろうね, ということでやってみた

 

結果とスクリプトはこちら

gist88666f8b36ae927edefef14486572565

とりあえずグラフにしてみよう.

f:id:meech:20180221125656p:plain

f:id:meech:20180221125708p:plain

(しれっとlzopないけど、package入れてなかったのでとばした)

サイズでいうとlzipかxz, まあxzの方がよく見るしxzを選ぶといい感じだろうか. ただパフォーマンスは悪い

パフォーマンスでいうとlz4とzstdがよい. ただサイズも見ると, zstd一択といったところか.

よく使われるbzip2とgzipは, よく知られている通りのぼちぼちな結果で, 互いにサイズとパフォーマンスにトレードオフがある. そうだね.


サイズで選んでxzなのか, パフォーマンスで選んでzstdなのか, (デフォルトのbzip2のぼちぼちでいくか)Gentooのバイナリパッケージにはどちらがよいだろうか.

xzの場合, 全体で300秒ほどかかっているが, 今回作ったパッケージは45個なので1つあたりの平均6.76秒.

一方, xzはzstdに対して72%のサイズになる.

5,6秒ならコンパイル時間に比べれば些細なものだし, サイズをとってxzでいいのではないか. 展開は速くていいみたいだしね, debianもそうしてるよね

 

 

 

 

投稿して気づいたけど、 複数インスタンスの話をしてねえ!!

FEATURES=binpkg-multi-instance ってのが増えて, USEフラグ違いとかdepend違いで同じebuildに対して個別にbinary packageを作れるようになった。べんり〜。これでいろんなUSEとかでもbinary packageを活用できるようになったぞ!

Gentooのbinary packageの夜明けじゃああ!

今日のLKML: テストしてテストしてからbitを立てる

こういうpatchが投稿されている

[10/12] writeback: only allow one inflight and pending full flush - Patchwork

full flushが一度に1つまでしか走らないようにしたいね、というものなのだけど、中身は本題には関係はない

patchの中に以下のようなコードがある

+       if (test_bit(WB_start_all, &wb->state))
+               return;
+
+       set_bit(WB_start_all, &wb->state); 

 "wb->state"のWB_start_allのbitが立っていたらreturnして、立ってなかったら立てる、というもの。このWB_start_allで"full flush"の排他をしていることになる。

厳密には、これは排他になっていない。1つ目のスレッドがtest_bit()してから、set_bit()するまでの間に、2つ目のスレッドがtest_bit()でbitを見にいくことがある。その場合、2つのスレッドが同時に"critical section"に入ってしまう。

ここを厳密な排他にするには、test_and_set_bit()を使う。

linux/atomic.h at master · torvalds/linux · GitHub

もちろんそんなことはkernel developerには、わかっている。patchのコメントは以下のように書いてある

It doesn't matter if we race a little bit on this, so use the faster separate test/set bit variants. 

 もともとfull syncが2つ以上一緒に走ると重いよね、ぐらいのものなのでここで厳密な排他は必要ないのだ。

こういうatomicityが必要でない場合には、test_bit()とset_bit()とを分けた方がいい時もある。

test_and_set_bit()を使うと必ずbitを立てる、すなわち値を更新してしまう。すると、cache lineがdirtyになる。頻繁に呼ばれ、多くの場合にbitが立っているような場所では、この「cache lineを汚すかどうか」が大きな違いになってくる。

 

で、頻繁に呼ばれるところで無駄にcache lineを汚さずにtest_and_set_bitしたいな〜となると以下のようなコードを書くことになる

if (test_bit(bit, addr) || test_and_set_bit(bit, addr))
...

 スレッドはpatchの内容をすっかり離れて、こうした"idiom"のdocumentないし、それもあわせてなんか関数作っとく?という話になる。そこで関数の名前の話になり、"test_and_test_and_set_bit"がいいとか、"test_then_test_and_set_bit"がいいとか、patchの話どうしたみたいな感じになっていってる。

まあ以下のように解説があるぐらい知られている話なのだけど、関数名として見ると"test and test and set bit"ってなんかむずっとする名前だよな・・・と思った次第

Test and test-and-set - Wikipedia

systemd-networkdがオンラインにならないの

sysmtedはなんでもできるつおい子なので、ネットワークの設定もやってくれる. つおい

どうやって設定書くの? ということで、 man systemd.network すると, こんな風に使える感じのサンプルが載っている

Example 2. DHCP on ethernet links

# /etc/systemd/network/80-dhcp.network
[Match]
Name=en*

[Network]
DHCP=yes

This will enable DHCPv4 and DHCPv6 on all interfaces with names starting with "en" (i.e. ethernet interfaces).

だいたいDHCPでよろしくやるとべんりなので、これをこぴぺするわけだが、これで困ったことになったという話。ちゃんと名前を明示しないと、オンラインになるまで待つserviceがタイムアウトまで待ってしまう

NFSと自動生成サービス

systemdは/etc/fstabからそれぞれのmount pointに対応したserviceを自動的に生成する。 mountもserviceの起動という形で行われる。たとえば、以下のようなfstabを書いておく

naota:/linux /remote/linux nfs defaults,hard,intr 0 0

 すると、以下のようにservice fileが自動生成される

$ sudo systemctl cat remote-linux.mount
# /run/systemd/generator/remote-linux.mount
# Automatically generated by systemd-fstab-generator

[Unit]
SourcePath=/etc/fstab
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
Before=remote-fs.target

[Mount]
Where=/remote/linux
What=naota:/linux
Type=nfs
Options=defaults,hard,intr

 この時mount先がNFSなので、ここには明示的には書いていないが、"network-online.target"への依存が自動的に付加される。すなわち, networkがオンラインになってからNFSにつなぎにいく, ということになる。

systemd-networkdでのnetwork-online.target

systemd-networdを使う場合、network-online.targetの前に、systemd-networkd-wait-online.serviceが起動される。

$ sudo systemctl cat systemd-networkd-wait-online.service
# /lib/systemd/system/systemd-networkd-wait-online.service
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.

[Unit]
Description=Wait for Network to be Configured
Documentation=man:systemd-networkd-wait-online.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
Requires=systemd-networkd.service
After=systemd-networkd.service
Before=network-online.target

[Service]
Type=oneshot
ExecStart=/lib/systemd/systemd-networkd-wait-online
RemainAfterExit=yes

[Install]
WantedBy=network-online.target

 このserviceは、"/lib/systemd/systemd-networkd-wait-online"を起動する、というもの。 このプログラム(systemd-networkd-wait-online)は、マシンのnetwork interfaceをなめて全て(loなどはのぞくが)つながるまで待つ。

具体的にどうやって「つながった」と判定されるのか見ていこう。/run/systemd/netif/links/*にinterfaceの数だけのファイルがある。たとえばloだとこんな感じ。

$ cat /run/systemd/netif/links/1
# This is private data. Do not parse.
ADMIN_STATE=unmanaged
OPER_STATE=carrier

特に管理対象じゃありません(ADMIN_STATE=unmaged)となっている。

一方でDHCPにつないでるやつだとこんな感じ。 DHCPIPアドレスをもらっていて、 ADMIN_STATE=configuredとなっている。

$ cat /run/systemd/netif/links/3
# This is private data. Do not parse.
ADMIN_STATE=configured
OPER_STATE=routable
NETWORK_FILE=/etc/systemd/network/80-dhcp.network
DNS=なんたらかんたら
NTP=なんとかかんとか
DOMAINS=
ROUTE_DOMAINS=
LLMNR=yes
MDNS=no
ADDRESSES=.....
ROUTES=.....
DHCP4_ADDRESS=.....
DHCP_LEASE=/run/systemd/netif/leases/3

 DHCP待ちなどのタイミングでは、これが"ADMIN_STATE=configuring"となっている。

結局のところ、systemd-networkd-wait-onlineはこのへんのファイルをなめて、ADMIN_STATE=configuring(やpending)の間ループするといった動きをする。

もしも、configuredにならない場合にはdefaultで120秒のタイムアウトで終了する

LANケーブルをさしてない口があるとつらい

といった状況でLANケーブルをさしていないportがあると、その口は"configuring"のままなのでsystemd-networkd-wait-onlineはタイムアウトするまで待ちつづける. 結果として、あちこち起動が遅れてめんどいなあという気持ちになる

それでどうするの?

Name=en*にしとくとdevice名見なくてもあれこれできるけど、オンライン待ちがうざいのでちゃんと名前を書こうねという話だった