Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 7 Nov 2018 00:01:54 +0900
From:      =?utf-8?B?5YaF6JekIOelkOS4gOmDjg==?= <naito.yuichiro@gmail.com>
To:        WATANABE Kazuhiro <CQG00620@nifty.ne.jp>
Cc:        FreeBSD-users-jp <freebsd-users-jp@freebsd.org>
Subject:   [FreeBSD-users-jp 96345] Re:  =?utf-8?b?RnJlZUJTRC0xMS4yIOOBriBqYV9KUC5ldWNKUCDnkrDlooM=?=
Message-ID:  <45FE3A88-FE74-4F73-800B-598A18AE5E6D@gmail.com>
In-Reply-To: <201811061314.wA6DErDn002299@conssluserg-02.nifty.com>
References:  <20181014194410.b466d0bbf0e976ffbcab2969@mogami.com> <20181024115101.f6049ef61a82a1fdbab1a404@mogami.com> <201811061314.wA6DErDn002299@conssluserg-02.nifty.com>

next in thread | previous in thread | raw e-mail | index | archive | help

内藤です。

> FreeBSD ワークショップでも話が出た (出ない?) ようですが、

私がワークショップで話した内容は次の通りです。

11.2のソースのlib/libedit/read.c の L350-L378 に以下のコードがあり、
read(2) で1バイトずつ読み込んでは mbrtowc で wchar_t に変換するコードがあります。

		switch (ct_mbrtowc(cp, cbuf, cbp)) {
		case (size_t)-1:
			if (cbp > 1) {
				/*
				 * Invalid sequence, discard all bytes
				 * except the last one.
				 */
				cbuf[0] = cbuf[cbp - 1];
				cbp = 0;
				break;
			} else {
				/* Invalid byte, discard it. */
				cbp = 0;
				goto again;
			}
		case (size_t)-2:
			/*
			 * We don't support other multibyte charsets.
			 * The second condition shouldn't happen
			 * and is here merely for additional safety.
			 */
			if ((el->el_flags & CHARSET_IS_UTF8) == 0 ||
			    cbp >= MB_LEN_MAX) {
				errno = EILSEQ;
				*cp = L'\0';
				return -1;
			}
			/* Incomplete sequence, read another byte. */
			goto again;

mbrtowc()ではlocaleに従いバイト列を wchar_t に変換するわけですが、
eucJP や UTF-8 では当然1バイト読んだだけでは変換できません。
2バイト目や3バイト目が必要なため、mbrtowc()は -2 を返し、
続きを読み込んでくれと返しますが、呼び出し元では
el_flags に CHARSET_IS_UTF8 のフラグが立っていないとリトライせずに
エラー終了します。

libedit のこの関数(read_char)がエラー終了すると /bin/sh は標準入力が
閉じられたのと同じ動作をして終了してしまいます。

このCHARSET_IS_UTF8 がどこで立てられているのかと言うと、
lib/libedit/el.c:L97-105 の部分なのですが、

	/*
         * Initialize all the modules. Order is important!!!
         */
	el->el_flags = 0;
	if (setlocale(LC_CTYPE, NULL) != NULL){
		if (strcmp(nl_langinfo(CODESET), "UTF-8") == 0)
			el->el_flags |= CHARSET_IS_UTF8;
	}

この処理は /bin/sh の起動直後で ~/.profile が読み込まれる前に実行されます。
そのため、/bin/sh の起動時に locale が UTF-8 だと UTF-8 は扱えますが、
ログインシェルのように /bin/sh の起動時の locale が C だと UTF-8 が通りません。

試しに /etc/login.conf で locale を UTF-8 にすると /bin/sh の起動時に locale が
設定されるので、UTF-8が通るようになります。

また、見ての通り、locale が eucJP の場合も CHARSET_IS_UTF8 が立つことは
ありません。

ここで、locale を一切無視してに常に CHARSET_IS_UTF8 を立てるようにすると、
とりあえず eucJP も UTF-8 も通りましたが、eucJP の場合でヒストリに
ゴミが入る問題が手元では発生しましたので、使える状態ににはありませんでした。

と、ここまでがワークショップで話した内容です。

その後なのですが、私個人が eucJP を使いたいとは思っていないこともあり
調査は進んでいません。

UTF-8 を通すだけのパッチならば、先ほどの CHARSET_IS_UTF8 の初期化位置を
直せば良いので簡単なのですが、それでは解決になってませんから。。。

また、この内容では平林さんの /bin/sh の parser.h が問題だという話は
全く無関係に思いますし、渡辺さんの UTF-8 では入力できたという報告とも
矛盾します。

はてさて、真実はどこにといった感じもあり、あんまり進展していません。。。

-- 
内藤 祐一郎
naito.yuichiro@gmail.com





Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?45FE3A88-FE74-4F73-800B-598A18AE5E6D>