NetBSD-9.0のmount_qemufwcfg(8)でqemuのfw_cfgデータを参照してみる

[NetBSD Advent Calendar 2020](https://qiita.com/advent-calendar/2020/netbsd) 5日目の記事です。
今日はNetBSD-9.0で追加された、[mount_qemufwcfg(8)](https://man.netbsd.org/mount_qemufwcfg.8)コマンドの紹介をしようと思います。
NetBSD-9.0のリリースアナウンス([Announcing NetBSD 9.0 (Feb 14, 2020)](https://github.com/qemu/qemu/blob/master/docs/specs/fw_cfg.txt))を見ると、"support for the QEMU firmware configuration device"という一文があります。これに対応する機能が今日紹介する[mount_qemufwcfg(8)](https://man.netbsd.org/mount_qemufwcfg.8)のようです。

> Improvements for using NetBSD as a guest OS, with support for the QEMU firmware configuration device, ...

## mount_qemufwcfgとQEMU fw configuration file

[QEMU](https://www.qemu.org/)には[QEMU fw_cfg](https://github.com/qemu/qemu/blob/master/docs/specs/fw_cfg.txt)(QEMU firmware configuration)という、ゲストOS側に対してBootの順番やACPIやSMBIOSテーブル、仮想マシンに割り振るUUID、SMPやNUMA情報といったデータをホストOS側から提供するためのインタフェースが提供されています。
NetBSD-9.0で追加された、[mount_qemufwcfg(8)](https://man.netbsd.org/mount_qemufwcfg.8)は、この[QEMU fw_cfg](https://github.com/qemu/qemu/blob/master/docs/specs/fw_cfg.txt)データを仮想ファイルシステム経由でmountして参照可能にするコマンドになります。

## ホストOS側でのqemu起動オプション

さっそく順を追って試してみましょう。[mount_qemufwcfg(8)](https://man.netbsd.org/mount_qemufwcfg.8)はゲストOS側のNetBSDで実行するため、まずはホストOS側でqemuを起動します。起動時に `-fw_cfg name=<NAME>,file=<FILE>` のような形で必要なデータをゲストOS側に渡します。

`name=...` で指定した内容がゲストOS側での参照パスになります。 `opt/...` から始まるパスがユーザ用に予約されているため(※)、この例では `opt/com.mycompany/blob` というパスを指定しています。
※ [QEMU fw cfg](https://wiki.osdev.org/QEMU_fw_cfg)では以下のように記載されています。

> Names that begin with "opt/" are reserved for users.

そして `file=...` には参照させるファイルを指定します。この例では"Hello,World."というテキストが書かれたファイルを渡しています。

```sh
$ echo 'Hello,World.' > my_blob.bin
$ qemu-system-x86_64 \
        -m 512M \
        -boot order=c \
        -net nic \
        -net user,hostfwd=tcp::2222-:22 \
        -fw_cfg name=opt/com.mycompany/blob,file=./my_blob.bin \
        -snapshot \
        -hdd nbsd.img
```

## mount_qemucfgを使用する

### 必要な準備

ゲストOSが起動したら[mount_qemufwcfg(8)](https://man.netbsd.org/mount_qemufwcfg.8)を試してみたいところですが、いくつか準備が必要です。
[/usr/src/sbin/mount_qemufwcfg/fwcfg.c](https://github.com/NetBSD/src/blob/trunk/sbin/mount_qemufwcfg/fwcfg.c#L47)のソースコードを見ると、 `/dev/qemufwcfg` を参照するようです。

```c
#define _PATH_FWCFG "/dev/qemufwcfg"
```

ゲストOS側のNetBSDでデバイスノードを作成するため、 `mknod -l` で `qemufwcfg` のmajorデバイス番号を確認します。

```sh
$ mknod -l | grep qemufwcfg
qemufwcfg character major 342
```

`mknod qemufwcfg c 342 0` で `qemufwcfg` のデバイスノードを作成します。

```sh
$ cd /dev
$ sudo mknod qemufwcfg c 342 0
$ ls -l /dev/qemufwcfg
crw-r--r--  1 root  wheel  342, 0 Dec  4 10:21 /dev/qemufwcfg
```

### mount_qemufwcfg(8)でfw_cfgデータを参照する

これで[mount_qemufwcfg(8)](https://man.netbsd.org/mount_qemufwcfg.8)を使用してfw_cfgデータをマウントできます。この例では `/mnt` をマウントポイントにしています。

```sh
$ sudo mount_qemufwcfg /mnt
```

どうやら[PUFFS](https://en.wikipedia.org/wiki/PUFFS_(NetBSD))(NetBSDのユーザランド上でファイルシステムを実現する機能)を利用しているようですね。
[PUFFS](https://en.wikipedia.org/wiki/PUFFS_(NetBSD))については、過去のNetBSD Advent Calendarにて紹介していますので併せてご参照ください。

 * [NetBSDのユーザ空間でファイルシステムを動作させるpuffsを試してみる](https://qiita.com/furandon_pig/items/794e04991ac2ee1ca29a)
 * [NetBSDのlibpuffsを使ってpuffsの簡単なサンプルを作ってみる](https://qiita.com/furandon_pig/items/9972136c226c734a3b0e)

```sh
$ df -h
Filesystem         Size       Used      Avail %Cap Mounted on
...
/dev/puffs           0B         0B         0B 100% /mnt
```

マウントしたディレクトリ以下では色々なデータが参照できる状態になっています。今回は `-fw_cfg` オプションで渡した `/mnt/opt/com.mycompany/blob` を見てみます。

```sh
$ find /mnt -type f | xargs file
/mnt/bios-geometry:            empty
/mnt/bootorder:                empty
/mnt/etc/acpi/rsdp:            data
/mnt/etc/acpi/tables:          data
/mnt/etc/boot-fail-wait:       ISO-8859 text, with no line terminators
/mnt/etc/e820:                 data
/mnt/etc/smbios/smbios-anchor: data
/mnt/etc/smbios/smbios-tables: SVr2 curses screen image, big-endian
/mnt/etc/system-states:        lif file
/mnt/etc/table-loader:         data
/mnt/etc/tpm/log:              empty
/mnt/genroms/kvmvapic.bin:     BIOS (ia32) ROM Ext. (18*512)
/mnt/opt/com.mycompany/blob:   UTF-8 Unicode text
```

ファイルの中身を見てみると、ホストOS側で指定したファイルが参照できています！
ただし、ゲストOS起動後にホストOS側がファイルの中身を変更してもゲストOS側のファイルには変更が反映されず、どうやらゲストOS起動時点ででのファイルの中身が常に参照されるという挙動になっています。

```sh
$ cat /mnt/opt/com.mycompany/blob
Hello,World.
```

## まとめ

[NetBSD 9.0](https://www.netbsd.org/releases/formal-9/NetBSD-9.0.html)で追加された[mount_qemufwcfg(8)](https://man.netbsd.org/mount_qemufwcfg.8)コマンドを試してみました。ホストOS側からファイルを渡すことができるため、ゲストOS起動時にssh鍵やクレデンシャル情報等を渡すといった柔軟な応用ができそうです。

## 参考URL

 * [Announcing NetBSD 9.0 (Feb 14, 2020)](https://www.netbsd.org/releases/formal-9/NetBSD-9.0.html)
 * [mount_qemufwcfg(8)](https://man.netbsd.org/mount_qemufwcfg.8)
 * [qemucfg(4)](https://man.netbsd.org/qemufwcfg.4)
 * [src/sbin/mount_qemufwcfg/fwcfg.c](https://github.com/NetBSD/src/blob/trunk/sbin/mount_qemufwcfg/fwcfg.c)
 * [QEMU](https://www.qemu.org/)
 * [QEMU fw cfg](https://wiki.osdev.org/QEMU_fw_cfg)
 * [qemu/docs/specs/fw_cfg.txt](https://github.com/qemu/qemu/blob/master/docs/specs/fw_cfg.txt)