Not only is the Internet dead, it's starting to smell really bad.:2010年09月07日分

2010/09/07(Tue)

[NetBSD] funopen(3) / open_wmemstream(3)

以前紹介した funopen(3) ですが seekfn って要するに fseek(3) の中の人なんだけども
これ第2引数が fpos_t なのはちょっとイケてないよな。

FILE *
funopen(void *cookie, int (*readfn)(void *, char *, int),
    int (*writefn)(void *, const char *, int),
    fpos_t (*seekfn)(void *, fpos_t, int), int (*closefn)(void *));

fpos_t ってそもそも実装依存の opaque object なんだよね。
NetBSD だと off_t と identical だけど、実装によっては構造体だったりする。
例えば glibc2 なんかは

typedef struct
{
  __off_t __pos;
  __mbstate_t __state;
} _G_fpos_t;

となってますな。まぁ fpos_t が mbstate_t 持つ実装ってどうなんよとは思うけど、これは後述。

よって funopen(3) を使う実装ってのは、seekfn 内で fpos_t を扱うところは
OS dependendent(そもそも *BSD にしかないけど) かつ machine dependent になるのよね(fpos_t は MD)。
一例をあげるに、 さっきのページのサンプルコードだと libz による gzip ファイル操作を
通常の FILE 経由の read/write で隠ぺいしてるんだけど

fpos_t
seekfn(void *cookie, fpos_t offset, int whence)
{
	gzFile *p = (gzFile *)cookie;
	return (fpos_t)gzseek(p, (z_off_t)offset, whence);
}

本来 opaque であるべき fpos_t を無理矢理 z_off_t にキャストしてるけど
厳密に言えばこれはアウト、でも他にやりようが無いので困っちゃうよなーと。

一方同様に移植性はないけど、glibc2 の fopencookie(3) はその点マシで、

#define _G_off64_t        __off_t
...
#define _IO_off64_t _G_off64_t
...
typedef int __io_seek_fn (void *__cookie, _IO_off64_t *__pos, int __w);
...
typedef __io_seek_fn cookie_seek_function_t;

と off64_t という public type なので、fpos_t のように問題にはなりません。

おまけ。
さっき glibc2 は fpos_t の実装は off_t と mbstate_t なフィールドを持つ構造体だと書いたけど
なぜそんな実装になっとるの?という話。

これは glibc2 では open_wmemstream(3) なんかの実装用にワイド文字専用インタフェースを
持ってるからなんですよな。

/* Extra data for wide character streams.  */
struct _IO_wide_data
{
  wchar_t *_IO_read_ptr;        /* Current read pointer */
  wchar_t *_IO_read_end;        /* End of get area. */
...

fwide(3) 的は stream すなわち FILE はバイト指向とワイド文字指向どちらかのモードを持つので
そのへんパラノイアックに前者は open_memstream(3) 後者は open_wmemstream(3) として
実装したよんということだと思うんだけど、これ N でもやろうとすると影響箇所が多杉で死ねる。

そもそも通常のファイルってのはバイトなわけでして、ワイド文字なファイルというものは存在せんので

が正しい実装だったと思うんだけどねぇ、まーでもすでに POSIX:2008 に open_wmemstream(3) も
入ってしまってるので南無阿弥陀仏。

まぁ open_wmemstream(3) の仕様では FILEにワイド文字用インタフェース設けずとも
fflush(3) あるいは fclose(3) のタイミングでmbsrtowcs(3) で変換するでもいいのだけど
N の場合 fflush(3) が呼ばれたことを知るためのフックが用意されていないので
writefn が呼ばれる度に mbsnrtowcs(3) で変換せざるをえずとても効率が悪いのね。
頭が痛いお。

(追記) これワイド文字専用インタフェースの問題じゃないです、 訂正記事読んでちょ。

[NetBSD] ATF(Automated Testing Framework) 義務化

だそうです

そもそも チュートリアルが間違ってるという NetBSD クオリティ。
テストケースのテンプレに以下の間違いがありまする、これ釣りなのかな。

とか。

まぁATF独自のマクロだらけでテストの生産性が上がるどころか下がってるので
htdocsのdocbook化のせいでドキュメント直す人が減ったのと同じ結末になる予感。
正直早いとこYA(Yet Another)ATF作ろうぜという感じ。
少なくとも libc のテストのような OS 非依存のテストケースは plain test の方が好ましい。
とりあえず今回は以下のようなムリヤリな ifdef 書いて他のOSでも動くようにしたけど。

#if defined(__NetBSD__)
#include <atf-c.h>
#else
#include <assert.h>
#include <stdio.h>
#define ATF_TC(arg0)            static void arg0##_head(void)
#define ATF_TC_HEAD(arg0, arg1) static void arg0##_head()
#define atf_tc_set_md_var(arg0, arg1, ...) do { \
        printf(__VA_ARGS__);                    \
        puts("");                               \
} while (/*CONSTCOND*/0)
#define ATF_TC_BODY(arg0, arg1) static void arg0##_body()
#define ATF_CHECK(arg0)         assert(arg0)
#define ATF_TP_ADD_TCS(arg0)    int main(void)
#define ATF_TP_ADD_TC(arg0, arg1) arg1##_head(); arg1##_body()
#define atf_no_error()          0
#endif

YAATFを考えるならテストケースは JUnit みたいに

void
testFoo()
{
    ...
}
void
testBar()
{
    ...
}
int
main(void)
{
    testFoo();
    testBar();
}

と prefix 決めうちの plain testにして、runner は gcc -fPIE と dlopen(3)で
みたいな感じじゃだめなのかなーと思ったり。