NetBSDのlibpuffsを使ってpuffsの簡単なサンプルを作ってみる

[NetBSD Advent Calendar 2018](https://qiita.com/advent-calendar/2018/netbsd) 15日目の記事です。
今日は[puffs](http://netbsd.gw.com/cgi-bin/man-cgi?puffs+3+NetBSD-current)で自分でも簡単なサンプルを作成してみようと思います。

## 作成するサンプル

[昨日の記事](https://qiita.com/furandon_pig/items/794e04991ac2ee1ca29a)では、NetBSDカーネルソースツリーに含まれて提供されているpuffsのサンプルプログラムを動かしてみる方法を紹介していました。
今日の記事では、実際に `libpuffs` を使用したサンプルを作成してみます。

具体的には以下のような、 `puffs` でmountしたディレクトリを `ls` すると、今日の日付がついたファイル名を返すというサンプルを作成してみます。

```sh
# ls /mnt
20181215-015853.txt
```

ソースコードはGistに用意してあります。

 * [furandon-pig/datefs.c](https://gist.github.com/furandon-pig/66ae7b2479693d6a3d85ca021092e8f6)

## サンプルのひな型を用意する。

さっそくサンプルを作成してみます。いつものように、既存のサンプルコードを流用する形でひな型を用意します。
機能紹介した、 `pnullfs.c` を元にサンプルを作成しましょう。

```sh 
# cd /usr/src/share/examples/puffs
# cp -r pnullfs/ datefs
# mv pnullfs.c datefs.c
# grep pnullfs datefs.c
/*      $NetBSD: pnullfs.c,v 1.18 2008/11/26 14:03:48 pooka Exp $       */
 * pnullfs: puffs nullfs example
        if ((pu = puffs_init(pops, argv[0], "pnullfs", NULL, pflags)) == NULL)
# sed -i "s/pnullfs/datefs/g" datefs.c
```

## puffsの使い方を概観する

[昨日の記事](https://qiita.com/furandon_pig/items/794e04991ac2ee1ca29a#%E3%81%A1%E3%82%87%E3%81%A3%E3%81%A8%E3%81%A0%E3%81%91%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%92%E7%9C%BA%E3%82%81%E3%81%A6%E3%81%BF%E3%82%8B)でもちょっとだけサンプルコードの流れを眺めていましたが、今回もサンプルを作り込んでゆく前に `puffs` (というか `libpuffs` )の使い方を把握しておきましょう。
ソースファイルの `main()` を見ると、`struct puffs_*` な構造体がいくつか宣言されています。

```c 
 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;
```

最初に `PUFFSOP_INIT()` と `puffs_null_setops()` を呼び出して `struct puffs_ops` を初期化しています。

```c 
 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");
```

`PUFFSOP_INIT()` は `/usr/src/lib/libpuffs/puffs.h` で宣言されており、 `struct puffs_ops` が使用するメモリの確保を行っています。

```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_null_setops()` は `/usr/src/lib/libpuffs/null.h` で定義されています。 `mkdir` とか `read` `write` があるので、それらの処理に対応する関数を定義しているようです。

```c 
/usr/src/lib/libpuffs/null.h:
162 /* This should be called first and overriden from the file system */
163 void
164 puffs_null_setops(struct puffs_ops *pops)
165 {
166
167         PUFFSOP_SET(pops, puffs_null, fs, statvfs);
168         PUFFSOP_SETFSNOP(pops, unmount);
169         PUFFSOP_SETFSNOP(pops, sync);
170         PUFFSOP_SET(pops, puffs_null, fs, fhtonode);
171         PUFFSOP_SET(pops, puffs_null, fs, nodetofh);
172
173         PUFFSOP_SET(pops, puffs_null, node, lookup);
174         PUFFSOP_SET(pops, puffs_null, node, create);
175         PUFFSOP_SET(pops, puffs_null, node, mknod);
176         PUFFSOP_SET(pops, puffs_null, node, getattr);
177         PUFFSOP_SET(pops, puffs_null, node, setattr);
178         PUFFSOP_SET(pops, puffs_null, node, fsync);
179         PUFFSOP_SET(pops, puffs_null, node, remove);
180         PUFFSOP_SET(pops, puffs_null, node, link);
181         PUFFSOP_SET(pops, puffs_null, node, rename);
182         PUFFSOP_SET(pops, puffs_null, node, mkdir);
183         PUFFSOP_SET(pops, puffs_null, node, rmdir);
184         PUFFSOP_SET(pops, puffs_null, node, symlink);
185         PUFFSOP_SET(pops, puffs_null, node, readlink);
186         PUFFSOP_SET(pops, puffs_null, node, readdir);
187         PUFFSOP_SET(pops, puffs_null, node, read);
188         PUFFSOP_SET(pops, puffs_null, node, write);
189         PUFFSOP_SET(pops, puffs_genfs, node, reclaim);
190 }
```

さらに `PUFFSOP_SET()` の定義を見てみると、構造体と思われる `ops` のメンバ変数に値を紐づけています。

```c
431 #define PUFFSOP_SET(ops, fsname, fsornode, opname)                      \
432     (ops)->puffs_##fsornode##_##opname = fsname##_##fsornode##_##opname
```

`PUFFSOP_SET()` に渡している `ops` の実体は `struct puffs_ops` で、以下のような定義になっています。
なので、 `struct puffs_ops->puffs_node_readdir = <readdir()に対応する関数>` というコードに置き換えられる形になります。

```c
/usr/src/lib/libpuffs/puffs.h:
162 /* callbacks for operations */
163 struct puffs_ops {
164         int (*puffs_fs_unmount)(struct puffs_usermount *, int);
...
216         int (*puffs_node_readdir)(struct puffs_usermount *,
217             puffs_cookie_t, struct dirent *, off_t *, size_t *,
218             const struct puffs_cred *, int *, off_t *, size_t *);
...
228         int (*puffs_node_read)(struct puffs_usermount *, puffs_cookie_t,
229             uint8_t *, off_t, size_t *, const struct puffs_cred *, int);
```

## サンプルプログラムからlsした時の対応処理を見てみる

今回作成するサンプルで、 `ls` した時に適当なファイル名を返すような処理を考えています。
既存の `rot13fs.c` でも同様の動作( `ls` したら `ROT13` で暗号化したファイル名を返す)なので、こちらを参考にしてみます。

ファイルの先頭で `PUFFSOP_PROTOFS()` しています。まずはこの内容を見てみましょう。

```c
/src/share/examples/puffs/ot13fs/rot13fs.c:
 45 PUFFSOP_PROTOS(rot13)
 ...
 94 int
 95 main(int argc, char *argv[])
 96 {
```

`PUFFSOP_PROTOS()` は `/usr/src/lib/libpuffs/puffs.h` で定義されています。単に関数のプロトタイプ宣言を行うコードを生成するマクロのようです。

```c
/usr/src/lib/libpuffs/puffs.h:
316 #define PUFFSOP_PROTOS(fsname)                                          \
317         int fsname##_fs_unmount(struct puffs_usermount *, int);         \
318         int fsname##_fs_statvfs(struct puffs_usermount *,               \
319             struct statvfs *);
...
424         int fsname##_node_fdiscard(struct puffs_usermount *,            \
425             puffs_cookie_t, off_t, off_t);
```

`puffs` の初期化の部分は先ほど見てきたコードと同じですね。
`PUFFSOP_SET()` で `readdir` `read` `write` を設定しています。

```c
100         struct puffs_node *pn_root;
...
147         PUFFSOP_INIT(pops);
148         puffs_null_setops(pops);
149
150         PUFFSOP_SET(pops, rot13, node, readdir);
151         PUFFSOP_SET(pops, rot13, node, read);
152         PUFFSOP_SET(pops, rot13, node, write);
```

例えば `PUFFSOP_SET(pops, rot13, node, readdir)` に対応する関数は以下の関数です。
`read` `write` も同様に対応する関数を用意する形になります。

```c
191 int
192 rot13_node_readdir(struct puffs_usermount *pu, void *opc, struct dirent *dent,
193         off_t *readoff, size_t *reslen, const struct puffs_cred *pcr,
194         int *eofflag, off_t *cookies, size_t *ncookies)
195 {
196         struct dirent *dp;
197         size_t rl;
198         int rv;
199
200         dp = dent;
201         rl = *reslen;
202
203         rv = puffs_null_node_readdir(pu, opc, dent, readoff, reslen, pcr,
204             eofflag, cookies, ncookies);
205         if (rv)
206                 return rv;
207
208         while (rl > *reslen) {
209                 flipflop((uint8_t *)dp->d_name, dp->d_namlen);
210                 rl -= _DIRENT_SIZE(dp);
211                 dp = _DIRENT_NEXT(dp);
212         }
213
214         return 0;
215 }
```

## サンプルを実装してみる

今回のサンプルファイルを以下の場所に置いてありますが、 `readdir` に対応する関数をどう実装しているか見てみます。

 * [furandon-pig/datefs.c](https://gist.github.com/furandon-pig/66ae7b2479693d6a3d85ca021092e8f6)

日付のファイル名を含む文字列は以下の関数で作成しています。単に `strftime()` した日付文字列を返しているだけです。

```c
static void
get_date(char *buf, size_t bufsz)
{
	static time_t tval;
	struct tm *tm;

	if (time(&tval) == -1) {
		strncpy(buf, "unknown_date.txt", bufsz);
		return;
	}

	if ((tm = localtime(&tval)) == NULL) {
		strncpy(buf, "unknown_date.txt", bufsz);
	}

	strftime(buf, bufsz, "%Y%m%d-%H%M%S.txt", tm);

	return;
}
```

`readdir` に対応する関数は以下です。

```c
int
datefs_node_readdir(
	struct puffs_usermount *pu,
	void *opc, struct dirent *dent,
        off_t *readoff,
	size_t *reslen,
	const struct puffs_cred *pcr,
        int *eofflag,
	off_t *cookies,
	size_t *ncookies)
{
        struct dirent *dp;
        size_t rl;
        int rv;
	char buf[BUFSIZ];

        dp = dent;
        rl = *reslen;

        rv = puffs_null_node_readdir(pu, opc, dent, readoff, reslen, pcr,
            eofflag, cookies, ncookies);
        if (rv)
                return rv;

	get_date(buf, BUFSIZ);

        while (rl > *reslen) {
		strcpy(dp->d_name, buf);
		dp->d_namlen = strlen(buf);

                rl -= _DIRENT_SIZE(dp);
                dp = _DIRENT_NEXT(dp);
        }

        return 0;
}
```

`struct dirent *dent->d_name` に対し、以下の箇所で日付文字列を設定しているだけです。

```c
	get_date(buf, BUFSIZ);

        while (rl > *reslen) {
		strcpy(dp->d_name, buf);
		dp->d_namlen = strlen(buf);

                rl -= _DIRENT_SIZE(dp);
                dp = _DIRENT_NEXT(dp);
        }
```

## サンプルを動かしてみる


さっそく `make` してサンプルを動かしてみます。

```sh
# cd /usr/src/share/examples/puffs/datefs
# make
# mkdir _datefs_dir
# mount | grep \/mnt
./_datefs_dir/ on /mnt type puffs|datefs
```

mountしたディレクトリを `ls` すると、(実際には存在しない)日付名のファイルが取得できます。
これで `libpuffs` を使用した簡単なサンプルが作成できました。

```sh
# ls /mnt
20181215-022737.txt
# find /mnt
/mnt
/mnt/20181215-022739.txt
# date
Sat Dec 15 02:27:40 JST 2018
```

## まとめ

NetBSDで `libpuffs` を使用したサンプルを実際に作成してみました。
基本的には `readdir` `write` `read` といった、ファイル操作に対応する関数群を自分で定義してゆくことで `puffs` の機能を利用することができそうです。