複数バージョンから選べるNetBSDインストーラーイメージ

この記事は、NetBSD Advent Calendar 2019の14日目の記事です。 

![multiversion](http://www.grotto.jp/~bsh/forQiita/02/DSC_1371.jpg)

# NetBSDの UEFI ブートローダー
build.sh を

```shell:
build.sh -m amd64 -R RELEASEDIR release install-image
```
と実行すると、RELEASEDIR/images/NetBSD-VERSION-amd64-uefi-install.img.gz ができます。
このファイルをddとかでUSBメモリに書き込むと UEFI から起動できるインストールメディアになります。

インストーラーイメージは、GPTでパーティションが切られていて、次のように構成されています。

```
   start     size  index  contents
        0        1         PMBR
        1        1         Pri GPT header
        2       32         Pri GPT table
       34     2014         Unused
     2048   262144      1  GPT part - EFI System
   264192  2908160      2  GPT part - NetBSD FFSv1/FFSv2
  3172352     2015         Unused
  3174367       32         Sec GPT table
  3174399        1         Sec GPT header
```

UEFIブートローダーのソースを読んでいた時[^1]、GPTパーティションを hd0a, hd0b, ... のように指定できることに気付きました。
上の構成では、 EFI パーティションが hd0a、NetBSDが hd0b になります。 ということは、 もう一つNetBSDのパーティションを作ってブートローダーのプロンプトから

```
 boot hd0c:netbsd
```

と起動してやれば、追加のパーティションから NetBSDが起動できそうです。一個のUSBメモリで -current、9.0_BETA、8.1 を選んでインストールできるようになるわけです。

# マルチバージョン化の手順

1. 元のインストールイメージの先頭からEFIパーティションまでをコピーして出力イメージとする。
2. 出力イメージにNetBSDパーティションを入れる部分を確保して、さらに1MiBytesの領域を追加。これはGPTのセカンダリテーブルに使われる。
3. GPTテーブルを新規作成[^2]。元イメージの構成にあわせてEFIパーティションを追加。
4. 各バージョンについて、
    1. NetBSDのGPTパーティションを作成。このパーティションのGUIDを後で使用する。
    2. 元のインストールイメージからNetBSDパーティションの部分を取り出す。
    3. ファイルシステムイメージをマウントして、
    3. /etc/fstab の ルートデバイスのNAME= 指定を新しいGUIDに書き換える。
    4. /boot.cfg からメニューを取得。最初のNetBSDパーティションでは、/boot.cfg を書き換えてメニューを増やす。
    5. 更新したファイルシステムイメージを出力イメージの中に書き込む
 
以上を実行するシェルスクリプトを [github](https://github.com/bsh-git/netbsd-multi-version-install-image) に置きました。

# 作成例

-current, 9.0_BETA, 8.1_STABLE の三つのイメージからマルチバージョン化したインストーラーイメージのGPTパーティションは、次のようになります。

```
   start     size  index  contents
        0        1         PMBR
        1        1         Pri GPT header
        2       32         Pri GPT table
       34     2014         Unused
     2048   262144      1  GPT part - EFI System
   264192  2908160      2  GPT part - NetBSD FFSv1/FFSv2          # -current
  3172352  2908160      3  GPT part - NetBSD FFSv1/FFSv2          # 9.0_BETA
  6080512  2560000      4  GPT part - NetBSD FFSv1/FFSv2          # 8.1_STABLE
  8640512     2015         Unused
  8642527       32         Sec GPT table
  8642559        1         Sec GPT header
```

ブートローダーのメニュー

```boot.cfg
banner=Welcome to the NetBSD/amd64 (multiple versions) installation image
banner================================================================================
banner=
banner=If you encounter a problem while booting, report a bug at
banner=https://www.NetBSD.org/.
menu=Install NetBSD/amd64 9.99.17:boot netbsd
menu=Install NetBSD/amd64 8.1_STABLE:boot hd0d:netbsd
menu=Install NetBSD/amd64 8.1_STABLE (no ACPI):boot hd0d:netbsd -2
menu=Install NetBSD/amd64 8.1_STABLE (no ACPI, no SMP):boot hd0d:netbsd -12
menu=Install NetBSD/amd64 9.0_BETA:boot hd0c:netbsd
menu=Drop to boot prompt:prompt
timeout=30
clear=1
```

# おまけ

## gpt(8)コマンドの不思議仕様
この記事を書いていて気付いたのですが、TOOLDIR/bin/nbgpt[^3] の引数の順番が gpt(8) と異なっています。

```
/sbin/gpt command args device
TOOLDIR/bin/nbgpt device command args
```

gpt(8)のソースを見ると、argv[0]が "gpt" の時とそうでない時で、わざわざ動作を変えています。
src/sbin/gpt/Makefile を見ると、

```
#LINKS=  ${BINDIR}/gpt ${BINDIR}/gptlabel
#MLINKS= gpt.8 gptlabel.8
```

となっていて、コメントアウトされていますが、gptlabel という名前にして引数の順番を変更しようとしたのかな、と推測します。
gptコマンドの引数の順番は、atactl, dkctl などと統一がとれてなくて気持悪い、とは私も思うので意図は理解できますが、gpt と nbgpt で動作が違うのはダメじゃないかな。

## Linux の mount -o loop
この作業では、ファイルシステムイメージの中身をいぢる手順があります。 (/etc/fstab と /boot.cfg を変更)

NetBSDでこれをやるには、

```shell
vndconfig -c vnd0 image_file
mount /dev/vnd0d /mnt
edit /mnt/foo
umount /mnt
vndconfig -u vnd0
```
と実行します。Linuxでは、同等のことを

```shell
mount -o loop image_file /mnt
edit /mnt/foo
umount /mnt
```
と、より簡単にできます。NetBSDの方法だと、vnd0 がすでに使われている場合に失敗するので、空いているvndを探すという手間も発生します。

ちょっと頑張れば、mount -o loop は実装できそうな気がするんですが……


[^1]: ブートローダーを調べたかったのではなくて、libsa (stand alone library: ブートローダーなどNetBSDが起動する前の環境で動作するプログラム用のライブラリ) で UEFIアプリケーションが作れるか調べていた。で、横道に逸れたのでその件は進捗してない。 
[^2]: 元イメージのGPTテーブルをそのまま使ってもよいが、その場合はセカンダリテーブルもコピーする必要があって面倒。
[^3]: ```build.sh tools``` で作成されるホストツールの一つ