NetBSDのカーネルモジュールからホスト名を設定してみる

[NetBSD Advent Calendar 2023](https://qiita.com/advent-calendar/2023/netbsd) 24日目の記事です。今日はカーネルモジュールからNetBSDカーネル空間内のデータを操作してみます。

## カーネル空間内のデータ

カーネルモジュールはその名前の通り、カーネル空間に組み込んで使用します。言い換えると、カーネル空間内のデータに自由にアクセスできるということになります。今日の記事では、作成したカーネルモジュールからカーネル空間内の既存のデータを操作してみます。

## ホスト名書き換えカーネルモジュール

NetBSD(に限らずUNIX系OS)では、[hostname(1)](https://man.netbsd.org/hostname.1)でホスト名を設定できます。カーネルの実装という観点からすると、カーネル内にホスト名の情報を保持する変数を用意する形で実装されています。具体的には[/usr/src/sys/kern/kern_sysctl.c](https://github.com/NetBSD/src/blob/trunk/sys/kern/kern_sysctl.c)の以下の個所で宣言されている変数でホスト名が保持されています。

```c
 152 char hostname[MAXHOSTNAMELEN];
 153 int hostnamelen;
```

`MAXHOSTNAMELEN` は[/usr/src/sys/sys/param.h](https://github.com/NetBSD/src/blob/trunk/sys/sys/param.h)で以下のように定義されています。

```c
124 #define MAXHOSTNAMELEN  256     /* max hostname size */
```

`char hostname[]` はカーネル空間内であればどこからでも参照・変更可能です。今回はデバイスファイルへの[write(2)](https://man.netbsd.org/write.2)経由でホスト名を設定し、カーネルモジュール側で `write()` されたデータを受け取り、新しいホスト名として設定するサンプルを作成してみます。

## カーネルモジュールのサンプルコード

カーネル空間側(カーネルモジュール)のコードは以下になります。

https://gist.github.com/furandon-pig/cb7bff367c79970999f9e92459fe9b57

使用するデバイス `/dec/hostname_sample` への `write(2)` は `hostname_sample_write()` 関数に紐づけられており、ユーザ空間から書き込まれたデータ(ホスト名文字列)をカーネル空間にコピーしたのち、変数 `hostname` に設定することでホスト名を設定しています。

カーネルモジュールビルド用の `Makefile` を作成します。この `Makefile` とCソースファイルは `/usr/src/sys/modules/example/hostname_sample` のようなディレクトリに配置します。

```
.include "../Makefile.inc"

#S?=    /usr/src/sys
KMOD=   hostname_sample
SRCS=   hostname_sample.c

.include <bsd.kmodule.mk>
```

`make` コマンドでビルドします。

```sh
# make
...
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-gcc -O2 -g   -std=gnu99    -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wno-sign-compare  -Wsystem-headers   -Wno-traditional   -Wa,--fatal-warnings  -Wreturn-type -Wswitch -Wshadow -Wcast-qual -Wwrite-strings -Wextra -Wno-unused-parameter -Wno-sign-compare -Wold-style-definition -Wsign-compare -Wformat=2  -Wno-format-zero-length  -Werror   -ffreestanding  -fno-strict-aliasing -Wno-pointer-sign -mno-red-zone -mno-mmx -mno-sse -mno-avx -msoft-float -mcmodel=kernel -fno-omit-frame-pointer   -I/usr/src/common/include --sysroot=/ -I/usr/src/common/include  -nostdinc -I. -I/usr/src/sys/modules/examples/hostname_sample -isystem /usr/src/sys -isystem /usr/src/sys/arch -isystem /usr/src/sys/../common/include -D_KERNEL -D_LKM -D_MODULE -DSYSCTL_INCLUDE_DESCR -c    hostname_sample.c
/usr/src/tooldir.NetBSD-9.3-amd64/bin/nbctfconvert -L VERSION hostname_sample.o
#      link  hostname_sample/hostname_sample.kmod
/usr/src/tooldir.NetBSD-9.3-amd64/bin/x86_64--netbsd-gcc  --sysroot=/ -Wl,--warn-shared-textrel -Wl,-z,relro -nostdlib -r -Wl,-T,/usr/src/sys/../sys/modules/xldscripts/kmodule,-d  -Wl,-Map=hostname_sample.kmod.map  -o hostname_sample.kmod hostname_sample.o
/usr/src/tooldir.NetBSD-9.3-amd64/bin/nbctfmerge -t -L VERSION -o hostname_sample.kmod hostname_sample.o
```

デバイスノードを作成します。データを書き込む想定であるため、 `chmod 666` でデバイスファイルに書き込み権限を付与しておきます。これでカーネル側での準備は完了です。

```sh
# mknod /dev/hostname_sample c 210 0
# chmod 666 /dev/hostname_sample
```

## ユーザ空間側のサンプルコード

ユーザ空間側のコードは以下になります。`write(2)` でホスト名を設定し、`read(2)` で現在のホスト名を取得しています。

https://gist.github.com/furandon-pig/075cbc5f8841a11ed8bba1a5de041305

コンパイルして実行してみます。無事にホスト名の設定と取得が行えていることが確認できます。

```sh
# gcc -Wall -g -o hostname_sample hostname_sample.c
#
# # 現在のホスト名を取得する。
# ./hostname_sample
mbsd93test
#
# # カーネルモジュール経由でホスト名を設定する。
# ./hostname_sample newname
newname
#
# # 設定されたホスト名を確認する。
# ./hostname_sample
newname
```

## まとめ

カーネルモジュールからカーネル空間内の既存のカーネルデータを操作してみました。今回のサンプルはカーネル内のデータだけでなく、カーネルが提供する機能を試してみるケースにも応用できそうです。