CPUを変更して、不正命令で動作しなくなったGentooシステムの再構築

Gentoo システムでは、`CFLAGS` をカスタマイズできるので、`-march=native` などとして、自環境に特化したバイナリを作成することがしばしばあります。
ところが、PC の CPU 交換や、VPS の収容ホスト変更などにより、実行できる命令セットが変化することがあります。
命令セットが増えるだけならばよいのですが、命令セットが減ってしまうと、新しい環境では不正命令例外が発生し、起動不能となる場合があります。
丁度、そのような事態に遭遇したため、行った復旧手順を紹介します。

# 手順概要

 1. Gentoo インストールメディアで起動します。
 2. 再構築対象のファイルシステム上に、さらに stage3 を展開します。
 3. 上記で展開した stage3 に含まれる portage とビルドツールを用いて、パッケージの再構築を行います。emerge コマンドに、 --root と --config-root を指定するのがキモです。


# 手順詳細

まず、Gentoo インストール用のメディアから起動します。
起動完了後、時刻がずれていたら調整しておきます。

    # date
    Mon Jan 11 17:29:52 UTC 2016  ← 時刻がずれている場合は調整。UTC で表示されていることに注意。
    # date --set '-9 hours'
    Mon Jan 11 08:30:02 UTC 2016

次に、復旧対象のシステムのファイルシステムをマウントやスワップの有効化を行います。
筆者のシステム場合、以下のようになりました。具体的なパーティションの指定はシステム毎に合ったものを指定して下さい。

    # mount LABEL=/ /mnt/gentoo
    # mount LABEL=/boot /mnt/gentoo/boot
    # swapon LABEL=swap

通常のインストール作業では、ここから proc、 /dev、 /sys などもマウントし、 `chroot` するところですが、ここではそれらを行いません。(chroot したところで不正命令で終了するだけです)

作業用ディレクトリを復旧対象の /root にします (他のディレクトリでも構いませんが、ここが散らかりにくいかと)。

    # cd /mnt/gentoo/root

stage3 をダウンロード・展開します。今回は 2016-01-07 版の stage3 を使用しました。

    # wget http://ftp.iij.ad.jp/pub/linux/gentoo/releases/amd64/autobuilds/current-stage3-amd64/stage3-amd64-20160107.tar.bz2
    # mkdir tmp-system
    # cd tmp-system
    # tar jxvpf ../stage3-amd64-20160107.tar.bz2

展開した stage3 に chroot して作業が行えるように設定し、chroot します。この時、chroot した後の stage3 から対象のファイルシステムが見えるようにしておきます。
portage ツリーも<del>手抜き</del>で対象のファイルシステムのものを使います。

    # cp /etc/resolv.conf etc/
    # mount -t proc proc proc
    # mount --rbind /sys sys
    # mount --make-rslave sys
    # mount --rbind /dev dev
    # mount --make-rslave dev
    # mkdir mount/target
    # mount --rbind /mnt/gentoo mnt/target
    # mkdir -p usr/portage
    # mount --bind mnt/target/usr/portage usr/portage
    # chroot . /bin/bash

全パッケージの再構築をします。
これだけですんなりと通ればよいのですが、ここが一番のハマり所。なかなかうまく行かないと思います。後述の[emerge の通し方](#emerge-の通し方)を参照して下さい。

    # emerge --root /mnt/target --config-root /mnt/target -tvae system --keep-going

これが正常終了したら、念のため、 /bin, /sbin/, /usr/bin/, /usr/sbin, /lib/, /usr/lib/ に対し、 `ls -lt` して、未更新のファイルがあれば、そのファイルを含むパッケージを再 emerge するとよいでしょう。確認が完了したら再起動し、HDD から起動します。無事に起動することを祈りましょう。


# emerge の通し方

## layman 用ディレクトリを /var/lib/layman にマウントする。

layman を使用している場合、 /mnt/target/etc/portage/make.conf に、

```bash
source /var/lib/layman/make.conf
```


と書いてあると思います。これが絶対パス指定であり、 stage3 側にはそのようなファイルがないので、読み込みに失敗します。
また、 layman 側の make.conf の中身も絶対パスで記述されているため、 /mnt/target/etc/portage/make.conf を一時的に書き換える、という手法ではうまくいきません。

    # mkdir /var/lib/layman
    # mount --bind /mnt/target/var/lib/layman

として、 /var/lib/layman が見えるようにしておきましょう。[^1]
[^1]: `mount --bind` ではなく、シンボリックリンク張るだけで十分だということに後から気付きました。

## その他 PORTDIR_OVERLAY に指定したツリーもマウントする。

PORTDIR_OVERLAY に指定したツリーがあれば、それらも stage3 から見えるようにマウントします。
例えば、PORTDIR_OVERLAY に /usr/local/portage を指定している場合、以下のようにします。[^1]

     # mkdir -p /usr/local/portage
     # mount --bind /mnt/target/usr/local/portage /usr/local/portage

## プロファイルを合わせる

`emerge` に `--root /mnt/target --config-root /mnt/target` を指定することで、基本的には /mnt/target/etc/portage 内の設定を見に入ってくれますが、stage3 側にもインストールする必要があるパッケージも大量に出てきます。[^2]
[^2]: 詳細は追い掛けていませんが、 ebuild に DEPEND で指定されているパッケージがこうなっているような。

この時、プロファイルが合っていなければ、USE 変数やマスクなどの設定が食い違うことで、依存関係の解決に失敗することがあります。[^3]

[^3]: 筆者は、プロファイルを default/linux/amd64/13.0/systemd にしていたのですが、 stage3 はデフォルトの default/linux/amd64/13.0 のままにしていたところ、 systemd と udev がケンカして Block が発生していました。

## make.conf を合わせる

プロファイルを合わせるのと同じ理由で、make.conf も一緒にしておいた方がよいです。特に USE を大量にカスタマイズしている場合など。

## その他 package.* 等の調整

これも、基本的に、プロファイルや make.conf と同様に合わせておけばよいのですが、場合によってはカスタマイズしなければならない場合があります。

筆者の場合、 stage3 用の app-text/asciidoc をビルドするためには、`PYTHON_SINGLE_TARGET="python2_7"` の指定が必要と言われたのですが、 make.conf では、`PYTHON_SINGLE_TARGET="python3_4"` と指定していました。
そこで、 stage3 用 app-text/asciidoc のみ、 PYTHON_SINGLE_TARGET を変更するために、以下の設定を /etc/portage/ に置いて対処しました。<del>[^4]</del>

[2016-01-14追記: 元のシステムの起動が成功した後、念のため `emerge -e world` したら、app-text/asciidoc で同様のハマり方をしました。注釈4[^4]で書、/etc/portage は共用しない方がよいと書きましたが、実は共有するだけで大丈夫なのかもしれません]


```text:package.doc/asciidoc
app-text/asciidoc python_single_target_2_7
```

```text:env/python_single_target_2_7
PYTHON_SINGLE_TARGET="python2_7"
```

[^4]: こういったパターンがあるので、 /etc/portage については、 /mnt/target/etc/portage を `mount --bind` したり、シンボリックリンクにしたりするとハマる恐れがあります。