The Man Who Fell From The Wrong Side Of The Sky:2010年1月16日分

[最新版] [一覧] [前月] [今月] [翌月]

2010/1/16(Sat)

[NetBSD] libedit I18N化への道(その1)

タイトルはインスパイヤ・ザ・NeXTじゃなくてNEWSちうことで :D

@ まずは変更点の確認を

まず手始めに、今回の commit logより read.cの監査から第1回はスタートすることにしましょう。

rev1.52 と rev1.53 の 差分を眺めてみると

といった作業が行われていることがわかります。

@ バイト(char)指向APIからワイド文字(wchar_t)指向APIへの置換

Char および Isprint は今回cvs(1) addされた chartype.hで定義されており

#ifdef WIDECHAR

#define Char			wchar_t
#define Isprint(x)  iswprint(x)

#else /* NARROW */

#define Char			char
#define Isprint(x)  isprint((unsigned char)x)

#endif

と定義されていて

という、ソレナンテMicrosoft Visual C++の TCHARマクロのようなもの。
これはもう 痴漢置換レベルの作業です、楽勝ですな。詳細についてはまた次回以降で。

@ 低水準APIと国際化対応(高水準APIに置き換え可能?)

次はread_char()なのですが…

312 private int
313 read_char(EditLine *el, Char *cp)
314 {
315         ssize_t num_read;
316         int tried = 0;
317         char cbuf[MB_LEN_MAX];
318         int cbp = 0;
319         int bytes = 0;
320
321  again:
322         el->el_signal->sig_no = 0;
323         while ((num_read = read(el->el_infd, cbuf + cbp, 1)) == -1) {
324                 if (el->el_signal->sig_no == SIGCONT) {
...
327                         goto again;
328                 }
329                 if (!tried && read__fixio(el->el_infd, errno) == 0)
...
335         }
336
337 #ifdef WIDECHAR
338         if (el->el_flags & CHARSET_IS_UTF8) {
339                 if (!utf8_islead((unsigned char)cbuf[0]))
340                         goto again; /* discard the byte we read and try again */
342                 if ((bytes = ct_mbtowc(cp, cbuf, cbp)) == -1) {
343                         ct_mbtowc_reset;
...
348                         goto again;
349                 }
350         } else  /* we don't support other multibyte charsets */
351 #endif
352                 *cp = (unsigned char)cbuf[0];
...

これはひどい!両生類のクソをかき集めた価値も無いコードです。

we don't support other multibyte charsets

weって誰っすか、 cargo cult?

それでは早速コードの解説。

というコードです。

このコードが通常のファイルを扱うi18nプログラミングであるならば、迷うことなくread(2)のような
低水準API(システムコール)の使用は止めましょう、ぐちゃぐちゃ書いてバグ仕込んでる暇があったら

	wchar_t wc;

	wc = fgetwc(el->el_infile);
	if (wc == WEOF) {
		*cp = 0;
		return -1;
	}
	*cp = wc;

	return 1;

と高水準API(ライブラリ)であるfgetwc(3)使えばおk。

…なのですが今回はちょっと特殊な処理が間に挟まっています、本当に高水準APIに置き換えても大丈夫でしょうか?

@ SIGCONTってなーにー?

まずは324行目で、SIGCONTがセットされているかをチェックしてる様子、さてこれは何ぞ。
shell上で実行中のアプリはCtrl+Zを送ることで停止し、fg <ジョブID>で復帰しますが
この復帰をアプリにお知らせするシグナルがSIGCONTです、確認してみまひょ。

ksh$ cat > test_sigcont.c
#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>

static sigjmp_buf stop_jmp;

static void
stop_handler(int signo)
{
	siglongjmp(stop_jmp, signo);
}

int
main(void)
{
	struct sigaction act;
	int ret;
	char ch;

	act.sa_handler = &stop_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	sigaction(SIGCONT, &act, NULL);
	ret = sigsetjmp(stop_jmp, 1);
	if (ret == SIGCONT)
		puts("catch SIGCONT.");

	puts("waiting input.");
	read(0, &ch, 1);

	return 0;
}
^D

ファイル記述子0、つまり標準入力から1byte読み込んで終了するだけの簡単なお仕事。
コンパイルして実行してみます。

ksh$ make test_sigcont
cc -O2   -o test_sigcont test_sigcont.c
ksh$ ./test_sigcont
waiting input.

この状態でCtrl+Zを入力し、そしてfgで復帰させてみましょう。

^Z[1] Stopped               ./test_sigcont
ksh$ fg %1
catch SIGCONT.
waiting input.
a
ksh$

確かにSIGCONTシグナルが送られていますね。

どうやらこのコードはread(2)に失敗した場合、アプリが復帰したことを検知したら
リトライする必要があるようです、また次回以降に理由を調べてみることにしましょう。

@ × ブクロキング ○ブロッキング (ふー、びっくりした)。

次に329行目、read__fixio()という関数でerrnoを元にゴニョっているようです。
そのゴーニョゴーニョ魚の子の内容ですが、ifdefの嵐なのでコードは貼りませんがこんな感じ。

この非ブロッキングモードの使用例は、CERT Secure Coding Standardの FIO32-C.
「通常ファイルに固有の操作をデバイスファイルに対して行わない」あたりをどぞ。

@ さて、結論は?

うーんどうやらここはread(2)のままにして置かないと良くないことが起きそうな予感。
それよりもなにも、この場所以外でもread(2)を使いまくりなことがネックになってきます。

常識の範囲と思われるので詳しく説明しませんが、入出力において低水準(intなファイル記述子を引数にとる)と
高水準(FILE構造体を引数にとる)の両者のAPIをまじぇまじぇすることは禁物です、後者はstdio.hで定義される
BUFSIZ定数分バッファリングしますので、混在させると予期しない結果になる恐れあり。

少なくともバッファリングだけは、setbuf(3)やsetvbuf(3)によって無効にすることも可能です。

	FILE *fp;

	setbuf(fp, NULL); /* バッファを無効 */

しかし、fgetwc(3)が内部で使用するmbrtowc(3)の引数として渡すmbstate_tだけは
FILE構造体にセットしたり逆にこれをぶっこ抜く標準的な手段は今のところ用意されていません。
# これやっぱC1Xにはset_mbstate_fgetwc/set_mbstate_fputwcとかゆーAPI必要だと思うんですが…

さすがに全てを高水準APIに置き換えていくのは、国際化対応ってレベルジャネーゾ。
また「今動いてるものはなるべく触らない(なぜなら今動いてるのは奇跡だから)」ルール
を思い出してください、元のコードをなるべく尊重した形で作業していく、これ大事。

@ 次回予告

かなりの文章量になってしまったので今回はここまでにして、実際のread.cの書き直しは次回に譲り
またI18Nコードにおけるチート行為についてその問題点を検証する予定、disるぜ〜glibc2〜超disるぜ〜


[ホームへ] [ページトップへ]