Not only is the Internet dead, it's starting to smell really bad.:2003年08月分

2003/08/02(Sat)

今日の作業

@zW

むー、CJK本の著者による有名な↓くらいしか資料ないですね。
ftp://ftp.ora.com/pub/examples/nutshell/ujip/doc/cjk.inf

zWで始まる行をGB2312として扱うってことは
ASCIIで^zWを表現するには^z\nWになるわけだ、すると

#include <locale.h>
#include <wchar.h>
main(void)
{
    wchar_t wc;
    mbstate_t state;
    size_t siz;

    setlocale(LC_CTYPE, "zh_CN.zW");
    memset(&state, 0, sizeof(*state));
    siz = mbrtowc(&wc, "z", 1, &state);
}

はsiz = (size_t)-2になるわけですな。これはHZでも'~'喰わすと同じになる。

ここでちと疑問に思ったのが、

setlocale(LC_CTYPE, "zh_CN.zW");
wc = btowc((int)'z');

でwc = WEOFってのは正しいの?ってこと。
^z$はそれでvalidだからL'z'がreturnされるべきのような気がビミョーにするのだが。

まあbtowc(3)は決してascii2wcharではないので

setlocale(LC_CTYPE, "zh_CN.HZ");
wc = btowc((int)'~');

はwc = WEOFでいいとは思うんだけどね。
b = wctob(L'~') は "~~" の2byteなので b = EOF だし。

もしwc = L'z'が正しいとするとFreeBSDやnewlibのような
btowc(3)の内部でmbrtowc(3)が動くような実装は×で
NetBSD/CitrusでもlibZWはcitrus_ctype_template.hをincludeしないで
自前でbtowc(3)を用意する必要があるよな。

http://www.opengroup.org/onlinepubs/007908799/xsh/btowc.html

"a valid (one-byte) character in the initial shift state."

だけじゃどうとでもとれるんだよな。

@でもやっぱり

b = wctob(L'z') は b = 'z' でなく、"z\n"の2byteに変換し b = EOFとして
安全側に倒せば、wc = btowc('z')が wc = WEOFでもいいかって気になってきた。
結局「btowc(3)は決してascii2wcharではない」ってこった。

まあbtowc/wctobのようなdeprecatedな関数に頭使うだけ無駄ァッかもしれん。

2003/08/03(Sun)

しかし暑いね

@zW

訂正、"zW"は"z\nW"ではダメですね。
http://www.ibiblio.org/pub/packages/ccic/software/unix/convert/HZ-2.0.tar.gz
に含まれるzw2hzで変換した結果を見てみると、ASCII行の改行は無視しない様子。
じゃあどうするかってと、GB2312行内でもスペースに続く文字はASCIIとするという
ルールがあるようなので、それを利用して

zW z W

あるいは

zW z
W

となるわけかな。

zw2hzのlicenseは改変に関する条項が無いので、ソースを読めないのはツライね。 しかも

zW~~

を喰わせると

~{~~~}~

とか平気でillegal byte sequenceを吐いてくれるし、どこまで信用できるのか。
zW→HZの一方通行でしか変換できないしねぇ。

2003/08/04(Mon)

いーかげんだらだらやっとりますが

@HZ+

ZWDOS3.0のCCEACODE.DOCを読むと

~@    --  reserved for Mongolian/Cyrillic support (Single-Width)
~#    --  reserved for Mongolian/Cyrillic support (Single-Width)

つーのがあるね、対策をいれとこうかね。
ってもZWDOS自体もう10年近く更新されてないし
将来的にサポートされることはまず無いだろうけど。

HZは簡体中国語メールではわりとポピュラーなんで
(OutLookExpressとかでもsupportされてる)
iconvに必須だもんで、さっさとsend-prしたいんだが。

2003/08/07(Thu)

(ry

@HZ

/distfiles/citrus/NetBSD/citrus_HZ-20030807.patch.bz2
けっこう書き直した、正直飽きた。

@zW

少ない情報と脳味噌でブレインストーミング
mbstate_tがGB2312モードの場合、wcrtombが{ L'z', L'W' }を続けて変換した結果は

a)
  L'z' -> { '\n', 'z', 'W', ' ', 'z', '\n' }
  L'W' -> { 'W' }
b)
  L'z' -> { '\n', 'z' }
  L'W' -> { 'W', ' ', 'z', '\n', 'W' }

のどっちで返すのが好ましいか。

zW的にはどっちも正しいんだけど、L'z'を変換した時点で次がL'W'かどうかは
wcrtombは知らないので、実は次のワイド文字がL'M'でしたなんて場合

a)
  L'z' -> { '\n', 'z', 'W', ' ', 'z', '\n' }
  L'M' -> { 'M' }
b)
  L'z' -> { '\n', 'z' }
  L'M' -> { 'M' }

と変換され、a)は正しいんだけど冗長な結果になる。
なんでb)の方が好ましい気になるんだけど、

  wcrtomb(buf, L'z', state);
  mbrtowc(NULL, NULL, 0, state);
  wcrtomb(buf, L'W', state);

なケース(変換中に初期化するのが100%間違いだが)を考えると

a)
  L'z' -> { '\n', 'z', 'W', ' ', 'z', '\n' }
  L'W' -> { 'W' }
b)
  L'z' -> { '\n', 'z' }
  L'W' -> { 'W' }

つー結果になる。
b)はエスケープと同じ^zWに誤変換され、以降の文字列は完全に化ける。
a)は運良く化けない、なおかつ実装もb)と比べると若干だが楽。

吐くバイト列の美しさ or 安全性と性能、どっちを選ぶか性格が現れる罠。

2003/08/08(Fri)

(ry

@zW

昨日の続き。
b)だともうひとつ問題があるな。

#include <locale.h>
#include <wchar.h>
main()
{
	size_t len, ret;
	char buf[MB_LEN_MAX];
	wchar_t wc;
	mbstate_t from, to;

	setlocale(LC_CTYPE, "zh_CN.zW");

	memset(&buf, 0, sizeof(*buf));
 
	memset(&from, 0, sizeof(*from));
	len = wcrtomb(buf, L'z', &from);
  
	memset(&to, 0, sizeof(*to));
	ret = mbrtowc(&wc, buf, len, &to);
	^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}

のようなケースを考えた場合、mbrtowcの呼び出し結果は

  a)
    buf = { 'z', 'W', ' ', 'z', '\n' } → ret = 5, wc = L'z' → 変換成功
  b)
    buf = { 'z' } → ret = (size_t)-2 → 変換失敗(つかrestart)

つー違いが出てきますね。
お行儀の良いプログラムならb)が問題になるようなケースは無いとは思うけども、
安全側に振るのであればa)でファイナル(ry

昨日の夜wcrtombだけb)でてきとーに実装したんだけど没かね。
ちなみにこんな感じ↓

typedef struct {
	int cell[UINT8_MAX+1];
#define	LEADBYTE	0x1
#define	TRAILBYTE	0x2
} _ZWEncodingInfo;

typedef struct {
	int chlen;
	char ch[5]; /* L'W'= "W z\nW" or ascii->gb2312 = "\nzWxx" */

	int charset;
#define	INIT		0
#define	AMBIGIOUS	1
#define	ASCII		2
#define	GB2312		3

} _ZWState;

static int
_citrus_ZW_encoding_module_init(_ZWEncodingInfo * __restrict ei,
				const void *__restrict var, size_t lenvar)
{
	int i;

	memset(ei, 0, sizeof(*ei));
	for (i = 0x21; i <= 0x7e; i++)
		ei->cell[i] |= LEADBYTE;
	for (i = 0x21; i <= 0x7e; i++)
		ei->cell[i] |= TRAILBYTE;

	return (0);
}

static int
_citrus_ZW_wcrtomb_priv(_ZWEncodingInfo * __restrict ei, char *__restrict s,
			size_t n, wchar_t wc, _ZWState * __restrict psenc,
			size_t * __restrict nresult)
{
	int charset;
	u_int8_t c;

	/* check chlen */
	if (psenc->chlen != 0)
		goto invalid;

	/* check charset */
	switch (charset = psenc->charset) {
	case NONE: case AMBIGIOUS: case ASCII: case GB2312:
		break;
	default:
		goto invalid;	
	}

	if ((u_int32_t)wc < 0x100U) {
		if (wc == L'\0' || wc == L'\n') {
			switch (charset) {
			case GB2312:
				if (n-- < 1)
					goto e2big;
				psenc->ch[psenc->chlen++] = (wc == L'\0') ? '\n' : '#';
			/*FALLTHROUGH*/
			case ASCII:
			/*FALLTHROUGH*/
			case AMBIGIOUS:
				charset = NONE;
			}
		} else {
			switch (charset) {
			case NONE:
				charset = (wc == L'z') ? AMBIGIOUS : ASCII;
				break;
			case AMBIGIOUS:
				if (wc == L'W') {
					if (n < 4)
						goto e2big;
					n -= 4;
					memcpy(&psenc->ch[psenc->chlen], "W z\n", 4);
					psenc->chlen += 4;
				}
			/*FALLTHROUGH*/
			case GB2312:
				charset = ASCII;
				break;
			}
		}
		if (n-- < 1)
			goto ilseq;
		psenc->ch[psenc->chlen++] = (char)(u_int8_t)(u_int32_t)wc;

	} else {
		switch (charset) {
		case ASCII:
		/*FALLTHROUGH*/
		case AMBIGIOUS:
			if (n-- < 1)
				goto e2big;
			psenc->ch[psenc->chlen++] = '\n';
		/*FALLTHROUGH*/
		case NONE:
			if (n < 2)
				goto e2big;
			n -= 2;
			memcpy(&psenc->ch[psenc->chlen], "zW", 2);
			psenc->chlen += 2;
			charset = GB2312;
		/*FALLTHROUGH*/
		case GB2312:
			if (n < 2)
				goto e2big;
			n -= 2;
			c = (u_int8_t)((u_int32_t)wc >> 8);
			if (!(ei->cell[c] & LEADBYTE)) 
				goto ilseq;
			psenc->ch[psenc->chlen++] = (char)c;
			c = (u_int8_t)(u_int32_t)wc;
			if (!(ei->cell[c] & TRAILBYTE))
				goto ilseq;
			psenc->ch[psenc->chlen++] = (char)c;
			break;
		default:
			goto invalid;
		}
	}

	memcpy(s, psenc->ch, psenc->chlen);
	*nresult = psenc->chlen;
	psenc->chlen = 0;
	psenc->charset = charset;

	return (0);

invalid:
	return (EINVAL);

e2big:
	return (E2BIG);

ilseq:
	*nresult = (size_t)-1;
	return (EILSEQ);
}

そしてmbrtowc。

wchar_t wc;
mbstate_t ps;
char c;

memset(&ps, 0, sizeof(ps));
c = 'z';
len = mbrtowc(&wc, &c, 1, &ps);
c = 'a';
len = mbrtowc(&wc, &c, 1, &ps);

1回目 len = -2でrestart、psの中身は↓となる。

    (_ZWState)ps = { 1, { 'z', } AMBIGIOUS }

2回目 len = 1、wc = L'z'で変換成功するんだけど、
psの中身は↓になってしまっている。

    (_ZWState)ps = { 1, { 'a', } ASCII }

mbstate_tにvalidなsingle-byteである'a'が残ってしまう。
この時点ですでにマズーなんだけど無視し、さらに続けて

c = 'b';
len = mbrtowc(&wc, &c, 1, &ps);

を実行すると、
3回目 len = 0、wc = L'a'という結果が戻ってくることになる。

SUS/C99的にはlen = 0を返すケースは通常はL'\0'まで変換した場合なので
かなーりマズイんでないかいな。

2003/08/09(Sat)

(ry

@GB18030

http://www.freebsd.org/cgi/cvsweb.cgi/src/lib/libc/locale/gb18030.c?rev=1.1&content-type=text/x-cvsweb-markup
http://www.freebsd.org/cgi/cvsweb.cgi/src/share/mklocale/zh_CN.GB18030.src?rev=1.1&content-type=text/x-cvsweb-markup
FreeBSDは相変わらずwchar_tは31bitなので
0x81398193->0x01398139で逃げた模様、ダサいな。
オマエモナーとか言われそうだがな。

@HZ

http://www.ibiblio.org/pub/packages/ccic/software/unix/convert/EHZ-2.0.tar.gz
に含まれるmix.ehzをiconv(1)で変換しようとEINVALで失敗するんだよね。

このファイルをhexdumpして原因判明、null terminateされてないです。
HZとその仲間はbyte sequenceの終端はASCII modeで終らなければならないので
最後の文字がnot ASCIIだった場合、最後に~}を付与する必要がある。
しかし~}\0でなく~}をmbrtowcに喰わせると(size_t)-2を返すわけで
それを見て_citrus_iconv_std_iconv_convertはEINVALを投げるという次第。

iconv的には

  1. null terminateしろやゴルァ、んなファイル面倒みるかや
  2. (size_t)-2が返ってきたらとりあえず'\0'を喰わせて様子を見る

のどっちが正しいんだろう...とりあえずは放置ケテーイ。

2003/08/10(Sun)

(ry

@HZ

libISO2022とのmklocale src共有の是非もあるかと思うので、
send-prじゃなくてbsd-localeの方にpost。

2003/08/11(Mon)

...

@HZ wchar_t mapping

そういえばUCS4って31bitでしたね...
libISO2022では32bitめをonにすることでUCS4をも扱うことを想定していたようで。
つーわけでlibHZはlibISO2022に寄生するわけにはいかないようです。

結局libHZは独自にwchar_t mappingとmklocale srcを用意することになるのですが、
wchar_t mappingでまず考え付くのは、エスケープ文字を<<24したbitを立てる方法。

  1. EHZではGB2312のエスケープが"{"と"A"と2通りある
  2. HZ+とEHZではBig5を3つのplaneに分割している
  3. HZ+とEHZはエスケープ文字が異なる

あたりが嫌らしいんだよな。なんかうまいやり方ないかね。

@UTF-7

あり?もしかしてUTF-7もzWと同じでmbrtowcがL'\0'を変換したわけでも
ないのに(size_t)0を返す?
base64モードを抜けるのは[A-Za-z0-9\/\+]以外の文字をハッケソした時なんだけど、
それってvalid('-'は無視されるけど)なsingle-byte(ascii)だよなぁ。

typedef struct _UTF7State {
  int chlen;
  char ch[6くらい?];
  int mode;
#define NONE 0
#define BASE64 1
} _UTF7State;

len = mbrtowc(pwc, "+", 1, ps) -> len = (size_t)-2, (_UTF7State *)ps = { 0, {}, BASE64 }
len = mbrtowc(pwc, "A", 1, ps) -> len = (size_t)-2, (_UTF7State *)ps = { 1, { 'A' }, BASE64 }
len = mbrtowc(pwc, "A", 1, ps) -> len = (size_t)-2, (_UTF7State *)ps = { 2, { 'A', 'A' }, BASE64 }
len = mbrtowc(pwc, " ", 1, ps) -> len = 1, pwc = 'A' << 6 | 'A', (_UTF7State *)ps = { 1, { ' ' }, NONE }
len = mbrtowc(pwc, "A", 1, ps) -> len = 0, pwc = L' ', (_UTF7State *)ps = { 1, { 'A' }, NONE }

大嘘ですた、2003/8/21を参照。

2003/08/12(Tue)

...

@UTF7

mbrtowcも頭痛いけどwcrtombもちょっとアレですな。
wchat_tは1文字1文字

0x3000 -> "+MAA-"

みたいに毎回base64モードを閉じる必要がありそだね。
iconvにかなり不利だ罠。

@続UTF7

まあ、libiconv_utf7.soってのもありか。

2003/08/13(Wed)

zW

とりあえず置いとく。
/distfiles/citrus/NetBSD/citrus_zW-20030813.tar.bz2
やぱしSUS/C99のmbrtowcの仕様ではまともにzW encodingを扱うことはできないね。
いちどに喰わせるmultibyteの量 > 1なら、initial stateかつ最初の文字が'z'の場合
次の文字が'W'か否かを「盗み見る」ことで誤魔化せるんだけど、

do {
  char c = fgetc(fp);
  ret = mbrtowc(&wc, &c, 1, &state);
  if (errno)
    return(-1);
} while (ret == (size_t)-2);

みたいなコード(fgetwcなんかはまさにこんな実装)のように
1byteずつくべられるとまともに動かん。

2003/08/18(Mon)

libHZ

そろそろ直すべ。
移植性はどーでもよくなってきたので sys/queue.h を使うことにして
自前のダサいもどきをざっくり削除。
SIMPLEQならOpenBSDでも動くべ。
これでちっとはすっきりしたはず。

2003/08/20(Wed)

libHZ

@...

かなりの部分を書き直した。
/distfiles/citrus/NetBSD/citrus_HZ-20030820.patch.bz2
ちゃんとテストしたら再度bsd-localeへ、ついでにzWネタも出す予定。
まあ、libiconv_zw.soになるのだとは思うが。

@bsd-localeに投げたわけだが

パッチの日付が20030821って未来から来た人ですか?>漏れ(首釣りAA略)

2003/08/21(Thu)

UTF-7

@...

ぐにゅーiconvにいろいろ喰わせてみる。
んで、UTF-7はzWみたいな問題は
発生し得ないことに気づく。つかもっとはよ気づけ。
ひと安心ですが嘘ついてスマンカッタ

2003/08/24(Sun)

暑い...

@作業状況

週末は暑さにやられたのでOpenBSDのiconv対応を終らせたのみ。
subversionに移行中につきpatchのうpは明日の夜くらい。

2003/08/25(Mon)

...

@Citrus XPG4DL for OpenBSD

20030825版 うpしますた。
/distfiles/citrus/OpenBSD/HEAD-xpg4dl-20030825.tar.bz2
今回はHEAD向けのみ。
iconv関係でupdateの手順が変わってるのでINSTALL読んでちょ。

@

src/share/locale/ctype以下が最新のNetBSD-currentにcatch upしてないとか
src/lib/libc/gen/errlist.cにEILSEQを追加し忘れてるとか
(ホントは勝手にsys/errno.hまわりいぢるとバイナリ互換崩れる恐れがあるんだけどね...)
src/lib/libc/include以下にゴミ落ちてるとか
実害はないけど、いたらない部分がいくつか見受けられるので
明日もう一回うpし直しますです。

2003/08/26(Tue)

...

@subversion

cvsとは比較にならん位便利だけど、比較にならん位遅い罠。
diffとるのにcvsなら10秒もかからんところが5分近くかかる。
svn copyが多いとダメっぽい。これじゃまだまだ移行するには早いな...

2003/08/28(Thu)

Citrus XPG4DL for OpenBSD

@大変御迷惑をお掛けしますた

20030828版をうpしました。
20030825からの変更点としては

  • strerror(3)にEILSEQを渡してもメッセージに変換されなかった。
    この変更において、ABIが壊れないよう__RENAMEマクロを使っています。
    よってrename.patchは全てのarchで必須になりました(以前はsparc64では必要なかった)。
  • fwide.cにあったtypoのせいでlink errorやruntime errorが発生する(最悪ですな...)バグの修正
  • bg_BG.CP1251など、いくつかのロケールの追加。

2003/08/29(Fri)

...

@UTF-7

書いた。
VARIABLEにセットしたフラグに応じて、iconvから呼ばれると

"あい" -> +MEIwRA

と変換し、 mbrtowc系だと

"あい" -> +MEI-+MEQ-

と冗長な変換をするように作ったけど、
mbsrtowcsはどっちで返した方がいいのかね。