The Man Who Fell From The Wrong Side Of The Sky:2007年9月22日分

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

2007/9/22(Sat)

[NetBSD] GNU libiconv emulation

joerg氏に今何の機能が足りてなくて、何のアプリで困ってるのか再確認するメール投げた。

忘れたぜwwwってちょ、おまwwwwwヒドスwwwww
xmllint/xmlprocで問題があった鴨って、ちょwwwPyXMLはiconv(3)使ってなくね?

以下おさらいの意味で、GNU libiconvとCitrus iconvの挙動の違い。

(A) GNU libiconvとCitrus iconvは、変換を行うbyte sequenceが不正
(mbrtowc(3)に食わせるとEILSEQを返すような場合ね)である時は
同じ動作をします、ま当然ですけどね、フフン。

[test.c]
#include <assert.h>
#include <errno.h>
#include <iconv.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int
main(void)
{
        iconv_t cd;
        char buf[BUFSIZ];
        char *s, *in, *out;
        size_t n, nin, nout, ret;

        s = "ABCD\xa0"; /* ABCD + illegal */
        n = strlen(s);

        cd = iconv_open("US-ASCII", "UTF-8");
        if (cd == (iconv_t)-1)
                abort();

        in = s;
        nin = n;
        out = &buf[0];
        nout = sizeof(buf);

	errno = 0;
        ret = iconv(cd, (const char **)&in, &nin, &out, &nout);
        printf("ret: %d\n", ret);
        printf("%s\n", strerror(errno));
        printf("in - converted:[%.*s]\n", n - nin, s);
        printf("in - illegal:[%.*s]\n", nin, in);
        printf("nin:[%zd]\n", nin);
        printf("out:[%.*s]\n", sizeof(buf) - nout, buf);
        printf("nout:[%zd]\n", nout);

        return 0;
}

* GNU libiconvの場合(A-1)
$ CFLAGS="-I/usr/pkg/include" \
> LDFLAGS="-L/usr/pkg/lib -liconv -Wl,-rpath=/usr/pkg/lib" make test
$ nm test | grep iconv
         U libiconv
         U libiconv_open
$ ldd test
test:
        -liconv.2 => /usr/pkg/lib/libiconv.so.2
        -lc.12 => /usr/lib/libc.so.12
$ ./test
ret: -1
Illegal byte sequence
in - converted:[ABCD]
in - illegal:[\xa0]
nin:[1]
out:[ABCD]
nout:[1020]

* Citrus iconvの場合(A-2)
$ rm -f test && make test
$ nm test | grep iconv
         U iconv
         U iconv_open
$ ldd test
test:
        -lc.12 => /usr/lib/libc.so.12
$ ./test
ret: -1
Illegal byte sequence
in - converted:[ABCD]
in - illegal:[\xa0]
nin:[1]
out:[ABCD]
nout:[1020]

inの先頭は不正なバイト列を指し、戻り値は(size_t)-1で
errnoにはEILSEQがセットされます。

(B)しかし一方で

ケースでは、両者は異なる動作をします。

[test.c.diff]
--- test.c.orig    2007-09-22 04:45:32.000000000 +0900
+++ test.c 2007-09-22 04:45:19.000000000 +0900
@@ -13,7 +13,7 @@
        char *s, *in, *out;
        size_t n, nin, nout, ret;
 
-       s = "ABCD\xa0"; /* ABCD + illegal */
+       s = "\xc2\xa0""ABCD"; /* U+00A0 + ABCD */
        n = strlen(s);
 
        cd = iconv_open("US-ASCII", "UTF-8");

* GNU libiconvの場合(B-1)
$ patch -p0 < test.c.diff
$ CFLAGS="-I/usr/pkg/include" \
> LDFLAGS="-L/usr/pkg/lib -liconv -Wl,-rpath=/usr/pkg/lib" make test
$ ./test
ret: -1
Illegal byte sequence
in - converted:[]
in - illegal:[\xc2\xa0ABCD]
nin:[6]
out:[]
nout:[1024]

* Citrus iconvの場合(B-2)
$ rm -f test && make test
$ ./test
ret: 1
Undefined error: 0
in - converted:[\xc2\xa0ABCD]
in - illegal:[]
nin:[0]
out:[?ABCD]
nout:[1019]

GNU libiconvは不正なbyte sequenceに出会ったときと同じ動作をします。
一方Citrus iconvは?や〓といった代替文字に置換して
戻り値として不可逆変換な文字数を返します。

IEEE Std 1003.1-2001には、このケースについて

``If iconv() encounters a character in the input buffer that is valid,
but for which an identical character does not exist in the target codeset,
iconv() shall perform an implementation-defined conversion on this character.''

と書かれていて、実装依存でいいから何かしら変換をしろとあります。
つまり、GNU libiconvが勝手に変換を止めてしまうのは規格違反の疑いがあります。

しかし、libxml2などではこのGNU libiconvの規格違反な動作に依存した
コーディングがあるらしく、 このスレッドで議論になってます。
一方の当事者、Bruno Haible氏も登場しますな。
私の投げたpatchを適用してみましょう。

[test.c.diff]
--- test.c.orig    2007-09-22 04:53:35.000000000 +0900
+++ test.c 2007-09-22 04:52:12.000000000 +0900
@@ -11,7 +11,7 @@
        iconv_t cd;
        char buf[BUFSIZ];
        char *s, *in, *out;
-       size_t n, nin, nout, ret;
+       size_t n, nin, nout, ret, dummy;
 
        s = "\xc2\xa0""ABCD"; /* U+00A0 + ABCD */
        n = strlen(s);
@@ -26,7 +26,8 @@
        nout = sizeof(buf);
 
        errno = 0;
-       ret = iconv(cd, (const char **)&in, &nin, &out, &nout);
+       ret = __iconv(cd, (const char **)&in, &nin, &out, &nout,
+       __ICONV_F_STOP_NO_CORRESPONDING_CHAR, &dummy);
        printf("ret: %d\n", ret);
        printf("%s\n", strerror(errno));
        printf("in - converted:[%.*s]\n", n - nin, s);

* Citrus iconv(C-1)
$ cd /usr/src
$ patch -p0 -E -l < ~/patch-iconv
$ cd lib/libc
$ make depend all install
$ cd ../i18n_module/iconv_std
$ make depend all install
$ cd
$ patch -p0 < test.c.diff
$ make test
$ ./test
ret: -1
Illegal byte sequence
in - converted:[]
in - illegal:[\xc2\xa0ABCD]
nin:[6]
out:[]
nout:[1024]

GNU libiconvの挙動をemulateし、C-1とB-1の結果が同じになりました。
しかしまあ、こんなpatchを下手にcommitして将来縛られたくないので絶賛放置中。

余談ですが、iconv(3)の戻り値の不可逆変換された文字数は
(A)のケースと絡むと信用できないので注意が必要。

[test.c.diff]
--- test.c.orig	2007-09-22 04:53:35.000000000 +0900
+++ test.c	2007-09-22 13:05:09.000000000 +0900
@@ -13,7 +13,7 @@
 	char *s, *in, *out;
 	size_t n, nin, nout, ret;
 
-	s = "\xc2\xa0""ABCD"; /* U+00A0 + ABCD */
+	s = "\xc2\xa0\xa0"; /* U+00A0 + illegal */
 	n = strlen(s);
 
 	cd = iconv_open("US-ASCII", "UTF-8");
* Citrus iconv(D-1)
$ make test
$ ./test
ret: -1
Illegal byte sequence
in - converted:[\xc2\x0]
in - illegal:[\xa0]
nin:[1]
out:[?]
nout:[1023]

戻り値として不正な変換が行われたことを表す(size_t)-1が返り
不可逆変換された文字数1は失われてしまいます。
まあ普通はEILSEQだったらそこで終了なはずなので
失われてもいいという判断なのですが、どうも無理矢理keep goingしたがる
アプリが世の中多いようで。


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