天泣記

2007-06-01 (Fri)

#1

最近、termios (と terminfo) を使って書いたプログラムに resize の clone がある。

#!/usr/bin/env ruby

# xterm's "resize" command clone.
#
# It assumes VT100 compatible terminal.

require 'terminfo'
require 'termios'

def stty(io)
  termios = Termios.getattr(io)
  old = Marshal.load(Marshal.dump(termios))
  begin
    yield termios
  ensure
    Termios.setattr(io, Termios::TCSADRAIN, old)
  end
end

def noecho_raw(io)
  stty(io) {|termios|
    termios.iflag &= ~(Termios::ISTRIP|Termios::PARMRK|Termios::INLCR|Termios::ICRNL|Termios::IGNCR|Termios::IXON|Termios::IXOFF)
    termios.oflag &= ~(Termios::OPOST|Termios::ONLCR|Termios::OCRNL|Termios::ONOCR|Termios::ONLRET)
    termios.lflag &= ~(Termios::ISIG|Termios::ICANON|Termios::ECHO|Termios::IEXTEN)
    termios.cc[Termios::VMIN] = 1
    termios.cc[Termios::VTIME] = 0
    Termios.setattr(io, Termios::TCSADRAIN, termios)
    yield
  }
end

tty = File.open(TermInfo.ctermid, "r+")
tty.sync = true

str = nil
noecho_raw(tty) {
  tty.print(
    "\e7" +             # DECSC -- Save Cursor (DEC Private)
    "\e[999;999H" +     # CUP -- Cursor Position
    "\e[6n")            # DSR -- Device Status Report
  str = tty.readpartial(16)
  tty.print "\e8"       # DECRC -- Restore Cursor (DEC Private)
}

exit false if /\e\[(\d+);(\d+)R/ !~ str

rows = $1.to_i
cols = $2.to_i

TermInfo.tiocswinsz(tty, rows, cols)

print <<"End"
COLUMNS=#{cols};
LINES=#{rows};
export COLUMNS LINES;
End

resize はカーソルを画面右下にもっていき、そのときのカーソルの位置を端末に報告させる。そこから画面サイズを得て tty に設定する。

プログラムの本筋はそういうことなのだが、できあがったプログラムでは、前半が termios による tty の設定に費されている。

そこでやっているのは端末とのやりとりに余計な操作が加わらないようにする、ということなのだが、それだけのことがこのようなフラグ操作の山になってしまう。(実際のところ、不適切なところもあるかもしれない)

これをそらで書けといってもそれは無理である。

ここで余計な操作を抑制するという概念はあるので、その概念を直接支援する機構が欲しい。

その概念の名前の候補としては、termios 以前の tty からの歴史的経緯として、raw というものがある。(raw 以外には、普通の状態の cooked と、その中間的なものとして cbreak がある)

ただ、raw という概念は echo しないことを含むか、という問題がある。

用法としては、おそらく echo しないことが大多数である。raw の用途としては典型的にはスクリーンエディタがあげられる。resize もそうだが、これら用途では入力がそのまま echo されては困る。

4.3BSD をみると、(あまり確信はないが) RAW にしても echo は抑制されないように思う。

また、curses に raw(), noraw(), echo(), noecho() という関数がある。この raw, echo は独立であって raw にしても echo は行われるのだが、(ncurses の) マニュアルの先頭の方に注意が書いてあって、いかにも失敗が多そうな雰囲気である。というか、注意書きの存在がデザインの失敗を示しているという例であろう。

Stevens の APUE では termios 以降を扱っているため、仕様としての raw は出てこない。しかし、tty_cbreak, tty_raw 関数がサンプルとして提示されており、その仕様の中でエコーを止めることがあげられている。もちろん実装も ECHO フラグを外している。

思うに、Stevens を見習って (もしくは、いざというときには Stevens のを参考にしたと責任を押しつけて) echo しないものを raw という名前で作るのがいいのではないか。

というわけで、(自動復旧をブロックで実現して) Termios.raw(io) { ... } だろうか。

2007-06-02 (Sat)

#1

termios では端末との通信速度を設定できる。ここで、ホストから端末への速度と逆方向の速度を別々に設定することもできる。

まぁ、RS-232C によるシリアル接続を行わない限り意味のない値だが。

これらの値は termios 構造体にたいして cfgetispeed と cfgetospeed を呼ぶことで得られ、cfsetispeed と cfsetospeed で設定できる。

ただ、ここで得られる・設定するのは B9600 とか B38400 といったマクロで表される値である。

GNU/Linux では、たとえば B9600 が 0000015, B38400 が 0000017 となっている。この値は適当に区別できるようなビットの組合せであり 9600 や 115200 といった値とは異なる。

しかし、4.4BSD では、B9600 が 9600, B38400 が 38400 というように、その速度の値そのものになっている。

どちらがわかりやすいかといえばどう考えても 4.4BSD である。概念がひとつ少なくて済んでいる。

さて、ruby-termios では、Termios::Termios#{ispeed,ospeed} で速度が得られ、Termios::Termios#{ispeed=,ospeed=} で速度を設定できる。

ここで得られる・設定できる速度は Termios::B9600 や Termios::B38400 といったものであり、もちろん OS のマクロの値そのものである。

例えば、Linux で 9600 のときに Termios.getattr(STDIN).ispeed とすると 13 が得られる。FreeBSD では 9600 が得られる。

これをどちらでも 9600 を得るにはどうするかというと、Termios::BAUDS というマクロの値からマクロの名前への hash が用意されているので、Termios::BAUDS[Termios.getattr(STDIN).ispeed] とすると :B9600 が得られる。シンボルじゃなくて整数が欲しければ文字列化して先頭一文字を削って整数に変換すれば良い。つまり Termios::BAUDS[Termios.getattr(STDIN).ispeed].to_s[1..-1].to_i とする。

ここで、すれば良い、っていうのはたしかにそうすれば動くが、なんでそのように手間をかけなければならないのか、という疑問がわく。

ポータブルにするには Termios::BAUDS[Termios.getattr(STDIN).ispeed].to_s[1..-1].to_i としなければならないが、4.4BSD では Termios.getattr(STDIN).ispeed で済むとすれば、4.4BSD で開発しているひとは常にそう書きたいという誘惑にさらされることになる。

速度の設定も、どちらも動くようにするには Termios.getattr(STDIN).ispeed = Terimos::B9600 としなければならないが、4.4BSD なら Termios.getattr(STDIN).ispeed = 9600 で済むという誘惑がある。

誘惑に負けると、4.4BSD の人には問題ないが Linux の人には悲しいプログラムができあがるわけである。

この問題を解決する案をいくつか考えてみた。

まず最初の案は Termios::Termios#{ispeed,ospeed,ispeed=,ospeed=} を変えて、B9600 じゃなくて 9600 という値を扱わせるというものである。しかし、これには非互換という問題がある。

次の案は B9600 と 9600 の両方を受け付けて非互換性を避けようというものである。つまり Termios::Termios#{ispeed=,ospeed=} で B9600 と 9600 の両方を受け付ける。しかし、もし値が衝突してしまうような環境があると実現不能だし、Termios::Termios#{ispeed,ospeed} をどうするかという問題がある。

次の案は名前を変えて非互換性を避けるというもので、Termios::Termios#{ispeed_baud,ispeed_baud=,ospeed_baud,ospeed_baud=} を新設する。でも、この名前ではもとの名前よりも長くなってしまって、使いやすいほう (使ってほしい方) を短くするという原則に反する。

次の案は名前を短くするというもので、Termios::Termios#{ibaud,ibaud=,obaud,obaud=} である。これは一案であるが、baud という単語の身近さがちょっと気になる。

次の案は ispeed と ospeed は普通同じ値にすることを利用して、Termios::Termios#{speed,speed=} を新設する。これは、ispeed, ospeed の両方を 9600 とかで指定するものである。ただ、ispeed/ospeed と speed の名前の違いで機能の違いが表現できていないように思うというのはある。

最後は Termios::Termios#{baud,baud=} で、直前2つの組合せである。単語に関する懸念は残る。

完璧といえる案は思い付かなかった。

まぁ、シリアル接続しない限り関係ない話なので、問題自身の重要性は低い。というわけで、すっきりしないうちは放っておくのがいいか。

2007-06-03 (Sun)

#1

wfo で digest authentication を扱う。

へー、HA1 があれば password はいらないから password はすぐにメモリから消せるんだな。

2007-06-04 (Mon)

#1

gnuplot の fit の結果がなんかおかしくて、最小二乗法のプログラムを自分で書いてみる

2007-06-05 (Tue)

#1

そういえば、駅の改良工事というのは、リファクタリングに通じるものがあるように思う

2007-06-06 (Wed)

#1

歩いていて、ふと水道工事にいきあたる。

なんとなしにそこにあった立看板をみて、住所が書いてあることに気がつく。

工事現場の住所である。

ここで、工事は道路の片側の車線で行われている。

その、片側の車線の住所なのだろうか?

もし道路が住所の境目だったら、反対側の車線の工事では違う住所になるのだろうか?

2007-06-08 (Fri)

#1

secrand.rb を入れて、cgi/session.rb の cookie 生成はそれを使うようにする。

2007-06-09 (Sat)

#1

お茶の水

#2

shell が tail call の最適化を行うことがある、というのは常識ではないらしい。そりゃそうか。

でも、そういう shell は珍しくない。

たとえば、bash はそうである。

以下のように、bash -c '/bin/true' とすると、/bin/true を execve する。つまり、tail call の最適化を行っている。

% strace -fe clone,execve bash -c '/bin/true' 
execve("/bin/bash", ["bash", "-c", "/bin/true"], [/* 39 vars */]) = 0
execve("/bin/true", ["/bin/true"], [/* 38 vars */]) = 0
Process 19057 detached

それに対し、bash -c '/bin/true;:' とすると、/bin/true を直接 execve するのではなく、clone で新しいプロセスを作って、その新しいのが /bin/true を execve する。つまり、tail call でないので最適化は行われない。

% strace -fe clone,execve bash -c '/bin/true;:'
execve("/bin/bash", ["bash", "-c", "/bin/true;:"], [/* 39 vars */]) = 0
clone(Process 19070 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7d85708) = 19070
Process 19069 suspended
[pid 19070] execve("/bin/true", ["/bin/true"], [/* 38 vars */]) = 0
Process 19069 resumed
Process 19070 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 19069 detached

ちなみにバージョン。

% bash --version
GNU bash, version 3.1.17(1)-release (i486-pc-linux-gnu)
Copyright (C) 2005 Free Software Foundation, Inc.

bash だけじゃなくて、zsh もそういう動作をする。

% strace -fe clone,execve zsh -c '/bin/true'
execve("/bin/zsh", ["zsh", "-c", "/bin/true"], [/* 39 vars */]) = 0
execve("/bin/true", ["/bin/true"], [/* 39 vars */]) = 0
Process 19094 detached
% zsh --version
zsh 4.3.2 (i686-pc-linux-gnu)

pdksh と ash はしない。

% strace -fe clone,execve ash -c '/bin/true'
execve("/bin/ash", ["ash", "-c", "/bin/true"], [/* 39 vars */]) = 0
clone(Process 19441 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7e6e928) = 19441
Process 19440 suspended
[pid 19441] execve("/bin/true", ["/bin/true"], [/* 39 vars */]) = 0
Process 19440 resumed
Process 19441 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 19440 detached
% pdksh -c 'echo $KSH_VERSION'
@(#)PD KSH v5.2.14 99/07/13.2

% strace -fe clone,execve pdksh -c '/bin/true'
execve("/bin/pdksh", ["pdksh", "-c", "/bin/true"], [/* 39 vars */]) = 0
clone(Process 19444 attached
child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7de9928) = 19444
[pid 19444] execve("/bin/true", ["/bin/true"], [/* 39 vars */]) = 0
Process 19444 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 19443 detached
% dpkg -s ash|grep Version 
Version: 0.5.3-7

2007-06-10 (Sun)

#1

お茶の水

#2

飲み会で提案された callcc の対処

  1. callcc を call_with_current_continuation に改名する
  2. callcc を call_with_current_continuation_note_that_I_accept_core_dump に改名する
  3. call_with_current_continuation_note_that_I_accept_core_dump で第一引数が "YES" でなければ ArgumentError
  4. callcc を一回でも使ったら、[BUG] の表示が [Not a BUG] に変わる。

2007-06-16 (Sat)

#1

唐突に、小さな扇風機を買ってみる。

2007-06-18 (Mon)

#1

シグナルがプロセスの終了を意図するとして、その意図を実現するにはどうしたらいいか。

シグナルが来ても I/O でブロックしたままという論外な事態は腐る程考えたので、それは解決したものとして考えよう。

I/O でブロックしていて無限に終わらないというのは論外だが、そうでなくても終了までに 1分かかるとかはかんべんしてもらいたい。

では、仮に、シグナルを受け取ってから 1秒以内にプロセスが終了することを仕様としたらどうなるだろうか。(いや、時間を測って処理を変えるつもりはないので、1秒というよりは十分に短期間で、というあたりか。)

その場合、プログラムはどのような制約を受けるか。

ひとつすぐにわかるのは、後始末が必要な処理は十分に抑える必要がある、ということである。

入力の大きさに比例して後始末が増えていくと、短期間に終了することができない場合が出てくる。

2007-06-19 (Tue)

#1

Emacs の文字ってなんだっけ、と思って調べ直してみる。

GNU Emacs 22 では整数である。?a は 97 になる。

XEmacs 21 では文字型である。?a は ?a になる。

#2

予想外の場所で予想外の人と出会う。

えーと、9日ぶり?

2007-06-25 (Mon)

#1

生成されるファイルをリポジトリに入れるのは愚かな事だとされる。

よくある話としては、configure とか。あるいは、Ruby の lex.c とか。

にもかかわらずそういうことをしたがる人がいるのは、checkout してから build する際に必要なツールを少なくしたいからである。

configure を入れておけば autoconf が不要になるし、lex.c を入れておけば gperf が不要になる。

そういうことがなぜ愚かであるかというと、トラブルを引き起こすからである。

よくあるのが configure で、リポジトリに新しい configure が commit され、ローカルで configure が再生成されていると、マージが起きる。このとき、当然のように conflict が起きる。対処するには、ローカルの configure を削除して update すればいいが、面倒くさい。また、対処を忘れて configure を実行するとうまく動かない。

そういう類のトラブルを避け、かつ、必要なツールを少なくするにはどうしたらいいか。

2007-06-28 (Thu)

#1

valgrind を ruby にかけると、たまに、generic_iv_tbl のあたりで問題が検出されることがある。

かなり前から気になっていたのだが、ついに原因がわかった。

どうも、スタックから取り出した未初期化の値が偶然 is_pointer_to_heap とかを通り抜けて、generic_iv_tbl によるインスタンス変数のマークのところまでたどり着き、そこでハッシュを検索するところで初期化されていない値を使った、ということになるらしい。

これを suppressions file に書くのは可能だが、本質をついていない。GC においてスタックから値を読み出すときには未初期化かどうかは気にしない、という指定をするのが素直であろう。

たしか valgrind のための指定をソースに入れるという話があったな、と思って探してみると [ruby-core:05729] である。

ただ、ここに出ているパッチだと GC 時にスタック領域を無条件に初期化済み状態に破壊的に変更するので、GC 後にスタック内での問題を検出できなくなりそうである。

それを避けるためには、んー、VALGRIND_GET_VBITS/VALGRIND_SET_VBITS で保存・回復すればいいのか?

やってみると、さらに、スタックの先端よりも先を呼んでいるところにも対処が必要であった。


[latest]


田中哲