LuaスクリプトからNetBSD Luaカーネルモジュールの値を参照できるようにする

またしても遅ればせながら [NetBSD Advent Calendar 2018](https://qiita.com/advent-calendar/2018/netbsd) 25日目の記事です。

NetBSDの[intro(9lua)](http://netbsd.gw.com/cgi-bin/man-cgi?intro+9lua+NetBSD-current)には、Luaカーネルモジュールが提供する機能が記載されています。
提供されているLuaのモジュールとして[systm(9lua)](http://netbsd.gw.com/cgi-bin/man-cgi?systm+9lua+NetBSD-current)や[pmf(9lua)](http://netbsd.gw.com/cgi-bin/man-cgi?pmf+9lua+NetBSD-current)があり、これらのモジュールをLuaスクリプト内から呼び出すことで、NetBSDカーネル内部の機能やデータを参照できます。が、現状で利用できるモジュールは[systm(9lua)](http://netbsd.gw.com/cgi-bin/man-cgi?systm+9lua+NetBSD-current)と[pmf(9lua)](http://netbsd.gw.com/cgi-bin/man-cgi?pmf+9lua+NetBSD-current)の2つくらいで、Luaスクリプトから利用できるモジュールを増やしたいところです。

今回の記事では、Luaスクリプトから利用できるLuaカーネルモジュールのモジュール(※1)を作成する手順を紹介しようと思います。
※1：[systm(9lua)](http://netbsd.gw.com/cgi-bin/man-cgi?systm+9lua+NetBSD-current)とかはLuaから `local systm = require 'systm'` で利用可能になるので、モジュールと言えるのですが、Luaカーネルモジュールと呼称が被るので良い呼称が欲しいところです...。

## 今回作成するサンプル

今回作成するサンプルの挙動は以下になります。
 * Luaカーネルモジュール＋Luaスクリプトを用意し、 `read(2)` が呼ばれたらLuaスクリプト内の関数を呼び出し、結果を返す。
 * [systm(9lua)](http://netbsd.gw.com/cgi-bin/man-cgi?systm+9lua+NetBSD-current)のようなモジュールを作成し、Luaスクリプト内から参照できるようにする。
   * 今回は `luaread` という名前のモジュールにしてみます。

### Luaカーネルモジュールを作成する

[24日目の記事](https://qiita.com/furandon_pig/items/e6356334bd135b76b2b2)で紹介した[luareadhappy](https://qiita.com/furandon_pig/items/e6356334bd135b76b2b2#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%81%AEluaread%E3%82%92%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%82%93%E3%81%A7%E3%81%BF%E3%82%8B)モジュールを参考に必要な機能を実装してみます。

作成したサンプルは以下のGistにUPしてあります。

 * [furandon-pig/netbsd_lua_kern_module_sample.c](https://gist.github.com/furandon-pig/764cf2aa763d62226afe3284aae926a0)

### Luaスクリプトから参照可能なモジュールを用意する

Luaスクリプト側から `local systm = require 'systm'` の形で参照できるモジュールを用意します。具体的にはLua stateに対し、 `luaL_newlib()` `lua_pushstring()` 等で関数ポインタと値を紐づけるだけです。 コード中の `const luaL_Reg system_lib[]` に関数名と関数へのポインタのペアを列挙します(今回のサンプルは `{NULL, NULL}` のみなので紐づく関数は存在しません)。
`lua_pushstring()` では、モジュールに文字列値を紐づけています。今回のサンプルでは、 `luaread.copyright` でNetBSDのCopuright文字列が参照できるようになります。
(変数 `copyright` はNetBSDカーネル内でグローバル変数として定義されているようです)

```c 
 69 static int
 70 luaopen_systm(lua_State *L)
 71 {
 72         const luaL_Reg systm_lib[] = {
 73                 { NULL, NULL }
 74         };
 75         luaL_newlib(sc.kL->L, systm_lib);
 76
 77         lua_pushstring(sc.kL->L, copyright);
 78         lua_setfield(sc.kL->L, -2, "copyright");
 79
 80         return 1;
 81 }
 ...
157 static int
158 luaread_modcmd(modcmd_t cmd, void *arg __unused)
159 {
...
164         switch (cmd) {
165         case MODULE_CMD_INIT:
...
168                 klua_mod_register("luaread", luaopen_systm);
181         case MODULE_CMD_FINI:
...
185                 klua_mod_unregister("luaread");
...
193         }
```

### read(2)が呼ばれたときにLuaスクリプト内の関数が呼ばれるようにする

`read(2)` が呼ばれたときにLuaスクリプト内部の関数が呼ばれるようにしましょう。まずは `read(2)` とLuaカーネルモジュール側の関数を紐づけます。
`struct cdevsw luaread_cdevsw` の `d_read` メンバ変数に対応する関数ポインタと指定します。この例では `luaread_read()` になります。

```c 
 43 dev_type_read(luaread_read);
 ...
 45 static struct cdevsw luaread_cdevsw = {
 ...
 48         .d_read = luaread_read,
 ...
 58 };
 ...
157 static int
158 luaread_modcmd(modcmd_t cmd, void *arg __unused)
159 {
...
164         switch (cmd) {
165         case MODULE_CMD_INIT:
...
170                 if (devsw_attach("luaread", NULL, &bmajor, &luaread_cdevsw,
171                                  &cmajor))
172                         return ENXIO;
```

`luaread_read()` の全体像は以下のようになります(デバッグ用のコードがちょっと残っていますが...)。Luaスクリプト内の `myread()` という関数を呼び出し、その戻り値をそのまま `read(2)` で読み出したデータとして返します。
ここで呼び出すLuaスクリプトの関数は、あらかじめ `luactl load luaread ./sample.lua` 等でロードしておきます。 `myread()` 関数が存在しない(見つけられない)場合は、単にエラーリターンします。

```c
109 int
110 luaread_read(dev_t self __unused, struct uio *uio, int flags __unused)
111 {
112         int len;
113         int e;
114
115         printf("-=> luaread_read()\n");
116
117         klua_lock(sc.kL);
118         lua_getglobal(sc.kL->L, "myread");
119
120         if (!lua_isfunction(sc.kL->L, -1)) {
121                 printf("-=> fail: lua_isfunction()\n");
122
123                 lua_pop(sc.kL->L, 1);
124                 klua_unlock(sc.kL);
125                 return -1;
126         }
127
128         if (lua_pcall(sc.kL->L, 0 /* args */, 1 /* res */, 0) != 0) {
129                 printf("-=> fail: lua_pcall()\n");
130
131                 lua_pop(sc.kL->L, 2);
132                 klua_unlock(sc.kL);
133                 return -1;
134         }
135
136         if (!lua_isstring(sc.kL->L, -1)) {
137                 printf("-=> fail: lua_isstring()\n");
138
139                 lua_pop(sc.kL->L, 2);
140                 klua_unlock(sc.kL);
141                 return -1;
142         }
143
144         char line[256];
145         strncpy(line, lua_tostring(sc.kL->L, -1), 254);
146         line[255] = '\0';
147
148         /* Send it to User-Space */
149         if ((e = uiomove(line, len, uio)))
150                 return e;
151
152         return 0;
153 }
```

Luaスクリプトは以下になります。 `read(2)` された際にスクリプト内の `myread()` が呼ばれ、Luaカーネルモジュールが提供する `luaread.copyright` の値が返され、それがNetBSDカーネル内で `read(2)` で読み出した値としてユーザランド側に返されます。

```lua
local luaread = require 'luaread'

function myread()
        return luaread.copyright
end
```

## 動かしてみる

```c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
        char buf[BUFSIZ];
        int fd;

        fd = open("/dev/happy", O_RDONLY);
        if (fd == -1) {
                fprintf(stderr, "open() failed.\n");
                exit(-1);
        }

        read(fd, buf, BUFSIZ);
        printf("%s", buf);

        close(fd);

        return 0;
}
```

ビルドしたLuaカーネルモジュールを動かしてみます。サンプルプログラム( `a.out` )から `read(2)` すると、NetBSDのCopyright表示が取得できます。NetBSDのカーネルメッセージだと緑色で表示されるので、ちゃんとユーザランド側で値を表示されていることが分かります。



## まとめ

NetBSDのLuaカーネルモジュールで `read(2)` 時にLuaスクリプト内の関数を呼び出す方法と、カーネルモジュール内でLuaスクリプト側にモジュールとして値を見せる手順を紹介しました。Luaカーネルモジュールで提供される機能はまだ少ないので、もっと便利なモジュールが増えてくれればと思います。

## おわりに

今年もどうにか[NetBSD Advent Calendar 2018](https://qiita.com/advent-calendar/2018/netbsd)を書ききることができました。今年はいつもより投稿が遅くなってしまい申し訳ありませんでした。

Advent Calendar にご参加いただいた、[ryoon](https://blog.onodera.asia/)様、[oshimyja](https://twitter.com/oshimyja)様、[ebijun](https://github.com/ebijun)様(投稿日順です)、ありがとうございます。ぜひ来年も開催できればと思いますので、どうか何卒よろしくお願いいたします。