読者です 読者をやめる 読者になる 読者になる

テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(nim-fuse report) nim-fuseにおけるreply関数の引数

lowlevel fuseでは各opが利用出来るreply関数が決まっているが, C言語版ではこれを静的に制限することが出来ない. rust-fuseはreplyオブジェクトを取ることによって利用出来るreply関数を制限させるという手法をとっているが, 粒度が粗いすぎるという問題がある. nim-fuseではテンプレートを使い, ドキュメントと同粒度(つまり理想)の制限を実現しているということを前回の記事で述べた.

今回はreply関数の引数について述べる.

まず, c-fuseの設計は最悪である. スタンプ結合のオンパレード. スタンプ結合とは, 構造体が渡されているが実際にはメンバの一部しか使われていないという結合度の分類である.

さらに, 構造体の後方に暗黙的にメンバを配置することでデータを受け渡しする設計も見られる. 以下のST_xxxというマクロは, stbufのstat分に続いて配置されたメンバにアクセスしている. この実装は, アーキテクチャを吸収するものなのだが, ひどすぎる. おそらく後方互換性のゴミ屑であると想像する.

tatic void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
{
        attr->ino       = stbuf->st_ino;
        attr->mode      = stbuf->st_mode;
        attr->nlink     = stbuf->st_nlink;
        attr->uid       = stbuf->st_uid;
        attr->gid       = stbuf->st_gid;
        attr->rdev      = stbuf->st_rdev;
        attr->size      = stbuf->st_size;
        attr->blksize   = stbuf->st_blksize;
        attr->blocks    = stbuf->st_blocks;
        attr->atime     = stbuf->st_atime;
        attr->mtime     = stbuf->st_mtime;
        attr->ctime     = stbuf->st_ctime;
        attr->atimensec = ST_ATIM_NSEC(stbuf);
        attr->mtimensec = ST_MTIM_NSEC(stbuf);
        attr->ctimensec = ST_CTIM_NSEC(stbuf);
}

rust-fuseは幾分ましである. 以下のreply_entry相当の関数クラスは, fuse_entry_outというバッファに書くためのヘッダを生成するための情報を, アプリケーションに近いデータから受け渡してもらうという設計をとっている. しかしこの設計には何の理もない. まず第1に, ヘッダの構造が変更されたら引数が変更となるので脆い. 第2に, このような薄すぎる抽象を被せることは得はなく損だけをする(基本的に抽象化は, 先に損をする行為である. なぜならばソフトウェアの形を決め打ちするから). 薄すぎる抽象化は往々にして害である. むしろ中身をさらけ出すことが適切な抽象化であることも多い.

impl ReplyEntry {
    /// Reply to a request with the given entry
    pub fn entry (self, ttl: &Timespec, attr: &FileAttr, generation: u64) {
        self.reply.ok(&fuse_entry_out {
            nodeid: attr.ino,
            generation: generation,
            entry_valid: ttl.sec,
            attr_valid: ttl.sec,
            entry_valid_nsec: ttl.nsec,
            attr_valid_nsec: ttl.nsec,
            attr: fuse_attr_from_attr(attr),
        });
    }

nim-fuseでは, fuse後方互換を保証しているのは, あるreply関数が返すものの形である. つまり, あるヘッダ型データが書かれ, 続いてデータが書かれるとか. ここで, そのヘッダの形状については今までがそうだったように, 変更される可能性があるという前提に立つ. フレームワークユーザにとっては大きなリスクファクタである. そして, nim-fuseでは, 以下のように設計する. つまり, ヘッダを直接渡す. 薄い抽象化によって意味もなく制限を増やすのは良いことではない. rust-fuseの引数設計は, 一見して直感的に, 悪いということがわかるものであるが, その理由は薄すぎるからだったのだ. rust-fuseの開発者は, 設計の基本を学んでいない.

template defEntry(typ: typedesc) =
  proc entry*(self: `typ`, hd: fuse_entry_out) =
    self.sendOk(hd)

ヘッダを受け取る設計は, 却ってわかりやすい. 理由は, ヘッダ型についてはfuseプロトコルのレベルで定義されており, rust-fuseのように自ら定義するインターフェイスのように, 一体何を入れたらいいのか型以外明確でないという弊害は少ない. c-fuseでは, posixのデータ構造に依存していることが多いが, 前述の通りスタンプ結合であるし, わけのわからん暗黙的なインターフェイスも定義してしまっている. posixのデータ構造に依存することは, 文脈を考えれば適切な感じはするのだが, たぶん, fuseの仕様を膨らましすぎたのが歪みの原因だと思う.

書いては消し書いては消しを繰り返して, かなり苦労したが, opからのreplyについて設計が決まったと思う. 私としては, 最善だと信じている.