ところで、画面の再描画に LCS を使うのはいかがなものかという気がしないでもない。
再描画は、端末が画面を書き換えることであり、最悪でも画面のサイズに比例する手間しかかからない。
しかし、LCS は O(n**2) であるから、画面のサイズの二乗の手間がかかるかもしれない。
たしかに LCS を使えば端末との通信量を減らせるかもしれないが、そのためにオーダが悪い処理を導入してもいいのか、というのが問題である。
人間の都合により画面のサイズは際限なくおおきくはならないという理屈はそれなりに説得力を持つが、unreadable とか、そういう理屈を越える用途を見つけるのも人間ではある。
setitimer で ITIMER_VIRTUAL として SIGVTALRM が起きるようにしたとき、システムコールが EINTR で終了することはないように思える。というのは、ITIMER_VIRTUAL はプロセスが実行している時間をはかるので、システムコールで止まっているときにはタイマが動かないからである。
が、しかし、マルチスレッドだと EINTR になることもあるようだ。システムコールで止まっているのとは別のスレッドが時間を消費して SIGVTALRM が発生し、止まっているスレッドにいく、ということが有りうる。
ところで、EINTR っていつからあるんだろうと思って調べてみると、Version 6 からのようだ。
とすると、socket も select も non-blocking I/O もなかったころである。
プロセス間通信は pipe か、そうでなければファイルとシグナルでどうにかする、という世界である。
「遅い」デバイスとしては tty と pipe くらいしか思い浮かばない。
とすると、EINTR の想定用途は端末上でシェルから起動するコマンドやフィルタであろう。(daemon なら tty が無いし、pipe を使っているとも思えない)
しかし、signal handler から exit するのでは済まない状況となると、どういうものかな。
Version 6 における signal の用法を調べてみる。
errno が EINTR であることを調べているところは見当たらない。
signal handler から exit しない用法はちらほらと見つかる。
いくらかは setjmp/longjmp のような大域脱出である。setjmp/longjmp はまだなかったようで、setexit/reset という jmp_buf がグローバルにひとつしかないようなものか、アセンブラで sp を回復するのだが。
大域脱出でないものは、グローバル変数をインクリメントしてるのがいくつか見つかる。そして、read 時の SIGINT 検出に使っている用法も見つかった。あと、read 中であることを確認してスタックをいじっているように思えるのがあった。
カーネルの仕様を決定する立場になったと仮定して考えると、システムコール中のシグナルはどういうふうに扱う選択肢があるか考える。
まず、システムコールが終わるまで待たせておく、というのがある。
そうしないとすれば、どうにかしてその時点でユーザプロセスに伝えるわけだが、シグナルを伝えるというのはすなわちシグナルハンドラを起動することなので、起動することになる。
起動した後にどうするかというところにシステムコールを中断するか続行するかという選択がある。中断するのが EINTR で、続行するのが再起動である。
シグナルが来る前に副作用が起きてしまっていて、中断や続行が簡単にはいかないときの問題はあるが、他の選択肢はちょっと思い浮かばない。
用法を考えると、待たせておくというのは論外で、残りは中断か続行であるが、プリミティブとしては中断を選んで再起動したければユーザプロセスに任せるというのは考え方としてはありうる。
プロセスにとって時間はいくつかある。
いくつかあるうち、実世界の時間と対応しているものを wall clock ということがある。
つまり壁時計であるが、なぜ腕時計や懐中時計や目覚し時計やビデオの時計や携帯電話の時計や歩数計付属の時計でなくとくに壁時計が選ばれたのかはよくわからない。
ところで wall というコマンドがあってこれを使うと端末に時刻も出る。これは wall clock と関係あるか、ってあるわけないか。
EINTR を検出した場所では、一般に EINTR の原因となったシグナルの目的を把握できない。
シグナルの目的は、シグナルハンドラを設定した場所では知っているわけだが、EINTR を検出する場所はレイヤが違ったりしてその知識が得られない。
とすれば、EINTR は、シグナルがきたけれどもシグナルハンドラの中で全部処理することはできないからすみやかに処理できる場所まで制御を移動せよ、という指示と考えられる。(シグナルハンドラの中で全部処理できるんなら SA_RESTART をつけて EINTR が起こらないようにできる)
ブロックしてないときにシグナルが来たら EINTR にならないが、レースコンディションについては現代に至るまで解決できていないので、ここではそれは考えないことにしよう。端末からシグナルを送るのであれば、繰り返しキーを押せばいい。
ここで、シングルスレッドとすれば、処理できる場所もしくはレイヤというのは、EINTR を検出した場所からスタックを縮めていけばどこかで出会える。
ただし、何も考えずに縮めていいのか、というとそうでもない。
シグナルの処理で必要になる程度にはプロセスがまともな状況でなければならない。たとえば、malloc を使うなら、malloc のデータ構造がまともでなければならない。
また、中断した処理を再開するのであれば、スタックの縮めた部分の情報をなんらかの形で保存しておくか、再生成できなければならない。
そういう要求を実現する方法のひとつとしては event driven framework がある。というか、そのくらいやらないとそういうことはできない。
とすると、EINTR のまともな処理には、event driven framework が要求される?
ふと、arm なマシンを使ってみた。
Ruby で EINTR に出会う方法 (1)
以下のプログラムを実行し、sysread でブロックしているときにウインドウのサイズを変える。
% ruby-1.8.6 -rcurses -e ' Curses.init_screen Curses.close_screen p STDIN.sysread(10) ' -e:4:in `sysread': Interrupted system call (Errno::EINTR) from -e:4
ポイント:
SIGWINCH シグナルハンドラがインストールされていなければ、シグナルが来ても無視されて、EINTR が起きない
マルチスレッドだと ruby は内部的に read システムコールの前に select を行う。この select に対して EINTR が起きると、自動的に select の再起動を行うので、スクリプトには EINTR が出てこない
ruby 1.9 (yarv) では、(少なくとも手元の環境では) SIGWINCH が read でブロックしているスレッドじゃなくてタイマースレッドで発生するため、EINTR にならない
Ruby で EINTR に出会う方法 (2)
以下のプログラムを実行する。
% ./ruby -ve 'Thread.new {} r, w = IO.pipe r.sysread(10) ' ruby 1.8.6 (2007-05-10 patchlevel 5000) [i686-linux] -e:3:in `sysread': Interrupted system call (Errno::EINTR) from -e:3
ポイント:
Ruby 1.8 のスレッドでは、コンテキストスイッチのために SIGVTALRM を使用する。そのために、いったんマルチスレッドになった時点から、SIGVTALRM が繰り返し発生する仕掛けになっている。
--enable-pthread では、SIGVTALRM を繰り返し発生するスレッドが生成され、nanosleep で 10ms 眠りつつ、rb_trap_immediate なときだけ SIGVTALRM を送る。rb_trap_immediate なときしか送らない、というのは TRAP_BEG ... TRAP_END の中、ということである。
Ruby で EINTR に出会う方法 (2.5)
以下のプログラムを実行する。
% ./ruby -ruri -rnet/http -e ' Thread.new {} h = Net::HTTP.new("www.ruby-lang.org") h.read_timeout = nil h.get("/") ' /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/protocol.rb:135:in `sysread': Interrupted system call (Errno::EINTR) from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/protocol.rb:135:in `rbuf_fill' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/timeout.rb:48:in `timeout' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/timeout.rb:76:in `timeout' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/protocol.rb:134:in `rbuf_fill' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/protocol.rb:116:in `readuntil' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/protocol.rb:126:in `readline' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/http.rb:2017:in `read_status_line' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/http.rb:2006:in `read_new' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/http.rb:1047:in `request' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/http.rb:1034:in `request' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/http.rb:543:in `start' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/http.rb:1032:in `request' from /home/akr/chkbuild/tmp/build/ruby-1.8-pth/20070511T201831/lib/ruby/1.8/net/http.rb:769:in `get' from -e:5
ポイント:
Net::HTTP では、sysread は timeout のブロック内で実行される。timeout はスレッドを使うので、通常は sysread はマルチスレッド状態で動作する。しかし、h.read_timeout = nil でタイムアウトを抑制すると、マルチスレッドにならず、sysread がシングルスレッドで起動する。
Ruby で EINTR に出会う方法 (3)
以下のプログラムを実行する。
% ./ruby -e 'Thread.new {} r, w = IO.pipe p w.syswrite("a" * 1024 * 1024) p w.syswrite("a" * 1024 * 1024) ' 65536 -e:4:in `syswrite': Interrupted system call (Errno::EINTR) from -e:4
ポイント:
write システムコールが EINTR を返すのは、1byte も書き込まない時点でシグナルが来たときなので、1byte も書き込めないよう、パイプを事前に満杯にしておく。
パイプを満杯にするには、パイプの大きさ以上のデータを syswrite で書き込む。通常、ブロッキングモードでの write システムコールは全部書き込むまでブロックするが、この場合、SIGVTARLM によって中断して partial write になる。パイプを満杯にするには write_nonblock を使うこともできるが、その場合には、その後の syswrite をブロックさせるためにブロッキングモードに戻さなければならない。
Ruby で EINTR に出会う方法 (4)
以下に示す拡張ライブラリを使ってプログラムを実行する。
% cat extconf.rb require 'mkmf' have_library('pthread') create_makefile('pth') % cat pth.c #include <stdlib.h> #include <errno.h> #include <signal.h> #include <pthread.h> static void * loop(void *arg) { sigset_t sigset; int ret; if (sigfillset(&sigset) == -1) { perror("sigfillset"); exit(1); } ret = pthread_sigmask(SIG_BLOCK, &sigset, NULL); if (ret != 0) { perror("pthread_sigmask"); exit(1); } while (1); } void Init_pth() { pthread_t pth; int ret; ret = pthread_create(&pth, NULL, loop, NULL); if (ret != 0) { errno = ret; perror("pthread_create"); exit(1); } } % ruby-1.8 -rpth -e ' Thread.new {} r, w = IO.pipe r.sysread(10)' -e:4:in `sysread': Interrupted system call (Errno::EINTR) from -e:4
ポイント:
--enable-pthread でな ruby 1.8 では、コンテキストスイッチのための SIGVTALRM の生成に setitimer (ITIMER_VIRTUAL) を利用する。ITIMER_VIRTUAL のタイマーはプロセスが実行しているあいだしか進まない、つまり read システムコールでブロックしている間は進まない、ので通常はブロックしているあいだには SIGVTALRM は生成されず、EINTR にもならない。
しかし、複数のネイティブスレッドがあると話は変わってくる。ひとつのネイティブスレッドが read でブロックしている間でも、他のネイティブスレッドがタイマーを進め、SIGVTALRM が発生する。発生した SIGVTALRM がブロックしているスレッドに到着し、EINTR になることがある。
Ruby で EINTR に出会う方法 (5)
以下のプログラムを実行する。
% ./ruby -e ' Thread.new { } n = 5000 w = STDOUT loop { w.write("a" * n) w.write("b" * n) w.flush } '|./ruby -e ' n = 5000 r = STDIN loop { sleep 0.1 s = r.read(n); p [s.length, s.squeeze] sleep 0.1 s = r.read(n); p [s.length, s.squeeze] } ' [5000, "a"] [5000, "b"] [5000, "a"] [5000, "b"] [5000, "a"] [5000, "b"] [5000, "a"] [5000, "b"] [5000, "a"] [5000, "b"] [5000, "a"] [5000, "ba"] [5000, "ab"] [5000, "ba"] [5000, "ab"] ...
ポイント:
ruby が IO.pipe により内部で生成するパイプは最初から sync mode であるが、標準出力はパイプであっても sync mode でない。その結果、stdio によるバッファリングが行われる。
fflush が TRAP_BEG ... TRAP_END で囲われているところがある。これは、--enable-pthread であれば、fflush 実行中に SIGVTALRM が発生することを意味する。もし write がブロックしている時点で発生すれば、EINTR になる。
一般に stdio はシステムコールのエラーに対する対処が甘い。
curses のことを考えよう。
なお例によってレースコンディションは考えない。
curses では端末のサイズが変わった後、すみやかに画面を再表示したい。そして、その後で、あたかもそんな再表示はなかったかのように元の処理を続けたい。
SIGWINCH のシグナルハンドラは端末のサイズ変更を即座に検知するのに使われる。
が、シグナルハンドラの中から画面を再表示できるかというと、これは微妙である。curses 関連のなんらかの処理をしているのであれば、画面データの一貫性が崩れているかもしれない。
curses 関連の処理をしていないのであれば、これはさらに微妙である。malloc とかはまずそうなので、そういうのを使わずにやっていけるだろうか。
そういう微妙なところを避けようとすれば、シグナルハンドラから出てから再表示することになる。シグナルハンドラは (レースコンディションを無視しているので) どこかでブロックしているときに発生するとして、EINTR が発生するので、ソフトウェアのどこかで EINTR になったら、すみやかに curses まで制御を渡してやらなければならない。そして、それが終わったらすみやかに元に戻らなければならない。
えーと、それって無理な気が?
curses とえんもゆかりもないモジュールが EINTR のときに curses を直接呼び出すのは無理なので、呼ぶなら間接的にしないといけない。そういう作業を管理する、EINTR の処理を登録しておく標準のフレームワークがあって、みんながそれを前提にしてソフトウェアを書くのであれば、できるだろうけど、そんなのは無いし。
再表示が終わったら元の処理を続行する、という条件が厳しい。そのためには、処理を中断するのが難しい。その結果、EINTR を出すシステムコールの直後に外部から処理を挿入する仕掛けが必要になる。
イベントドリブンフレームワークを使うのであれば、ブロックするシステムコールは集まっているので容易に実現できるだろうが、そういうフレームワークがあれば、というところで同じことか。
ところで、EINTR のときに何か処理をするというフレームワークがあったとすると、EINTR が起きると何か処理が行われる可能性がある。
つまりそのときに何かが変化してしまう可能性があるということで、ブロックするシステムコールの後には一貫性が崩れているかもしれない。
これで思い出したのが DragonFly BSD の Serializing Tokens である。これはブロックしているときには開放される Mutex で、ブロックした後には一貫性が崩れているかもしれないというのが似ている。
Serializing Tokens について思うのは、リモートからロックをするようなサーバは書けないのではないか、ということである。サーバが通信でブロックするとロックを開放してしまうので、ロックはあくまでもローカルにやらないといけなくて、ネットワーク透過にならない。
まぁ、ロックはネットワーク透過にはならない、と考えるのが見識かもしれない。
そういえば、ブロック中にロックが外れるというのは、pselect にも似ている?
pselect, sigsafe, cancelation point, Serializing Tokens
いずれも、他のスレッド・プロセスからの非同期通信はブロックするところで受けとるというものである。
これだけ揃うと、やっぱそうだよなぁ、という感触がかなり確信に近付く。
結局、ブロックしているときには外から中断する方法が欲しい。というか、外から中断しろといっているのに耳を貸さずにブロックしつづけているのは我慢ならない、ということか。
レースコンディションを考えあわせると、スレッドに通知した信号がブロックする時点で発火するような機能が必要になる。
ところで、ブロックとはなんだろう
端末からの読み込み待ちはとてもそれっぽい
では、ソケットからならどうか
ディスクからならどうか
ディスクからでも仮想記憶のメモリアクセスから生じるアクセスはどうか
あるいは、キャッシュミスによるメモリアクセスはどうか
はたまた、RPC はどうか
もし、Serializing Tokens でキャッシュミス時にロックが開放されるとしたら危なすぎるのではないだろうか。キャッシュミスみたいな小さなブロックは気にしないで終わるのを待つ、というのが妥当であろう。
とすると、全部のブロックを同じように扱う理由はなく、プログラムは、種々のブロックを外から中断可能とするかどうかを選べてもよいのかもしれない。
しかし、シグナルを送る側から見ると、プログラムが何でブロックしていようが知ったことではない。
どのくらいの時間でシグナルが処理されるのかという点だけが興味のあるところである。
それに対し、受け取る側では、処理しやすいタイミングもあれば、処理しにくいタイミングもある。
とすると、ある時間内に処理しやすいタイミングが来るかどうか、というのが問題か。
そういう考え方をすれば、その処理しやすいタイミング以外での EINTR は全部再起動が適切になるな。
エレベータの基本的なユーザインターフェースは以下のようになる。
ところで、階が 2つしかない場合、3つめの行きたい階の指定は冗長である。
今日、とある B1F と 1F しかないエレベータに乗ったところ、この冗長な指定を要求されて違和感を持った。
自分の心を考えると、エレベータ間の一貫性よりも、冗長な情報は提供不要という原則のほうが強く働いているようだ。
また別のエレベータに乗ってみる。
このエレベータも階が 2つしかない。(改札階、ホーム階)
すると、こちらのエレベータでは、冗長な指定は要求されなかった。
ふむ、ちゃんと考えている開発者もいるのか。
いや、ちゃんと考えていない開発者もいるのか、と思いたいというか期待したいところだ。
それはそれとして、むしろ最初の上に行くか下に行くかという指定自体が冗長であるという考え方もできる。
どうせ行きたい階を指定するのだから、その指定から上に行くか下に行くかは導出できる。
したがって、最初に行きたい階を指定するようにすれば、ステップをひとつ省くことができる。
というか、API の気分で検討すれば、そうするほうが常識的であろう。
にもかかわらずなんで現実がそうなっていないのか、ということで思い付く理由としては以下のものがある。
もうひとつ別のエレベータに乗ってみる。
これも階が 2つしかない。(改札階、連絡通路階)
ここでは冗長な指定が必要だった。
うぅむ。
おぉ、麻疹で全学休講2週間
まぁ、そういうこともあるか
なるほど、複数のエレベータがあると困りますね。
カメラかなにかで利用者をトラックして、乗り込んだ利用者の行きたい階を判断すれば、というのは、まぁ、うまく動かなそう。
それに、利用者全員が行きたい階を指定しなければならないというのもよろしくないか。いまはある階に行きたいことは、最低一人が指定すればいいのに、そうでなくなってしまう。
ところで、階が 2つよりも多い場合でも、最上階のひとつ下から上に行く場合と、最下階のひとつ上から下に行く場合は冗長性を見出せるように思う。
Web サーバの graceful shutdown を考える。
適当な signal を受け取ったら、新しいリクエストの受付を停止し、処理中のリクエストが終わったら、終了する。
ここで、ブラウザがリクエストを中途半端にしか送らない可能性を考慮すると、処理中のリクエストのうち、リクエストが全部到着しておらず副作用が生じていないものは中断しなければならない。
ここで、たとえば CGI で処理する場合の処理順序は以下のようになる。
これらの処理のうち、最初のリクエストの読み込み段階であれば中断するが、それ以降に処理が進んでしまっていれば、終わるまで待つことになる。
ここからわかるのは、まず、書き込みは (ブラウザへの書き込みでも CGIプログラムへの書き込みでも) 中断しないということである。ということは、EINTR が書き込みで起きた場合は、再起動すれば良いことになる。書き込みというのは内部の処理結果を外部に伝えるものだと考えれば、一般にそういう性質が成り立つということが期待できるかもしれない。
それに対して、読み込みはブラウザからの読み込みは中断するが、CGIプログラムからの読み込みは中断しない。
graceful shutdown を実装するには、この違いをプログラム内で表現することになる。
どうやってプログラム上で表現するのがいいだろうか。
ruby-termios をいろいろと書き換えたので作者に送る。
diff -uN ruby-termios-0.9.4/ChangeLog ruby-termios/ChangeLog --- ruby-termios-0.9.4/ChangeLog 2002-10-13 00:15:25.000000000 +0900 +++ ruby-termios/ChangeLog 2007-05-30 04:05:04.000000000 +0900 @@ -1,3 +1,32 @@ +2007-05-29 Tanaka Akira <akr@fsij.org> + + * extconf.rb: check rb_io_t. + check fd member in OpenFile. + + * termios.c: support Ruby 1.9. + use unsigned long for flags bigger than Fixnum such as CRTSCTS. + use rb_sys_fail instead of rb_raise(rb_eRuntimeError). + (Termios_to_termios): use rb_ary_entry to access cc_ary to avoid SEGV. + (termios_tcgetpgrp): use pid_t. + (termios_tcsetpgrp): ditto. + (Termios::Termios#dup): defined to duplicate cc. + (Termios::Termios#clone): ditto. + (Termios::POSIX_VDISABLE): defined. + (Termios::CCINDEX_NAMES): defined. + (Termios::IFLAG_NAMES): defined. + (Termios::OFLAG_NAMES): defined. + (Termios::OFLAG_CHOICES): defined. + (Termios::CFLAG_NAMES): defined. + (Termios::CFLAG_CHOICES): defined. + (Termios::LFLAG_NAMES): defined. + (Termios::BAUD_NAMES): defined. + (Termios::IUTF8): defined. + (Termios::VDSUSP): defined for 4.4BSD. + (Termios::VSTATUS): defined for 4.4BSD. + (Termios::MDMBUF): defined for 4.4BSD. + + * lib/termios.rb: new file. inspect and pretty_print defined. + 2002-10-13 akira yamada <akira@arika.org> * termios.c diff -uN ruby-termios-0.9.4/extconf.rb ruby-termios/extconf.rb --- ruby-termios-0.9.4/extconf.rb 2000-11-15 19:09:17.000000000 +0900 +++ ruby-termios/extconf.rb 2007-05-29 21:39:34.000000000 +0900 @@ -2,5 +2,10 @@ if have_header('termios.h') && have_header('unistd.h') + + unless have_type("rb_io_t", ["ruby.h", "rubyio.h"]) + have_struct_member("OpenFile", "fd", ["ruby.h", "rubyio.h"]) + end + create_makefile('termios') end diff -uN ruby-termios-0.9.4/termios.c ruby-termios/termios.c --- ruby-termios-0.9.4/termios.c 2002-10-13 00:15:25.000000000 +0900 +++ ruby-termios/termios.c 2007-05-30 04:07:36.000000000 +0900 @@ -12,6 +12,18 @@ #include <unistd.h> #include <string.h> +#ifdef HAVE_TYPE_RB_IO_T +typedef rb_io_t OpenFile; +#endif + +#if defined(HAVE_TYPE_RB_IO_T) || defined(HAVE_ST_FD) +# define FILENO(fptr) (fptr->fd) +#else +# define FILENO(fptr) fileno(fptr->f) +#endif + +#define validate_ulong(v) ULONG2NUM(NUM2ULONG(v)) + static VALUE mTermios; static VALUE cTermios; static VALUE tcsetattr_opt, tcflush_qs, tcflow_act; @@ -21,8 +33,7 @@ termios_set_iflag(self, value) VALUE self, value; { - Check_Type(value, T_FIXNUM); - rb_ivar_set(self, id_iflag, value); + rb_ivar_set(self, id_iflag, validate_ulong(value)); return value; } @@ -31,8 +42,7 @@ termios_set_oflag(self, value) VALUE self, value; { - Check_Type(value, T_FIXNUM); - rb_ivar_set(self, id_oflag, value); + rb_ivar_set(self, id_oflag, validate_ulong(value)); return value; } @@ -41,8 +51,7 @@ termios_set_cflag(self, value) VALUE self, value; { - Check_Type(value, T_FIXNUM); - rb_ivar_set(self, id_cflag, value); + rb_ivar_set(self, id_cflag, validate_ulong(value)); return value; } @@ -51,8 +60,7 @@ termios_set_lflag(self, value) VALUE self, value; { - Check_Type(value, T_FIXNUM); - rb_ivar_set(self, id_lflag, value); + rb_ivar_set(self, id_lflag, validate_ulong(value)); return value; } @@ -71,8 +79,7 @@ termios_set_ispeed(self, value) VALUE self, value; { - Check_Type(value, T_FIXNUM); - rb_ivar_set(self, id_ispeed, value); + rb_ivar_set(self, id_ispeed, validate_ulong(value)); return value; } @@ -81,8 +88,7 @@ termios_set_ospeed(self, value) VALUE self, value; { - Check_Type(value, T_FIXNUM); - rb_ivar_set(self, id_ospeed, value); + rb_ivar_set(self, id_ospeed, validate_ulong(value)); return value; } @@ -102,13 +108,13 @@ rb_ary_store(cc_ary, i, INT2FIX(0)); } - rb_ivar_set(self, id_iflag, Qnil); - rb_ivar_set(self, id_oflag, Qnil); - rb_ivar_set(self, id_cflag, Qnil); - rb_ivar_set(self, id_lflag, Qnil); + rb_ivar_set(self, id_iflag, INT2FIX(0)); + rb_ivar_set(self, id_oflag, INT2FIX(0)); + rb_ivar_set(self, id_cflag, INT2FIX(0)); + rb_ivar_set(self, id_lflag, INT2FIX(0)); rb_ivar_set(self, id_cc, cc_ary); - rb_ivar_set(self, id_ispeed, Qnil); - rb_ivar_set(self, id_ospeed, Qnil); + rb_ivar_set(self, id_ispeed, INT2FIX(0)); + rb_ivar_set(self, id_ospeed, INT2FIX(0)); rb_scan_args(argc, argv, "07", &c_iflag, &c_oflag, &c_cflag, &c_lflag, @@ -147,19 +153,19 @@ obj = rb_funcall(cTermios, rb_intern("new"), 0); - termios_set_iflag(obj, INT2FIX(t->c_iflag)); - termios_set_oflag(obj, INT2FIX(t->c_oflag)); - termios_set_cflag(obj, INT2FIX(t->c_cflag)); - termios_set_lflag(obj, INT2FIX(t->c_lflag)); + termios_set_iflag(obj, ULONG2NUM(t->c_iflag)); + termios_set_oflag(obj, ULONG2NUM(t->c_oflag)); + termios_set_cflag(obj, ULONG2NUM(t->c_cflag)); + termios_set_lflag(obj, ULONG2NUM(t->c_lflag)); cc_ary = rb_ary_new2(NCCS); for (i = 0; i < NCCS; i++) { - rb_ary_store(cc_ary, i, INT2FIX(t->c_cc[i])); + rb_ary_store(cc_ary, i, CHR2FIX(t->c_cc[i])); } termios_set_cc(obj, cc_ary); - termios_set_ispeed(obj, INT2FIX(cfgetispeed(t))); - termios_set_ospeed(obj, INT2FIX(cfgetospeed(t))); + termios_set_ispeed(obj, ULONG2NUM(cfgetispeed(t))); + termios_set_ospeed(obj, ULONG2NUM(cfgetospeed(t))); return obj; } @@ -172,23 +178,19 @@ int i; VALUE cc_ary; - t->c_iflag = FIX2INT(rb_ivar_get(obj, id_iflag)); - t->c_oflag = FIX2INT(rb_ivar_get(obj, id_oflag)); - t->c_cflag = FIX2INT(rb_ivar_get(obj, id_cflag)); - t->c_lflag = FIX2INT(rb_ivar_get(obj, id_lflag)); + t->c_iflag = NUM2ULONG(rb_ivar_get(obj, id_iflag)); + t->c_oflag = NUM2ULONG(rb_ivar_get(obj, id_oflag)); + t->c_cflag = NUM2ULONG(rb_ivar_get(obj, id_cflag)); + t->c_lflag = NUM2ULONG(rb_ivar_get(obj, id_lflag)); cc_ary = rb_ivar_get(obj, id_cc); for (i = 0; i < NCCS; i++) { - if (TYPE(RARRAY(cc_ary)->ptr[i]) == T_FIXNUM) { - t->c_cc[i] = NUM2INT(RARRAY(cc_ary)->ptr[i]); - } - else { - t->c_cc[i] = 0; - } + VALUE elt = rb_ary_entry(cc_ary, i); + t->c_cc[i] = NUM2CHR(elt); } - cfsetispeed(t, FIX2INT(rb_ivar_get(obj, id_ispeed))); - cfsetospeed(t, FIX2INT(rb_ivar_get(obj, id_ospeed))); + cfsetispeed(t, NUM2ULONG(rb_ivar_get(obj, id_ispeed))); + cfsetospeed(t, NUM2ULONG(rb_ivar_get(obj, id_ospeed))); } @@ -201,9 +203,8 @@ Check_Type(io, T_FILE); GetOpenFile(io, fptr); - if (tcgetattr(fileno(fptr->f), &t) < 0) { - rb_raise(rb_eRuntimeError, - "can't get terminal parameters (%s)", strerror(errno)); + if (tcgetattr(FILENO(fptr), &t) < 0) { + rb_sys_fail("tcgetattr"); } return termios_to_Termios(&t); @@ -229,7 +230,7 @@ Check_Type(opt, T_FIXNUM); if (CLASS_OF(param) != cTermios) { char *type = rb_class2name(CLASS_OF(param)); - rb_raise(rb_eArgError, + rb_raise(rb_eTypeError, "wrong argument type %s (expected Termios::Termios)", type); } @@ -243,9 +244,8 @@ old = termios_tcgetattr(io); GetOpenFile(io, fptr); Termios_to_termios(param, &t); - if (tcsetattr(fileno(fptr->f), tcsetattr_option, &t) < 0) { - rb_raise(rb_eRuntimeError, - "can't set terminal parameters (%s)", strerror(errno)); + if (tcsetattr(FILENO(fptr), tcsetattr_option, &t) < 0) { + rb_sys_fail("tcsetattr"); } return old; @@ -268,9 +268,8 @@ Check_Type(duration, T_FIXNUM); GetOpenFile(io, fptr); - if (tcsendbreak(fileno(fptr->f), FIX2INT(duration)) < 0) { - rb_raise(rb_eRuntimeError, - "can't transmits break (%s)", strerror(errno)); + if (tcsendbreak(FILENO(fptr), FIX2INT(duration)) < 0) { + rb_sys_fail("tcsendbreak"); } return Qtrue; @@ -292,8 +291,8 @@ Check_Type(io, T_FILE); GetOpenFile(io, fptr); - if (tcdrain(fileno(fptr->f)) < 0) { - rb_raise(rb_eRuntimeError, "can't drain (%s)", strerror(errno)); + if (tcdrain(FILENO(fptr)) < 0) { + rb_sys_fail("tcdrain"); } return Qtrue; @@ -317,13 +316,13 @@ Check_Type(qs, T_FIXNUM); queue_selector = FIX2INT(qs); if (rb_ary_includes(tcflush_qs, qs) != Qtrue) { - rb_raise(rb_eTypeError, + rb_raise(rb_eArgError, "wrong queue-selector value %d", queue_selector); } GetOpenFile(io, fptr); - if (tcflush(fileno(fptr->f), queue_selector) < 0) { - rb_raise(rb_eRuntimeError, "can't flush (%s)", strerror(errno)); + if (tcflush(FILENO(fptr), queue_selector) < 0) { + rb_sys_fail("tcflush"); } return Qtrue; @@ -352,9 +351,8 @@ } GetOpenFile(io, fptr); - if (tcflow(fileno(fptr->f), action) < 0) { - rb_raise(rb_eRuntimeError, - "can't control transmitting data flow (%s)", strerror(errno)); + if (tcflow(FILENO(fptr), action) < 0) { + rb_sys_fail("tcflow"); } return Qtrue; @@ -372,16 +370,15 @@ VALUE io; { OpenFile *fptr; - int pid; + pid_t pid; Check_Type(io, T_FILE); GetOpenFile(io, fptr); - if ((pid = tcgetpgrp(fileno(fptr->f))) < 0) { - rb_raise(rb_eRuntimeError, - "can't get process group id (%s)", strerror(errno)); + if ((pid = tcgetpgrp(FILENO(fptr))) < 0) { + rb_sys_fail("tcgetpgrp"); } - return INT2FIX(pid); + return LONG2NUM(pid); } static VALUE @@ -396,14 +393,14 @@ VALUE io, pgrpid; { OpenFile *fptr; + pid_t pgrp; Check_Type(io, T_FILE); - Check_Type(pgrpid, T_FIXNUM); + pgrp = NUM2LONG(pgrpid); GetOpenFile(io, fptr); - if (tcsetpgrp(fileno(fptr->f), FIX2INT(pgrpid)) < 0) { - rb_raise(rb_eRuntimeError, - "can't set process group id (%s)", strerror(errno)); + if (tcsetpgrp(FILENO(fptr), pgrp) < 0) { + rb_sys_fail("tcsetpgrp"); } return Qtrue; @@ -425,46 +422,65 @@ return rb_funcall2(cTermios, rb_intern("new"), argc, argv); } +static VALUE +termios_dup(self) + VALUE self; +{ + VALUE result; + VALUE cc_ary; + + result = rb_call_super(0, 0); + cc_ary = rb_ivar_get(self, id_cc); + rb_ivar_set(result, id_cc, rb_ary_dup(cc_ary)); + + return result; +} + void Init_termios() { - VALUE ccindex, iflags, oflags, cflags, lflags, bauds; + VALUE ccindex, ccindex_names; + VALUE iflags, iflags_names; + VALUE oflags, oflags_names, oflags_choices; + VALUE cflags, cflags_names, cflags_choices; + VALUE lflags, lflags_names; + VALUE bauds, bauds_names; /* module Termios */ mTermios = rb_define_module("Termios"); - rb_define_module_function(mTermios,"tcgetattr", termios_s_tcgetattr, 1); - rb_define_module_function(mTermios, "getattr", termios_s_tcgetattr, 1); - rb_define_method(mTermios, "tcgetattr", termios_tcgetattr, 0); - - rb_define_module_function(mTermios,"tcsetattr", termios_s_tcsetattr, 3); - rb_define_module_function(mTermios, "setattr", termios_s_tcsetattr, 3); - rb_define_method(mTermios, "tcsetattr", termios_tcsetattr, 2); - - rb_define_module_function(mTermios,"tcsendbreak",termios_s_tcsendbreak,2); - rb_define_module_function(mTermios, "sendbreak",termios_s_tcsendbreak,2); - rb_define_method(mTermios, "tcsendbreak",termios_tcsendbreak, 1); - - rb_define_module_function(mTermios,"tcdrain", termios_s_tcdrain, 1); - rb_define_module_function(mTermios, "drain", termios_s_tcdrain, 1); - rb_define_method(mTermios, "tcdrain", termios_tcdrain, 0); - - rb_define_module_function(mTermios,"tcflush", termios_s_tcflush, 2); - rb_define_module_function(mTermios, "flush", termios_s_tcflush, 2); - rb_define_method(mTermios, "tcflush", termios_tcflush, 1); - - rb_define_module_function(mTermios,"tcflow", termios_s_tcflow, 2); - rb_define_module_function(mTermios, "flow", termios_s_tcflow, 2); - rb_define_method(mTermios, "tcflow", termios_tcflow, 1); - - rb_define_module_function(mTermios,"tcgetpgrp", termios_s_tcgetpgrp, 1); - rb_define_module_function(mTermios, "getpgrp", termios_s_tcgetpgrp, 1); - rb_define_method(mTermios, "tcgetpgrp", termios_tcgetpgrp, 0); - - rb_define_module_function(mTermios,"tcsetpgrp", termios_s_tcsetpgrp, 2); - rb_define_module_function(mTermios, "setpgrp", termios_s_tcsetpgrp, 2); - rb_define_method(mTermios, "tcsetpgrp", termios_tcsetpgrp, 1); + rb_define_singleton_method(mTermios,"tcgetattr", termios_s_tcgetattr, 1); + rb_define_module_function(mTermios, "getattr", termios_s_tcgetattr, 1); + rb_define_method(mTermios, "tcgetattr", termios_tcgetattr, 0); + + rb_define_singleton_method(mTermios,"tcsetattr", termios_s_tcsetattr, 3); + rb_define_module_function(mTermios, "setattr", termios_s_tcsetattr, 3); + rb_define_method(mTermios, "tcsetattr", termios_tcsetattr, 2); + + rb_define_singleton_method(mTermios,"tcsendbreak",termios_s_tcsendbreak,2); + rb_define_module_function(mTermios, "sendbreak",termios_s_tcsendbreak,2); + rb_define_method(mTermios, "tcsendbreak",termios_tcsendbreak, 1); + + rb_define_singleton_method(mTermios,"tcdrain", termios_s_tcdrain, 1); + rb_define_module_function(mTermios, "drain", termios_s_tcdrain, 1); + rb_define_method(mTermios, "tcdrain", termios_tcdrain, 0); + + rb_define_singleton_method(mTermios,"tcflush", termios_s_tcflush, 2); + rb_define_module_function(mTermios, "flush", termios_s_tcflush, 2); + rb_define_method(mTermios, "tcflush", termios_tcflush, 1); + + rb_define_singleton_method(mTermios,"tcflow", termios_s_tcflow, 2); + rb_define_module_function(mTermios, "flow", termios_s_tcflow, 2); + rb_define_method(mTermios, "tcflow", termios_tcflow, 1); + + rb_define_singleton_method(mTermios,"tcgetpgrp", termios_s_tcgetpgrp, 1); + rb_define_module_function(mTermios, "getpgrp", termios_s_tcgetpgrp, 1); + rb_define_method(mTermios, "tcgetpgrp", termios_tcgetpgrp, 0); + + rb_define_singleton_method(mTermios,"tcsetpgrp", termios_s_tcsetpgrp, 2); + rb_define_module_function(mTermios, "setpgrp", termios_s_tcsetpgrp, 2); + rb_define_method(mTermios, "tcsetpgrp", termios_tcsetpgrp, 1); rb_define_module_function(mTermios,"new_termios",termios_s_newtermios, -1); @@ -480,15 +496,17 @@ id_ispeed = rb_intern("@ispeed"); id_ospeed = rb_intern("@ospeed"); - rb_attr(cTermios, rb_intern("iflag"), 1, 0, Qtrue); - rb_attr(cTermios, rb_intern("oflag"), 1, 0, Qtrue); - rb_attr(cTermios, rb_intern("cflag"), 1, 0, Qtrue); - rb_attr(cTermios, rb_intern("lflag"), 1, 0, Qtrue); - rb_attr(cTermios, rb_intern("cc"), 1, 0, Qtrue); - rb_attr(cTermios, rb_intern("ispeed"), 1, 0, Qtrue); - rb_attr(cTermios, rb_intern("ospeed"), 1, 0, Qtrue); + rb_attr(cTermios, rb_intern("iflag"), 1, 0, Qfalse); + rb_attr(cTermios, rb_intern("oflag"), 1, 0, Qfalse); + rb_attr(cTermios, rb_intern("cflag"), 1, 0, Qfalse); + rb_attr(cTermios, rb_intern("lflag"), 1, 0, Qfalse); + rb_attr(cTermios, rb_intern("cc"), 1, 0, Qfalse); + rb_attr(cTermios, rb_intern("ispeed"), 1, 0, Qfalse); + rb_attr(cTermios, rb_intern("ospeed"), 1, 0, Qfalse); rb_define_private_method(cTermios, "initialize", termios_initialize, -1); + rb_define_method(cTermios, "dup", termios_dup, 0); + rb_define_method(cTermios, "clone", termios_dup, 0); rb_define_method(cTermios, "iflag=", termios_set_iflag, 1); rb_define_method(cTermios, "oflag=", termios_set_oflag, 1); @@ -516,24 +534,41 @@ /* constants under Termios module */ rb_define_const(mTermios, "NCCS", INT2FIX(NCCS)); + rb_define_const(mTermios, "POSIX_VDISABLE", INT2FIX(_POSIX_VDISABLE)); ccindex = rb_hash_new(); + ccindex_names = rb_ary_new(); rb_define_const(mTermios, "CCINDEX", ccindex); + rb_define_const(mTermios, "CCINDEX_NAMES", ccindex_names); iflags = rb_hash_new(); + iflags_names = rb_ary_new(); rb_define_const(mTermios, "IFLAGS", iflags); + rb_define_const(mTermios, "IFLAG_NAMES", iflags_names); oflags = rb_hash_new(); + oflags_names = rb_ary_new(); + oflags_choices = rb_hash_new(); rb_define_const(mTermios, "OFLAGS", oflags); + rb_define_const(mTermios, "OFLAG_NAMES", oflags_names); + rb_define_const(mTermios, "OFLAG_CHOICES", oflags_choices); cflags = rb_hash_new(); + cflags_names = rb_ary_new(); + cflags_choices = rb_hash_new(); rb_define_const(mTermios, "CFLAGS", cflags); + rb_define_const(mTermios, "CFLAG_NAMES", cflags_names); + rb_define_const(mTermios, "CFLAG_CHOICES", cflags_choices); lflags = rb_hash_new(); + lflags_names = rb_ary_new(); rb_define_const(mTermios, "LFLAGS", lflags); + rb_define_const(mTermios, "LFLAG_NAMES", lflags_names); bauds = rb_hash_new(); + bauds_names = rb_ary_new(); rb_define_const(mTermios, "BAUDS", bauds); + rb_define_const(mTermios, "BAUD_NAMES", bauds_names); tcsetattr_opt = rb_ary_new(); rb_define_const(mTermios, "SETATTR_OPTS", tcsetattr_opt); @@ -546,15 +581,30 @@ #define define_flag(hash, flag) \ { \ - rb_define_const(mTermios, #flag, INT2FIX(flag)); \ + rb_define_const(mTermios, #flag, ULONG2NUM(flag)); \ rb_hash_aset(hash, rb_const_get(mTermios, rb_intern(#flag)), \ ID2SYM(rb_intern(#flag))); \ + rb_ary_push(hash##_names, ID2SYM(rb_intern(#flag)));\ } #define define_flag2(ary, flag) \ { \ rb_define_const(mTermios, #flag, INT2FIX(flag)); \ rb_ary_push(ary, rb_const_get(mTermios, rb_intern(#flag)));\ } +#define define_choice(hash, mask, value) \ + { \ + VALUE a; \ + rb_define_const(mTermios, #value, ULONG2NUM(value)); \ + rb_hash_aset(hash, rb_const_get(mTermios, rb_intern(#value)), \ + ID2SYM(rb_intern(#value))); \ + rb_ary_push(hash##_names, ID2SYM(rb_intern(#value)));\ + a = rb_hash_aref(hash##_choices, ID2SYM(rb_intern(#mask))); \ + if (a == Qnil) { \ + a = rb_ary_new(); \ + rb_hash_aset(hash##_choices, ID2SYM(rb_intern(#mask)), a); \ + } \ + rb_ary_push(a, ID2SYM(rb_intern(#value))); \ + } /* c_cc characters */ #ifdef VINTR @@ -572,11 +622,11 @@ #ifdef VEOF define_flag(ccindex, VEOF); #endif -#ifdef VTIME - define_flag(ccindex, VTIME); +#ifdef VEOL + define_flag(ccindex, VEOL); #endif -#ifdef VMIN - define_flag(ccindex, VMIN); +#ifdef VEOL2 + define_flag(ccindex, VEOL2); #endif #ifdef VSWTC define_flag(ccindex, VSWTC); @@ -590,8 +640,8 @@ #ifdef VSUSP define_flag(ccindex, VSUSP); #endif -#ifdef VEOL - define_flag(ccindex, VEOL); +#ifdef VDSUSP + define_flag(ccindex, VDSUSP); #endif #ifdef VREPRINT define_flag(ccindex, VREPRINT); @@ -605,8 +655,14 @@ #ifdef VLNEXT define_flag(ccindex, VLNEXT); #endif -#ifdef VEOL2 - define_flag(ccindex, VEOL2); +#ifdef VSTATUS + define_flag(ccindex, VSTATUS); +#endif +#ifdef VTIME + define_flag(ccindex, VTIME); +#endif +#ifdef VMIN + define_flag(ccindex, VMIN); #endif /* c_iflag bits */ @@ -637,21 +693,24 @@ #ifdef ICRNL define_flag(iflags, ICRNL); #endif -#ifdef IUCLC - define_flag(iflags, IUCLC); -#endif #ifdef IXON define_flag(iflags, IXON); #endif -#ifdef IXANY - define_flag(iflags, IXANY); -#endif #ifdef IXOFF define_flag(iflags, IXOFF); #endif +#ifdef IUCLC + define_flag(iflags, IUCLC); +#endif +#ifdef IXANY + define_flag(iflags, IXANY); +#endif #ifdef IMAXBEL define_flag(iflags, IMAXBEL); #endif +#ifdef IUTF8 + define_flag(iflags, IUTF8); +#endif /* c_oflag bits */ #ifdef OPOST @@ -660,12 +719,12 @@ #ifdef OLCUC define_flag(oflags, OLCUC); #endif -#ifdef ONLCR - define_flag(oflags, ONLCR); -#endif #ifdef OCRNL define_flag(oflags, OCRNL); #endif +#ifdef ONLCR + define_flag(oflags, ONLCR); +#endif #ifdef ONOCR define_flag(oflags, ONOCR); #endif @@ -678,74 +737,80 @@ #ifdef OFDEL define_flag(oflags, OFDEL); #endif +#ifdef ONOEOT + define_flag(oflags, ONOEOT); +#endif +#ifdef OXTABS + define_flag(oflags, OXTABS); +#endif #ifdef NLDLY define_flag(oflags, NLDLY); #endif #ifdef NL0 - define_flag(oflags, NL0); + define_choice(oflags, NLDLY, NL0); #endif #ifdef NL1 - define_flag(oflags, NL1); + define_choice(oflags, NLDLY, NL1); #endif #ifdef CRDLY define_flag(oflags, CRDLY); #endif #ifdef CR0 - define_flag(oflags, CR0); + define_choice(oflags, CRDLY, CR0); #endif #ifdef CR1 - define_flag(oflags, CR1); + define_choice(oflags, CRDLY, CR1); #endif #ifdef CR2 - define_flag(oflags, CR2); + define_choice(oflags, CRDLY, CR2); #endif #ifdef CR3 - define_flag(oflags, CR3); + define_choice(oflags, CRDLY, CR3); #endif #ifdef TABDLY define_flag(oflags, TABDLY); #endif #ifdef TAB0 - define_flag(oflags, TAB0); + define_choice(oflags, TABDLY, TAB0); #endif #ifdef TAB1 - define_flag(oflags, TAB1); + define_choice(oflags, TABDLY, TAB1); #endif #ifdef TAB2 - define_flag(oflags, TAB2); + define_choice(oflags, TABDLY, TAB2); #endif #ifdef TAB3 - define_flag(oflags, TAB3); + define_choice(oflags, TABDLY, TAB3); #endif #ifdef XTABS - define_flag(oflags, XTABS); + define_choice(oflags, TABDLY, XTABS); #endif #ifdef BSDLY define_flag(oflags, BSDLY); #endif #ifdef BS0 - define_flag(oflags, BS0); + define_choice(oflags, BSDLY, BS0); #endif #ifdef BS1 - define_flag(oflags, BS1); + define_choice(oflags, BSDLY, BS1); #endif #ifdef VTDLY define_flag(oflags, VTDLY); #endif #ifdef VT0 - define_flag(oflags, VT0); + define_choice(oflags, VTDLY, VT0); #endif #ifdef VT1 - define_flag(oflags, VT1); + define_choice(oflags, VTDLY, VT1); #endif #ifdef FFDLY define_flag(oflags, FFDLY); #endif #ifdef FF0 - define_flag(oflags, FF0); + define_choice(oflags, FFDLY, FF0); #endif #ifdef FF1 - define_flag(oflags, FF1); + define_choice(oflags, FFDLY, FF1); #endif /* c_cflag bit meaning */ @@ -806,20 +871,29 @@ #ifdef EXTB define_flag(cflags, EXTB); #endif +#ifdef PARENB + define_flag(cflags, PARENB); +#endif +#ifdef PARODD + define_flag(cflags, PARODD); +#endif #ifdef CSIZE define_flag(cflags, CSIZE); #endif #ifdef CS5 - define_flag(cflags, CS5); + define_choice(cflags, CSIZE, CS5); #endif #ifdef CS6 - define_flag(cflags, CS6); + define_choice(cflags, CSIZE, CS6); #endif #ifdef CS7 - define_flag(cflags, CS7); + define_choice(cflags, CSIZE, CS7); #endif #ifdef CS8 - define_flag(cflags, CS8); + define_choice(cflags, CSIZE, CS8); +#endif +#ifdef HUPCL + define_flag(cflags, HUPCL); #endif #ifdef CSTOPB define_flag(cflags, CSTOPB); @@ -827,15 +901,6 @@ #ifdef CREAD define_flag(cflags, CREAD); #endif -#ifdef PARENB - define_flag(cflags, PARENB); -#endif -#ifdef PARODD - define_flag(cflags, PARODD); -#endif -#ifdef HUPCL - define_flag(cflags, HUPCL); -#endif #ifdef CLOCAL define_flag(cflags, CLOCAL); #endif @@ -854,12 +919,48 @@ #ifdef B460800 define_flag(bauds, B460800); #endif +#ifdef B500000 + define_flag(bauds, B500000); +#endif +#ifdef B576000 + define_flag(bauds, B576000); +#endif +#ifdef B921600 + define_flag(bauds, B921600); +#endif +#ifdef B1000000 + define_flag(bauds, B1000000); +#endif +#ifdef B1152000 + define_flag(bauds, B1152000); +#endif +#ifdef B1500000 + define_flag(bauds, B1500000); +#endif +#ifdef B2000000 + define_flag(bauds, B2000000); +#endif +#ifdef B2500000 + define_flag(bauds, B2500000); +#endif +#ifdef B3000000 + define_flag(bauds, B3000000); +#endif +#ifdef B3500000 + define_flag(bauds, B3500000); +#endif +#ifdef B4000000 + define_flag(bauds, B4000000); +#endif #ifdef CIBAUD define_flag(cflags, CIBAUD); #endif #ifdef CRTSCTS define_flag(cflags, CRTSCTS); #endif +#ifdef MDMBUF + define_flag(cflags, MDMBUF); +#endif /* c_lflag bits */ #ifdef ISIG @@ -868,8 +969,8 @@ #ifdef ICANON define_flag(lflags, ICANON); #endif -#ifdef XCASE - define_flag(lflags, XCASE); +#ifdef IEXTEN + define_flag(lflags, IEXTEN); #endif #ifdef ECHO define_flag(lflags, ECHO); @@ -886,15 +987,18 @@ #ifdef NOFLSH define_flag(lflags, NOFLSH); #endif +#ifdef XCASE + define_flag(lflags, XCASE); +#endif #ifdef TOSTOP define_flag(lflags, TOSTOP); #endif -#ifdef ECHOCTL - define_flag(lflags, ECHOCTL); -#endif #ifdef ECHOPRT define_flag(lflags, ECHOPRT); #endif +#ifdef ECHOCTL + define_flag(lflags, ECHOCTL); +#endif #ifdef ECHOKE define_flag(lflags, ECHOKE); #endif @@ -904,8 +1008,14 @@ #ifdef PENDIN define_flag(lflags, PENDIN); #endif -#ifdef IEXTEN - define_flag(lflags, IEXTEN); +#ifdef ALTWERASE + define_flag(lflags, ALTWERASE); +#endif +#ifdef EXTPROC + define_flag(lflags, EXTPROC); +#endif +#ifdef NOKERNINFO + define_flag(lflags, NOKERNINFO); #endif /* tcflow() and TCXONC use these */ diff -uN ruby-termios-0.9.4/lib/termios.rb ruby-termios/lib/termios.rb --- ruby-termios-0.9.4/lib/termios.rb 1970-01-01 09:00:00.000000000 +0900 +++ ruby-termios/lib/termios.rb 2007-05-30 11:15:28.000000000 +0900 @@ -0,0 +1,151 @@ +require 'termios.so' + +module Termios + VISIBLE_CHAR = {} + [ + "^@", "^A", "^B", "^C", "^D", "^E", "^F", "^G", + "^H", "^I", "^J", "^K", "^L", "^M", "^N", "^O", + "^P", "^Q", "^R", "^S", "^T", "^U", "^V", "^W", + "^X", "^Y", "^Z", "^[", "^\\", "^]", "^^", "^_", + "<sp>", "!", "\"", "#", "$", "%", "&", "'", + "(", ")", "*", "+", ",", "-", ".", "/", + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", ":", ";", "<", "=", ">", "?", + "@", "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "[", "\\", "]", "^", "_", + "`", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "}", "~", "^?", + "M-^@", "M-^A", "M-^B", "M-^C", "M-^D", "M-^E", "M-^F", "M-^G", + "M-^H", "M-^I", "M-^J", "M-^K", "M-^L", "M-^M", "M-^N", "M-^O", + "M-^P", "M-^Q", "M-^R", "M-^S", "M-^T", "M-^U", "M-^V", "M-^W", + "M-^X", "M-^Y", "M-^Z", "M-^[", "M-^\\", "M-^]", "M-^^", "M-^_", + "M-<sp>", "M-!", "M-\"", "M-#", "M-$", "M-%", "M-&", "M-'", + "M-(", "M-)", "M-*", "M-+", "M-,", "M--", "M-.", "M-/", + "M-0", "M-1", "M-2", "M-3", "M-4", "M-5", "M-6", "M-7", + "M-8", "M-9", "M-:", "M-;", "M-<", "M-=", "M->", "M-?", + "M-@", "M-A", "M-B", "M-C", "M-D", "M-E", "M-F", "M-G", + "M-H", "M-I", "M-J", "M-K", "M-L", "M-M", "M-N", "M-O", + "M-P", "M-Q", "M-R", "M-S", "M-T", "M-U", "M-V", "M-W", + "M-X", "M-Y", "M-Z", "M-[", "M-\\", "M-]", "M-^", "M-_", + "M-`", "M-a", "M-b", "M-c", "M-d", "M-e", "M-f", "M-g", + "M-h", "M-i", "M-j", "M-k", "M-l", "M-m", "M-n", "M-o", + "M-p", "M-q", "M-r", "M-s", "M-t", "M-u", "M-v", "M-w", + "M-x", "M-y", "M-z", "M-{", "M-|", "M-}", "M-~", "M-^?", + ].each_with_index {|s, i| + VISIBLE_CHAR[i] = s + VISIBLE_CHAR[[i].pack("C")] = s + } + VISIBLE_CHAR[POSIX_VDISABLE] = "<undef>" + VISIBLE_CHAR[[POSIX_VDISABLE].pack("C")] = "<undef>" + + class Termios + def inspect + str = "\#<#{self.class}" + if self.ispeed == self.ospeed + speed = (BAUDS[self.ispeed] || "B???").to_s[1..-1] + str << " speed #{speed} baud;" + else + ispeed = (BAUDS[self.ispeed] || "B???").to_s[1..-1] + ospeed = (BAUDS[self.ospeed] || "B???").to_s[1..-1] + str << " ispeed #{ispeed} baud; ospeed #{ospeed} baud;" + end + + CCINDEX_NAMES.each {|ccindex| + next if ccindex == :VMIN || ccindex == :VTIME + str << " #{ccindex.to_s[1..-1].downcase}" + str << "=#{VISIBLE_CHAR[self.cc[::Termios.const_get(ccindex)]]}" + } + str << " min=#{self.cc[VMIN]}" + str << " time=#{self.cc[VTIME]}" + + [ + [:cflag, + CFLAG_NAMES-[:CBAUD, :CBAUDEX, :CIBAUD, :EXTA, :EXTB], + CFLAG_CHOICES], + [:iflag, IFLAG_NAMES, nil], + [:oflag, OFLAG_NAMES, OFLAG_CHOICES], + [:lflag, LFLAG_NAMES, nil] + ].each {|l| + str << ";" + flag_type, flag_names, choices = l + flags = self.send(flag_type) + choice_names = choices ? choices.values.flatten : [] + (flag_names-choice_names).each {|name| + str << " " + if choices and ns = choices[name] + mask = ::Termios.const_get(name) + ns.each {|n| + if (flags & mask) == ::Termios.const_get(n) + str << n.to_s.downcase + break + end + } + else + str << "-" if (flags & ::Termios.const_get(name)) == 0 + str << name.to_s.downcase + end + } + } + + str << ">" + str + end + + def pretty_print(q) + q.object_group(self) { + if self.ispeed == self.ospeed + speed = (BAUDS[self.ispeed] || "B???").to_s[1..-1] + q.fill_breakable; q.text "speed #{speed} baud;" + else + ispeed = (BAUDS[self.ispeed] || "B???").to_s[1..-1] + ospeed = (BAUDS[self.ospeed] || "B???").to_s[1..-1] + q.fill_breakable; q.text "ispeed #{ispeed} baud;" + q.fill_breakable; q.text "ospeed #{ospeed} baud;" + end + q.breakable + + q.seplist(CCINDEX_NAMES-[:VMIN, :VTIME], + lambda { q.fill_breakable }) {|ccindex| + q.text ccindex.to_s[1..-1].downcase + q.text "=#{VISIBLE_CHAR[self.cc[::Termios.const_get(ccindex)]]}" + } + q.breakable; q.text "min=#{self.cc[VMIN]}" + q.fill_breakable; q.text "time=#{self.cc[VTIME]}" + + [ + [:cflag, + CFLAG_NAMES-[:CBAUD, :CBAUDEX, :CIBAUD, :EXTA, :EXTB], + CFLAG_CHOICES], + [:iflag, IFLAG_NAMES, nil], + [:oflag, OFLAG_NAMES, OFLAG_CHOICES], + [:lflag, LFLAG_NAMES, nil] + ].each {|l| + q.text ";" + q.breakable + flag_type, flag_names, choices = l + flags = self.send(flag_type) + choice_names = choices ? choices.values.flatten : [] + q.seplist(flag_names-choice_names, + lambda { q.fill_breakable }) {|name| + if choices and ns = choices[name] + mask = ::Termios.const_get(name) + ns.each {|n| + if (flags & mask) == ::Termios.const_get(n) + q.text n.to_s.downcase + break + end + } + else + q.text "-" if (flags & ::Termios.const_get(name)) == 0 + q.text name.to_s.downcase + end + } + } + } + end + end +end
tcsetattr は Termios.setattr で呼び出せる。しかし、Termios.setattr は (変更前の状態を返すために) tcgetattr も呼び出す。
そして、tcgetattr を呼び出さず tcsetattr だけを呼び出す方法がない。
これをどう考えるか?
まず、プリミティブを提供していない、というのは事実である。
従って、tcsetattr を呼び出したいだけなのに、というときには無駄な tcgetattr に気分が悪くなる。しかし、無駄であるという以上の問題ではない。
では、利点があるかというと、どうだろうか。
なぜこのような動作になっているかという記述は見つけられなかったので推測になるが、おそらく、あとで状態を元に戻すことを支援しているのではないかと思う。あとで状態を戻すなら、もともとの状態をどこかで取得しなければならず、それを容易にしているのではないか。たしかに状態を元に戻すのは支援してほしい。
しかし、Termios.setattr には、termios オブジェクトをわたさなければならない。ということは、Termios.setattr 以前のどこかで Termios.getattr で状態を得ているはずである。
それなら、その状態を複製すればいいわけで、Termios.setattr でもう一回状態を得るのは、あまり支援になっていない感じがする。
ただ、dup は cc の配列を複製しない (しなかった) ので、それを避けるためには使えるか。でも、それが理由なら dup を定義しなおすほうがいいと思う。
また、状態を元に戻すことを推奨するなら、もっと直接的に支援したほうがいいのではないか。せっかく Ruby でブロックが気楽に使えるのだから、ブロックから出たら元に戻るのがいいと思う。
Debian の libtermios-ruby1.8 にも送っておく。
お、maintainer と作者が違うのか。
<URL:http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=426861>
ついでに関係ないが xterm にも送る。
<URL:http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=426863>
[latest]