次のエントリにあるように、gdbを使うとプロセスの標準入出力を別の端末(tty)に繋ぎ変えることができる。
しかし実際にはあるプロセスからサブプロセスが複数呼ばれている状況もあり、そのような場合にはそれぞれのプロセスについてgdbでアタッチして端末を繋ぎ変える必要がある。 そこで、この操作を楽に行うためのシェルスクリプトを書いた。
#!/bin/bash # grabtty.sh rootpid="$1" newtty=$(tty) if [[ $(whoami) != "root" ]]; then echo "must be root." exit 1 fi if [[ -z "$rootpid" ]]; then echo "Usage: $0 PID" exit 1 fi grabpid() { local pid="$1" local children=$(ps --ppid=$pid -o pid --no-headings) for child in $children; do grabpid "$child" done gdb -q -p "$pid" &>/dev/null <<__EOF__ p close(0) p open("$newtty", 0) p close(1) p open("$newtty", 1) p close(2) p open("$newtty", 1) __EOF__ } grabpid "$rootpid" tpgid="ps --pid=$rootpid -o tpgid --no-headings" if [[ $($tpgid) -eq -1 ]]; then trap "kill -SIGTERM $rootpid" SIGINT else trap "kill -SIGINT -\$((\$($tpgid)))" SIGINT fi while ps --pid=$rootpid >/dev/null; do sleep 1 done
上のスクリプトでは、引数に指定したPIDを持つプロセス以下の子プロセスを再帰的にたどり、それらすべてを手元の端末に繋ぎ変えた後、指定したプロセス(ルートプロセス)が終了するまで待つ。 また、Ctrl+Cによる端末へのシグナル送信についても、元の端末を操作している感覚で扱えるようにtrapする。 具体的には、ルートプロセスが端末に繋がっている場合にはフォアグラウンドプロセスグループにそのままSIGINTを送信し、繋がっていない場合にはルートプロセスに直接SIGTERMを送り終了を試みる。
上のスクリプトを実際に使ってみる。 まず、1台のLinuxマシンに二つの端末からSSHログインし、片方の端末で次のようにプロセスツリーを作る。
$ tty /dev/pts/0 $ python Python 2.7.6 (default, Jun 22 2015, 17:58:13) [GCC 4.8.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os >>> os.system("irb") irb(main):001:0> system("sh") $ ps auxf USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND (snip) root 921 0.0 0.3 61368 3060 ? Ss 22:29 0:00 /usr/sbin/sshd -D root 1850 0.0 0.4 103560 4140 ? Ss 22:54 0:00 \_ sshd: user [priv] user 1898 0.1 0.1 103560 1936 ? S 22:54 0:00 | \_ sshd: user@pts/0 user 1899 0.1 0.6 26544 6644 pts/0 Ss 22:54 0:00 | \_ -bash user 1976 0.0 0.5 31856 5440 pts/0 S 22:54 0:00 | \_ python user 1977 0.0 0.0 4440 644 pts/0 S 22:54 0:00 | \_ sh -c irb user 1978 0.4 0.8 41484 8756 pts/0 Sl 22:54 0:00 | \_ irb user 1980 0.0 0.0 4440 652 pts/0 S 22:54 0:00 | \_ sh user 1982 0.0 0.1 19556 1376 pts/0 R+ 22:54 0:00 | \_ ps auxf root 1913 0.0 0.4 103560 4144 ? Ss 22:54 0:00 \_ sshd: user [priv] user 1961 0.0 0.1 103560 1952 ? S 22:54 0:00 \_ sshd: user@pts/1 user 1962 0.1 0.6 26544 6644 pts/1 Ss+ 22:54 0:00 \_ -bash (snip)
そして、もう片方の端末で別の端末に繋がっているbash以下のプロセスツリーを手元に繋ぎ変えて操作してみる。
$ tty /dev/pts/1 $ sudo ./grabtty.sh 1899 [ENTER] $ ^C $ exit => true irb(main):002:0> ^C irb(main):002:0> exit 2 >>> KeyboardInterrupt >>> exit() $ exit logout
それぞれのプロセスにSIGINTが適切に送られつつ、操作できていることがわかる。 また、最後にルートプロセスであるbashを終了すると、もう片方の端末のSSH接続が切断されることが確認できる。