数年前に、他のターミナルの操作をのぞき見できるttylogというツールについて書いてたのだけど、そのツールの動作を調べてGolangで書き直したという内容の記事を見かけた。

読んでいると、どうやらttylogは以下のステップで他のターミナルの内容をのぞき見しているらしい。

  • ttyのログインプロセスのIDを調べる
  • pidに対してstraceコマンドを使ってシステムコール(read/write)を見る
  • straceの内容を加工して出力する

…あれ? このくらいの内容なら、straceを使ってgrepやsedを使ったワンライナーでもできるじゃないか? と思ったので、やってみることにした。

以下がそのコード。ちょっと非効率な書き方しているので、動作が遅かったりするけど、まぁこの時点ではそこはいいかなと(多分Perlのワンライナーとかのほうが早い)。 ログインプロセスのIDは、sshであればそのプロセスIDを指定する。 当たり前の話だけど、 strace は必要になるので注意。

strace -e read,write -s 16384 -x -p <ログインプロセスのID> 2>&1 | grep --line-buffered -E '^(read|write)\([12]' | sed -u -r -e 's/^(read|write)\(([0-9]+), "//' -e 's/", [0-9]+\).*$//;s/\\\\/\\\\\\\\/g' -e 's/"/\\\\"/g' -e "s/'/\\\\\\\\'/g" -e "s/ /\\\\' \\\\'/g" | xargs -I@ printf $'@'

多少読みやすくしたのが以下。 いろいろなキャラクタが出力されるので、xargsでprintfに渡す際に結構加工しないといけないのが面倒だ。

strace -e read,write -s 16384 -x -p <ログインプロセスのID> 2>&1 | \
  grep --line-buffered -E '^(read|write)\([12]' | # grepでread,writeの1,2ディスクリプタを使うものだけに絞り込む \
  sed -u -r \
    -e 's/^(read|write)\(([0-9]+), "//' \
    -e 's/", [0-9]+\).*$//;s/\\\\/\\\\\\\\/g' \
    -e 's/"/\\\\"/g' \
    -e "s/'/\\\\\\\\'/g" \
    -e "s/ /\\\\' \\\\'/g" | # sedでstraceの出力を加工する \
  xargs -I@ printf $'@' # xargsで加工した内容を出力する

実際の動作しているとこがこちら。

シングルクォーテーションのエスケープが結構面倒くさい。 多分、PerlやRubyだけで記述すればもっと短く書けそうだけど、とりあえずは動いているから良しとしよう。

ちなみに、どうやらstraceはLinuxシステムコールでptraceシステムコールというものを利用して他のプロセスの入出力を取得しているようなので、そのあたりをちゃんとコード書ければstraceもいらないかもしれない。 RubyやPerlにもラッパーライブラリがあるようだし、ptraceシステムコールさえ使えればいいので、CやGo、Rustでちゃんとしたのを書いても良さそうだ(多分Cがいいんだろうけど)。