Hatena::ブログ(Diary)

mooz deceives you

(about 'mooz) ; => "See http://mooz.github.com/index-ja.html"

 | 

July 18 (Mon), 2011

Rios::Proxy について #rubykaigi 2011 で発表しました

Ruby 会議 2011

先日行われた Ruby 会議 2011 の LT で Rios という自作のモジュールについて発表する機会を頂きました.SlideShare に発表資料を上げましたので,以下に URL を載せておきます(何を血迷ったか自作の別ツールを宣伝してしまってスミマセン……).

この記事では LT 中に触れられなかった点について,簡単な補足などを行ないます.

Rios

Rios を一言で説明すると「CLI (Command Line Interface) のためのプロキシフレームワーク」となります.「プロキシフレームワーク」という言葉自体が私による全くの造語なため,イメージもわきにくいことでしょう.

「CLI」という言葉は,Rios の対象とするアプリケーションを意味します.こうしたアプリケーションでは,インタフェースが文字のみで構成されており,ユーザも文字を打ち込んで操作を行ないます.例としては bashzsh などのシェルや,Emacs / Vim などのエディタなどがあげられます.

「プロキシ」という言葉はRios の挙動を表しています.Rios は以下の図に示すように,コマンドラインアプリケーションとユーザの間に入り込み,彼らの間で交わされる全ての文字的メッセージを仲介し,監視し,時には改変します.この動作はまさにプロキシ的です.

f:id:mooz:20110719004744p:image

「フレームワーク」という言葉は,「Rios を用いることによって以下に示すような様々なアプリケーションを容易に構築することが出来る」という理由によりつけたものです.「ライブラリ」と呼んでも語弊はないでしょう.しかし「フレームワーク」の方が,響きが良い気がしています.

Rios の開発は GitHub で行なっています.何か面白いことを思いついた方はお気軽に pull/request を.

mooz/rios - GitHub

仕組み

非常に雑駁となってしまいましたが,ここでは Rios の仕組みについて解説します.

3 つのプロセス

Rios は基本的に script コマンドと同様のアイディアで動いています.具体的には fork()*1 を 2 回行ない,計 3 つのプロセスを立ち上げます.そして以下のような役割分担をして処理を行なっていきます.

プロセス作成方法役割
親プロセス元のプロセスユーザの入力をフックし,処理結果をプロキシ対象に渡す
子プロセス親プロセスから fork()プログラムからの出力をフックし,処理結果を端末に表示する
孫プロセス子プロセスから fork()プロキシ対象の起動(プログラムなら exec(),ブロックなら実行)

簡略化したコードで示すと,以下のようになります(rios/lib/rios/proxy.rb).

fork {
  fork {
    do_command()
  }
  do_output()
}
do_input()

ここで,本家である script コマンドのやっていることを説明しておきましょう.script コマンドは do_output() 内で,端末への出力と同じ内容をファイルへ書き込んでいるのです.どうでしょうか.非常に単純です.

Rios では script コマンドにおける「端末への出力と同じ内容をファイルへ書き込む」という処理部分をユーザが Ruby で自由に記述できるようになっているため,さらなる応用が可能となっています*2

コマンドライン入出力のフック

さて,プロキシ対象のコマンドライン出力と入力はどのようにしてフックするのでしょうか.この部分を,簡単な手順として以下に書き下しました.

  1. openpty(3) により pty (master) と tty (slave) のペアを作る(この時以下の関係が成り立つ)
    • pty の出力は tty への入力となる
    • tty の出力は pty への入力となる
  2. プロキシ対象の $stdin, $stdout, $stderr を全て tty (slave) に切り替える
    • プロキシ対象は,この tty (slave) に対して出力を行ない,また tty (slave) から入力を受け取る
  3. Rios 側では
    • 親プロセスが $stdin からユーザの入力を受け取り,それを pty に書き込む
      • ここで pty へ書き込んだ内容が,tty (プロキシ対象)への入力として渡る
    • 子プロセスが pty から入力を受け取り,それを端末に出力する
      • ここで pty から得られる内容はプロキシ対象が tty に書き込んだものとなっている

リンク関係のある pty, tty のペアを利用して,うまく横流しをやっているというわけです.これに関しては他にも色々と特筆すべき点があるのですが,この辺りの説明は以下の良書に譲りたいと思います.

Rabbit

今回は Ruby 会議での発表ということや様々な事情があり,発表資料の作成には Rabbit を用いています.元々は使い慣れた PowerPoint で資料を作り,それを PDF 形式で保存したへ上で Rabbit により「ウサギと亀」を表示する予定でしたが,途中で Windows と Ubuntu を行き来することに嫌気が指したために,結局は RDoc 形式で作成を行なうことになりました.実際にスライドを作成してみるとレンダリングの綺麗さ,そしてテキストベースなスライド作成の行いやすさに感銘を受けてしまい,これからも Rabbit にはお世話になっていくだろうと感じているところです.

RDoc で作成した発表資料を PDF 形式に変換することも簡単にできるので,驚きました.以下のように -p オプションを渡した上で,出力先に拡張子が .pdf なファイルを指定するだけです.

rabbit ./slide.rd -p -o slide.pdf

CodeRay を使ってハイライトしたソースコードをスライドに含めると,そのスライドの表示が幾分もたつく点が気になったので,時間を見つけて Rabbit のソースコードも探検してみようと考えているところです.

*1:Windows や NetBSD 版の Ruby では fork が動かないという話もありましたが,今はどうなっているのでしょうかね

*2:もちろん script コマンドのような挙動は容易に模倣が可能です

 |