だからそんな事に spawn を使うなと!

この記事は 2013 年 1 月 16 日の「Syn の独り言」の記事を移行したものです。

いらん所で spawn を使おうとしている人がいたので、つい。

普通、プログラムを書くときはデフォルトで stdin, stdout, stderr という入出力ストリームが始めから開いている。でも、tty 上にはこれ以外の文字列が表示される事もある。

たとえば、sudo コマンド実行時にパスワードが聞かれたりする場合。
$ sudo env >/dev/null 2>&1
と実行すると、stdout も stderr も /dev/null へ捨てられるはず。でも、パスワードの入力を促す文字列(プロンプト)はちゃんと tty に表示される。

だから、「パスワード入力を促されたら “MyPass” と入力する」プログラムを書こうとした場合、「パスワード入力を促されたら」の確認部分を stdout や stderr を監視で実装する事はできない。繰り返しになるが、入力を促す文字列は tty にのみ出力されるからだ。

ではどうしたら良いか?
spawn を使えば実装できる。

spawn とは fork してから仮想 tty を開く関数の一般名。色々な言語で実装されている。そして、tty に出力される文字列を全て (stdout や stderr も含めて) 取得できる。もちろんプロンプトもだ。

ただ、安直に spawn を使う前に考えてほしい。

何で sudo はパスワード入力を stdout や stderr ではなく、プロンプトに出力するのか?

それは、sudo 実装者が「プログラムではなく、人間に見せたい」と考えたからだろう。つまり、そもそも論として「sudo のパスワードをに入力するプロラム」自体がsudo 実装者の意図に反しており、根本の設計から良くない可能性がある。

個人的には「プロンプトを制御する」とは、「例外を握りつぶす」と似ていると思う。絶対に間違っているとは言わないが、基本的には避けて通りたい。

では、プログラムの中で sudo コマンドを実行したい場合、どうすればいいか?パスワード入力をやめればいい。sudo の場合は設定次第でパスワード無しで実行可能に出来る。だから、そもそもプログラムからパスワードを入力する必要が無いのだ。

なぜ spawn を使うよりパスワード無しにした方が良いのか。例えば、パスワードを入力させるためにはどこかにパスワードを記載する必要があるから。もしプログラムにパスワードを記載すると、subversion などのアクセス権のある人全員にパスワードがばれてしまう。

もちろんソースコードを厳重に管理するとか、実行可能な sudo コマンドを制限するとか方法はいくらでもある。でも、いらんタスクや制限を増やすのは優秀なエンジニアのする事ではない。「コード中にパスワードを絶対に書くな」とは言わないけれど、それが sudo の思想なのだ。素直に従うべきだろう。

sudo に限らず、tty に出力をするコマンドはプログラムから実行するための抜け道を用意している事が多い。その抜け道がどうしても見つからない場合や、何等かの原因で抜け道を実行する事が出来ない場合は spawn を使ってもいいかもしれない。
でも、その前に「本当にそれでいいのか?」と自問自答をする必要がある。

spawn が本当に役に立つのはクロスプラットフォームで動作する、汎用性の高いプログラムやライブラリを書く時ではないだろうか?

spawn は windows でも unix でもインターフェースが似ているのでコードの OS 依存部分が少なくなる。

また、sudo の例で言うとオプションでパスワードを入力できるようにすれば何らかの理由で sudo の設定を変更出来ない環境でも使用できるようになる。
(もちろん、設定変更できる場合に備えて空パスでも動くように実装するべき)

まあ、「動けばいい」っていう考え方もあるんだけど、こういう所からバグって生まれる気がするんだよね。

rsync の隠れた利点と強制終了

この記事はSyn の独り言 から移行、修正したものです。

とりあえず、週 1回は blog を書こうと思ったのもつかの間。前回からあっという間に 1ヶ月がすぎてしまった。

とりあえず、gmail にログインしたら Buzz という物ができて、Python についても少しずつ分かってきて、書きたい事は盛りだくさん。

もうすぐ、東京 Ruby 会議 03 もあるし。

そんな事はさておき、本日は rsync の話。

rsync と言えば、linux の差分バックアップコマンド。
主な特徴は以下

      ssh を通して、リモートのマシンにもバックアップ (コピー) 可能
      以前のバックアップ結果と比較して、新しいファイルのみコピーする事が可能
      当然、前回のバックアップ時から消されたファイルを削除する事も出来る
      Linux のハードリンクを使用することで、複数のバージョンのバックアップをとっても、更新されていないファイルは HDD 上に 1個だけにする事が可能(ディスク容量が少なくて済む)
      ファイルのバイナリ差分を取ることで、大きいファイルの更新された部分だけ差分バックアップを取ることが可能

端的に言うと、scp と cp コマンドに差分とハードリンク機能を加えたような物。詳しい事は、rsync で検索してください。この 2個の記事なんかが詳しい。
はじめてrsyncを使う方が知っておきたい6つのルール
rsyncで差分バックアップを行うための「–link-dest」オプション

さて、上記の特徴だけ見ると良いことだらけに思えるかもしれない。実際、悪い評判は聞いたことがない。でも、私は今までこのコマンドを毛嫌いしていた。

そもそも、rsync を使うのはどんな時だろう。このコマンドは shell に手入力するような物ではない。そんな時は、scp と cp だけで十分だ。

では、運用中のサーバーで定期的にバックアップを取る時はどうだろう。これには、信頼性が低い。rsync は、しょせん shell のコマンドだ。サーバーやネットワークに異常があれば、すぐにエラーとなる。

堅牢性のためにはストレージ等のハードウェアの機能や、DRBD のような信頼性の高い機能を使うべき。

費用面で難しければ、詳細なログを書き出すバックアップスクリプトを作成すればよい。信頼性が足りない分、せめて詳細なログを残したい。バイナリ差分についてはハードルが高いかもしれないが、それ以外は誰でも十分に実装する事が可能だろう。

要は、rsync とは、バックアップという重要な場面において信頼性と利便性が中途半端なコマンドと認識していた。

ところが、先日、自分の意思には反しつつも rsync を使用したshell スクリプトを作成する事があった。

スクリプトを作成したら、その後はテスト。異常発生時の挙動を調べるため、プロセスを kill したくてps コマンドにて rsync のプロセス ID を調べた。

すると、複数の rsync プロセスが存在するではないか。もちろん、rsync は 1回しか実行していない。おそらく、バックアップ速度を上げるために fork して複数プロセスが立ち上がったのだろう。

これは便利。
子プロセスの数をどうやって決定しているのかは知らないが、ほとんどの場面で、バックアップの速度が上がるだろう。

スクリプトで同様の実装をすることも可能だが、自前で fork をするとテストや引継ぎが急に難しくなる。その点、rsync ならば十分にテストされているはずだし、ドキュメントもいたる所にある。

「rsync の利便性が低い」というのは、実は私の勘違いだったのだ。やっぱり、世間の評判が高い事にはそれなりの理由が有ったのだ。

あと、信頼性はやっぱり低い。特定の子プロセスを kill したりすると、失敗した。

なお、マルチプロセスで立ち上がった rsync を意図的に途中で止めたいときは、
$ pkill rsync
とかやると良い。
タイミングによっては全て止まらない可能性もあるので、その場合は再度上記のコマンドを実行。ただ、このコマンドは全部の rsync プロセスを殺そうとするので注意してください。