VAIO Pro 11 に gentoo をインストールした

一度 Gentoo というものを使ってみたかったのでチャンレンジしてみた。 インストール元は、Arch Linux が入っていたので、そこから LVM を Gentoo 用に切り出して、stage3 を使ってインストールした。

Kernel config は Sony Vaio Pro SVP-1x21 のページを参考にポチポチやればよかった。 あとは、systemd を使いたかったので emerge でインストールする。また、systemd-boot を利用するために USE フラグで gnuefi を有効にする必要がある。

あとはガーッとインストールして、/dev/sda3 を /boot にマウントして、bootctl install すればOK。

今回は LVM の上にインストールしたので、initramfs を作る必要があるっぽい。 genkernel-next を emerge して genkernel --lvm initrmfs を実行する。

最後に、systemd-boot 用の設定を書く。 /boot/loader/entries/gentoo.conf とかを作り、以下のように設定する。

title Gentoo Linux
linux /vmlinuz-4.6.3-gentoo
options initrd=initramfs-genkernel-x86_64-4.6.3-gentoo dolvm root=/dev/mapper/arch-gentoo rw rootflags=subvol=@ init=/usr/lib/systemd/systemd

でこれでリブートすると、なぜか journald がこけたりする。 どうやら /etc/machine-id がないのがダメみたいで、systemd-machine-id-setup を実行したら作ってくれた。

UML で hostfs と overlayfs を使う

User mode linux で hostfs でホストのファイルシステムを参照しつつ、書き込みは別の場所にしたかったので overlayfs を使ってみました。

User mode linux (UML) は linux kernel をいろいろ頑張って普通のプロセスとして実行するものです。 ptrace とかで頑張っているらしいけど、ちゃんと調べていないです。

hostfs は UML からホストのファイルシステムを透過的に見せる薄いファイルシステムで kernel の fs/hostfs 以下で実装されています。

overlayfs は unionfs とか言われるやつで二つの FS を組み合わせて ro なベースイメージに差分だけ別の箇所に書くみたいなことに使うようです。 組込デバイスで使ったり、Docker では storage backend として aufs を使っています。 aufs は out-of-tree ですけど overlayfs は kernel tree に含まれています。

先ずは UML をビルドします。今回は githublinusリポジトリから HEAD をもってきました。 menuconfig するときに File systems から Overlay filesystem support を有効にしておきます。 あと、いらなそうな機能は削っておくとビルドが早いです。

git clone https://github.com/torvalds/linux.git
cd linux
make defconfig ARCH=um
make manuconfig ARCH=um
make ARCH=um

ビルドが終わると vmlinux という実行ファイルができるので、次のように実行します。

./vmlinux rootfstype=hostfs init=/bin/sh mem=128M

あとは UML の中で、overlayfs を使ってみます。

mount -t tmpfs none /tmp
cd tmp
mkdir l u w m
mount -t hostfs -o/,ro none /tmp/l
mount -t overlay -olowerdir=/tmp/l,upperdir=/tmp/u,workdir=/tmp/w none /tmp/m

適当なファイルを読んでみましょう。

cat /tmp/m/etc/hosts

すると以下のように失敗すると思います。

cat: /tmp/m/etc/hosts: No such file or directory

ホスト側と UML から strace してみます。

strace /bin/cat /tmp/m/etc/hosts
  • ホスト
strace -p $UML_PID

すると、UML 側では以下のように open に失敗しています。

open("/tmp/m/etc/hosts", O_RDONLY)      = -1 ENOENT (No such file or directory)

ホスト側では以下のようになっています。

open(" \205Ui/etc/hosts", O_RDONLY)     = -1 ENOENT (No such file or directory)

なんかすごく怪しいですね。

カーネルを見てみると、open は fs/hostfs/hostfs_kern.c の hostfs_open でやっています。

 name = dentry_name(file_dentry(file));
    if (name == NULL)
        return -ENOMEM;

    fd = open_file(name, r, w, append);

dentry_name は __dentry_name を呼び出していて、以下のようになっています。

 char *p = dentry_path_raw(dentry, name, PATH_MAX);
    char *root;
    size_t len;

    root = dentry->d_sb->s_fs_info;
...
        strcpy(name + len, p);

    return name;
}

fs_info には mount 時の起点のパスが入るようになっています。 今は -o/,ro としたので / が入るはずです。 上では p には /etc/hosts が入っており、root には / が入るはずです。

しかし実際には fs_info に謎の \205Ui というデータが入ってしまっています。

cat は呼べているため overlayfs 側の問題のように見えます。 git log してみると、怪しいコミットがありました。

overlayfs: Make f_path always point to the overlay and f_inode to the underlay

どうやら file->f_path が hostfs のものでなく overlayfs のものになっているため、superblock に入っている fs_info が別のものを指すようになったようです。

これに対して、fs: add file_dentry() で正しい dentry を参照するための 関数が導入されています。 また、続くコミットで、nfs, ext4 が修正されています。 が、何故かそれだけで、btrfs や f2fs は独自に改修しています。

原因は分かったので、適当にパッチ を当てたツリーを使ってビルドしたら正しく動きました。

UMLメーリングリストにパッチを投稿したら以下の返信がありましたが、なんでそうしなかったのかよくわからないです。

User-mode Linux kernel port / Mailing Lists

Is there a reason why not all instances of file->f_path.dentry have been replaced by file_dentry()? I smell more fallout...

現状では overlayfs は ext4 や btrfs で動かすのが安全そうです。

VyOS で unbound を利用する

VyOS というより EdgeOS なのだけれど。

自宅の環境はプロバイダのルータと自前ルータ(EdgeRouter)の二段構えなのだが、EdgeRouter の DNS のフォワード先はプロバイダのルータにしていた。 しかしプロバイダのルータの DNS がたまに応答しなくなったり不安定だったので、EdgeRouter 自身に unbound を立てることにした。

とりあえず以下を参考にして apt を使えるようにする。

EdgeMAX - Add other Debian packages to EdgeOS – Ubiquiti Networks Support and Help Center

しかる後、unbound をインストールする。

sudo apt-get update
sudo apt-get install unbound

ここでポート 53 を dnsmasq が listen しているので unbound の起動がコケる。 /etc/unbound/unbound.conf を適当に書き換えてポート番号を変えておく。

server:
   ...
   interface: 127.0.0.1
   port: 10053

そして起動する。

sudo service unbound restart

あとは、configure で dnsmasq の設定をする。 非標準ポートなので options で指定して、/etc/resolv.conf を見ないように no-resolv も入れておく。

set service dns forwarding options server=127.0.0.1#10053
set service dns forwarding options no-resolv
commit
save

/etc/dnsmasq.conf を見て設定が正しいか確認しておくと良いだろう。

unbound が働いているかどうかは、sudo unbound-control stats をしてみればわかる。

systemd-networkd では netdev だけではデバイスは UP しないらしい

LXC 用の Bridge インターフェースを systemd-networkd で管理しようとして、.netdev ファイルを以下のように作った。

[NetDev]
Name=br0
Kind=bridge

この bridge に対して physical デバイスで以下のように接続しておいた。

[Match]
Name=en*

[Network]
...
Bridge=br0

これだけだと、ip link でみてみると link が DOWN になっていて、接続が出来ない。

以下のような空の .network ファイルを bridge に対して設定してやったらめでたく UP になってくれた。

[Match]
Name=br0

SSD が壊れた。

サーバ機の OS 領域として使っていた SSD が壊れてしまった。 モノは、シリコンパワー製の 32GB MLC のドライブ。型番は SP032GBSSDE20S25 らしい。 購入したのは、2012年3月なので、3年と8ヶ月で壊れたことになる。

壊れたのに気づいたのは、ホストが勝手にシャットダウンして mackerel からアラートが上がったから。 とりあえず立ち上げてみたが、ブート途中でストップしてしまった。 しばらく放置してみたら errno 16 が出ていた。EBUSY なのは応答がないからだろう。

ホスト側のコネクタ周りとかがおかしいかもしれなかったので、SATA-USB 変換器でつなげてみたが、 rw マウントしたらやはりエラーになってしまってマウントできなかった。 幸いにも ro ではマウントできデータ化けもなかったようなので、バックアップをとって余っていた HDD に適当にコピーして復旧した。

ちょっと確認したところ、特に smart ではエラーが無いように見えた。 また、OS 領域に使っていたのでログが残っておらず調査が面倒だった。

週末に IntelSSD でも買ってこよう。今度はどのくらい持つだろうか。

VyOS でプライベート DNS コンテンツサーバを参照する

VyOS は DNS フォワーダを持っているが、標準的な設定ではフォワード先は(当然ながら)フルリゾルバを指定する。 しかし、内部用の DNS コンテンツサーバを参照するだけでフルリゾルバを立てるのが面倒くさい。 特定のドメイン、及びサブドメインだけをフォワードする方法がわかったので記述する。

VyOS の DNS フォワーダは dnsmasq で実現されている。dnsmasq は特定のドメインだけ指定した DNS サーバに飛ばす設定ができるが、 VyOS の set service dns forwarding name-server コマンドではこの指定ができない。 しかし set service dns forwarding options コマンドを使うことで dnsmasq の任意の設定が記述できる。 具体的には、以下のように記述すれば良い。

set service dns forwarding options server=/my.home/10.x.y.z

rust の lifetime

まずは 1.0 alpha リリースおめでとうございます。

リリースしてドキュメント関係が差し替わって Book (旧 guide) に lifetime について乗るようになったぽい。

C++ はリファレンスによって NULL ポインタのデリファレンスを防げるようになったが、ダングリングリファレンスは依然として起こってしまう。 rust の lifetime の概念はこれを解決するためのものらしい。

lifetime は Book にあるように、 場合によって省略可能だが、コンパイラが決定できない場合ユーザーが明示的に指定する必要がある。 具体的には、以下のような場合。

fn f<'a> (x: &'a i32, y: &i32) -> &'a i32 {
    x
}

fn main() {
    let x = 1;
    let r: &i32;
    {
        let y = 2;
        r = f(&x, &y);
    }
}

この場合、f の仮引数の x と戻り値の lifetime が同一であると 'a として lifetime parameter を明示することで、 コンパイラに教えている。 この場合だと、x をリターンしているので、コンパイラでも検出できると思うが、以下のような場合を考えると、省略ルールから外れた場合は常に指定する必要があるというのは、デザインとして納得ができる。

fn f<'a> (x: &'a i32, y: &'a i32) -> &'a i32 {
    if *x == 0 {
        x
    } else {
        y
    }
}

ちなみに、最初の例で、f(&y, &x) とすると、y はスコープアウトするのに r に参照がわたってしまうので、コンパイルエラーになる。 こんな感じで rust は安全性を担保してるようだ。