TERM
cross compile for go
ふと思いたって、医者に提出用血圧グラフに、平均血圧を追加するようにしてみた。修整は、下記のように簡単だった。
func (ds AAy) pp(fn string) {
xf,_ := os.Create(fn)
defer xf.Close()
for _, el := range ds {
fmt.Fprintf(xf, "%d %d %d %d\n", el[ymdh], el[hi], el[low], (el[hi] - el[low]) / 3 + el[low])
}
}
set title '起床時: ' . amh . aml . when
plot "am.dat" using 1:2 with lines lt -1, "am.dat" using 1:3 with lines lt -1, "am.dat" using 1:4 with lines lt -1
set title '就寝時: ' . pmh . pml . when
plot "pm.dat" using 1:2 with lines lt -1, "pm.dat" using 1:3 with lines lt -1, "am.dat" using 1:4 with lines lt -1
が、これを動かすのはWindows10なマシン。って事は、Windowsにgoの環境を入れて、コンパイルしろ。あほくさ。そこでクロスコンパイルですよ。Debian(64bit)に入っていた、go1.17.3 linux/amd64 で、トライ。
sakae@pen:/tmp/hoge$ GOOS=windows GOARCH=amd64 go build try.go
これで出来た、try.exeをWindowsにもって行くだけ。結果はバッチグーでした。平均と言うだけあって、標準偏差が、見た目、拡張時血圧と同じぐらい安定してた。
血圧グラフ(nbld.go) 元プログラムはnbld.goなんだけど、今回折線グラフが1本増えたので、try.goって名前にした。tryはトライアングルからのパクリだ。どうでもいい事だけど。。
go なかなかいいな、お手軽で。そうそう、ググルが新しい言語に手を出しているみたいですね。進化の袋小路に入って身動き出来無くなった、C++ の置き換え用だそうです。
C++の後継目指すプログラミング言語「Carbon Language」
こうして、みんなググルの下僕にされてしまうんです。無料でgmailが使える訳、そして検索出来るわけ。身包み剥されて、ぐぐる大儲け。goに秘密のコードが埋め込まれていないか? なんせ、前科者が開発してますからねぇ。ありゃ、人違いだったかな。
xterm
FreeBSDでのgosh問題。consoleをxtermにしとくと、入力できず。新にVirtualBOXにも入れてみたけど、同様な症状。そもそもxtermなんて設定した覚えがないのに、どこからか湧いてくる。どこだ。調べたら、 /etc/ttys
ttyv0 "/usr/libexec/getty Pc" xterm onifexists secure # Virtual terminals ttyv1 "/usr/libexec/getty Pc" xterm onifexists secure : # Serial terminals # The 'dialup' keyword identifies dialin lines to login, fingerd etc. ttyu0 "/usr/libexec/getty 3wire" vt100 onifconsole secure :
ふむ、馬鹿端末と揶揄されるvt100に切り換えてみるか。でもおなじ症状。
この際だから、ちょっと復習。どうxtermが選ばれるか? 長い道程。
カーネルは、起動の最後に /sbin/init を呼ぶ。その中で、/etc/ttysに記載された端末(ttyv0,ttyu0 …)をイニシャライズする。それから、libexec/gettyを使って、それぞれの端末と言うか回線を監視する。
gettyは回線に信号が來ると、回線スピードを整えた後、 libexec/login_xxx プログラムを起動する。パスワード認証がPASSすれば、お待ちかねのshellが起動するって流れ。
xtermとかの設定は、gettyの中にそれっぽいのが有った。
最初の関門、/etc/ttysをinitが読み込んでいるはずなんだけど、見付からない。init(8) -> ttys(5)と辿ってみたら、専用ライブラリィーが提供されてた。曰くgetttyent(3)
struct ttyent {
char *ty_name; /* terminal device name */
char *ty_getty; /* command to execute */
char *ty_type; /* terminal type */
#define TTY_ON 0x01 /* enable logins */
#define TTY_SECURE 0x02 /* allow uid of 0 to login */
#define TTY_LOCAL 0x04 /* set 'CLOCAL' on open */
#define TTY_RTSCTS 0x08 /* set 'CRTSCTS' on open */
#define TTY_SOFTCAR 0x10 /* ignore hardware carrier */
#define TTY_MDMBUF 0x20 /* set 'MDMBUF' on open */
int ty_status; /* flag values */
char *ty_window; /* command for window manager */
char *ty_comment; /* comment field */
};
それから、TERMの設定は、getty/subr.c の中。
makeenv(char *env[])
{
static char termbuf[128] = "TERM=";
if (TT && *TT) {
strlcat(termbuf, TT, sizeof(termbuf));
TTをくっ付けてる。それは、
#define TT gettystrs[8].value
で定義されてた。多分terminfo(5)の大事な部分を抜き出したものだろう。
terminfo(5)すると、関連コマンドがわんさかと出てくる。
SEE ALSO
@TABS@(1), tic(1M), infocmp(1M), curses(3X), curs_color(3X),
curs_variables(3X), printf(3), term(5). term_variables(3X).
user_caps(5).
端末特性の記述したtermcapをticってコマンドでコンパイルすると、バイナリー版のterminfoが出来上がる、はずなんだけど、そうなっていない。メンテナンス不足で実体とかけ離れてしまっているのかな? 大体、ticもinfocmpも無いしね。
後で調べてみたら、ports/devel/ncursesに、これらのコマンドとかが用意されてた。FreeBSDは独自な事をしてるんだな。
OpenBSDで確認
ob$ infocmp xterm
# Reconstructed via infocmp from file: /usr/share/terminfo/x/xterm
xterm|xterm terminal emulator (X Window System),
am, bce, km, mc5i, mir, msgr, npc, xenl,
colors#8, cols#80, it#8, lines#24, pairs#64,
acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
bel=^G, blink=\E[5m, bold=\E[1m, cbt=\E[Z, civis=\E[?25l,
:
Gauche Chaton
で、アドバイスを頂いた。普通に、make-default-console 出来るか? これは、問題なくできた。今度は少しいたずら。
sakae@fbox:~ $ export TERM=hogefuga
Cannot read termcap database;
using dumb terminal settings.
sakae@fbox:~ $ echo $TERM
hogefuga
sakae@fbox:~ $ emacs
emacs: Cannot open terminfo database file
sakae@fbox:~ $ vi
vi: No terminal database found
sakae@fbox:~ $ MINE/bin/gosh -fno-read-edit
gosh> (use text.console)
gosh> (make-default-console)
*** ERROR: Unsupported terminal type: hogefuga
Stack Trace:
_______________________________________
0 (eval expr env)
at "/home/sakae/MINE/share/gauche-0.98/0.9.12/lib/gauche/interactive.scm":336
No terminal
viでの反応を少し調べる。
/usr/src/contrib/nvi/cl/cl_main.c
term_init(char *ttype)
{
int err;
/* Set up the terminal database information. */
setupterm(ttype, STDOUT_FILENO, &err);
switch (err) {
case -1:
errx(1, "No terminal database found");
case 0:
errx(1, "%s: unknown terminal type", ttype);
}
}
The setupterm routine reads in the terminfo database, initializing the
terminfo structures, but does not set up the output virtualization
structures used by curses. These are its parameters:
If ERR is returned, examine errret:
1 means that the terminal is hardcopy, cannot be used for
curses applications.
setupterm determines if the entry is a hardcopy type by
checking the hc (hardcopy) capability.
0 means that the terminal could not be found, or that it is
a generic type, having too little information for curses
applications to run.
setupterm determines if the entry is a generic type by
checking the gn (generic) capability.
-1 means that the terminfo database could not be found.
ふむ、何かライブラリィーを用意してるのかな?
sakae@fbox:~ $ ldd /usr/bin/vi
/usr/bin/vi:
libutil.so.9 => /lib/libutil.so.9 (0x2049f000)
libncursesw.so.9 => /lib/libncursesw.so.9 (0x204b6000)
libc.so.7 => /lib/libc.so.7 (0x2051e000)
emacsにも、/lib/libncursesw.so.9 が含まれていたぞ。ncursesのワイド版、日本語対応って事かな。
hist
lib/gauche/interactive.scm を散策してたら、ヒストリーファイルなんてのに出会った。 こちら方面がどういう応答か見ておく。 OpenBSDでは、ちゃんとヒストリーファイルに記録されるのに、FreeBSDのコンソールから起動した場合、ログが残らない。
但し、PuTTYを使ってFreeBSDに乗入れ、そこでgoshを起動すると、ちゃんとヒストリーも残り、REPLも正常に動く。FreeBSD自身の端末ドライバーを使った場合、応答が異常になるっていう総合診断を下さざるを得ない。pty(4),pts(4)の関係かしら?
sakae@fbox:~ $ cat .gosh_history (gauche-history-version 1) "1234" "(+ 123 456)"
コンソールでは、REPLが受け付けるのは、 ^C で、Input interrupted って反応と、^\ のSIGQUIT で、goshを終了する反応だけのように見える。それ以外は、123と入力するも、それに対するechoすら無い。
goshのマニュアル見ていたら、ヒストリーファイルは、goshが正常終了した場合に作成されるとなってた。上記の場合は、強制quitと言うかSIGQUITだから、正常とは看做されないのだな。
gdb登場
goshを作る時のMakefileを眺めていたら、CFLAGS= -g -O2 が目に入った。gdb welcomなんだね。
on console(/dev/ttyv0)
まずは、コンソールから gdb gosh して走らせる。gosh$ になった所で123とか、わざとエラーになるようにAAAとかを評価するも、何の反応も無い。現象再現。
端末環境がどうなってるか、C-c してgdbに戻り、そこで ! stty -a >LOG した。だって、コピペ出来無いもの。ログをPuTTYな端末から確認。
speed 9600 baud; 25 rows; 80 columns;
lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl
-echoprt -altwerase -noflsh -tostop -flusho -pendin -nokerninfo
-extproc
iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel -ignbrk
brkint -inpck -ignpar -parmrk
oflags: opost onlcr -ocrnl tab0 -onocr -onlret
cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow
-dtrflow -mdmbuf rtsdtr
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
eol2 = <undef>; erase = ^?; erase2 = ^H; intr = ^C; kill = ^U;
lnext = ^V; min = 1; quit = ^\; reprint = ^R; start = ^Q;
status = ^T; stop = ^S; susp = ^Z; time = 0; werase = ^W;
こちらは、対比する為のPuTTY端末。マスター/スレーブな端末だ。
sakae@fbox:/tmp $ tty
/dev/pts/1
sakae@fbox:/tmp $ stty -a
speed 9600 baud; 31 rows; 80 columns;
lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl
-echoprt -altwerase -noflsh -tostop -flusho -pendin -nokerninfo
-extproc
iflags: -istrip icrnl -inlcr -igncr ixon -ixoff ixany imaxbel -ignbrk
brkint -inpck -ignpar -parmrk
oflags: opost onlcr -ocrnl tab0 -onocr -onlret
cflags: cread cs8 -parenb -parodd hupcl -clocal -cstopb -crtscts -dsrflow
-dtrflow -mdmbuf rtsdtr
cchars: discard = ^O; dsusp = ^Y; eof = ^D; eol = <undef>;
eol2 = <undef>; erase = ^?; erase2 = ^H; intr = ^C; kill = ^U;
lnext = ^V; min = 1; quit = ^\; reprint = ^R; start = ^Q;
status = ^T; stop = ^S; susp = ^Z; time = 0; werase = ^W;
結果はrowsを除いて、同一だった。ひょっとして、goshの環境じゃなくてgdbの環境を見ているのかな。ならば、 viを起動して、その環境でstty -aを確認。
sakae@fbox:/tmp $ diff LOG VI 1,2c1,2 < speed 9600 baud; 25 rows; 80 columns; < lflags: icanon isig iexten echo echoe -echok echoke -echonl echoctl --- > speed 9600 baud; 31 rows; 80 columns; > lflags: icanon isig iexten echo echoe echok echoke -echonl echoctl
goshの中を散策
コンソールでgoshを起動しておいて、gdbをアタッチしてみる。勿論、gdbはコピペ出来るPuTTY上から、emacs経由。
(gdb) bt #0 0x20ace2ad in _read () from /lib/libc.so.7 #1 0x209a3ed1 in ?? () from /lib/libthr.so.3 #2 0x20acc5cf in read () from /lib/libc.so.7 #3 0x205cbde3 in file_filler (p=0x20e62ee0, cnt=1) at port.c:1304 #4 0x205c938a in bufport_fill (p=<optimized out>, min=1, allow_less=0) at port.c:1020 #5 0x205c94ff in Scm_Getc (p=0x20e62ee0) at ./portapi.c:638 #6 0x2059cf3d in run_loop () at ./vminsn.scm:1339 #7 0x2057a04d in user_eval_inner (program=<optimized out>, codevec=0xffbfeb14) at vm.c:1572 #8 0x2057afc4 in apply_rec (vm=<optimized out>, proc=<optimized out>, nargs=<optimized out>) at vm.c:1691 #9 Scm_ApplyRec (proc=<optimized out>, args=<optimized out>) at vm.c:1711 #10 safe_eval_wrap (kind=1, arg0=0xb, args=0xb, cstr=0x401f46 "(read-eval-print-loop)", env=0x20871f34 <userModule>, result=0x0) at vm.c:1844 #11 0x2057b10a in Scm_EvalCString (expr=0x401f46 "(read-eval-print-loop)", env=0x20871f34 <userModule>, packet=0x0) at vm.c:1877 #12 0x00405d24 in enter_repl () at main.c:753 #13 0x00406498 in main (ac=1, av=0xffbfed14) at main.c:897
frame 4 あたりまでは、下働きっぽいので、 Scm_Getc にBPを張ってみる。そしてREPLに、9RETURN を入力。
Thread 1 hit Breakpoint 1, Scm_Getc (p=0x20e62ee0) at ./portapi.c:687 687 if (c == '\n') PORT_LINE(p)++; (gdb) p/c c $1 = 57 '9' (gdb) c Continuing. Thread 1 hit Breakpoint 1, Scm_Getc (p=0x20e62ee0) at ./portapi.c:687 687 if (c == '\n') PORT_LINE(p)++; (gdb) p/c c $2 = 13 '\r' (gdb) c Continuing.
REPLに9が表示するはずだけど、何も出てこない。
(gdb) display/c c 1: /c c = 81 'Q' (gdb) c Continuing. Thread 1 hit Breakpoint 1, Scm_Getc (p=0x20e62ee0) at ./portapi.c:687 687 if (c == '\n') PORT_LINE(p)++; 1: /c c = 13 '\r' (gdb) c Continuing.
今度は、エラーを期待して、QRETURN するも、梨の礫。
RETURNを受け取った後の挙動をnextで追跡するも、VMマシンに阻まれて、路を見失ってしまった。まあ、入力はちゃんと受け取っていると予想されるから、出力系に不都合な真実が隠されているのかな。
待て、REPLから、(exit)と入力しても終了しないでgosh$のプロンプトのままって事は、あながち表示系だけの問題ではないのかも。でもSIGQUITはちゃんと機能するからなあ。
悪あがき
上でやった結果がプチ気になっていた。RETURNを叩いた時、却ってくる文字コードが \r て所。ソースでは、 if ( c = '\n' ) PORT_LINE (p)++ とかいてあるしね。
こういう時は、正しく動くものと見比べるのがよかろう。PuTTYの端末だと、9RETURNの入力で
9 \r \033 [ 2 1 ; 7 R \033 [ 3 2 ; 8 0 R
という応答だった。なんかエスケープシーケンスがユーザー入力の後に続いているな。何これ?
The VT100 terminal Programmer Information
これの CPR – Cursor Position Report – VT100 to Host に相当するのかな。
でこれを想像してみると、21行目の7cols目から、32行目の80桁までを指定してるっぽい。 今の端末はシリアル接続のVT100(by TeraTerm)ってやつ。
sakae@fbox:~ $ echo $TERM vt100 sakae@fbox:~ $ stty -a speed 9600 baud; 32 rows; 80 columns;
範囲指定しておいて、別のコマンドでガサッとschemeコードを取り込んでいるのかも。
でも、FreeBSDが提供するコンソール端末では、そんなエスケープシーケンスを許容しない。ゆえに、いつまでたっても、S式の入力完了とならない。
マニュアルにある、C-M-x で、評価器へ直送ってのも、コンソールでは動作しない。これは、ひょっとしてWindows 10がシーケンスを取り上げてしまうからかも知れないけどね。
serial通信
折角なので、VirturalBOXにシリアルボードを刺して、通信してみる。シリアルを選んで、COM1を追加、接続時うんぬんはOFF。パイプで模倣する。パイプ名は、TeraTermの設定と合わせる。TeraTermはシリアル接続を選ぶ。 両端末は、シリアルと思っているけど、経路はnamed pipeで接続されると言う、現代風な仕掛けだ。昔、シリアル回線を使って、TCPのパケットを送受するっていうslip接続ってのがあったけど、それを彷彿させるな。
FreeBSD側の設定は、昔とちと塩梅が違っているみたいなので、 HandBook 第18章 シリアル通信 を参照。
/etc/ttys
# The 'dialup' keyword identifies dialin lines to login, fingerd etc. ttyu0 "/usr/libexec/getty std.9600" vt100 on insecure ttyu1 "/usr/libexec/getty 3wire" vt100 onifconsole secure :
設定をちょっと書き換え。初期設定のttyu1風だと、動かなかったので。
sakae@fbox:~ $ dmesg|grep uart uart0: <Non-standard ns8250 class UART with FIFOs> port 0x3f8-0x3ff irq 4 flags 0x10 on acpi0 sakae@fbox:~ $ ps a PID TT STAT TIME COMMAND 846 u0 Is+ 0:00.01 /usr/libexec/getty std.9600 ttyu0 771 v1 Is+ 0:00.00 /usr/libexec/getty Pc ttyv1 772 v2 Is+ 0:00.00 /usr/libexec/getty Pc ttyv2
dmesgにsioってのが出て来るってなってたけど、FreeBSD 13.1R では、uartってなってた。acpi0にぶら下がっているから、名前が違うんだな。昔はisaだったけど、virtualBOXも進化してるのね。こういう楽しみはLinuxでは難しいので、BSDはいいよ。
NetBSD 9.3: A 2022 OS that can run on late-1980s hardware
NetBSD 9.3 が出たそうだけど、30年前のハードがそれで動いているとな。どこかのOSよ。爪のあかを煎じて飲めよ。主流が64Bitなんで、とんでもない奢りだぞ。軽々と古いCPUを切り捨てるOSとか、32bitな石はもうサポートしませんとか言うデストリとかね。
ああ、件のgosh問題だけど、TeraTermから接続したvt100な端末では、問題なたかった。
FreeBSD/i386 (fbox) (ttyu0) FreeBSD 13.1-RELEASE-p1 GENERIC sakae@fbox:~ $ echo $TERM vt100 sakae@fbox:~ $ gosh gosh$ 123 123 gosh$ (exit) sakae@fbox:~
Setup FreeBSD in VirtualBOX
BSDな、アンバサダーとして、一言宣伝。
BSDをインストールする過程で、カーネルデバッグ用のコードとか、ソース一式とかportsのソースをインストール出来る。押しつけじゃなくて、自由選択だけどね。
是非、ソース一式をインストールして欲しい。カーネル、ユーザーコマンド、libcとかのソース、リリースとかに必要なソースが/usr/srcの下に入る。i386なシステムだと、
sakae@fbox:/usr $ du -sh src 1.4G src
こんなものだ。時間もインストールのどさくさなんで、そんなにきにならない。
portsは、諸般の事情で、後から入れた方がよい。rootになって
portsnap fetch extract
後は、時々、/usr/portsに移動して、make update するだけで、最新の追加ソフトをコマンド一発でコンパイルする環境が手に入る。丁度1Gの容量になってた。
portsのバイナリーは、年に4回更新されるんで、pkg install gauche とかすれば最新が手に入るはず。残念ながら、gaucheはメンテナーが不在だけど。オイラーが名乗りをあげても、今回のような問題を持ち込まれると、困っちゃうんで、隅に隠れていますよ。