The Man Who Fell From The Wrong Side Of The Sky:2017年5月分

2017/5/1(Mon)

[NetBSD] debugging Henry Spencer regex(3) (その1)

昨日の続き。

以前にHenry Spencer regex(3)を弄ったのはnvi-1.81付属のwide character対応に改造した奴がボロボロで、日本語localeでまともに動かなかった時以来でもう9年前とか忘却の彼方だし、そもそもregex engineの実装とか興味ないから全然わからん。

だが意地でもバグだけは直させてもらう、いややっぱりめんどくさいから途中でやめる可能性もあるけど。

@ 障害箇所のアタリをつける

とりあえずエラーメッセージを元にgソースコードをどこから読みはじめるか調べましょう。

$ grep -nr "invalid argument to regex routine" /usr/src/lib/libc/regex
/usr/src/lib/libc/regex/regerror.c:152: { REG_INVARG,   "REG_INVARG",   "invalid argument to regex routine" },

このメッセージには「REG_INVARG」という定数っぽいものが関連付けられてるようなのでそっちで再検索。

$ grep -nr "REG_INVARG" /usr/src/lib/libc/regex/
/usr/src/lib/libc/regex/engine.c:212:           return(REG_INVARG);
/usr/src/lib/libc/regex/regcomp.c:255:          return(REG_INVARG);
/usr/src/lib/libc/regex/regcomp.c:259:                  return(REG_INVARG);
/usr/src/lib/libc/regex/regerror.c:128: = #define       REG_INVARG      16
/usr/src/lib/libc/regex/regerror.c:152: { REG_INVARG,   "REG_INVARG",   "invalid argument to regex routine" },
/usr/src/lib/libc/regex/regex.3:562:.It Dv REG_INVARG

ちゅーことでエラーメッセージを出していると思われる個所は

  • engine.c内のmatcher()関数
    176 static int                      /* 0 success, REG_NOMATCH failure */
    177 matcher(
    ...
    210         if (stop < start)
    211                 return(REG_INVARG);
    
  • regcomp.c内のregcomp()関数
    233 int                             /* 0 success, otherwise REG_something */
    234 regcomp(
    ...
    253         cflags = GOODFLAGS(cflags);
    254         if ((cflags&REG_EXTENDED) && (cflags&REG_NOSPEC))
    255                 return(REG_INVARG);
    256
    257         if (cflags&REG_PEND) {
    258                 if (preg->re_endp < pattern)
    259                         return(REG_INVARG);
    

のいずれかに絞られました。

そんで前回の記事でこのエラーが発生する条件として

  • 文字列の末尾にトリム対象のBase64パディング(=)がある場合はエラーは起きない
    $ echo -n 'A=' | sed -e 's/=*$//g' | od -x
    0000000     0041
    0000001
    
  • パディングが無いとエラーが発生
    $ echo -n 'A' | sed -e 's/=*$//g'
    sed: RE error: invalid argument to regex routine
    

というとこまでは分かっていますので、正規表現のコンパイルを行うregcomp()関数が犯人はありえないので除外できます。

ということで犯行現場はengine.cのmatcher()関数と察しがつくわけです、ここまでは誰でもできる推理っすね。

@ matcher関数をデバッグする

とゆーことでさらに調査を進めるために、libcとsed(1)をデバッグシンボルつきでビルドします。

$ cd /usr/src/lib/libc
$ sudo make USETOOLS=no cleandir depend all install
$ cd /usr/src/usr.biin/sed
$ sudo make USETOOLS=no cleandir depend all install

こうやって雑な手順でmake installしてるとlibc壊してるの忘れてて死が訪れますが、その時は適当に復活の呪文唱えてください。

そんでgdb(1)にsed(1)を読み込ませて、engine.c:210行目にブレークポイントをおいて実行します。

$ echo -n 'A' >test.txt
$ gdb --quiet /usr/bin/sed
Reading symbols from /usr/bin/sed...done.
(gdb) b /usr/src/lib/libc/regex/engine.c:210
No source file named /usr/src/lib/libc/regex/engine.c.
Make breakpoint pending on future shared library load? (y or [n]) y

Breakpoint 1 (/usr/src/lib/libc/regex/engine.c:210) pending.
(gdb) run -e 's/=*$//g' test.txt
Starting program: /usr/bin/sed -e 's/=*$//g' test.txt

Breakpoint 1, smatcher (g=0x7f7ff7b10180, string=0x7f7ff7b04000 "A", nmatch=1, pmatch=0x7f7ff7b16080, eflags=4)
    at /usr/src/lib/libc/regex/engine.c:210
210             if (stop < start)
(gdb) p stop
$1 = 0x7f7ff7b04001 ""
(gdb) p start
$2 = 0x7f7ff7b04000 "A"
(gdb) cont
Continuing.

Breakpoint 1, smatcher (g=0x7f7ff7b10180, string=0x7f7ff7b04002 "", nmatch=1, pmatch=0x7f7ff7b16080, eflags=5)
    at /usr/src/lib/libc/regex/engine.c:210
210             if (stop < start)
(gdb) p stop
$3 = 0x7f7ff7b04001 ""
(gdb) p start
$4 = 0x7f7ff7b04002 ""
(gdb) cont
Continuing.
sed: RE error: invalid argument to regex routine
[Inferior 1 (process 8072) exited with code 01]
(gdb)

こないなトレースが取れました、Breakpointが matcherでなくsmatcherになってますが、これはengine.cの冒頭で

 81 #ifdef SNAMES
 82 #define matcher smatcher
...
 92 #endif
 93 #ifdef LNAMES
 94 #define matcher lmatcher
...
104 #endif

というマクロが定義されてるのと、engine.cはregexec.cでincludeされていて

130 /* function names */
131 #define SNAMES                  /* engine.c looks after details */
132
133 #include "engine.c"

と「s」プレフィックスがつくようSNAMESがdefineされてるから。

@ gdbのトレースを読み解く

勘のいい人はお気づきでしょうが、そもそも210行目においたブレークポイントを2回通過してるんですよな。 元々の正規表現「$」は末尾にマッチする正規表現なので1回マッチしたらそこで終わりなはずなんだけどね。

しかも2回目の呼出は

Breakpoint 1, smatcher (g=0x7f7ff7b10180, string=0x7f7ff7b04002 "", nmatch=1, pmatch=0x7f7ff7b16080, eflags=5)
    at /usr/src/lib/libc/regex/engine.c:210
210             if (stop < start)
(gdb) p stop
$3 = 0x7f7ff7b04001 ""
(gdb) p start
$4 = 0x7f7ff7b04002 ""

と正規表現の評価対象である入力文字列である変数string(=start)が、末尾のNUL文字のさらに次のアドレスを指しててオーバーランしてるのよね。

0x7f7ff7b04000 -> 'A'
0x7f7ff7b04001 -> '\0'
0x7f7ff7b04002 -> オーバーラン

なーんか前回のバグの発生条件にあった「g(global search)フラグありの場合だけ発生する」が怪しいよねこれ。

ということで今日のところは仮説として

  • g(global search)フラグを付けた場合の実装がバグってる
  • バグのトリガーは入力文字列の最後が改行で終わってないせい
  • 末尾に到達してそこで評価終了のはずなんだけど、gフラグのバグで2回目の評価が走る
  • オーバーランを検知してエラーメッセージ

となってるんじゃないかと。

だいぶ酒が深まってきたのでおしまい、明日は出かけるんだってば。

2017/5/2(Tue)

[NetBSD] debugging Henry Spencer regex(3) (その2)

前回はregex(3)のprivate APIであるsmatcher関数がなぜか2回呼ばれてることが判りましたが、そもそも lib/libc/regexの下をgrepしても

$ grep -nr smatcher /usr/src/lib/libc/regex/
/usr/src/lib/libc/regex/engine.c:82:#define     matcher smatcher
/usr/src/lib/libc/regex/regexec.c:231:          return(smatcher(g, s, nmatch, pmatch, eflags));

というようにpublic APIであるregexec関数が1度読んでるっきりなのよね、smatcherあるいはregexec自身が再帰してるわけでもないし。

ということはregexec(3)そのもののバグというより、regexec(3)の使い方が間違ってる可能性が出てきました。うーんトリックに引っかかった。

海よりも深く反省しつつもう一度前回のgdbでのトレース結果をみると

Breakpoint 1, smatcher (g=0x7f7ff7b10180, string=0x7f7ff7b04000 "A", nmatch=1, pmatch=0x7f7ff7b16080, eflags=4)
    at /usr/src/lib/libc/regex/engine.c:210

Breakpoint 1, smatcher (g=0x7f7ff7b10180, string=0x7f7ff7b04002 "", nmatch=1, pmatch=0x7f7ff7b16080, eflags=5)
    at /usr/src/lib/libc/regex/engine.c:210

お判りいただけたであろうか、1回目と2回目でeflagsの値が4 → 5に変わってるね。

このeflagsは

int
regexec(const regex_t * restrict preg, const char * restrict
string, size_t nmatch, regmatch_t pmatch[], int eflags);

の第5引数そのまま渡ってくるので、やっぱregexec(3)の呼び方が間違ってるっぽい。

このeflagsの値は

/* regexec() flags */
#define REG_NOTBOL      00001
#define REG_NOTEOL      00002
#define REG_STARTEND    00004
#define REG_TRACE       00400   /* tracing of execution */
#define REG_LARGE       01000   /* force large representation */
#define REG_BACKR       02000   /* force use of backref code */

として regex.hに定義されとります、ということで1回目はREG_STARTEND、2回目は(REG_NOTBOL|REG_STARTEND)で呼ばれてるよね。

ということでREG_NOTBOLつきで実行してるところを usr.bin/sed以下から検索してみると

$ grep -nr REG_NOTBOL /usr/src/usr.bin/sed/
/usr/src/usr.bin/sed/process.c:414:             } while (slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen));
/usr/src/usr.bin/sed/process.c:423:                     if (!regexec_e(re, s, REG_NOTBOL, 0, slen))

ということで process.cのsubstitute関数がヒットします。

355  * substitute --
356  *      Do substitutions in the pattern space.  Currently, we build a
357  *      copy of the new pattern space in the substitute space structure
358  *      and then swap them.
359  */
360 static int
361 substitute(struct s_command *cp)
362 {
...

そしてREG_NOTBOLの出現箇所のコードを読んでみると

378         if (!regexec_e(re, s, 0, 0, psl))
379                 return (0);
380
381         SS.len = 0;                             /* Clean substitute space. */
382         slen = psl;
383         n = cp->u.s->n;
384         lastempty = 1;
385
386         switch (n) {
387         case 0:                                 /* Global */
388                 do {
…
414                 } while (slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen));
…
419         default:                                /* Nth occurrence */
420                 while (--n) {
…
423                         if (!regexec_e(re, s, REG_NOTBOL, 0, slen))
424                                 return (0);
425                 }
…
426                 /* FALLTHROUGH */
427         case 1:                                 /* 1st occurrence */

378行目のregexec_e関数(regexec関数のラッパー)を実行した後で再びregexec_eをREG_NOTBOLつきで実行してますな。 gdbのトレース結果は1回目のsmatcherは378行目、2回目は414行目あるいは423行目と考えると犯行現場はまずここで間違いないと。

コードを読まないで適当にコメントから想像するに、switch文で条件分岐してるのは

という量指定子かなーと、?(0あるいは1回)が無いのが気になるけど。

そんでこれをみて

という疑問が湧きますが、結論から書いちゃうと前者やね。

自前でパースしてるのは compile.cの中で、そもそもsed(1)の-eに渡された文字列をそのままregex(3)の正規表現に渡すわけにもいかんし当たり前やな。

そんでさっきのprocess.c383行目のcp->u.s->nという可読性の欠片も無い素晴らしいコードから、こいつが defs.hに定義されたstruct s_substのフィールドであることを確認します。

...
 96 /*
 97  * Substitution command
 98  */
 99 struct s_subst {
100         int n;                                  /* Occurrence to subst. */
...

そんでこのフィールドに値がセットされるのはcompile_flagsやね。

569 static char *
570 compile_flags(char *p, struct s_subst *s)
571 {
…
575         s->n = 1;
582                 case 'g':
583                         if (gn)
584                                 err(COMPILE,
585 "more than one number or 'g' in substitute flags");
586                         gn = 1;
587                         s->n = 0;
...

はえーコメントにも「Occurrence」とあるし正規表現の量指定子についての情報と思ったんだけど、そうではなくてフラグの

についての情報だったのね、まぁ前回の推理の中でgフラグの有無で挙動が変わるってのがあったのでよりここが犯行現場であるという確証が強まった。

@ 次回

修正すべき箇所は特定できたっぽいので、さてどう直すのが正しいのか考える。というかちゃんとユーザーが存在してバグに気づいて修正されているまっとうな実装からパチって終わりの可能性も高い(ぉ

2017/5/3(Wed)

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

タイトル改めたけど 昨日の続き。

@ zero-length matching

元よりdehydratedの正規表現がマヌケで

$ sed -e 's/=*$//g'

という行末にbase64のパディングある=が存在しなくても文字列置換が発生する、つまり「zero-length matching」ちゅーコーナーケースであることは 初回に説明しました。

前回現場をprocess.cのsubstitute関数内のswitch文でn = 0(global search)の部分という事は察しがついてます。

378         if (!regexec_e(re, s, 0, 0, psl))
379                 return (0);
380
381         SS.len = 0;                             /* Clean substitute space. */
382         slen = psl;
383         n = cp->u.s->n;
384         lastempty = 1;
385
386         switch (n) {
387         case 0:                                 /* Global */
388                 do {
389                         if (lastempty || match[0].rm_so != match[0].rm_eo) {
390                                 /* Locate start of replaced string. */
391                                 re_off = match[0].rm_so;
392                                 /* Copy leading retained string. */
393                                 cspace(&SS, s, re_off, APPEND);
394                                 /* Add in regular expression. */
395                                 regsub(&SS, s, cp->u.s->new);
396                         }
397
398                         /* Move past this match. */
399                         if (match[0].rm_so != match[0].rm_eo) {
400                                 s += match[0].rm_eo;
401                                 slen -= match[0].rm_eo;
402                                 lastempty = 0;
403                         } else {
404                                 if (match[0].rm_so == 0)
405                                         cspace(&SS,
406                                             s, match[0].rm_so + 1, APPEND);
407                                 else
408                                         cspace(&SS,
409                                             s + match[0].rm_so, 1, APPEND);
410                                 s += match[0].rm_so + 1;
411                                 slen -= match[0].rm_so + 1;
412                                 lastempty = 1;
413                         }
414                 } while (slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen));
415                 /* Copy trailing retained string. */
416                 if (slen > 0)
417                         cspace(&SS, s, slen, APPEND);
418                 break;

414行目の2度目のregexec_e関数の呼出がバグの元ということまでは前回までで判明しとりますが、このdo〜whileループの中にはどこにもbreakなり脱出コードが無いんですよな。 よってwhileの条件節に直前で現れる

slen > 0

の条件あるいは値自体がおかしいんじゃねという推測がまず立ちます。

実際にslenの値を確認してみましょ

$ gdb --quiet /usr/bin/sed
Reading symbols from /usr/bin/sed...done.
(gdb) b /usr/src/usr.bin/sed/process.c:413
Breakpoint 1 at 0x404fcf: file /usr/src/usr.bin/sed/process.c, line 413.
(gdb) r -e 's/=*$//g' test.txt
Starting program: /usr/bin/sed -e 's/=*$//g' test.txt

Breakpoint 1, substitute (cp=0x7f7ff7b0b050) at /usr/src/usr.bin/sed/process.c:414
414                     } while (slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen));
(gdb) p slen
$1 = 18446744073709551615
(gdb) p (ssize_t)slen
$2 = -1
(gdb)

うーんこの、クソ古いコードにありがちな size_t underflowですなぁ。

なのでとりあえず

--- process.c.orig      2017-05-03 18:38:13.000000000 +0900
+++ process.c   2017-05-03 18:38:31.000000000 +0900
@@ -411,7 +411,7 @@
                                slen -= match[0].rm_so + 1;
                                lastempty = 1;
                        }
-               } while (slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen));
+               } while ((ssize_t)slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen));
                /* Copy trailing retained string. */
                if (slen > 0)
                        cspace(&SS, s, slen, APPEND);

というパッチを適用してsedを作り直し、例の問題の正規表現を実行すると

$ echo -n 'A' |sed -e 's/=*$//g' | od -x
0000000     0041
0000002

はい、これで直ったっぽいね。

@ 他の*BSDでの対応

これで終わりにすんべと答え合わせ的にOpenBSDのコードを読んでみたんですが、この1行どころでなく辺り一帯を焼き尽くす修正やってますね。

というのも前回解説したようにsedの文字列置換の検索パターンには

  • s///g … global search、文字列の最後まで繰り返し検索して置換、Search & Destroy!
  • s///<number> … 先頭から<number>回まで検索して置換
  • s/// … 先頭から1回だけ検索して置換

の3パターンがあって、問題のコードに出てくる

386         switch (n) {
387         case 0:                                 /* Global */
...
419         default:                                /* Nth occurrence */
...
427         case 1:                                 /* 1st occurrence */
...

それぞれ対応するんですが、少なくともs///<number>のパターンでも2以上指定されたらs///gと同じバグ発生するはずだよね。 まぁでもコード読む限り

419         default:                                /* Nth occurrence */
420                 while (--n) {
421                         s += match[0].rm_eo;
422                         slen -= match[0].rm_eo;
423                         if (!regexec_e(re, s, REG_NOTBOL, 0, slen))
424                                 return (0);
425                 }
426                 /* FALLTHROUGH */

2回目のregexec_eで失敗してもreturn (0)してるしこっちは顕在化しない気がしないでもない。

$ echo -n 'A' |sed -e 's/=*$//2' | od -x
0000000     0041
0000001

というかしなかった。

まぁOpenBSDは潔癖症なので、そっちも修正してるうちに switch文ごと焼き払ってコードを簡略したっぽいね。

 Rewrite the main loop of the "sed s/..." command, shortening it by ten
 lines and simplifying it by removing the switch statement implementing
 /g, /1, and /2 separately and repetitively.
 The idea to make the loop control variable slen, i.e. the length of the
 string remaining to be processed, signed, and stay in the loop even when
 slen == 0 (i.e. at the end of the string), lifted from FreeBSD by otto@.
 On i386, process.o shrinks by 440 bytes, and the sed binary by 23 bytes.

 This fixes multiple aspects of the replacement of multiple (/g) or
 specific (e.g. /2) instances of zero-length matches, both with BREs and
 EREs, both with and without a trailing newline character on the input.

 Feedback and OK otto@.

process.oは440バイトsedは23バイト小さくなったぜ、ハハハという

まぁこういう全部書き直してやったぜハハハってのにはリスクがあるんですな、実際に リグレッションしてバックアウトする羽目になってます。

 Backout previous, naddy@ found the following regression:
 When the input does not end in a trailing newline character
 and there is an empty match at the end, the new code adds
 a spurious '\0' character.
 I have a fix, but otto@ prefers backout and full re-evaluation
 after release.

最終的な修正は これ

 Rewrite the main loop of the "sed s/..." command, to fix multiple
 issues regarding the replacement of zero-length strings.

 This commit brings back rev. 1.16, but without the regression that
 forced the backout:  No NUL bytes will be output now, not even when
 the input file lacks a trailing newline character and there is a
 zero-length match at the end.

 OK otto@ deraadt@;
 and naddy@ (who originally found the regression) checked that
 the regression is indeed fixed.

ということでワイのオレオレN6リポジトリにはOpenBSDでの修正をそのまま 取り込みました、テストされてる方がまぁ安心だからね。

[SCM] bitbucketの新WebUIがクソ重い

ワイはgithubはどうもあの草生やしたりするUIが余計なお世話で好きになれんからbitbucketを主に使ってるんよね。 ところが先日から新WebUIに切り替わってこれが重い、重すぎる十万石まんじゅうで困った。

サイドバー的なの表示がページロードして数秒経たないと終わらない、Chromeのデベロッパー(えの素っぽい)ツールでみるとDomContentsLoadで8秒弱、うーんこの。

さすがにそのうち改善するでしょ…するよね?

[Mobile Phone] Nexus 5でLTEが使えず3Gのままになる病

Nexus 5はイモバで0円購入後に2年縛り終わった後に解約、いやーほんとソフトバンク系って電波入ら以下略。 んで某MVNOのSIM入れて1年ほど使ってたんだけど、この連休入ったあたりで急にLTEを掴まずに3Gしか使えなくなって困った事に。

通信事業者はドコモの回線なので

のアンテナを掴むからAndroid上での表示は「JP DOCOMO|NTT DOCOMO」になってたはずなんだけど この3Gしか掴まない状態になってからは「NTT DOCOMO」しか出てこないのよね。

ということで

という割当になってんじゃね仮説を立てたんだけど、ネットに転がってる文章をみると

ということらしい、MVNO事業者にはFOMAプラスエリアだけ解放してる?無いわなそんなん。 そんでしばらくしたらまたJP DOCOMOもときどき現れるようになったので無関係っぽい。

どうもいろいろ検索するとLTE掴まなくなった時は

で直ったという体験談とそれを裏付ける 調査結果もあったんだけど、まったく改善しない感じ。

お高い機材無いとどこでコケてるのかデバッグできんみたいだし、Androidは金輪際買わんで対処するしか無さそうなのがにゃんとも。

新しいの買うといってもお金は無いわけで、NTT-Xで安売りしてる クッソ怪しいWindows Phoneにでもチャレンジ(T芝用語)するんですかね…

それにつけても金の欲しさよ。

2017/5/4(Thu)

[Mobile Phone] Nexus 5でLTEが使えず3Gのままになる病(原因判明)

相変わらず自宅だとまったくLTE掴まないんだけど、iPhone5sは問題ないしSIM交換しても同じ現象なのでこれはもう故障やね観念して新しい端末買う金をどないするか考えてたんだけど、街中だと場所によってはLTE掴んで通信できたのでこれはハード故障ではなく連休対策にドコモが何かやってんのか単純に社畜と学生が野に放たれたせいらしい。

そんで目の前にある箱でいろいろ調べた結果

という感じみたい、ということで連休終わって社畜と学生が収容されるまでは3Gしか繋がらないのを覚悟しとかんとならんようで…

北米版のNexus 5(D820)ならBand 19対応してて、そっちのNVRAMの内容を(検閲)みたいな話も転がっててほんま電波の世界は怖い近寄らんとこなのである。

2017/5/5(Fri)

[旅行] 城ヶ島から臨むダイヤモンド富士

MURもとい三浦半島というと YRPという数多の犠牲者を出し2038年ユネスコに「負の世界遺産」認定された(未来完了形)収容所施設があったり、チャンスで凡退守備でエラーそして四球祭りに飛翔炎上した選手のSNSに球場から京急安針塚駅までの経路案内が貼られるイメージですが、城ヶ島行ってきたよ。

城ヶ島には頻繁に年中訪れてるんですが、黄金週間は天気も良く空気も澄んでる上にちょうど富士山頂に太陽が沈むいわゆる「ダイヤモンド富士」が観られるのよね。

ピッタリと火口に陽が沈む5/3は曇りだったので、その前日と翌日の写真だけどこっちの方がまぁ味があるよね。

ダイヤモンド富士は年2回チャンスがあるんだけども、神奈川から臨む富士山の名所だと例年

の前後になるんですが、城ヶ島以外のスポットは3〜4月の春霞で条件の悪い時期な上にフツーの人は休日の問題があってなかなか難しいのでなぁ。

余談ですが(そもそも全てが余談だ)、ダイヤモンド富士がいつ見られるかをシミュレーションするには

というソフトの機能である「カシバード」が便利です。

地図が有償(年間サブスクリプション)なのですが、フリー基本セット + 基盤地図情報(標高)プラグインをインストールして国土地理院の地形図と標高データを入れれば地図買わなくてもいけます、ただし

ちゅー感じなのでとっとと金払った方がマシです(二度とやりたくないと思った)。

国民の休日.csvの騒動よりかはマシなんだけど、XMLデータなんだしgitでもなんでもいいからSCM的なもので配布して欲しいよね…おそらく手作業でXMLをzipに固めるだけの仕事のオッサンとかいるんだぜ絶対(ぉ

年間サブスクリプションより安く済ませたいなら、同じ作者さんがカシミール3Dをクラウド+iOSアプリにした

でも充分かな、こっちは全機能をアンロックするのに1回課金すれば終わり。

またカシミール3Dでは使えないGoogle Mapも使えるのが便利なのでなぁ、これ昔プラグインあったんだけどライセンス的な問題で削られちゃったんだよな、アプリ内課金なら問題ないのかな。

あとカシミール3Dはあくまで地形の標高データを基にしているので建物などの障害物が判らんのだけど

なんかを使うとストリートビューの画像に太陽や月の軌跡と現在の位置をオーバーレイしてくれるので、ロケハンに便利。

ストリートビュー通ってない場所は使えないけど、山とか明確な目標物が無くてもシミュレーションできるからね、まぁちょっとお値段が高いのですけども。

馬ノ背洞門から登る月を撮影したいなんて場合もシミュレーションできます(というかここまでストリートビュー通ってるんだ…)。

同日同時間の写真は(スマホで雑に撮った写真でアレだけど)これ、ちょっと立ち位置の関係で月が高いけどだいたいイメージ通りになるよね。

2017/5/9(Tue)

[Windows][PowerShell] 国土地理院 基盤地図情報(数値標高モデル) ダウンローダー

先日の記事でカシミール3Dにちょっと興味を持った人でも、国土地理院の 基盤地図情報ダウンロードサービスのWebインタフェース無残なデキで(これでも以前より若干マシにはなった)、日本国全土をダウンロードするのはちょっと人力じゃ厳しい件、自分がでっち上げたスクレイピング用のPowerShellスクリプトをちょっと整理して 公開しといたよ。

とりあえずそろそろPowerShellには怒りしか感じなくなってきたゾ、誰だこんな車輪の再発明といいながらタイヤが四角形の車を作ったピーは。

このスクリプトのご利用に関しては、日本は蛮国ですので Librahack事件のようにサイトに負荷をかけたとポリスメンが乗り込んでくる可能性もあるので以下略、いちお負荷かけないように複数のセッション張ってパラレルでダウンロードとかしてないから大丈夫だとは思いたい。

リニューアルの前からセッション管理周りに深い闇抱えてる感じの挙動を示すのでアレなんだよな、このサイト。

どうも検索条件設定画面で選択した2次メッシュは未ログイン状態でもサーバ側でステートフルに持ってるようで、ダウンロードファイルリスト画面に遷移すると前回選択分まで表示されてどんどん膨れ上がっていくちゅー、限りなくバグに近い挙動をする。

対策としては

なんだけど、前者やるとサーバー側はタイムアウトまでセッション残るだろうし、後者は ファイルダウンロード時に必要なパラメーターの選択リストIDという項目はそれでもリセットされずにどんどんインクリメントされていくしマジでアレ。

なのでログインしなくても悪意のあるクライアントが大量のコネクションを貼るだけでサーバがアレしそうな気がする、というか同じユーザIDで複数のセッション貼れる時点で以下略でもあるんだが。

あとダウンロードファイルのチェックサムみたいなあってしかるべきものは無いので、KB単位に切り上げたサイズだけでチェックしてるので以下略

とりま国土地理院におかれましてはただのXMLデータなんだしgithubとかで公開してくれんもんですかね、それともわざとダウンロードしにくくしないとならん理由があるんだろうか。

2017/5/10(Wed)

[GIS] 続・国土地理院 基盤地図情報

なんだgithubのアカウントあるやんけ

なのになんであのお察しくださいなダウンロードサービスを作ってしまったのか…

まーあそこで落とせるデータ普通に再配布OKなんで、例えばワイがbitbucketなりにリポジトリ作って配布しようが構わんちゅうのはあるけど。

昔仕事でちょっとOpenLayers/GeoExtとMapServer + PostGISそしてPL/R使ってとある業界向け営業支援のGISシステム作ったことあるけど完全に忘却の彼方でアレやな

[NetBSD][pkgsrc] py27-certbot crash

うーむ、certbot(=python2.7)がクラッシュするのpy27-cryptographyから呼ばれるlibsslの中でか

#0  0x00007f7ff7fd2fc0 in ?? ()
#1  0x00007f7ff1222e51 in ssl_check_clienthello_tlsext_early () from /usr/lib/libssl.so.10
#2  0x00007f7ff122e2e3 in ssl3_get_client_hello () from /usr/lib/libssl.so.10
#3  0x00007f7ff1232abe in ssl3_accept () from /usr/lib/libssl.so.10
#4  0x00007f7ff1239783 in ssl23_accept () from /usr/lib/libssl.so.10
#5  0x00007f7fee89c920 in _cffi_f_SSL_do_handshake (self=0x7f7ff398df18, arg0=0x7f7feff263b0)
    at build/temp.netbsd-6.1_STABLE-amd64-2.7/_openssl.c:50593
#6  0x00007f7ff7725cd8 in call_function (pp_stack=0x7f7feb7fd3b8, oparg=1) at Python/ceval.c:4340
#7  0x00007f7ff772207f in PyEval_EvalFrameEx (f=0x7f7fefbba430, throwflag=0) at Python/ceval.c:2989
#8  0x00007f7ff772621e in fast_function (func=0x7f7ff318a500, pp_stack=0x7f7feb7fd848, n=1, na=1, nk=0)
    at Python/ceval.c:4437
(中略)
#32 0x00007f7ff7680fc6 in function_call (func=0x7f7ff5587668, arg=0x7f7ff397d310, kw=0x0) at Objects/funcobject.c:523
#33 0x00007f7ff764e02b in PyObject_Call (func=0x7f7ff5587668, arg=0x7f7ff397d310, kw=0x0) at Objects/abstract.c:2547
#34 0x00007f7ff766378f in instancemethod_call (func=0x7f7ff5587668, arg=0x7f7ff397d310, kw=0x0) at Objects/classobject.c:2602
#35 0x00007f7ff764e02b in PyObject_Call (func=0x7f7ff0b34780, arg=0x7f7ff7b12050, kw=0x0) at Objects/abstract.c:2547
#36 0x00007f7ff77256de in PyEval_CallObjectWithKeywords (func=0x7f7ff0b34780, arg=0x7f7ff7b12050, kw=0x0)
    at Python/ceval.c:4221
#37 0x00007f7ff776ab9d in t_bootstrap (boot_raw=0x7f7ff21f81f0) at ./Modules/threadmodule.c:620
#38 0x00007f7ff6a0df8e in pthread__create_tramp (cookie=0x7f7feb400000) at /usr/src/lib/libpthread/pthread.c:501
#39 0x00007f7ff6697300 in ___lwp_park50 () from /usr/lib/libc.so.12
Cannot access memory at address 0x7f7feb800000

このPRと似てるんだけどこっちはconnectの最中で死んでるけど、N6はacceptの中で微妙に違う。

さーてどうしようかね…

[SCM] git難しい

P/ 久し振りにgitで大災害起した、間違えて大量にconflict出てるcvs co結果とgit subtree mergeの失敗をそのままcommitしてしまい、修正するのめんどくさくなって どうせ自分が使ってるだけだからと変更前に履歴を巻き戻してpush --forceしたんだけど、どっかで操作失敗したようでその枝にあった修正が全部消えて大草原。

久し振りにreflogのお世話になってしまったksg

2017/5/11(Thu)

[NetBSD] BSD sed(1) fix

@ BSD sed(1) a/i/c command bug

こないだのバグ以外にも何かあるやろ〜と例のアレ以外のBSD sed(1)のソースを読んでてcompile.cの履歴に

  • FreeBSD (差分)
    Fix from Keith Bostic <bostic@bsdi.com> for bug in sed dealing with
    continuation lines.
    
    Submitted by:	Keith Bostic via Kirk McKusick
    
  • OpenBSD (差分)
    fix for a line continuation bug, more than a year ald. work by mckusick,
    bostic, mark@linus.demon.co.uk, davidg, and bde.
    

というのを見つけたのよね、魔球先生からボス経由での報告だけど何でNだけ20年間忘れられてるんすかね。

可能性としては

  • 黒ヤギさん(クジラ偶蹄目ウシ科ヤギ属)はお手紙を読まずに食べた
  • ハブ(爬虫綱有鱗目クサリヘビ科ハブ属)られた

のいずれかだった可能性だろけ、ああ…これFだけに報告行ってOは拾ってきたパティーンかな。

とりあえずはオレオレN6には入れておこうかとまず現象を確認するべくテストケース…

なーんも
 あ
 りま
   せんで
   し
   た。

うーん20年前という時代のせいか

  • チケットねぇ
  • テストもねぇ
  • barさんとfooさんと数珠を握って空拝む

俺らこんな村〜なのでどんなバグなのかはソースから読み取るしか無いんだけど、どうやらsedのコマンドである

  • <位置>a\\\n<文字列> … <位置>の次行に<文字列>を追加
  • <位置>c\\\n<文字列> … <位置>の行を<文字列に>置換
  • <位置>i\\\n<文字列> … <位置>の行に<文字列を>追加

の部分に問題があり、<文字列>の中に「\\\n」による行継続が含まれてる場合のコーナーケースって感じ。

とりあえずソースみながら最小ケース作ってみたけど

$ cat >test.txt
aaa\^D^D
$ cat >test.sed
c\
bbb\^D^D
$ sed -f test.sed test.txt

みたいな感じ、Nだと最後の\の後に改行が無くても勝手に行継続と判断して

bbb

になってしまうんだけど、FやOはちゃんと(?)

bbb\

になる。

んー微妙過ぎてマージするか迷うレベル、もっとパッチの必要性がわかり哲也なケース思いつかんぞ…

ワイ、テキスト処理は即Perlのようなものでカネカネキンコしてしまうのでsed(1)力が低くバグの内容を理解するのに時間かかってしゃーない。

つーかGNU sedもダメっぽいんですが。

$ gsed --version
gsed (GNU sed) 4.4
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Jay Fenlason, Tom Lord, Ken Pizzini,
and Paolo Bonzini.
GNU sed home page: <http://www.gnu.org/software/sed/>.
General help using GNU software: <http://www.gnu.org/gethelp/>.
E-mail bug reports to: <bug-sed@gnu.org>.
$ gsed 'c\
> bbb\' test.txt
bbb
$

これ珍POSIX *1的にはどうなるんだろうな…

@ BSD sed(1) y command 8bit clean bug

もういっこ別のバグ、こっちも20年モノ。

  • FreeBSD (差分)
    Make 'y' command 8bit clean
    PR: 6458
    
  • OpenBSD (差分)
    make y command 8 bit clean; Elmar.Bartel@informatik.tu-muenchen.de
    

この修正を取り込んでLANG=ja_JP.SJISで半角カナ扱えるまで確認したけど、ほんとはBSD sed(1)もマルチバイト対応とか必要で8bit cleanとか前世紀級の修正してる場合じゃないんだけどNE。

ところで突然関係ない話挟み込むけど、最近驚いたのGoogle Chromeって半角カナの扱いって

  • カーソル移動でカナと(半)濁点の間にキャレットが移動できない(内部的にリガチャ的な扱いになってる?)
  • なのでIMEで「パ」と入力しF7キーで半角カナに変換し、「ハ」を削って「゜」残すのができない
  • 「パ」の前にキャレット移動してDelete押しても「ハ」「゜」2文字いっぺんに消える
  • 「パ」の後にキャレット移動してBackspace押すと「゜」だけ消えて「ハ」が残る

のにビックリした。

まぁそんな話は置いておいて、マルチバイトの文字置換もできるそうGNU sedならね(タートルネック感)

$ echo 'あいうえお' | gsed 'y/あ/ア/'
アいうえお

まぁyコマンド程度ならこっちもすぐ対応できるけど、他のコマンドともなるとそもそもlibc regex(3)がアレなのでなー

そういえば昔のGSoCでmultibyte regex(3)をスクラッチで書くぜと採択されて、 結局 こんな難しいとは…と他人の書いた実装(tre)マージしただけで終わったやつ、結局はagrep(1)が使ってるだけなんやな。

sed(1)をtreのregex(3)互換レイヤだけで対応できるんかなぁ、treは内部的にwchar_tモデルっぽいけども(いちおうmbtowcが見えた)。 それならこれまでのコードの互換性から考えて、Spencer regex(3)をワイド化したnvi-1.81のアレをlibcに持ってくる方が苦労は少ない気がしないでもない。

@ BSD sed(1) treats semicolon as part of label (hi, one-liner!)

もういっちょ、sed(1)で改行全部取っ払って一行に連結するのに

$ cat >test.txt
aaa
bbb
^D
$ sed -e '
:loop
N
$!b loop
s/\n//
' test.txt
aaabbb

とかラベル(ドビュッシーではない)やるのをGNU sedだとワンライナーで書けるように

$ sed -e ':loop;N;$!b loop;s/\n//' test.txt

と蝉コロンをデリミタに使えるんだけど、BSD sedは対応しとらんので

$ sed -e ':loop;N;$!b loop;s/\n//' test.txt
sed: 1: ":loop;N;$!b loop;s/\n//": unused label 'loop;N;$!b loop;s/\n//'
aaa
bbb

となってしまうのを、OpenBSDは律儀に 直してたようなのでそれもマージ。

ワイはそもそもワンライナーで実行せずスクリプト書く人なのでな…

*1:珍はPOSIXの枕詞であり語調を整えたり情緒を添える言葉で意味はありません、なお20年動くプログラムとは20年壊れたままなのである。

誤解

億年ぶりにはてブに※ついてる思ったら

ものすごい誤解だ、釣りだったらスマン

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のアルバムタイトルにもなってるね。

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/20(Sat)

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

@ 古代遺跡で廃品回収

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

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

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

  • 入力は-fで指定されたファイルだけでなく-eで指定された文字列もある
  • これらの入力を-e/-fで指定された数だけ全部、順々にシームレスに処理していきたい

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

/*
 * 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);

のように

  • 文字列などのメモリオブジェクトに対してFILEの皮を被せて
  • stdioの関数を使って操作できる
  • もうポインタとダンスする必要はなく両者共通のコードで処理できる

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

そして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/21(Sun)

debugging BSD sed(1) (その6)

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

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

  • a\(append)
  • i\(insert)
  • c\(change)

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

現在の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)せずに使い回した方が性能面で好ましいとは思うが。

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

  • textの指すメモリのアロケーション周りのコードが冗長、オーバーフローチェックとか境界値の計算とかミスりやすく死ゾ
  • 以前に記事にした20年モノのバグ修正絡みのesc_nlフラグを使った文字列のNUL terminateまわりのコードがこちらも冗長、やはりミスるとオーバーランでやはり死ゾ

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

これも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
$

ちゅーかんじでそもそも

  • 問題の改行を伴なわないエスケープ文字は表示されない → わかる
  • 2行以上あると最終行以外の本来出力されてはならないエスケープ文字が出力されてしまう →バグだろこれ

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

$ 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/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/23(Tue)

[音楽] The Smiths/Suffer Little Children

もう30年以上昔だけどイギリスのThe Smithsというバンドが「Suffer Little Children」という曲を書いた *1

この曲はかつてマンチェスターを震撼させた 連続殺人事件の被害者の少年少女について書かれたもので、犯人どころか被害者の実名まで登場する歌詞は悪趣味だと発表当初は大いに批判された。

この事件が起きたのはその当時からでも20年も前の話で今となっては半世紀も昔の事件なのだけども、数日前に犯人の最後の一人 イアン・ブレディが獄中死したとのニュースが流れ、そしてまだ存命である被害者の遺族、そして危うく殺されかけた生存者からのコメントが報道され、ああまだ終わっていない事件だったんだなぁと。

しょーもないSNSに毒された現代では大量殺人事件だろうが3日も経てば新しいニュースに埋もれ忘れ去ってしまうもんだけど、命を奪われた悲しみなんてのは当事者ならば50年どころの話でない苦しみなんだなという事に改めて気づかされた。

そういえばこの曲のサビ(といってもこの曲はサビらしいサビ無いんだけど)の歌詞には

Oh Manchester, So much to answer for

というフレーズが繰り返される部分がある、日本盤の歌詞カードではうろ覚えだけど

マンチェスターよ、お前は償うべきことが多過ぎる

という訳だったかと記憶している。

この翻訳について「なぜ犯人達でなくなぜマンチェスターが擬人化して責められるのか」という部分にひっかかりを感じて

おお(一部始終を見ていただろう)マンチェスター、お前は(事件の真実について)答えなければならないことが多過ぎる

のように「answer for」を「〜の責めを負う」でなく「〜について説明をする」と解釈してる人もみられる。

確かに歌詞の前後には未だ発見されない被害者がムーアの沼地から見つけてくれと呼びかけるセリフがあったり、この解釈も興味深い。

しかしマンチェスターが償わなければならないという解釈もそれはそれで妥当な気もしている、そのあたりの歴史についてはまたいずれ何か書こうかと思う。

そしてこの歌詞を書いた、今やイギリス史上最高の 皮肉屋詩人の一人として称賛されるまでになったThe SmithsのボーカルMorrisseyの誕生日である5月22日に何たる偶然か新たな悲劇が起きた、 自爆テロで22名が死亡、59名が負傷という痛ましいニュースだ。

この事件による被害者たちの苦しみはこれからまた50年以上は続くのだろう、今日はこの曲を聴いて寝る。

神による救いを自爆テロで得ようとする男の歌である、何という皮肉。

*1:この曲のタイトルで検索したら漫画「絶対可憐チルドレン」のエロ同人がヒットしたんですが、日本はこの手の検索汚染が酷過ぎませんかね…

2017/5/24(Wed)

[NetBSD] debugging sed(1) (その7)

先日のa\i\c\コマンドの実装、OpenGroupの sed仕様とかGNU sedの infoを読みながらどうしたら もっとも限りなく正解に近いCカップを実装できるか考えてラーラララーララ歌ってたんだけど、余計な昔語り書いたことでまたやる気が消し飛んだ。なので気分転換に他の簡単な作業をやってリハビリする。

ところでBSD sed(1)では正規表現中の文字クラスについて、libc regex(3)との差異を消し込む処理がcompile_ccl()という関数で実装されている。 この部分についてN HEADでは 前回も書いたけどchristosがおそらくfree BSDから持ってきたと思われるコードでまるっと置き換えたせいでデグっとるように見受けられる。

@@ -452,40 +443,32 @@ compile_ccl(char **sp, char *t)
 			for (c = *s; (*t = *s) != ']' || c != d; s++, t++)
 				if ((c = *s) == '\0')
 					return NULL;
-		} else if (*s == '\\' && s[1] == 'n')
-			    *t = '\n', s++;
+		}
 	return (*s == ']') ? *sp = ++s, ++t : NULL;
 }
 
...

この消えたコードはsedに渡された正規表現中に"\n"という文字列出現した場合、libc regex(3)が解釈できるように改行コード(通常は0xa)に置換するというコードなんだけど、そもそもPOSIX仕様においては

The special characters '.', '*', '[', and '\\' ( <period>, <asterisk>, <left-square-bracket>, and <backslash>, respectively)
shall lose their special meaning within a bracket expression.

とあって

よってこれまでのバグが修正されたといっても過言ではないんだな。

N6でのテスト結果

$ echo n | sed -e '/[\n]/c\
> unko'
n
$

はい[\n]は改行コードと解釈されてるので入力のnにマッチしてないね。

同様のコードをいまのN HEADと同じコードと思われるFreeBSD 11で試すと

$ echo n | sed -e '/[\n]/c\
> unko'
unko
$

となって正しくnとマッチしてc\(change)が実行されました。

しかしですな、このN6までの挙動って由緒正しき伝統的なsed動作であって、UNIX v7 sedの ソースにおいても

		switch (c) {

		case '\\':
...
			if(c == 'n') {
				c = '\n';
			}
			goto defchar;

と特別扱いしてるんですよな、というか関係ないけど Ancient UNIXで参照できるソースコードいつの間にか増えてませんか…

そもそもPOSIX(Portable Operation System Interface [for uniX])やSUS(Single Unix Spec)ってのは、XPG(X/Open Portability Guide)から発展したものでこいつらは

の違いをまとめた移植性ガイド、よって両者共に違うってことはPOSIX自体の間違いなんだよね。

そして実装も普通は後方互換性を考えて挙動は変更しないんだけどね、この手の作業は過去の歴史的経緯を調べないヤツにやらせちゃダメよ。

ちなみにGNU sedも

$ echo n | sed -e '/[\n]/c\
> unko'
n
$

と伝統的なUNIX v7と同じ動作をする、そしてGNU sedのinfoの エスケープシーケンスの部分を読むと

 The list of these escapes is:
 
 \a
 	Produces or matches a BEL character, that is an “alert” (ASCII 7).
 
 \f
 	Produces or matches a form feed (ASCII 12).
 
 \n
 	Produces or matches a newline (ASCII 10).
 
 \r
 	Produces or matches a carriage return (ASCII 13).
 
 \t
 	Produces or matches a horizontal tab (ASCII 9).
 
 \v
 	Produces or matches a so called “vertical tab” (ASCII 11).
 
 \cx
 	Produces or matches CONTROL-x, where x is any character. The precise effect of ‘\cx’ is as follows: if x is a lower case letter, it is converted to upper case. Then bit 6 of the character (hex 40) is inverted. Thus ‘\cz’ becomes hex 1A, but ‘\c{’ becomes hex 3B, while ‘\c;’ becomes hex 7B.
 
 \dxxx
 	Produces or matches a character whose decimal ASCII value is xxx.
 
 \oxxx
 	Produces or matches a character whose octal ASCII value is xxx.
 
 \xxx
 	Produces or matches a character whose hexadecimal ASCII value is xx.
 
 ‘\b’ (backspace) was omitted because of the conflict with the existing “word boundary” meaning.

イッパイアルネー、わあい(しろめ)。

これらのエスケープは環境変数POSIXLY_COLLECTあるいは--posixオプションをつけることで回避できるそうな。

余談だけど、このPOSIXLY_COLLECTという環境変数はGNUプロダクツ全般に使われてて

    Many GNU programs suppress extensions that conflict with POSIX if the
 environment variable 'POSIXLY_CORRECT' is defined (even if it is defined
 with a null value).  Please make your program recognize this variable if
 appropriate.

と、POSIX自身にバグがあるとかGNUの特殊仕様との整合性がアレな場合、こいつで動作を切替することができるよという。

さっきも書いたけどXPGはそもそもSysVとBSDの為のもので、GNUの挙動なんてのは蚊帳の外でケアされてなかったんだよね、SysVもBSDももはや完全に死んでLinuxがデファクトである時代からすると隔世の感があるけども。

この辺の顛末は 笛吹き髭おじさんのインタビューあたりから辿ってどうぞ、昔はPOSIX_ME_HARDER(私をもっと強くPOSIXにして)というクソ皮肉な名前だったのには草生えますよ。

ちなみにNおいても、この手の明らかにPOSIX仕様自体がアレだったケースについて、POSIX_MISTAKEというのがありますが これは環境変数ではなくifdef用のプリプロセッサマクロですし、もう現存してるとこってlibc regexくらいだと思う。

ということでオレオレN6は伝統的な動作をデフォルトにする方がよさげ、あと\n以外のGNU拡張のエスケープは後方互換考えるとNGなんだけど どうせ世の中GNU sedがデファクトやろうし、どうせPOSIXLY_CORRECT実装すんなら入れちまえって気にもなってきた。

2017/5/25(Thu)

[音楽][映画] Talking Heads/Once In A Lifetime

先月の話に今更気づいたんだけど ジョナサン・デミ監督死去、一般的には「羊たちの沈黙」が有名だけど、自分はやっぱりこっちの映画「 ストップ・メイキング・センス」だな。

この曲のPVも傑作なのよね、デヴィッド・バーンのスーツ姿と痙攣ダンスは、自分がスーツ買う時かならず試着室で真似している。

歌詞もとてもいい、空の間違った側を飛び続けて墜落した男はやがて水の渦に飲まれて消えるのである。

日々をやり過ごせ
水の流れに身を任せよう
日々をやり過ごせ
やがて水は低きへと流れ
いつか跡形もなく消える
気がつくと財布はスッカラカン
人生はたった一度きり
覆水は盆には還らない

2017/5/26(Fri)

[映画][音楽] Grant Gee/Joy Division

これはマンチェスター出身のバンド、ジョイ・ディヴィジョンに起きた悲劇を追ったドキュメンタリー映画だ、自分はおよそ10年前(もうそんなに経つのか…)に渋谷の小劇場で上映かかったのをギリギリ最終日に観た。

彼らを襲った悲劇、それは成功を目前とした初のアメリカ・ツアーに出発の前夜に起きた。メンバーのイアン・カーティス(Vocal)は自宅で首吊り自殺を図り、その死によってバンドはそのまま終焉を迎えた。

この映画の後日談となる話だが、残されたメンバー達は生活の為に演奏を続ける他になく新しいバンドの名前もまだ決まらぬままステージで

僕の友達は今日来れませんでした、ここに居るのは這い回る混乱からの唯一の生き残りです(Our mates couldn't make it, we're the only surviving members of Crawling Chaos)

この悲劇をクトゥルフの邪神の渾名に例え、そしてイアンが残していった曲を歌った。

残った中で一番歌がマシなバーナード・サムナー(Guitar)がVocalも兼任することになったが音程とくにライブは壊滅的で、新バンドの最初の10年は「史上最も音痴なバンド」とまで称される事になる。

監督のグラント・ジーという人はU2やレディオヘッド(彼らも熱心なジョイ・ディヴィジョンのファンだ)のPVなどを手掛けた映像作家で、インタビュー場面ですらも気を抜かず映像と音響のクオリティは高い。バンドが活動していた70年代から80年代にかけてのマンチェスターという街を記録した8mmフィルムの映像を集めそして丹念に繋ぎ、消えた風景ををスクリーン上に再構築している。

ちなみにインタビューの内容そのものはこの前年に撮影された別の監督によるドキュメンタリー「 Shadowplayers」の方がより詳細な内容なんだけど、なにせ関係者の自宅でホームビデオ回しただけの体裁であってマニアにしかお勧めはできない。

同年にはやはり同じ悲劇を題材とした映画「 Control」が上映されている。

監督はアントン・コービン(こちらもやはりU2のPVで有名な映像作家で、これまでもジョイ・ディヴィジョンのPVも手掛けている)、面白いことに興行的リスクもあっただろうにこの映画をカラーでなくモノクロで撮影した。その理由として彼の記憶しているマンチェスターはモノクロだったからと述べている。また再現ドラマなので失われてしまった風景はよく似た別の場所で撮影されてるし、ドラマとしての脚色もある。ピーター・フック(Bass)曰く「95%事実だ」そうだ。

イギリス人であり自分も同時代を同じ場所で過ごしたグラント・ジー監督に対して、アントン・コービン監督はオランダ人の異邦人で写真家としてモノクロ写真のフレームに彼らを収めてきたの視点の違いがそこにはある、比べて鑑賞するとそこも面白い。

彼らバンドの歴史の中で、イアン・カーティス、そしてマネージャーのロブ・グレットン、プロデューサーのマーティン・ハネットといった仲間達を失ったように *1、マンチェスターという街もまた数多くの風景を失なった。

ドキュメンタリーの冒頭、スティーヴン・モリス(Drums)がマンチェスターについて「レンガ積みの住宅街がやがて瓦礫の山となり、コンクリートの要塞が作られ未来的に思えたけども、やがてそれもコンクリートが腐り醜悪になった」と語る場面、ここで映し出される映像はイアン・カーティスの実家であるビクトリア・パーク・エステートという集合住宅である。

Victoria Park Estate, Macclesfield

Victoria Park Estate, Macclesfield

ここもすでに取り壊された過去の風景であり、映画「コントロール」のオープニングは別の場所でロケしている *2

マンチェスターの建築家団体らが設計したこのレトロ近未来的な建物は、60〜70年代のいわゆる プレハブ工法による巨大な集合住宅であり、マックルズフィールドという衰退した紡績の街に立ち並ぶ旧来のレンガ積みの低層住宅の中では異形の存在だったことは想像に難くない。

今の日本も不況による製造業の空洞化で、海外に逃げ出した工場の跡地にグロテスクなタワーマンションが雨後の筍のように建っている真っ最中。そして自分が育った街も日本の高度経済成長の最中に全国で判で押したようなデザインで作られたプレハブな集合住宅だから強いデジャヴュを感じるシーン。

それ以外にも数多くの「すでにない場所」がこのドキュメンタリーには記録されている。彼らが初めてライブをしたライブハウス「エレクトリック・サーカス」、デヴィッド・ボウイの曲からバンド名をとり「ワルシャワ」の名前で参加し、ネオナチバンドと危険視されるような発言をステージ上で飛ばし、時にはスキンヘッドとの乱闘にまで発展した。

そして自主制作のデビューシングル(音は最悪だ)を持ち込んだレコード店「ピップ」、そしてギグの依頼も無いままひたすら練習と曲を書き続けた練習スペース(彼らの代表曲ラブ・ウィル・テア・アス・アパートにも登場する)「リハーサルルーム」、同じパンクムーブメントのバンドでしのぎを削ったライブハウス「ラフターズ」などが次々と画面の登場する。

最も印象的なのは自主制作8mmフィルム「 No City Fun」の映像だろう、イベントで一度だけ流されただけでこれまで一度も流通に乗ったことはないようで、私も観たのは初めてだ。

このフィルムは同人誌「 City Fun」に寄せられた、パンクという音楽ムーブメントとマンチェスターという街の関係性について書かれた記事を元に、ジョイ・ディヴィジョンの曲をバックにマンチェスターの街並みの映像のコラージュが映し出される。

イギリスの産業革命はマンチェスターからはじまった、そして紡績産業が栄えたもののやがて不況で衰退してしまった。マンチェスターの名前が最後に新聞に載ったのは、1963年から1965年にかけて全英を恐怖に陥れた ムーアズ殺人事件くらいと揶揄されるくらいである( 先日の記事でも書いた)。

この事件の犯人のひとりで先日獄中死したイアン・ブレイディは文学趣味をきっかけにネオナチ思想に染まったという、これは不況下のマンチェスターでは珍しい話では無かったようだ。日本のインターネットでもちょいと石を投げればネットウヨサヨに当たるみたいなもんだ。

そもそもジョイ・ディヴィジョンの連中もバンド名からしてナチスの慰安施設を指す隠語だし、初めて作った自主制作レコード( An Ideal for Living)のジャケットはヒトラーユーゲントだし、一曲目「Warsaw」のオープニングの乱数カウントダウンはルドルフ・ヘスの囚人番号だという。

なのでモリッシーが(元相棒のジョニー・マーとは対照的に)インタビューで同郷のジョイ・ディヴィジョンに関する質問には、徹底して音楽的無関心と人格への不快感を示したのはこの初期のネオナチ趣味のせいだろうね。

またブレイディともうひとりの犯人ヒンドリーの屈折の原因としては幼少期の家庭内における虐待が挙げられるが、モリッシーは「残忍性は家庭で植え付けられる(Barbarism Begins At Home)」という曲を書いてるように、この凶行の原因をマンチェスターという環境に求めるだろうことは想像に難くない。

マンチェスターの不況と停滞、そして住人の心の荒廃という環境因子があったからこそ「Suffer Little Children」の歌詞で

マンチェスターよ、お前は償うべきことが多過ぎる(Oh Manchester, So much to answer for)

とモリッシーは歌ったのではないか。

そもそもイアン・カーティスが死を選んだのも、悪化する一方の持病のてんかん発作でバンド活動が困難になった事や経済的な苦境と結婚生活の冷え込みから自分の将来に悲観した為といわれている。

彼が最後にTVで観た番組はヴェルナー・ヘルツォークの映画「 シュトロツェクの不思議な旅」で、ストーリーは大道芸人がアメリカへ渡るも一文無しになり銀行強盗も失敗し自殺するという陰鬱な映画だ。 明日からアメリカツアーに出かける自分の姿の将来もこの大道芸人のような失敗になるのではないか?

そして彼はイギー・ポップのアルバム「イディオット(愚か者)」を流しながら縊死した、彼の死にマンチェスターは償うべきことはなかったのか?

話を戻そう、産業革命と連続殺人事件に続いて再びマンチェスターの名前を世界に知らしめることになったのは、このパンクを着火点として90年代に頂点を迎えた「マッドチェスター」「セカンド・サマー・オブ・ラブ」などと呼ばれる音楽×ドラッグの一大ムーブメントであり、前述の「City Fun」の記事はその未来を予見するものだった。

新しいバンドはイアンの死んだ翌日の陰鬱な気持ちをよりによって能天気なディスコビートに合わせて歌う「Blue Monday」の世界的なヒットで成功を手中にした。

そしてその稼ぎを全て注ぎ込んでマンチェスターが世界の音楽の中心となるきっかけの一つになったクラブ・ハシエンダの運営をはじめる、それはもうデタラメとしか言いようのないほどの放漫経営で。

その顛末は映画「 24 Hour Party People」が詳しいので省略するが、それは清々しいくらいの笑える破綻劇である。

ハシエンダはこの映画の撮影中に再開発業者によって取り壊され(解体工事の写真はニュー・オーダーを脱退したピーター・フックのEP「 1102|2011」のジャケットとして使われている)跡地は高級アパートメントになっている。

どんなムーブメントにも終わりがある、しかしその熱が去った後もマンチェスターは「音楽の街」として世界的な知名度を保ったままであり、もはやビートルズのリバプール、ストーンズのロンドンの陰に隠れることは無い。ハシエンダ閉店と入れ替るようにオープンしたマンチェスター・アリーナは2000年代において「最も忙しいコンサート会場」と言われるほどになった。

しかし今週まさにそのマンチェスター・アリーナを狙いムーアズ殺人事件すら霞んでしまうような大量殺人事件が発生し、再び世界中の新聞がマンチェスターの文字で埋まってしまった。

実行犯は両親はリビア移民の両親とマンチェスターで生まれ育った価値観の相違による軋轢と大学生活からの落伍によって過激な宗教思想へ傾倒と報道されている。 半世紀経ってもまた同じ理由だ。

またしてもマンチェスター、いや世界は償うべきことが多過ぎる。

*1:そしてこのドキュメンタリーから10年も経ち、インタビューに答えるレーベル経営者のトニー・ウィルソンとイアンの彼女アニーク・オノレも故人となってしまっている。
*2:劇中ではアビー・コートという名前になってるけどこれはロケ地そのままなのかな

2017/5/28(Sun)

[音楽] King Crimson/Heroes

おお、新生King CrimsonによるDavid Bowie/Heroesのカバーとは…

David BowieのライブだとギターのEarl Slickが EBow(日本だとE-Bow表記が多いかな)というギズモを使ってあの音の滝のようなフィードバック演奏を再現してるんだけど、やっぱりRobert Fripp本人が演奏するとアルバムと完全に同じ音が出るなぁ *1

この曲におけるRobert Frippのギター演奏については、プロデューサーのTonny Viscontiがマルチトラックを操作しながら解説する BBC ARTSの動画が詳しいです *2、あのフィードバックノイズは

という職人技なんだよね、ギターソロトラックだけ抜き出して聴かせてくれるの本当にありがたい。

ちなみに英語が不得手な方は、文章だけですがほぼ同じ内容のインタビューがリットーミュージック「サウンド & レコーディング・マガジン」の 【デヴィッド・ボウイ追悼】名曲「HEROES」の制作秘話で読めます。

あと先日これまでStation To Stationの限定ボックスに入ってた Live Nassau Coliseum '76の名演が独立したCDとしてリリースされたけど、今度は Live Los Angels '74がリリースされるのか、めでたい。

まぁ昔から同年のフィラデルフィア公演がアルバム「David Live」としてリリースされててセトリもほとんど一緒なんだけどね。とはいえマニア的には

という違いはあって、当時製作中だったものの結局お蔵入りにしたアルバム「The Gouster」に収録される予定だった曲の「It's Gonna Be Me」が聴けるあたりがポイントかな。 曲自体はRykodiscから再発されてた「Young Americans」のボーナストラックに入ってたし、先日出たボックスセット「Who Can I Be Now? (1974-1976)」でもリマスターされてはいるけど。

追悼商法という面もあるけど、これまでのRCA時代のボウイの音源ってEMIへ移管された後ほんとに酷い扱いだったことを考えるとほんと今はいい時期である、買う金ないのが問題だが(しろめ)。

ちなみにEMIのやらかしたクソな所業は覚えてる限りでこんだけある。

そらまぁジョニー・ロットンも「拝啓EMI殿」歌いますわな、こんな状態じゃEMIの経営は傾くのも当然だし「あの東芝」ですら提携止めて逃げ出したレベル。

その後EMIは金融危機やら詐欺騒動もあってめでたく解体の上でユニバーサルミュージックに買収され保有するカタログは傘下のパーロフォンに移ったものの、欧州独禁法の問題でパーロフォンごと売り出され現在はワーナー傘下にという流れ。

ワーナーは過去カタログは容赦なく時には中古対策に安売りし過ぎ(5CDセットで2000円とかおかしいやろ)と思うくらいには出し惜しみせず売るので、今後も期待したい。

*1:全般的にはリズム隊とくにベースのトニー・レヴィンの演奏がけっこう微妙やけど。
*2:一部を 翻訳したんだけどちょうどイーノとフリップの話の前で飽きてしまった…

2017/5/30(Tue)

[やきう] 交流戦

一昨年はポンセ屋舗、去年は平松遠藤斎藤だったけど、今年はなんと クルーンが来るンゴゴゴゴwww

TBSフロントとかいう衰えた佐々木というそびえたつピーに大金投じた挙句、ウッズやクルーンの年俸をケチって同一リーグに流出させる無能集団、ほんとトラウマですわ。