The Man Who Fell From The Wrong Side Of The Sky:最新 5 日分

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

2017/5/22(Mon)

[やきう] セ・パ交流戦間近

千葉ロッテマリーンズ自力優勝消滅、5月にこれはワイまで暗黒TBSベイス時代の90敗の記憶がフラッシュバックしてPTSDになりますわ…

しかし忘れないで欲しい、今年のオープン戦で 大洋ホエールズ横浜DeNAベイスターズはそんなロッテに一方的に虐殺され、ハマスタ0勝でシーズンを迎える羽目になった事を。

ほんと横浜クッソ弱くてもう今年心が折れている、横浜千葉名古屋大阪(神戸)みんな一緒にクソまみれになろうや…

[プログラミング幻語C][NetBSD] C2X宣言

C2X宣言だとさ、C99ももうすぐ成人式でとっくに淫行条例に引っかからない年とはね(しろめ)

まぁ202X年頃には野垂れ死にしとると思うしどうでもいい、ただ最低でもいい加減C99 __VA_ARGS__のクソさ(trailing comma問題ね)だけはどうにかして欲しい。 VC++と同じにしてくれ、あるいはgccの##__VA_ARGS__でも構わん、そういやこれC11に提案すら無かったんか…

もはやCはC++の尻拭い的案件(Thread Aware Localeやchar{16,32}_t他)を粛々と入れてくだけってのはC11ではっきりしたしな。

なんかの提案に胸 焼けを躍らせていた日々を返してほしいやな。

そいえばAppleがObjective-CからSwiftに舵切ったのって、これN1370が否決されてAppleはWG14(C)はWG21(C++)の金魚の糞でちゅーことで見切った可能性? どうなんやろね。

あとThread Aware LocaleというPTSDワードで思いだしたが、 誤解があるのようなので死人に口無しになる前に書いておく。

結果だけ見るといかにも

という意見を「彼が」主張したかのようにみえるけど、それは全然違うのよね。

そもそも彼の元々の主張は「glibc2なんかのダサいAPIなんか入れるなよ、俺が考えた最強のAPI *1ができるまでお前はコード触るな *2」だったのよ、そこから話が二転三転してるのでな…

そもそもuselocale(3)に反対と言いだしたのも私のプロポーザルから大分経過してからだったはず、最初はvaxのTLSサポートを理由にし、それが通らないとみるやどっかで聞きかじったFreeBSDでのctype性能問題の話を持ってきた *3しかも具体的な数値は一切出さずに。

「uselocale(3)以外はマージする」という結論は、膠着状態を仲裁すべく尽力してくださったcoreのyamtさんが引っ張り出した妥協点。それが「正しい」と感じたのであれば、yamtさんこそが称賛されるべき。 それだけははっきりと言っておきたい。

あと こっちも誤解やね。

ちゅーとこ。


*1:何度か個人メールでiconv(WCHAR_T)的なAPIを主張されたが、一行たりともコードは彼書いてなかったな、俺に書かせる気だったのかな…
*2:ワイの担当ワークエリアなのにそこで作業してはならないと、そのワークエリアの人間でもコアチームでも無い人間に言われて従うすじあいも無いんだけどね…
*3:そもそもプロポーザルの時点ではFreeBSDはまだ実装なかったからね


2017/5/21(Sun)

debugging BSD sed(1) (その6)

@ モダンなコードに書き直す

お次はcompile.cのcompile_text()のリファクタリング、この部分はsedの

の引数となるテキスト部分の境界(改行)を探して切り出すのと、エスケープ文字を消し込む処理をしている。

現在のN headのコード rev1.41あたりでchristosがどこから拾ってきたのかよく判らないバージョン(おそらくFreeBSDの実装だと思う)のBSD sed(1)と置換したようでのでいろいろデグって(最下層SIer用語)おられる。

以前はcu_fgets()に渡すlbufを固定長ではなく動的に確保する変更が入ってたんだけど、元に戻ってしまっている。 よって現在はsedに食わせるスクリプトの1行の長さは_POSIX2_LINE_MAX以上は切り詰められてしまう、滝川先生の通常営業やしどうせ誰も使ってないから問題ないよな。

/*
 * Compile the text following an a or i command.
 */
static char *
compile_text(void)
{
	size_t asize, size;
	int esc_nl;
	char *text, *p, *op, *s;
	char lbuf[_POSIX2_LINE_MAX + 1];

	asize = 2 * _POSIX2_LINE_MAX + 1;
	text = xmalloc(asize);
	size = 0;
	while (cu_fgets(lbuf, sizeof(lbuf), NULL)) {
		op = s = text + size;
		p = lbuf;
		for (esc_nl = 0; *p != '\0'; p++) {
			if (*p == '\\' && p[1] != '\0' && *++p == '\n')
				esc_nl = 1;
			*s++ = *p;
		}
		size += (size_t)(s - op);
		if (!esc_nl) {
			*s = '\0';
			break;
		}
		if (asize - size < _POSIX2_LINE_MAX + 1) {
			asize *= 2;
			text = xrealloc(text, asize);
		}
	}
	text[size] = '\0';
	p = xrealloc(text, size + 1);
	return (p);
}

こっちのコードはOpenBSDだとこんな感じ。

/*
 * Compile the text following an a, c, or i command.
 */
static char *
compile_text(void)
{
	int asize, esc_nl, size;
	char *lbuf, *text, *p, *op, *s;
	size_t bufsize;

	lbuf = text = NULL;
	asize = size = 0;
	while ((p = cu_fgets(&lbuf, &bufsize))) {
		size_t len = ROUNDLEN(strlen(p) + 1);
		if (asize - size < len) {
			do {
				asize += len;
			} while (asize - size < len);
			text = xrealloc(text, asize);
		}
		op = s = text + size;
		for (esc_nl = 0; *p != '\0'; p++) {
			if (*p == '\\' && p[1] != '\0' && *++p == '\n')
				esc_nl = 1;
			*s++ = *p;
		}
		size += s - op;
		if (!esc_nl) {
			*s = '\0';
			break;
		}
	}
	free(lbuf);
	text = xrealloc(text, size + 1);
	text[size] = '\0';
	return (text);
}

こっちは_POSIX2_LINE_MAX縛りはない、まぁlbufはstatic宣言してfree(3)せずに使い回した方が性能面で好ましいとは思うが。

どっちのコードについても

ちゅー感じで、いつチェーンソーで自分の両足切り落とさんか心配になるコードよね。

これもPOSIX:2008の新機能であるopen_memstream(3)を使えばコードはもっとシンプルかつ安全になるんですわ。

このコードをopen_memstream(3)を使って書き直すとこんな感じ。

/*
 * Compile the text following an a, c, or i command.
 */
static char *
compile_text(void)
{
	static char *lbuf;
	static size_t bufsize;
	char *text, *s;
	size_t len;
	FILE *fp;
	_Bool esc_nl;

	text = NULL;
	len = 0;
	fp = open_memstream(&text, &len);
	if (fp == NULL)
		err(EXIT_FAILURE, NULL);

	while ((s = cu_fgets(&lbuf, &bufsize))) {
		for (esc_nl = false; *s != '\0'; ++s) {
			if (*s != '\\')
				continue;
			if (*++s == '\0')
				break;
			if (*s == '\n')
				esc_nl = true;
			if (fputc(*s, fp) == EOF)
				err(EXIT_FAILURE, NULL);
		}
		if (!esc_nl)
			break;
	}
	fclose(fp);
	return (text);
}

まずopen_memstream(3)にメモリ管理をお任せしてしまうことでROUNDLEN()マクロとlen, asize, sizeそしてxreallocで死神と舞踏(ダンス)ってるコードが不要になる。そしてNUL terminateもお任せできるんで、こっちのややこしいフラグ処理も不要になる。

ただし *s = *p → fputc(*p) に書換えただけだと関数呼出が多くなるので

static inline void
efwrite(const void *p, size_t size, size_t nmemb, FILE *fp)
{
	if (nmemb > 0 && fwrite(p, size, nmemb, fp) != nmemb)
		err(EXIT_FAILURE, NULL);
}

/*
 * Compile the text following an a, c, or i command.
 */
static char *
compile_text(void)
{
	static char *lbuf;
	static size_t bufsize;
	char *text, *t, *s;
	size_t len;
	FILE *fp;
	_Bool esc_nl;

	text = NULL;
	len = 0;
	fp = open_memstream(&text, &len);
	if (fp == NULL)
		err(EXIT_FAILURE, NULL);

	while ((s = cu_fgets(&lbuf, &bufsize))) {
		for (t = s; *t != '\0'; ++t) {
			if (*t != '\\')
				continue;
			if (*++t == '\0')
				break;
			if (*t == '\n')
				esc_nl = true;
			efwrite(s, sizeof(*s), (t - 1) - s, fp);
			s = t;
		}
		efwrite(s, sizeof(*s), t - s, fp);
		if (!esc_nl)
			break;
	}
	fclose(fp);
	return (text);
}

くらいのことはしといた方がいいか、可読性は若干落ちるけど。

@ GNU sed の拡張

ちゅーことで書き直してテストしてて、 その4でなぜFが引数をまるっとコピーして強制改行してるかの理由に思い当ってしまった。

これおそらくGNU sedの拡張と同じような動作をしようとして四苦八苦してるんやな、今テストしてる a\ i\ c\コマンドの後のテキスト引数だけど、GNU sedは複数の-eで渡された文字列を連結して次の行として解釈するんだなこれ。

$ (echo 1; echo 2; echo 3) | gsed -e '2c\' -e 'unko'
1
unko
3
$

つまり-eは行として解釈しないと出力結果が一致しない、現にNとOでは改行がつかないので

$ (echo 1; echo 2; echo 3) | sed -e '2c\' -e 'unko'
1
unko3
$

ちゅーかんじでくっついてしまわれる。

そもそもPOSIXの仕様にはそんな事書いてないので、Solarisのxpg4 sedなんかは-eはそれぞれ別として完結して扱うから

$ (echo 1; echo 2; echo 3) | sed -e '2c\' -e 'unko'
sed: 編集スクリプトの終わりにエスケープ文字が見つかりました

とc\に引数が無いせいでシンタックスエラーで終了してしまう、なのでコマンドと引数は分けずに

$ (echo 1; echo 2; echo 3) | sed -e '2c\
> unko'
1
unko
3
$

といっこの-eで書く必要があるわけで、移植性無いからどうでもいいっちゃいいんだが。

なわけでさぞかしFはGNUと同じ挙動をしてくれるんだろうと確認してみたんだが

$ (echo 1; echo 2; echo 3) | sed -e '2c\' -e 'unko'
sed: 1: "unko
": invalid command code u
$

ファーwww同じcu_fgets()で処理してんのになぜか継続行の扱いにならない上に、引数に全部改行つけてるせいでエラーメッセージがおかしくなっておられる、全然意味ないじゃんこれ。

@ sed(1) is 闇深

あと前述の20年物のバグちゅーて魔球先生とボス先生が送って来た「テキストの最後が改行を伴なわないエスケープ文字\で終わるケース」をGNU sedで試すと

$ echo foo | gsed -e '/foo/c\
> unko\
> unko\
> unko\'
unko\
unko\
unko
$

ちゅーかんじでそもそも

ちゅー感じ、最後のエスケープがなければ

$ echo foo | gsed -e '/foo/c\
> unko\
> unko\
> unko'
unko
unko
unko
$

なのでどういう処理してんだか…

SolarisのXPG4 sedだとキストの最後が改行を伴なわないエスケープ文字\はエラーやね

$ echo foo | /usr/xpg4/bin/sed -e '/foo/c\
> unko\
> unko\
> unko\'
sed: 編集スクリプトの終わりにエスケープ文字が見つかりました

うーんsed(1) is 闇深、同じ闇ならワイはperlのようなもので書きますわ…

とにかくこのテキスト周りは仕様とか歴史的経緯とか拡張の使われ具合とか全部一度整理して整合性つけないとダメだわこれ。


2017/5/20(Sat)

[NetBSD] debugging BSD sed(1) (その5)

@ 古代遺跡で廃品回収

昔から*BSDのカビ臭いコードを弄るのって古代遺跡から出土した謎のロボットをよく判らずに動かすのに似て、どっか弄ると最悪全滅して全裸で因果地平の彼方に吹き飛ばされるからアレって前々からよく言ってるんだけど、また今日も致命傷で済んだ。

まぁGNUのどんなケミカルキメたらこのコードスタイルが美しいと感じるのかよりはマシかもしれんが。

そんでBSD sed(1)も古いコードなので今時のCならこう書き直した方がいいってコード多いのよね。 例えばmain.cのcu_fgets()、これはfgets(3)のようにストリームから一行を取り出すAPIなんだけども、それプラス

という拡張がされている。

/*
 * Like fgets, but go through the chain of compilation units chaining them
 * together.  Empty strings and files are ignored.
 */
char *
cu_fgets(char **outbuf, size_t *outsize)
{
	static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF;
	static FILE *f;		/* Current open file */
	static char *s;		/* Current pointer inside string */
	static char string_ident[30];
	size_t len;
	char *p;

	if (*outbuf == NULL)
		*outsize = 0;

again:
	switch (state) {
	case ST_EOF:
		if (script == NULL)
			return (NULL);
		linenum = 0;
		switch (script->type) {
		case CU_FILE:
			if ((f = fopen(script->s, "r")) == NULL)
				err(FATAL,
				    "%s: %s", script->s, strerror(errno));
			fname = script->s;
			state = ST_FILE;
			goto again;
		case CU_STRING:
			if ((snprintf(string_ident,
			    sizeof(string_ident), "\"%s\"", script->s)) >=
			    (int)(sizeof(string_ident) - 1))
				(void)strcpy(string_ident +
				    sizeof(string_ident) - 6, " ...\"");
			fname = string_ident;
			s = script->s;
			state = ST_STRING;
			goto again;
		}
	case ST_FILE:
		if ((p = fgetln(f, &len)) != NULL) {
			linenum++;
			if (len >= *outsize) {
				free(*outbuf);
				*outsize = ROUNDLEN(len + 1);
				*outbuf = xmalloc(*outsize);
			}
			memcpy(*outbuf, p, len);
			(*outbuf)[len] = '\0';
			if (linenum == 1 && p[0] == '#' && p[1] == 'n')
				nflag = 1;
			return (*outbuf);
		}
		script = script->next;
		(void)fclose(f);
		state = ST_EOF;
		goto again;
	case ST_STRING:
		if (linenum == 0 && s[0] == '#' && s[1] == 'n')
			nflag = 1;
		p = *outbuf;
		len = *outsize;
		for (;;) {
			if (len <= 1) {
				*outbuf = xrealloc(*outbuf,
				    *outsize + _POSIX2_LINE_MAX);
				p = *outbuf + *outsize - len;
				len += _POSIX2_LINE_MAX;
				*outsize += _POSIX2_LINE_MAX;
			}
			switch (*s) {
			case '\0':
				state = ST_EOF;
				if (s == script->s) {
					script = script->next;
					goto again;
				} else {
					script = script->next;
					*p = '\0';
					linenum++;
					return (*outbuf);
				}
			case '\n':
				*p++ = '\n';
				*p = '\0';
				s++;
				linenum++;
				return (*outbuf);
			default:
				*p++ = *s++;
				len--;
			}
		}
	}
	/* NOTREACHED */
	return (NULL);
}

汚いものは閉じ込めておくって意味では正解なんだけども、cu_fgets()自体の実装はとても読めたものじゃない(アロケーション周りほんとクソ)。

しかしPOSIX:2008で導入された新関数(ってもglibc由来だけど)

 NAME
      fmemopen -- open a stream that points to the given buffer
 
 LIBRARY
      Standard C Library (libc, -lc)
 
 SYNOPSIS
      #include <stdio.h>
 
      FILE *
      fmemopen(void  *restrict buffer, size_t size, const char *restrict mode);

のように

という新兵器を使えばグッと簡単に書ける。

そしてfgetln(3)のような移植性も無い上に使えばoff-by-oneの温床になるマニュアルを読んでたらまず使う気にならんモノも捨て、やはりこちらもPOSIX:2008から導入されたgetline(3)を使う。

 NAME
      getdelim, getline -- read a delimited record from a stream
 
 LIBRARY
      Standard C Library (libc, -lc)
 
 SYNOPSIS
      #include <stdio.h>
 
      ssize_t
      getdelim(char ** restrict lineptr, size_t * restrict n, int
      delimiter, FILE * restrict stream);
 
      ssize_t
      getline(char ** restrict lineptr, size_t * restrict n, FILE * restrict
      stream);

まぁそんな感じで文字列とダンスってるコードがガッツリ削った結果がこちら。

/*
 * Like fgets, but go through the chain of compilation units chaining them
 * together.  Empty strings and files are ignored.
 */
char *
cu_fgets(char **outbuf, size_t *outsize)
{
	static FILE *f;		/* Current open file */
	static char string_ident[30];
	ssize_t len;
	char *p;

	if (f == NULL) {
again:
		if (script == NULL)
			return (NULL);
		linenum = (size_t)0;
		switch (script->type) {
		case CU_FILE:
			fname = script->s;
			f = fopen(script->s, "r");
			break;
		case CU_STRING:
			if ((snprintf(string_ident,
			    sizeof(string_ident), "\"%s\"", script->s)) >=
			    (int)(sizeof(string_ident)))
				strlcpy(string_ident +
				    sizeof(string_ident) - 6, " ...\"", 5);
			fname = string_ident;
			f = fmemopen(script->s, strlen(script->s) + 1, "r");
		}
		if (f == NULL)
			err(EXIT_FAILURE, "%s", fname);
		if ((len = getline(outbuf, outsize, f)) == -1)
			goto reacheof;
		p = *outbuf;
		if (len >= 2 && p[0] == '#' && p[1] == 'n')
			nflag = true;
	} else if ((len = getline(outbuf, outsize, f)) == -1) {
reacheof:
		if (ferror(f))
			errx(EXIT_FAILURE, "%s: %s",
			    fname, strerror(errno ? errno : EIO));
		script = script->next;
		fclose(f);
		f = NULL;
		goto again;
	}
	linenum++;
	return *outbuf;
}

コード量はきっかり半分になった(まだこれでもデバック用の文言ごときにゴチャゴチャ書いてるので邪悪)上に、2つほどメモリ関係のバグが直ってる。

これまでもファイルの方をmmap(2)してメモリで扱い、コードをポインタ操作に寄せてしまってファイルとメモリの違いを意識しないみたいなコードは書けたんだけれども、その場合はstdinなんかの扱いに困るわけでな。

@ あゝNetBSD時間

ということで該当箇所をfmemopen(3) + getline(3)使うように書き直したんだけど、通常の/usr/bin/sedは問題ないのにtoolchainのnbsedだけクラッシュする。

うはーなんかバグ踏んだかなと思って検死したら、getline(3)からgetdelim(3)に入ってFILE構造体の内部を触ったとこでよく判らない死を遂げている

$ gdb -q /usr/src/obj.amd64/tooldir.NetBSD-6.1_STABLE-amd64/bin/nbsed
Reading symbols from /usr/obj.amd64/tooldir.NetBSD-6.1_STABLE-amd64/bin/nbsed...(no debugging symbols found)...done.
(gdb) run -e "s,/bin/sh,/bin/sh,g"  -e "s,{AWK:=.*},{AWK:="/usr/src/obj.amd64/tooldir.NetBSD-6.1_STABLE-amd64/bin/nbawk"},"  < /usr/src/tools/genassym/../../usr.bin/genassym/genassym.sh > genassym
Starting program: /usr/obj.amd64/tooldir.NetBSD-6.1_STABLE-amd64/bin/nbsed -e "s,/bin/sh,/bin/sh,g"  -e "s,{AWK:=.*},{AWK:="/usr/src/obj.amd64/tooldir.NetBSD-6.1_STABLE-amd64/bin/nbawk"},"  < /usr/src/tools/genassym/../../usr.bin/genassym/genassym.sh > genassym

Program received signal SIGSEGV, Segmentation fault.
0x00007f7ff7523970 in __getdelim (buf=0x6067a8, buflen=0x6067a0, sep=10, fp=0xfffffffff779a2a0)
    at /usr/src/lib/libc/stdio/getdelim.c:75
75              _SET_ORIENTATION(fp, -1);
(gdb) bt
#0  0x00007f7ff7523970 in __getdelim (buf=0x6067a8, buflen=0x6067a0, sep=10, fp=0xfffffffff779a2a0)
    at /usr/src/lib/libc/stdio/getdelim.c:75
#1  0x00007f7ff7523c1a in _getdelim (buf=0x6067a8, buflen=0x6067a0, sep=10, fp=0xfffffffff779a2a0)
    at /usr/src/lib/libc/stdio/getdelim.c:151
#2  0x00007f7ff74be008 in _getline (buf=0x6067a8, buflen=0x6067a0, fp=0xfffffffff779a2a0)
    at /usr/src/lib/libc/stdio/getline.c:44
#3  0x000000000040330f in cu_fgets ()
#4  0x0000000000401e05 in compile ()
#5  0x0000000000403189 in main ()

どう考えても俺無罪なので、ピーンときてcompat周りのコード読んだら src/tools/compat/compat_defs.hでの

/*
 * On NetBSD, ensure that _NETBSD_SOURCE does not get defined, so that
 * accidental attempts to use NetBSD-specific features instead of more
 * portable features is likely to be noticed when the tools are built
 * on NetBSD.  Define enough other feature test macros to expose the
 * features we need.
 */
#ifdef __NetBSD__
#define	_ISOC99_SOURCE
#define _POSIX_SOURCE	1
#define _POSIX_C_SOURCE	200112L
#define _XOPEN_SOURCE 600
#endif /* __NetBSD__ */

to とtoolchainのビルドにSUSv6を強制してるのが悪さしてるやんけこれ。NetBSD時間は未だに15年以上前を彷徨っているようでもう成立して10年経つPOSIX:2008の関数なぞいまだ存在しませんみたいなことやっとる。

なもんで以前の 後方互換性周りの記事で書いたようにこれらのマクロによって

#if (_POSIX_C_SOURCE - 0) >= 200809L || (_XOPEN_SOURCE - 0) >= 700 || \
    defined(_NETBSD_SOURCE)
__BEGIN_DECLS
FILE *fmemopen(void * __restrict, size_t, const char * __restrict);
FILE *open_memstream(char **, size_t *);
ssize_t	 getdelim(char ** __restrict, size_t * __restrict, int,
	    FILE * __restrict);
ssize_t	 getline(char ** __restrict, size_t * __restrict, FILE * __restrict);
__END_DECLS
#endif

今回使ったfmemopen+getlineのプロトタイプ宣言は消されてしまい、戻り値は強制的にintとしてリンクされ(そういえばbuild.sh tool中に警告出てたのを見落としていた)てたってことだ。FILE *だとポインタがintに丸められたらそらクラッシュしますわね。

これまでも getline(3)化の作業をやってたんだけど、こっちはssize_tからintにnarrowされてもまぁ問題なく動いてるようにみえたので顕在化しなかったわけだ、あーあ。

@ 結論

ありとあらゆるものがepoch(1970-01-01T00:00:00)に向かって退行するNetBSD時間、 ユービックスプレーをもってしても2017年の現代まで戻るのはP.K.ディックでも無理ゲー、イナゴ身重く横たわりガブルガブルガビッシュなのである。

まぁ

#define _POSIX_C_SOURCE	200809L
#define _XOPEN_SOURCE 700

にアプデすりゃいいんだが、POSIX:2008の一部機能の実装に反対して「Against the standard」までゆうた連中やし是非このまま15年前を生きていて欲しいもんである、3次元でなく4次元の世界の住人から観測すれば時間のスピードでワイと彼らが離れていく姿が観測できるわけだ、次元上げたいな。

この機能はクソだから仕様にあろうが実装しない方が賢明とかいう態度だから時間退行していくのやけどね、SolarisですらあれだけLinuxの痛々しいコスプレまでしたのにその甲斐なく無残に死んだわけでな。

まぁ未だC89で30年前の連中もいるからまだマシかもしれん、やはりCはもう捨てろ。


2017/5/19(Fri)

[NetBSD] debugging BSD sed(1) (その4)

オレオレN6にOでの変更は-i(inplace edit)以外はとりあえず全部mergeし終わった、-iについてはNのHEADもOも他の変更と一緒にミソクソでmergeしててコード履歴がクッソ汚いので パクリ元のFの変更を追いながら再実装することにして、とりあえず今度はFの変更履歴を最初から追ってる最中。

そんでFからの変更取込みがミソクソになるのも必然なのが判った、sedのコード修正してる連中ほとんどテストも書かずリグレッション何それうまいの状態で、変更ひとつ引っ張ってくると別の場所が壊れますわこれ。

さっきもGとHの動作が変という

の修正を取り込んだら、最終行に改行の無い入力やバイナリデータに対しても改行が強制される別のバグが爆誕して、そっち調べたら

が10年も経ってからようやく報告されてるレベル、その間に-iの実装でコード大きく変わってるのでそのまま適用できんしワイいまどうしようか検討中。

そんな感じで心の中の ICBMボタンを連打してながらソース読んでるんですが、-iオプション(inplace)ではなくて全く別の話の'i\'コマンド(insert)の方の話になるんだけど、FreeBSDでは

というこれまた古いPRが上がってて、これはFでは

の修正でfixされてるんだけども、他のOSでは

という状態なんよね。

なのでオレオレN6にマージしようかと思ったんだけどこの修正がまた無茶苦茶なんだよな。

--- head/usr.bin/sed/main.c	2000/05/11 16:57:45	60393
+++ head/usr.bin/sed/main.c	2000/05/11 17:01:52	60394
@@ -115,6 +115,7 @@ main(argc, argv)
 	char *argv[];
 {
 	int c, fflag;
+	char *temp_arg;
 
 	(void) setlocale(LC_ALL, "");
 
@@ -129,7 +130,10 @@ main(argc, argv)
 			break;
 		case 'e':
 			eflag = 1;
-			add_compunit(CU_STRING, optarg);
+			temp_arg=xmalloc(strlen(optarg) + 2);
+			strcpy(temp_arg, optarg);
+			strcat(temp_arg, "\n");
+			add_compunit(CU_STRING, temp_arg);
 			break;
 		case 'f':
 			fflag = 1;

うーん-eに渡されたスクリプト全部まるっとコピーして改行強制ってすげー無駄、sed -fせずにsed -eにARG_MAX何それで長文書く人結構いそうだしなぁ、というか居たSIerやはり絶滅以下略。

しかしこれ最終行の改行有無に関するバグをこれまで散々踏み抜いてるのにこの対応ができるってはっきり言って何も考えてないだろ(怒)。 つーかその後に消してまた戻すとかモグラ叩きもいいとこだしなぁ。

まっとうな方法を考えてみると、簡単なのはiコマンドの実行時(process.cのprocess関数)

			case 'i':
				(void)printf("%s", cp->t);
				break;

の部分を修正して

@@ -166,6 +166,8 @@ redirect:
 				break;
 			case 'i':
 				(void)printf("%s", cp->t);
+				if ((len = strlen(cp->t)) == 0 || cp->t[len - 1] != '\n')
+					putchar('\n');
 				break;
 			case 'l':
 				lputs((const char *)ps);

で対応できる、けれど処理対象のストリームやファイルを一行づつ処理する度にstrlen(3)が呼ばれて性能悪化しそうなのがアレ。

ただまぁこれは毎回strlen(3)呼ばんでもsedスクリプトをコンパイルする時(compile.cのcompile_stream関数)の

		case TEXT:                      /* a c i */
...
			cmd->t = compile_text();
			break;

の時点で文字列は確定してるので

@@ -80,6 +80,7 @@ struct s_command {
 struct s_command *next;		 /* Pointer to next command */
 struct s_addr *a1, *a2;		 /* Start and end address */
 	char *t;				 /* Text for : a c i r w */
+	size_t n;				 /* Length of Text */
 	union {
 		struct s_command *c;		 /* Command(s) for b t { */
 		struct s_subst *s;		 /* Substitute command */

みたいにstruct s_commandに文字列長のフィールドを追加して

@@ -261,6 +261,7 @@ nonsel:	/* Now parse the command */
                                error("extra characters after \\ at the end "
                                    "of %c command", cmd->code);
                        cmd->t = compile_text();
+                       cmd->n = strlen(cmd->t);
                        break;
                case COMMENT:                   /* \0 # */
                        break;

とコンパイルの時点で長さを事前に計っておいて、さっきのprocess.cを

@@ -166,6 +166,8 @@ redirect:
 				break;
 			case 'i':
 				(void)printf("%s", cp->t);
+				if (cp->n == 0 || cp->t[cp->n - 1] != '\n')
+					putchar('\n');
 				break;
 			case 'l':
 				lputs((const char *)ps);

と書きかえればマシになる、またstrlen(3)使わなくてもcompile_text()の中で返す文字列の長さは判ってるので

@@ -261,6 +261,7 @@ nonsel:	/* Now parse the command */
                                error("extra characters after \\ at the end "
                                    "of %c command", cmd->code);
-                       cmd->t = compile_text();
+                       cmd->t = compile_text(&cmd->n);
                        break;
                case COMMENT:                   /* \0 # */
                        break;

としてそっちでセットするようにすれば、コンパイル時コストも増えないし。

それに他のa/rコマンドですでに無駄に毎回strlen(3)呼ばれるのも不要になるよねこれ

			case 'a':
...
				appends[appendx].type = AP_STRING;
				appends[appendx].s = (const char *)cp->t;
				appends[appendx].len = strlen(cp->t);
				appendx++;
				break;
...
			case 'r':
				appends[appendx].type = AP_FILE;
				appends[appendx].s = (const char *)cp->t;
				appends[appendx].len = strlen(cp->t);
				appendx++;
				break;

また長さが事前に判ってればprintf("%s")という無駄に重いフォーマット命令使ってるとこでふつーにfwrite(3)が使えるようになるし。

@@ -166,6 +166,8 @@ redirect:
 				break;
 			case 'i':
- 				(void)printf("%s", cp->t);
+ 				(void)fwrite(cp->t, sizeof(*cp->t), cp->n, stdout);
+				if ((len = strlen(cp->t)) == 0 || cp->t[len - 1] != '\n')
+					putchar('\n');
 				break;
 			case 'l':
 				lputs((const char *)ps);

まー結論としては、モグラ叩きデバッグとその結果をただ漫然とコピペコーディングやってるといつかモグラが堀った穴で地盤沈下して埋もれて死ぬってこった。


2017/5/14(Sun)

[映画] Wim Wenders/Don't Come Knocking(2005)

ヴェンダース監督の「アメリカ、家族のいる風景(Don't Come Knocking)」をようやく観た。

彼の代表作「パリ、テキサス」以来20年ぶりにサム・シェパードを脚本にタッグを組んだ作品なので、公開当初から気にはなっていたけど未見のままだった。

あの名作のコンビとあって期待値が高過ぎたせいか一般的な評価はあまり芳しくない、自分は好きだけれどね。

そもそも「アメリカ〜」は「パリ〜」の続編ではないのだけれども、同じく彼の代表作である「ベルリン・天使の詩」で、天使ダミエルが人間界に身を堕として一人の女性を愛することで生きる喜びを知るストーリーの続編に「時の翼にのって〜ファラウェイ・ソー・クロース!」でダミエルを羨んだ天使カシエルがやはり人間になるも犯罪に加担するまで落ちぶれ、贖罪として命を落とすというバッドエンドを持ってきて低評価を受けたのとよく似ている。

一般受けはどうしてもロマンティックな方に軍配が上がっちゃうからこれはもう仕方がない。

「パリ〜」は共依存に陥った男女が人生もろとも破綻し、ふたりの関係はもう戻らないけれどもまだ幼い息子とだけは家族の絆を取り戻すという、こちらもロマンティックな話なんだけど、「アメリカ〜」は放蕩の限りを尽くしてきた男がミッドライフ・クライシスを迎え、それまで存在すら知らなかった子供をダシに過去の女に縋ろうとし拒絶される話で、人生経験の豊富でない若い男女が観て感動する話ではないのは確かである。

しかし同年に何かと比べられることの多いジム・ジャームッシュ監督もストーリーまる被りの「ブロークン・フラワーズ」を撮影してるんだけどシンクロニシティだね。 こういう役を演じさせるならサム・シェパードよりもビル・マーレイの方が向いてるし、そこがそれぞれの映画の点数の差なんだろう。

オープニングは アーチーズ国立公園の、まるで神の両眼かと思わせる洞窟からのぞく青空、そして大自然が作り出した砂岩のアーチの下を、主人公の西部劇俳優ハワード(サム・シェパード)が馬で疾走するシーンからはじまる。

「パリ〜」も人間の存在を許さないかのような虚無が支配するモハビ砂漠を、僅かな水も尽きてなお一心不乱に歩く主人公トラヴィス(ハリー・ディーン・スタントン)から物語はスタートした。

トラヴィスの行動は自殺行為ではあるけれども彼は死にたいわけじゃない、死んだ父母が結婚し自分を生んだ地(テキサス州パリス)、そこにはかつて気まぐれで通信販売で購入した空き地があり、そこへたどり着ければまた人生をやり直せるといういわば双六の「振り出しに戻る」という行為に妄執するがゆえの行動。

一方でハワードの行動はより短絡的かつ破滅的であり、映画の撮影現場から逃亡しあわよくば落馬事故で死んでしまいたいという自殺衝動。 「ライト・スタッフ」でサムの演ずるチャック・イエーガーが死をも恐れぬ自身のテストパイロットしても勇猛さの誇示に馬を走らせた演出との対比をも感じさせる。

砂漠で一夜を明かしひとまず憑き物が落ちたハワードは馬と身にまとった派手なカウボーイ衣装を捨て、身を隠す先として母の住む故郷へと決めるけれどもそれは思いつきでしかない。トラヴィスが弟ウォルトに何度も引き戻されようが隙あらば逃げ出し向かおうとしたパリスほどの思いは無い。

ハワードのロケバスには

こっちくんな(Don't come knocking)

の文字、これもドラヴィスが行き倒れた砂漠を越えた先のバーのカウンターに掛けられた

砂埃まみれ(の店)、座るでも立ち去るでも何でもご自由に(The dust has come to stay. You may stay or pass on through or whatever)

の看板 *1を思い出させる。これまですべてを拒絶してきた男ハワードと、翻弄され流されるままだったトラヴィスとの性格の違いを表しているのだろうか。

ハワードはどこからか車と携帯電話を手に入れて、夕暮れのハイウェイを走りながら長らく連絡を絶っていた母へ電話をかける。このシーンもトラヴィスがウォルトの運転する車でモーテルへ向かうシーンによく似ている(嵐が訪れる予感をさせる美しい夕焼け)。ハワードの惨めな心境そのままの歌詞を歌う若い男は(まだ物語の上では明かされないが)はハワードの(存在すら知らなかった)息子アール、トラヴィスもこのシーンで彼の失踪後にウォルトが引き取って育てていた息子ハンターの存在が明かされる。

車と携帯を捨てバスで母の家へ向かう道中にハワードは道端で全知全能の神の存在について歌う謎の男とすれ違う、これもトラヴィスがウォルトの元に身を寄せつかの間の安寧を得るけども眠れぬ夜に街を彷徨う折に「どこにも安全な場所など存在しない」と叫ぶ狂人と出会い、今の居候生活を続けることは出来ない事に気づくシーンを思い出させるけれど、いまいちストーリーとの関連性がわからない。

ハワードを追いかけるのは映画会社のエージェントで氷のように冷徹で彼を映画の撮影現場に連れ戻すという任務以外には何一つ興味を示さない男、トラヴィスを追いかけた弟ウォルトが常識人で温かい家庭を持ちとにかくトラヴィスに理解を示すのとは対極である。

字数が尽きた、まだ続くかもしれないし続かないかもしれない何でもご自由に。


*1:ネオアコバンドMexico 70のアルバムタイトルにもなってるね。


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