電源をリモートで ON/OFF できるのは素晴らしいのだが、どうもフィードバックに欠ける。
目の前にマシンがあれば、ディスプレイに無信号のメッセージが出るとか、ファンの音とかで電源の状態が分かるが、リモートだと分からない。
電源の ON/OFF 時にはシリアルにメッセージが出ないのである。
いや、正確には、シリアルからも分かるのである。電源が OFF になると、DSR と DCD が 0 になるのだ。
これに気がついたのは、kermit でつないでいるときに、電源を OFF したときに kermit のプロンプトに戻ってしまうからである。(ただし、この挙動に出会ったときには DSR や DCD といったことは理解していなかった)
しかし、この kermit の挙動は都合が悪い。電源を OFF にした後には ON にするわけで、そのときの boot メッセージをみるためには、carrier がなくてもつなぎっぱなしになってほしい。
そうするには set carrier-watch off すればいいのだが、そうすると、DCD の変化がわからない。
調べるだけは調べて報告するけれどプロンプトには戻らない、という挙動ができるといいのだが、kermit にはない模様である。
結局、statserial を使うと、(kermit とは別に) シリアル接続の各ピンの状態を表示できるようなのでので、そうしてみた。
statserial を strace してみると、ioctl で TIOCMGET をやっているようだ。
クロスケーブル (KRS-403XF-07K) の結線図を確認すると、こちらの DCD は向こうの RTS (と CTS) につながっているようだ。
こちらの DCD, DSR で向こうの状況が分かるということは、向こうは RTS, DTR で状況を示しているということか。
それはそれとして、RTS, CTS がそれぞれで短絡している。そういえば電源を切っても CTS が 1 のままなのが疑問だったが、そういうわけだったか。
ということは、RTS/CTS によるフロー制御 (stty crtscts) は使えなさそうだ。
あと、結線図には RI について記載されていないのだが、これは接続されていない? (statserial では常に 0 になっている)
<URL:http://www.sanwa.co.jp/product/cable/rs232c/index.html> をみると、サンワサプライでは RTS, CTS を短絡して他方の DCD につなぐのをクロス結線&リバース結線、RTS, CTS をひっくりかえして他方につなぎ DCD はつながないのをインタリンク用と呼びわけている?
Linux に signalfd という話があるのか。
シリアル接続の各ピンの意味をさらに調べる。
調べた結果、結局、AT コマンドの説明が欲しかった情報であった。
つまり、いくつかのピンの意味は AT コマンドによって変えられる、という。
まぁ、標準的な意味 (デフォルトの意味)、というのもあるわけだが。
POSIX の General Terminal Interface の項を読む。
232C とはどこにも書いていない、が、asynchronous という単語は出てくる。
これらがどういう意味なのかは定義されていないように思う。
また、modem という単語も出てくる。
status line や control line というのがいったいなんなのかというのも定義されていないように思う。
また、ビット数 (CS5, CS6, CS7, CS8)、パリティ (PARENB, PARODD)、ストップビット (CSTOPB) や break の扱いの指定 (BRKINT, IGNBRK) もある。
抽象化を狙ってはいるがしょせん無謀な試み、という印象。
modem の扱いに関しては、何が起きるのかいまひとつすっきりしない。
pty について調べる。
シリアル端末にできることを、pty の master 側はどのていどできるのだろう?
とりあえず、パリティエラーなデータはおくれそうにない。
また、break も無理っぽい。
modem disconnect は close すればできる感じ。
TIOCPKT について調べる。man によれば remote-echoed, locally `^S/^Q' flow-controlled remote login を rlogin で実現するのに使われているという。
rlogin/rlogind をみると、たしかに説明にあるとおりに使われている。
TCP は信頼性があってデータは失われないはずなので、flow control をリモートでやっていけないことはないはずだが、ローカルでやりたいというのは、やはり ^S を押した後、ネットワークレイヤに溜っているデータをすべて吐き出すまで止まらないというのは耐えられないということだろうか。
なんでこんなモードが必要かということを考えると... ^S/^Q をフローコントロールに使うかどうかという意図がリモートで変化するからか。その変化を検出するのが TIOCPKT_DOSTOP, TIOCPKT_NOSTOP で、これがなければ、tcgetattr でポーリングするはめになる、ということかな。ポーリングだとタイミングが遅れて悲しいという事情もあるか?
ふと思ったのだが、^S/^Q (XON/XOFF) なんて使うから、フローコントロールなのか、そのまま送りたいのか分からないわけである。ハードウェアフローコントロールであればそんな必要はなくて、とすれば、RTS に直結したフローコントロール専用のキーがあればいいのではないか、と考えた。
とりあえず、VT100 のキーボードはどうだったのであろうか、と思って探して、 VT100 User Guide というのを見つける。
キーボードレイアウトをみると、おぉ、NO SCROLL といういかにもそれっぽいキーがあるではないか。(あと、BREAK キーもある。)
その動作を調べて VT100 User Guide Chapter 3 - Programmer Information を読んでみると... これは XON/XOFF を送るためのキーの模様。うぅむ。
zsh の気に入っている点のひとつに、複数行を扱えるラインエディタがある。
vt100 を調べていて、ふと、ナイーブに実装するならそんなに難しくないんじゃないか、と思って簡単なものを作ってみる。
結局、必要だったエスケープシーケンスは、カーソルを上に動かす (terminfo でいえば parm_up_cursor) のとカーソルから行末までの削除 (clr_eol) で、あと (これはなくても実装可能だと思うが) カーソルを右に動かす (parm_right_cursor) を使った。もちろんそれ以外に CR と LF は使った。
なんともナイーブな実装で、なにか変更するたびに、管理している領域の左上に相対移動して、全部表示しなおし、カーソルを目的の位置に移動する、というそれだけである。
なお、画面の桁数は stty size で得ておき、文字は常に 1byte で幅は 1カラムと仮定している。
あと、初期状態においてカーソルは左端にあることを仮定している。
(ncurses の) terminfo(5) を読む。
SUSv2 についていた、X/Open Curses, Issue 4 Version 2 をちょっと眺める。
これって SUSv3 の一部の扱いなのだな。 <URL:http://www.unix.org/version3/overview.html>
文字の幅について調べる。
規格としては、文字の幅は locale によって決まり、wcwidth で得られる、ようだ。
% cat width.c #include <wchar.h> #include <stdlib.h> #include <stdio.h> #include <locale.h> int main(int argc, char **argv) { char *s; wint_t wc; wint_t wret; s = setlocale(LC_ALL, ""); if (s == NULL) { perror("setlocale"); exit(1); } while ((wc = getwchar()) != WEOF) { fputws(L"[", stdout); wret = putwchar(wc); if (wret == WEOF) { perror("putwchar"); exit(1); } wprintf(L"]: width:%d\n", wcwidth(wc)); } if (ferror(stdin)) { perror("getwchar"); exit(1); } return 0; } % echo -n α| nkf -w|LANG=ja_JP.UTF-8 ./width|nkf -W [α]: width:1 % echo -n α| nkf -e|LANG=ja_JP.EUC-JP ./width|nkf -E [α]: width:2
wchar を使ったのは初めてな気がするがそれはそれとして。
curses は wcwidth で得た幅を想定して画面を構成する。
したがって、端末は、wcwidth に従った文字幅で文字を描画しないと想定された通りの表示にならない。
で、端末がそうなっているかというと、そうではない場合もあるようだ。
とりあえず、xterm は ja_JP.EUC-JP でも「α」を幅 1カラムで描画する。(Debian の xterm でバージョンは 222-1etch2)
しかし、端末エミュレータのように、locale を環境変数で受け付けられるのであればなんとかすることもできるだろうが、受け付けられないものはどうだろう?
たとえば、コンソールはどうだろう? コンソールの locale をカーネルパラメータとかで設定できるようにすべきだろうか? その場合、違う locale を使うひとがログインしたらどうなるべきなのだろう?
あるいは、本物の端末だったらどうだろう? シリアル回線で接続される端末専用機は、locale を設定できる (もしくは特定の locale 専用にデザインされる) べきなのだろうか。
まぁ、locale をひとつに統一して関わるところすべてが従ってれば大丈夫、という locale の考え方に無理があるのだが、ではどうすべきだったかというと、さて。
せめて locale じゃなくて terminfo に情報をいれておけばましだったように思うが、本当は、端末ともっとまともな通信プロトコルで情報をやりとりして、文字の幅を問い合わせたいところだ。
ところで、vt100 には Cursor Position Report というエスケープシーケンスがあって、カーソルの位置を問い合わせられる。それを使って文字の出力前後に位置を調べれば、実際の端末における文字幅が分かると思うのだが、それを使った curses とかないのだろうか。
ただ、残念ながら terminfo には Cursor Position Report が標準化されていないのだが、ncurses では user7 にいれておくという慣習があるようで、無理という程でもないように思うのだが、どうなのだろうか。
ちょっとためしてみると、画面右端が問題になる。
vt100 には eat_newline_glitch があるので、出力した文字が画面右端に到達した場合、カーソルが文字幅未満しか動かない。そのため、カーソルの移動量では文字幅を計れない場合がある。
また、画面右端下端も留意が必要である。そこでの文字の出力の結果として行が溢れてスクロールが起きた場合、カーソルは依然として画面下端にあり、行が移動していないようにも見える。まぁ、これはカラム位置が減少していることを検査すればだいたいわかるが。
でも、画面の幅が充分に狭く、文字の幅がかなり大きい場合にはどうだろう? そのまま出力すると画面右端をこえるために次の行に (スクロールして) いって、行の先頭から出力されて、文字幅が大きいので以前のカーソル位置よりも右にいく、という状況は考えられるか?
そういうのがおきるのは、(境界条件は無視しておおざっぱに考えると)、画面幅が文字幅の倍よりも小さいとき、かな。
最大文字幅が 2カラムとすると、画面幅は 4カラム以下か。
画面幅が 1カラムの場合はそもそも文字が表示不能なので気にしないことにしよう。
画面幅が 2カラムの場合で 2カラムの文字を表示したときには常に行頭 1カラム目から表示されることになる。そんで、文字が表示された後、(vt100 的) eat_newline_glitch によりカーソルは画面右端に張りつく。画面右端にカーソルがいったときには文字幅は測らないのでこの場合も気にしないことにしよう。
画面幅が 3カラムの場合で 2カラムの文字を表示したときには 1カラム目から表示されるか 2カラム目から表示されるかの 2つの状況があるが、いずれにせよ表示後は 3カラム目 (画面右端) にカーソルがいくのでこれも文字幅は測らない、ので気にしない。
画面幅が 4カラムの場合で 2カラムの文字を表示したときに、カーソルが画面右端 (4カラム目) にいかないのは、1カラム目から表示したときだけである。そのとき表示後には 3カラム目にいく。文字出力により改行・スクロールが起きるのは 4カラム目にカーソルがあったときであるから、4カラム目から 3カラム目に移動することになり、カーソル位置が左に移動することで検出できる。
幅が足りなくて表示不能な文字を出力すると何が起こるのか試してみる。
... えーと、もしかして kterm/xterm は無限ループ?
SourceForge.net は Compile Farm をやめたのか。
<URL:https://sourceforge.net/forum/forum.php?forum_id=665363>
OpenBSD 4.0 で、sleep 100000001 と実行するとすぐに終わることに気がついた。
$ sleep 100000001 $
もちろん、3年待ったわけではない。(3年前には OpenBSD 4.0 は出ていない)
exit status をみると、失敗していることが分かる。
$ echo $? 22
なお、sleep 100000000 だと終わらない。(たぶん 3年くらい)
100000000 と 100000001 の間に何があるのだろう?
ソースは以下である。
http://www.openbsd.org/cgi-bin/cvsweb/src/bin/sleep/sleep.c?rev=1.17&content-type=text/x-cvsweb-markup
ソースをみると、途中に for (i = 100000000; i > 0; i /= 10) とかあって、おぉっ、と思ってしまうのだが、じつはそこは関係ない。
結局 nanosleep が EINVAL で失敗していることが分かった。
たしかに OpenBSD の nanosleep(2) をみると、100000000 よりも大きければ EINVAL と書いてある。なるほど。
[EINVAL] rqtp specified a nanosecond value less than zero or greater than 1000 million, or a second value less than zero or greater than 100 million.
POSIX をみると、second value に関しては制限がない。ふむ。
[EINVAL] The rqtp argument specified a nanosecond value less than zero or greater than or equal to 1000 million.
ruby-terminfo のポータビリティを調べてみようかと思って、NetBSD, FreeBSD, OpenBSD を試す。
NetBSD はそもそも terminfo を採用してない。
FreeBSD は ncurses を採用しており、ruby-terminfo はとくに問題なくビルドできるが、ncurses の中で double free の警告が出る。kern/108117 に似た症状の報告があって、ncurses を新しくして直ったんじゃないか、という話である。それなら放っておけばいいか。
OpenBSD も ncurses を採用しており、ruby-terminfo はとくに問題なくビルドできるが、やはり double free がでる。これが警告じゃなくていきなり abort する。さすが OpenBSD である。報告も見当たらないので、これは報告しておく。
OpenBSD library/5447 : del_curterm dumps core: free(): error: page is already free
あと、Solaris を試そうと思って、BeleniX を (それなりに苦労して) 試すが、んー、開発環境がない?
xen で Debian をふたつ動かしていたマシンがあった。
ちょっと前のことだが、このマシンの Debian を etch にあげるときに、xen はやめることにした。
domain0 だったほうの Debian を (xen でなく) 普通に起動し、アップグレードして、普通の etch がうごいた、というところまではたいして問題なく進み、常用可能となった。
しかし、そこで、もうひとつの domainU だったほうの Debian に必要なデータが取り残されていることを思い出した。
xen による仮想マシンのディスクは LVM をつかって管理していたので、/dev/volume-group/logical-volume でデバイスファイルは参照できる。
そんなら単にマウントして取り出せばいい... というわけにはいかなかった。
このデバイスはディスク全体を示すものであって、個々のパーティションではない。マウントしたいのは個々のパーティションなので、そのままではマウントできないのである。
実物のディスクでは、個々のパーティションに対応するデバイスがあるものだが、LVM ではないようである。
LVM でつくったディスクをさらにパーティションで分割するというのは想定外なのだろうか。(ていうか、名前は logical volume であってディスクとはいってないか。)
空のハードディスクにコピーするか、とも考えたが、スマートさに欠ける。
いくらか考えて、fdisk でパーティションの位置を調べ、dd でそこだけファイルとして取り出し、loop で mount した。
しかし、これでもあんまりスマートでない。
デバイスの任意の場所からマウントできれば、と思ったが、まぁ危険過ぎるかな。
結局、Solaris 10 をとってきて試す。
今回試している、NetBSD, FreeBSD, OpenBSD, Solaris は、ひとつのマシンで稼働させているが、これは単純にマルチブートである。
それぞれに primary partition を割り当てて、NetBSD の boot selector で選択している。
(FreeBSD の boot selector のほうが設定不要なところはいいのだが、boot 時のメニューに NetBSD, OpenBSD, Solaris と表示させられないかんじ。)
さて、この場合の問題はコンピュータの前にいないと他の OS を起動しなおせない、ということである。リモートで作業をしているときに他の OS を試したいと思っても使えない。
これをどうにかしようとおもって少し調べてみた。
まず思い付いたのが、最近別件で (別のマシンで) 使った grub によるシリアルコンソールである。しかし、このマシンはノート PC であって、シリアルポートがない、という点が問題である。USB や PCカードのシリアルアダプタを使えるか、というとよくわからない。USBシリアルについては手持ちがあるので今度試してみよう。でも望み薄かなぁ。
次に思い付いたのが、PXE である。ブートさせたいパーティションからブートするコードを PXE でネットワーク越しに送り込む、というわけである。ただ、検索してもそういう使いかたの例は見当たらない。(PXE というのは、install と diskless が主要な用途のようだ。) ただ、pxegrub といのがあるようで、これは自由度が高くて使えるかもしれない。が、今回のマシンの NIC を扱えるかどうかという点が怪しい。
最後に思い付いたのが、リブート前に MBR を書き換える、というものである。NetBSD の boot selector は何も入力しなければそのうち決まった選択肢が選ばれるので、OS 毎の起動用 MBR を用意しておいて適切に MBR を変えれば、ブートする OS を選べるはずである。これが簡単かなぁ。でも、失敗してブートしなくなると、箱の鍵は箱の中状態になってリモートからでは復旧できないから、できれば避けたい気分ではある。
リブートするときに、次に起動する OS のブートローダを指定できるような OS があってもいいはずだが、そういう OS はあるだろうか?
Linux-USB Gadget API Framework なるものをつかうと、USB device のふりをすることができるらしい。
とすると、boot code の入った Mass Storage Device のふりをして、ブートしたい OSによって boot code を変える、というのはどうだろうか。
って、そもそも普通の PC で使えるのか、という点があるか。
まぁ、おおげさ過ぎるか。
普通の USB メモリからブートさせて、その USB メモリの書き換えで、ブート対象を変えるのはどうか?
それで、USB Hub を経由させておいて、USB メモリの中身が壊れちゃったら、USB Hub の電源を切って、USB メモリが認識されないようにする、と。
ただ、電源を切ると Hub として動かなくなるものってあるのかなぁ?
いま AC 電源制御に使っているもの は、セルフパワー専用って書いてあるけど AC アダプタを付けなくても動くし。
USB切り替え器なるものがあるらしい。
こういうのが使えれば電源云々はいらないか。
AMIBIOS8 Support for USB というのを眺めていて、USB device をなんに見せかけるかという条件の記述を見つける。
その中で、block size が 512byte よりも大きい場合には CD-ROM、という条件に目を惹かれる。
もしかして、その条件を満たせば、CD-ROM 用の ISO イメージをそのまま使えるのだろうか。
HDD の MBR 書き換えによるリモートからのブート選択をやってみる。
まぁ、動く。デバイス名がことごとく違うのと、FreeBSD で kern.geom.debugflags をいじらないと書き込みができないことを除けば、問題はなかった。
ただ、Solaris のブートが途中で止まることがあり、これに対処するには電源 OFF/ON が必要で、状況を確認するにはやはりシリアルコンソールが欲しい。
ELECOM の MF-HU2256SV という USB メモリが手元にある。
<URL:http://www2.elecom.co.jp/data-media/usb-flash/mf-hu2/index.asp>
これはなかなか奇妙で、PC に挿すと 2つのメディアが見える。
マニュアルには、フロッピーとリムーバルディスクのアイコンが出る、と書いてあり、Windows で試すと確かにそうなる。
GNU/Linux (Etch) でも、挿すと sda, sdb と認識される。
ではどんな仕掛けになっているのか、と調べてみると、lsusb ではひとつしかデバイスが見当たらない。
% lsusb Bus 004 Device 025: ID 13fe:1a20 Bus 004 Device 001: ID 0000:0000 Bus 003 Device 001: ID 0000:0000 Bus 002 Device 001: ID 0000:0000 Bus 001 Device 001: ID 0000:0000
最初の Bus 004 Device 025 がこの USB メモリで、残りはハブである。
詳しくみてみると、これは Mass Storage の SCSI である。
% lsusb -v ... bInterfaceClass 8 Mass Storage bInterfaceSubClass 6 SCSI bInterfaceProtocol 80 Bulk (Zip) ...
SCSI としての情報をしらべてみると、ふたつ見付かる。
% lsscsi [8:0:0:0] disk MF-HU PMAP /dev/sda [8:0:0:1] disk MF-HU PMAP /dev/sdb
8:0:0:0 と 8:0:0:1 の最後の数字が違いであるが、これはどうやら Logical Unit Number らしい。そういえば SCSI にはそういうものもあったな。
普通の USB フロッピー (Mass Storage の UFI) ではないのに、Windows でこれがフロッピーのアイコンになるというのはちょっと不思議だが、サイズあたりで判断しているのだろうか。
ここしばらく、Cursor Position Report を使って文字の幅を測りながら画面の再描画を行うということについて、憑かれたように考えていた。
これがとても難しい。
前者についていえば LCS (diff) を使ってどうにかする論文が昔あった気がする。
が、後者を考ると、LCS で解決というわけにはいかない。
LCS を求めるには、まず再描画した後の状態を求めなければならないが、テキストの中に幅のわからない文字が含まれていると、文字の配置は確定できないし、テキストのどこまでが画面に納まるかもわからない。
では、最初に文字の幅を測るか、というと、幅の不明な文字のうち最初のものがどこから表示されるかはわかるが、再描画前の状態でそれをどこに対応づけるかというのが判断に迷う。
いろいろと考えた結果、dynamic programming で解くことを想定して、全部の状態を俯瞰してみると、情報が足りなくて最適解を確実に求めるのは無理っぽい、ということがわかった。
おそらく最初に文字幅をぜんぶ測ってしまうのが楽だろう。
[latest]