NetBSDのユーザ空間でファイルシステムを動作させるpuffsを試してみる

[NetBSD Advent Calendar 2018](https://qiita.com/advent-calendar/2018/netbsd) 14日目の記事です。
今日はNetBSDのユーザランド上でファイルシステムを実現する、[PUFFS](https://en.wikipedia.org/wiki/PUFFS_(NetBSD))を試してみようと思います。

## puffsって何？

[puffs(3)](http://netbsd.gw.com/cgi-bin/man-cgi?puffs+3+NetBSD-current)のmanページを見ると、ユーザ空間でファイルシステムを開発するためのフレームワークのようです。LinuxでいうところのFuseと考えて良さそうです。
また、 `libpuffs` というライブラリの形で機能が提供されているようです。


```sh
NAME
     puffs - Pass-to-Userspace Framework File System development interface

LIBRARY
     puffs Convenience Library (libpuffs, -lpuffs)
```

## puffsのサンプルを試してみる

まずはpuffsの使い方や動作を把握するため、サンプルを探してみます。
カーネルソースツリーの `/usr/src/share/examples/puffs` にサンプルがあるようです。

```sh 
$ sudo bash
# cd /usr/src/share/examples/puffs
# ls -F
CVS/      Makefile  dtfs/     icfs/     pgfs/     pnullfs/  rot13fs/
```

### pnullfsサンプルを試してみる

pnullfsディレクトリにある `pnullfs.c` を見ると、"puffs nullfs example"というコメントがあります。どうやらpuffsを利用した何もしないファイルシステムのサンプルのようです。
これを動かしてみれば、puffsの使い方を把握できそうです。
実際に試してみると分かるのですが、ここで言う「何もしない」はマウント先で書き込まれたデータはpuffs側で加工することなく、そのままファイルに書き出すという意味でのpnullfsになっています。
(てっきり `/dev/null` 的な意味での `pnullfs` だと勘違いしていました...)

```sh 
# cd pnullfs
# cat pnullfs.c
 ...
/*
 * pnullfs: puffs nullfs example
 */
 ...
```

さっそくビルドしてみます。 `pnullfs` という実行ファイルが生成されました。

```sh
# make
# ls -F
.gdbinit   CVS/       Makefile   pnullfs*   pnullfs.c  pnullfs.o
```

`nullpath` と `mountpath` を指定して起動すれば良さそうです。

```sh
# ./pnullfs
pnullfs: usage: pnullfs [-s] [-o mntopts] nullpath mountpath
```

適当なディレクトリを作成し、 `nullpath` に指定してみます。`pnullfs` コマンドを実行すると、 `/mnt` にファイルタイプが"puffs|pnullfs|としてマウントされています。

```
# mkdir _null_dir
# ./pnullfs ./_null_dir /mnt
#
# mount
...
./_null_dir on /mnt type puffs|pnullfs
```

マウント先に書き込んだファイルが、そのまま `nullpath` で指定した場所に書き込まれるという動作になっています。
これで大まかな使い方のイメージが把握できました。

```sh
# echo 'hello,world.' > /mnt/hello.txt
# cat ./_null_dir/hello.txt
hello,world.
```

### rot13fsを試してみる

`rot13fs` というサンプルもあるので、こちらも試してみます。[シーザー暗号](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%BC%E3%82%B6%E3%83%BC%E6%9A%97%E5%8F%B7)のひとつである[ROT13](https://ja.wikipedia.org/wiki/ROT13)の名前が付いているので、おそらくマウント先に書き込まれたデータを暗号化してファイルに書き込むというサンプルのように思えます。

さっそくビルドして動かしてみます。

```sh
# make
# ls -F
.gdbinit   CVS/       Makefile   _tmp/      rot13fs*   rot13fs.c  rot13fs.o
```

コマンドの実行時引数は先ほどの `pnullfs` と同じですね。

```
# ./rot13fs
rot13fs: usage: rot13fs [-s] [-o mntopts] rot13path mountpath
#
# mkdir _rot13_dir
# ./rot13fs ./_rot13_dir /mnt
# mount
...
./_rot13_dir on /mnt type puffs|rot13
```

```sh
# echo 'hello,world.' > /mnt/hello.txt
# ls /mnt
hello.txt
# ls _rot13_dir
uryyb.gkg
# cat ./_rot13_dir/uryyb.gkg
uryyb,jbeyq.
```

## ちょっとだけサンプルソースを眺めてみる

サンプルからpuffsの使い方と振る舞いがざっくり把握できました。サンプルソースの `pnullfs.c` から実装の大まかな流れも見てみましょう。

puffsの初期化は `PUFFSOF_INIT()` と `puffs_init()` で行うようです。

```c
/usr/src/share/examples/puffs/pnullfs/pnullfs.c:
     48 int
     49 main(int argc, char *argv[])
     50 {
     51         struct puffs_usermount *pu;
     52         struct puffs_ops *pops;
     53         struct puffs_pathobj *po_root;
     54         struct puffs_node *pn_root;
     ...
     96         PUFFSOP_INIT(pops);
     97         puffs_null_setops(pops);
     98
     99         if ((pu = puffs_init(pops, argv[0], "pnullfs", NULL, pflags)) == NULL)
    100                 err(1, "init");
```

`PUFFSOF_INIT()` の中ではpuffsに必要なメモリの確保を行っています。

```c
/usr/src/lib/libpuffs/puffs.h:
428 #define PUFFSOP_INIT(ops)                                               \
429     ops = malloc(sizeof(struct puffs_ops));                             \
430     memset(ops, 0, sizeof(struct puffs_ops))
```

`puffs_init()` で取得した `struct puffs_usermount` を指定して、 `puffs_pn_new()` を呼び出しだします。
puffsの `/` を設定しているようです。

```c
    102         pn_root = puffs_pn_new(pu, NULL);
    103         if (pn_root == NULL)
    104                 err(1, "puffs_pn_new");
    105         puffs_setroot(pu, pn_root);
    106         puffs_setfhsize(pu, 0, PUFFS_FHFLAG_PASSTHROUGH);
    107
    108         po_root = puffs_getrootpathobj(pu);
    109         if (po_root == NULL)
    110                 err(1, "getrootpathobj");
    111         po_root->po_path = argv[0];
    112         po_root->po_len = strlen(argv[0]);
    113         puffs_stat2vattr(&pn_root->pn_va, &sb);
```

最後に `puffs_mount()` でpuffs的なマウント処理を行うという流れになっています。

```c
    119         if (puffs_mount(pu, argv[1], mntflags, pn_root) == -1)
    120                 err(1, "puffs_mount");
    121         if (puffs_mainloop(pu) == -1)
    122                 err(1, "mainloop");
    123
    124         return 0;
```

## まとめ

NetBSDのpuffsのサンプルを試してみました。OS的な設定等は不要で、 `libpuffs` を利用したユーザランドのアプリケーションプログラムという位置付けで考えると良さそうです。
明日の記事では、このpuffsを使ってサンプルアプリを作る手順を紹介できればと思います。