NetBSD Advent Calendar 2018 15日目の記事です。
今日はpuffsで自分でも簡単なサンプルを作成してみようと思います。
作成するサンプル
昨日の記事では、NetBSDカーネルソースツリーに含まれて提供されているpuffsのサンプルプログラムを動かしてみる方法を紹介していました。
今日の記事では、実際に libpuffs
を使用したサンプルを作成してみます。
具体的には以下のような、 puffs
でmountしたディレクトリを ls
すると、今日の日付がついたファイル名を返すというサンプルを作成してみます。
# ls /mnt
20181215-015853.txt
ソースコードはGistに用意してあります。
サンプルのひな型を用意する。
さっそくサンプルを作成してみます。いつものように、既存のサンプルコードを流用する形でひな型を用意します。
機能紹介した、 pnullfs.c
を元にサンプルを作成しましょう。
# 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の使い方を概観する
昨日の記事でもちょっとだけサンプルコードの流れを眺めていましたが、今回もサンプルを作り込んでゆく前に puffs
(というか libpuffs
)の使い方を把握しておきましょう。
ソースファイルの main()
を見ると、struct puffs_*
な構造体がいくつか宣言されています。
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
を初期化しています。
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
が使用するメモリの確保を行っています。
/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
があるので、それらの処理に対応する関数を定義しているようです。
/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
のメンバ変数に値を紐づけています。
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()に対応する関数>
というコードに置き換えられる形になります。
/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()
しています。まずはこの内容を見てみましょう。
/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
で定義されています。単に関数のプロトタイプ宣言を行うコードを生成するマクロのようです。
/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
を設定しています。
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
も同様に対応する関数を用意する形になります。
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
に対応する関数をどう実装しているか見てみます。
日付のファイル名を含む文字列は以下の関数で作成しています。単に strftime()
した日付文字列を返しているだけです。
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
に対応する関数は以下です。
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
に対し、以下の箇所で日付文字列を設定しているだけです。
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
してサンプルを動かしてみます。
# cd /usr/src/share/examples/puffs/datefs
# make
# mkdir _datefs_dir
# mount | grep \/mnt
./_datefs_dir/ on /mnt type puffs|datefs
mountしたディレクトリを ls
すると、(実際には存在しない)日付名のファイルが取得できます。
これで libpuffs
を使用した簡単なサンプルが作成できました。
# 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
の機能を利用することができそうです。