The Man Who Fell From The Wrong Side Of The Sky:2010年2月分

2010/2/1(Mon)

今日

@

明日は雪で朝から Elvis Costelloみたいな歩き方の人が大量発生ですな。

@

置き場所ないので買わなけど、デジピが欲しくて欲しくて、最近時間があると 家電屋とか楽器屋のデジピコーナーをうろうろしている。

デザインと価格からすると KORG LP-350、これで最安5万代って世の中デフレ杉、キータッチもまぁまぁ(価格比を考えると神)。

音の良さなら断然 YAMAHA P-155だと感じたのですが、これってやっぱり元Y系音楽スタジオバイトの悲しい性なのだろうか。

2010/2/3(Wed)

[Unicode] IVS

件の記事で、1文字8バイトなにそれこわい!という話がだいぶ広まっとるようですな。

正確には IVSもUnicode的には独立した1文字の扱いなので

ということであって、UTF-8のMB_CUR_MAXはこれまで通り4バイト(RFC3629、ISO/IEC 10646ではまだ6バイト)
であることには変わりないことに若干注意。

そもそも Unicode正規化を考えたらCollationなんかで「ぱ」だって「は」「゜」に分解する必要もあったりするわけで
そうすると、ひらがな1文字でUTF-32では8バイトになるケースだって昔から存在したわけで。
これしきで驚いてる人はもっとUnicodeのヤバさを知るべきです。

正規化で一番長くなるケースはU+FDFA(ARABIC LIGATURE SALLALLAHOU ALAYHE WASALLAM)だったと思われ。

    U+FDFA = U+0635 + U+0644 + U+0649 \
    	+ U+0020 + U+0627 + U+0644 + U+0644 + U+0647 \
    	+ U+0020 + U+0639 + U+0644 + U+064A + U+0647 \
    	+ U+0020 + U+0648 + U+0633 + U+0644 + U+0645

つまりUTF-32で72バイトですな(最新版はチェックしてないけど)。

それにIVSだけでなく Named Character Sequencesの存在を考慮すると
非Unicodeでの1文字をUnicodeで表現するのに必要なバイト数はどっちみち無限大だよね :D

ま、M:N変換が発生するからこういうことになるわけで、Citrusでは
異なる符号化文字集合間での文字コード変換は全て自己責任で、ということになっとりやす。
だからCSIなんですよ。

2010/2/6(Sat)

今日

@

Sなんとか団カッケー!

腹筋崩壊、コーンウォール地方の節分は こんなんですか。

2010/2/7(Sun)

[NetBSD] libedit I18N への道(その3)

よっこい庄一、だいぶ時間が空いてしまいましたが第3回です。
今日のところは 前回予告した 1 から 3 は後回しにして、実際に read.c の修正をお粉ってみましょうか。
というかチート行為の話はともかく、多言語情報処理とか CSI の話をクソ真面目に書くのは結構大変なので
3行しか集中力の続かない私はめんどくさくなって放置してたのだったり。

@ mbtowc(3) は使うな

まずは私が source-changes-d@ に投げた 指摘の 4. についてすな。
mbtowc(3) をなぜ library function が使っちゃ駄目かの説明をば。

仕様には以下の一文があります。

For a state-dependent encoding, this function is placed into its initial state 
by a call for which its character pointer argument, s, is a null pointer.
Subsequent calls with s as other than a null pointer shall cause the internal 
state of the function to be altered as necessary.

そうです、eucJP や Shift_JIS そして UTF-8 のような stateless encoding だけを使ってると忘れがちなのですが
mbtowc(3) は内部にステート情報を持つ上「静的に確保」されるので、strtok(3) なんかと同様に再入可能じゃないのです。

お手元に NetBSD がございましたら烏賊のコードを試してみてください。
s0 は JIS X0208 の文字、s1 は US-ASCII の文字で、これらを交互に mbtowc(3) で変換します。

$ cat >iso2022.c
#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int
main(void)
{
	char *loc;
	char s0[] = "\x1b$B$\"$$$&$($*\x1b(B";
	char s1[] = "ABCD";
	wchar_t wc0, wc1;

	loc = setlocale(LC_CTYPE, "ja_JP.ISO2022-JP");
	if (loc != NULL) {
		wprintf(L"%s\n", loc);
		wprintf(L"%d\n", mbtowc(&wc0, &s0[0], sizeof(s0)));
		wprintf(L"%lc\n", wc0);
		wprintf(L"%d\n", mbtowc(&wc1, &s1[0], sizeof(s1)));
		wprintf(L"%lc\n", wc1);
	}
}
^D

さてさて、このコードの実行結果はどうなるでしょうか?

多くの人は s0 に対する mbtowc(3) の呼び出しで

  • 「 ESC(0x1b) + $ + B 」の 3byte が、G0 に JIS X0208 を指示する為のエスケープとして消費
  • 「 $ + " 」の 2byte が、JIS X0208 の 36 区 - 34 点すなわち L'あ' に変換される
  • よって変換されたバイト数は合計「5」byte

そして s1 に対しては

  • G0 はデフォルト US-ASCII なのでエスケープは不要、0byte也
  • 「 A 」の 1 バイトが US-ASCII のコードポイントとして L'A' に変換される

という予想します、しかしこれは完全にマテガイです。

ロンよりウッドじゃなくて証拠、実行してみましょう。

$ make iso2022
cc -O2   -o iso2022 iso2022.c
$ ./iso2022
ja_JP.ISO2022-JP
5
あ
2
疎

s1 の変換結果が 2byte かつ「 A 」ではなく「疎」になってしまっています、どうしてこうなった(AA略)かというと

  • s0 を変換することで JIS X0208 モードに遷移し、mbtowc(3) が内部に持つステート情報が更新される
  • 続けて s1 を変換すると、s1 はあたかも s0 の後続バイトとして扱われるので、JIS X0208 モードとして解釈される
  • その結果、"AB" は US-ASCII ではなく JIS X0208 の 65 区 - 66 点すなわち「疎」に変換され、2byte が消費される

ということです。つまりはある文字列の変換をいちど始めたら、終端 '\0' まで変換して内部状態が初期化されるか
mbtowc(NULL, NULL, 0) とすることで明示的に初期化するまでは他の文字列の変換を試みてはならないちうこと。

よってライブラリ関数が内部で mbtowc(3) を呼んでしまうと、アプリも同様に使ってた場合には
内部状態を壊しあい宇宙になってしまいますやね、gkbr

といっても実際のところ locking-shift を持たない stateless encoding の場合には問題にならない上に
ISO2022-JP を実際にサポートしている libc 実装なんてーのは世の中 NetBSD と newlib の C-JIS locale だけですので
(DragonFlyBSD では MB_LEN_MAX の都合上、libISO2022 は無効になっている)発覚することはまずないんですけどね。
だからといって手を抜いちゃ駄目ですよ。

それにですね、そもそもこういう1バイトづつ読み込んで変換可能かを試みるってなコードの場合
mbtowc(3) を使うとバッファ管理が自前になるのでひじょーに生産性が悪いんですよ。

#include <errno.h>
#include <limits.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int
main(void)
{
	FILE *fp;
	char buf[MB_LEN_MAX + 1];
	size_t n = 0;
	int ret;
	wchar_t wc;

	setlocale(LC_CTYPE, "");

	fp = ...

	mbtowc(NULL, NULL, 0);
	for (;;) {
		if (n == MB_CUR_MAX)
			abort(); /* too long escape sequence */
		buf[n++] = (unsigned char)fgetc(fp);
		if (ferror(fp))
			abort(); /* file read error */
		if (feof(fp))
			break; /* end of file */
		errno = 0;
		ret = mbtowc(&wc, &buf[0], n);
		if (ret == (size_t)-1) {
			if (errno == EILSEQ)
				abort(); /* illegal byte sequence */
		} else {
			if (wc == L'\0')
				break; /* NUL terminator */
			wprintf(L"%lc", wc);
			n = 0;
		}
	}
}

とてもめんどくさいです >< これを mbrtowc(3) を使うように書き直すバヤイ、こっちの関数は変換途中の
マルチバイトの欠片を mbstate_t 中に保持してくれるので、MB_*_MAX やらバッファ管理を意識する必要がなくなり

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int
main(void)
{
	FILE *fp;
	mbstate_t st;
	size_t n = 0, ret;
	char ch
	wchar_t wc;

	setlocale(LC_CTYPE, "");

	fp = ...

	mbrtowc(NULL, NULL, 0, &st);
	for (;;) {
		ch = (unsigned char)fgetc(fp);
		if (ferror(fp))
			abort(); /* file read error */
		if (feof(fp))
			break; /* end of file */
		ret = mbrtowc(&wc, &ch, 1, &st);
		if (ret == (size_t)-1)
			abort(); /* illegal byte sequence */
		if (ret != (size_t)-2) {
			if (wc == L'\0')
				break; /* NUL terminator */
			wprintf(L"%lc", wc);
		}
	}
}

とコードが若干スッキリします、まぁ前回も書いたけど fgetwc(3) 使えばもっと簡単に

#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>

int
main(void)
{
	FILE *fp;
	wchar_t wc;

	setlocale(LC_CTYPE, "");

	fp = ...

	for (;;) {
		wc = (wchar_t)fgetwc(fp);
		if (ferror(fp))
			abort(); /* file read error */
		if (feof(fp) || wc == L'\0')
			break; /* end of file or NUL terminator */
		wprintf(L"%lc", wc);
	}
}

なんですけどね、まぁ今回は低水準 FILE IO が絡むのでいたし方が無い。

おまけ、read_char.c 中のコメントに茶々入れてみるテスト。

344 			if (cbp >= MB_LEN_MAX) { /* "shouldn't happen" */
345 				*cp = '\0';
346 				return (-1);
347 			}

shouldn't happen (起きないだろうけど)とありますが、ダウト。
stateful encoding は冗長な escape sequence が発生する可能性があるのでよゆーでMB_LEN_MAX を超えます。
glibc2 の mbtowc(3) の man には MB_CUR_MAX を超えることはあるとまでは書いてあるんですが
なかなかそれなら MB_LEN_MAX だって超えるやん!とは気づかないもんでしてねぇ。
まぁでもちゃんと対策コードが入ってるのはエライ。

このへんの MB_LEN_MAX については 以前のエントリを参考にしてください。
ちなみに MB_LEN_MAX の最適値は 42 から 72 に訂正されました、 72 とは
オブイェークト。

@ 謎フラグ

さてさて、お次は read_char() の最後の方で出現する IGNORE_EXTCHARS という flag bit はなんなんだしょうか。

354 	if ((el->el_flags & IGNORE_EXTCHARS) && bytes > 1) {
355 		cbp = 0; /* skip this character */
356 		goto again;
357 	}

ソースを grep(1) してみると eln.cの以下の箇所で使われているようです。

47 public int
48 el_getc(EditLine *el, char *cp)
49 {
...
52
53 	el->el_flags |= IGNORE_EXTCHARS;
54 	num_read = el_wgetc (el, &wc);
55 	el->el_flags &= ~IGNORE_EXTCHARS;
...
60 }
...
72 public const char *
73 el_gets(EditLine *el, int *nread)
74 {
...
77 	el->el_flags |= IGNORE_EXTCHARS;
78 	tmp = el_wgets(el, nread);
79 	el->el_flags &= ~IGNORE_EXTCHARS;
...

さぁてもうお分かりですやね、editline(3) には

  • el_get{c,s} … (シングル)バイト指向
  • el_wget{c,s} … ワイド文字指向

という API が定義されているのですが、前者は後者の wrapper として実装しているわけです。

元々 el_get{c,s} は read.c に実装が ありました

...
 public int
-el_getc(EditLine *el, char *cp)
+FUN(el,getc)(EditLine *el, Char *cp)
...
-public const char *
-el_gets(EditLine *el, int *nread)
+public const Char *
+FUN(el,gets)(EditLine *el, int *nread)
...

ですが今回のうん国際化対応において chartype.h にある

...
 72 #define FUN(prefix,rest)        prefix ## _w ## rest
...
121 #define FUN(prefix,rest)        prefix ## _ ## rest

というマクロを使ったテンプレート技で

  • -DWIDECHARが定義されていないときはel_get{c,s}
  • されているときはel_wget{c,s}

と compile されるように変更してしまったのです。
そのため後者の場合失われることになる el_getc(3) を eln.cで

.if ${WIDECHAR} == "yes"
OSRCS += eln.c
SRCS += tokenizern.c historyn.c
CLEANFILES+=tokenizern.c.tmp tokenizern.c historyn.c.tmp historyn.c
CPPFLAGS+=-DWIDECHAR
.endif

として 補なったわけです、にゃるほど。

んでは IGNORE_EXTCHARS の flag bit が立ってた場合のコードを見直してみましょう。

354 	if ((el->el_flags & IGNORE_EXTCHARS) && bytes > 1) {
355 		cbp = 0; /* skip this character */
356 		goto again;
357 	}

つまりは前段の mbtowc(3) で変換したワイド文字がシングルバイト文字でない場合は
無視して最初っからやり直しをしてるというわけです。

えっ

えっ

これ明らかに挙動不審すね、例えば eucJP locale で { 0xA4, 0xA2, 0x0 } という入力があった場合
昔の el_getc(3) であれば 0xA1 を返してたのに、L'あ'に変換される 0xA4 + 0xA2 を読み飛ばして
いきなり 0x0 が帰ってきてしまうことに、それに

  • el_getc(3) はここで mbtowc(3) を呼ばないほうが得策。
    なぜなら ISO-C ではバイト指向とワイド文字指向の混在使用を 制限しているから。
  • そもそも必要ないのに呼んだら性能悪くなるだろ常考。

という問題もありますな。

さてこれどうしたもんですかね。アイデアとしてすぐにパッと思いつくのが
そもそも -DWIDECHAR を CPPFLAGS で与えなければ、旧来の el_getc(3) のままなんだから
wread.c として以下のようなファイルを追加するってー案その1。

#define WIDECHAR
#include "read.c"

すでにこの手の手法の法は bcopy(3) と memset(3) の実装なんかでやってます。

でも read.c には残念なことに FUN() マクロでテンプレート化されてないけど、public な API も含まれます。
よってこの方法では重複するシンボルを別ファイルに追い出すリファクタリングが必要になってしまいます。
ですが私のポリシーは「動いているものは触るな(何故ならそれは奇跡だから)」ですので お 断 り だ !

ここはもう変更箇所を最小限にすべくread_char()の中で

private int
read_char(EditLine *el, Char *cp)
{
	if (el->el_flags & IGNORE_EXTCHARS) {

		// el_get{c,s}向けの処理

	} else {

		// el_wget{c,s}向けの処理

	}
	...

と大きめに if〜else してしまうことでお茶を濁す方針ですかね。
リファクタリングは後回し(そして実際に対策することは無い…かも)ということで。

@ ちゅうわけで次回

ここまでを対策した patch が こちら、まだやることはいっぱいあるので参考までに。

お筆先様による自動筆記なので予定は未定ですが、以前から予告してる UTF-8 cheat と
glibc2 の話を書きたいっすね。

今日

@

むー、いつの間にかmbsnrtowcsなんかも POSIX入りして炭化、 Extended API Set, Part 1ねぇ。

ということでfmemopen(3)とopen_memstream(3)は昔書いたのを突っ込むか、ってかんじだけど
open_wmemstream(3)はそもそも仕様そのものがバグじゃねーかという気がしてならない。

@

錯乱坊フイタ、そいや 伝説のユンボでライブ会場破壊とか 写真あったのね。

@

Ceremony by Bad Lieutenant 歌い継がれるのか。

HaciendaFAC251 Factorymust be built.
テーブル代金で殴りあったTony WilsonもRob Grettonももう今はいないけど
観光に行くことがあったら行ってみたい。

2010/2/8(Mon)

今日

@

terminfo merged in -current、意外と拒否反応が多くて驚いたなり。

歴史的に *BSD = termcap に対して SysV/Linux = terminfo という構図だったとは思うのですが
現在では毎度おなじみ The Open Group の XSI Curses standard, Issue 4を読むと判りますが
terminfo 化が好ましいとあるのですよね。

なので UNIX(TM) 持ってる MacOS X も terminfoですな。

拒否感の原因としては tic(1) により compile された database ファイルが
/usr/share/terminfo/[1-9a-z]/* にばら撒かれるのが美しくないというのが多いみたい。
今回NetBSDに入ったtermcapの実装もそんな人達の意見を斟酌してか、他のOSの実装とは異なり
/usr/share/misc/terminfo.db という BerkeleyDBを使ったモノリシックな実装となってますな。

個人的には bdb って read only で使うには heavy (なので Citrus は専用の db を実装している)だし
ゴチャゴチャなのも大量の端末の種類が存在する Cruel な世界を覆い隠すための肥溜めの蓋なんだから
しょうがないし、効率の面から /usr/share/terminfo/[1-9a-z]/* という実装の方が
reasonableに思えるんですけどね。

termcap & terminfoの前書きには、terminfoの方が効率は上だけど管理はしづらいと書かれてますな。

ここ数年のdynamic linked root、PAM、nsswitch.conf への移行、そして今回の terminfo化も
昔から*BSD使ってる方々には評判良くないのかもね。
それぞれ理由があるしそれなりに議論ロッポンギした結果ではあるはずなんだけど。
今や config(1)だって kernel module 化でただのGENERIC kernel作る為だけの道具だし(ぉ

そのうちNetBSDに /usr/ucb が掘られる日も近いかもですよ、まずはps(1)から(ぉい

まぁ個人的にはかつての*BSDらしさは、OpenBSDにまかせたって感じなんですが。

えっ

2010/2/11(Thu)

今日

こないだスーツ新調したけど裾直しに失敗したくさいので困った、つんつるてん。

スーツ買うとき試着室で必ず Once in a lifetimeの振付を試すクラスタ(何それ)

最近のDavid Byrne、還暦まじかなのに30年前と変わらない変態ステップで感動した。
こっちもようやるわ、去年の来日観にいけばヨカタ。

ジョナサン・デミの出世作1:44からの横山ヤスシ〜痙攣運動の流れはいつみても最高。

[Unicode] なまえのないかいぶつ

最近つくづく思うのがUnicodeって なまえのないかいぶつなんだよねってこと。
他の全ての符号化文字集合のスーパーセットという名前が欲しくて
なんでもかんでも捕食して「バリバリ グシャグシャ バキバキ ゴクン」するモンスター。
そして最後に自分の分身、なまえなんてなくたっていいじゃない=Private User Areaすら
ゴックンしようとしてるのがガラケー絵文字騒動。

まぁそのへんの馬鹿馬鹿しさは、前にも 日欧妖怪統合とか いま妖怪が危ないで書いたので省略すっけど。

いつものネタ記事なので、天狗と GOBLIN を IVS で切り替えろとか
天狗の Unicode Name は LUCIFER の方がふさわしいとか脱線しっぱなしだけど
真面目な話、天狗はあくまで JAPANESE MONSTER TENGU として収録されないと駄目なんすよね。

例えば GNU libiconv の //TRANSLIT みたいな置換機能のせいで
「今日飲み会@天狗 20:00〜」が「今日飲み会@ルシフェル 20:00〜」に化けてしまい
ホストクラブ「ルシフェル」に入店し「…チガウ…今までの男とはなにかが決定的に違う。
スピリチュアルな感覚がアタシのカラダを駆け巡(以下略」とか困るだろ、JK。

また脱線した。

これ結局、西洋人中心の視点からの余計な翻訳であって L10Nの原則「原文に無い親切さを持ち込むな」
において失敗してるんだよね、こういう無頓着さがUnicodeの平常運転。

んで国旗の絵文字を見てもわかるとおり、変な平等主義ちうものが蔓延ってるので
日本の妖怪だけ収録するのはけしからんとか、斜め上の方向に脱線したりして。
これは バックベアード率いる西洋妖怪との妖怪大戦争の予感。
ちうか日本は八百万の神がいるので Unicode の 21bit じゃ足りないかな。

もう天狗じゃなくて天狗面、すなわちDILDOにすればいいんじゃね、つまりTENG(ry

2010/2/13(Sat)

今日

@

明日はバレンタインデーなので、ご利益のある リア充動画を貼っておこう。

@

UnicodeのUNIは「U浦沢 N長崎 コンビは Iいつも最後はgdgd」の略です。

八百万の神々はすべて主イエス・キリストにUnifyされました、アーメン。

2010/2/21(Sun)

今日

@

もう IKUZOって旬を過ぎたと思ってたんだけど このPはクオリティ高過ぎwwwww
Lose Yourself by Eminem この発想はなかった。
それと Ziggy Stardust by David BowieAnarcy In The U.K by Sex Pistols はまじワロタよ、Clashもいい。

2010/2/28(Sun)

しっとりうす

東京Ruby会議における なるせさん A REINTRODUCTION TO RUBY M17N
我らCitrus Projectが「しっとりうす」に改名したことが発表されました(00:48:50〜)。

最近かぼすとかライムとか柑橘類の名を課したソフトを悪用して
タイーホされた事件との関連性を疑われることが多いため、今回の改名に至りました。

しっとり、うす。オカモト製ですねわかります。

ってまだ4/1には早いか。

つか、なるせさんの発表はたいへん素晴らしいので観てください。
というか今俺を悩ませてる某社製ライブラリの担当者、今直ぐ観て自分の誤りに気づいて直しやがれw

それと 噂の本もかなりの力作なのでこんなチラシの裏をsite:指定してググってる暇あるなら
是非買いましょう、この落書きにあるのは脱線だけですよー。

[NetBSD] libedit I18N への道(その4)

相変わらず予定は未定ちうことで、 前回の予告はまたまた後回しの手抜きバージョン。

前回はmbtowc(3)の使い方のまずさを指摘しましたが、今回はmbstowcs(3)です、
vi.cのvi_histedit()などで使われていますが、これもいまいちなコードですなぁ。

ざーっと何をやってるか説明しまひょ、el_line に含まれる一行分の
データ(wchar_t 形式で保持)を一時ファイルに書き出し、vi(1) を起動し編集が終わるまで待ちます。
編集が終わった後この一時ファイルを読み込んで、el_line のデータを更新するという簡単なお仕事。

ではコードを抜粋して処理を追ってみませう。

1020 	len = (size_t)(el->el_line.lastchar - el->el_line.buffer);
1021 #define TMP_BUFSIZ (EL_BUFSIZ * MB_LEN_MAX)
1022 	cp = el_malloc(TMP_BUFSIZ);
1023 	if (cp == NULL)
1024 		return CC_ERROR;
1025 	line = el_malloc(len * sizeof(*line));
1026 	if (line == NULL) {
1027 		el_free((ptr_t)cp);
1028 		return CC_ERROR;
1029 	}
1030 	Strncpy(line, el->el_line.buffer, len);
1031 	line[len] = '\0';
1032 	ct_wcstombs(cp, line, TMP_BUFSIZ - 1);
1033 	cp[TMP_BUFSIZ - 1] = '\0';
1034 	len = strlen(cp);
1035 	write(fd, cp, len);
1036 	write(fd, "\n", 1);

という処理です。

まぁ動けばおkという悲観的なレビューならばこれでOK、しかしおいらが書くコードであれば
そもそもここでは wcstombs(3) は使わない使うべきではないという判断を下すでしょう。

というのも wcstombs(3) を使いたいあまりにとても無駄な富豪的コーディングになっているからです。
そもそもこの関数は NUL terminate されていることが前提の関数であるのに el_line.buffer から始まる
文字列の最後は改行コードですので、わざわざコピーせにゃ。わざわざ heap を?(なっち風) *1

またコピーに安全とはいえない関数 Strncpy() すなわち wcsncpy(3) を使っています。
1031行目で NUL terminate を明示的に付加してるので overrunすることはありませんが
なるべくならwcslcpy(3)のような安全な関数を使う癖をつけた方がいいでしょう。
多分 OpenBSD 方面だとぬっ頃されるコードその1。

このくそややこしいコードは簡潔に以下のように書き直せます

         len = (size_t)(el->el_line.lastchar - el->el_line.buffer);
#ifdef WIDECHAR
         int i;
         mbstate_t st;
         size_t len;
         char buf[MB_LEN_MAX];

         mbrtowc(NULL, NULL, 0, &st);
         for (i = 0; i < len; ++i) {
                 len = wcrtomb(&buf[0], el->el_line.buffer[i], &st);
                 if (len == (size_t)-1)
                         return CC_ERROR;
                 write(fd, &buf[0], len);
         }
#else
	write(fd, el->el_line.buffer, len);
#endif


要するに malloc(3) イラネってことです、ただし安直にこのまま write(2) を繰り返し呼ぶと
system call の呼出回数が増えて性能が劣化しますので、バッファリングのコードを追加する必要がありますが
これもchar buf[4096] くらいしにしてループを一段足せばいいんじゃねぇのと。

そもそも本来ならこんなん自前でやらずにfwrite(3)を使えば勝手にバッファリングして
適度にwrite(2)を発行してくれるんですが…

お次は編集の終わった一時ファイルから読み込んで

1054 		st = read(fd, cp, TMP_BUFSIZ);
1055 		if (st > 0) {
1056 			len = (size_t)(el->el_line.lastchar -
1057 			    el->el_line.buffer);
1058 			len = ct_mbstowcs(el->el_line.buffer, cp, len);
1059 			if (len > 0 && el->el_line.buffer[len -1] == '\n')
1060 				--len;
1061 		}

おーいおい、今度は read(2) した cp を NUL terminate してないので overrun するぜ(怒
mbstowcs(3)は第2引数の cp が'\0'に達するか、第3引数の len として渡したワイド文字列バッファ
(変換結果を保存、これは第1引数である el->el_line.buffer)の長さまで書き込まれたら終了です。
read(2) で上書きされなかった cp の中に残ったゴミ情報をワイド文字に変換しようとしてしまいますな。

まぁ len まで変換されれば止まるし、そもそも普通ゴミを変換すれば不正なマルチバイトと判定して
mbstowcs(3) は errno に EILSEQ をセットして止まってくれるので len まで進むことすらないので
overrun しても core 吐いて落ちるような事態にはなってないので発覚してないんでげそ(涙

こちらも前段のコードでmalloc(3)に依存しないコードに変更したので

#ifdef WIDECHAR
		mbrtowc(NULL, NULL, 0, &ps);
		for (i = 0; i < len; ++i) {
			while ((nread = read(fd, buf, 1)) > 0) {
				n = mbrtowc(&wc, &buf[0], 1, &ps);
				if (n == (size_t)-1)
					return CC_ERROR;
				if (n != (size_t)-2)
					break;
			}
			if (nread < 1 || wc == L'\n')
				break;
			el->el_line.buffer[i] = wc;
		}
#else
		read(fd, el->el_line.buffer, len);
#endif

というように書き直せます、まぁこっちも read(2) を1byteずつとゆーのも非効率ですので
バッファリング必要ですが。

そもそもそもそもですね、この部分って前回のコードとは異なり read/write 対象のファイルディスクリプタが
デバイスじゃなしに mkstemp(3) で作成した、ただのファイルであることが確定してるので
stdio.h の FILE を使用した高水準APIに書き直してもあんまり問題がないので心揺れるちゃうですよ。
動いてるものは触るな(なぜならそれは奇跡ry)の精神もだいぶ尽きてきたのと、 動いてないなら触れという話もあるので(ぉ

ただここは fork(2) も絡んでることだし fflush(3) 忘れなんかでバグを作り込みかねないので
とりあえずこのまま低水準APIを使ってバッファリングは自前で書くちう方針なのですが、あーどうしよう。
ま、実際に書き直したコードはまた次回ということで…

*1:まぁ最新のPOSIX 2008では Extended API Set, Part 1wcsnrtombs(3)が入ったよなので、これ使えばおk説。ただし L とか F くらいしか実装みたことないけど。