ssh経由でリモートホストで実行してるプロセスにSIGINT送りたい時

perlで色々管理スクリプト書いてるんですが、そのなかでこんなコードを書きました。

system("ssh remote 'rsync ...'");

で、rsyncが走ってる途中でやっぱやめたと思ってCtrl+C=SIGINTを送ったんですが、もちろんperlのプロセスは死ぬんですけど、remoteで動いてるrsyncはそのままゾンビになって残ってしまいました。

はて、いろんなところに原因が考えられるなぁということで調べてみました。

host1> ssh host2 'some-command'

host2> strace -p 20279 # some-command's pid
Process 20279 attached - interrupt to quit
read(0, 

# then "Ctrl+C" on host1

(host2)
"", 4096)                       = 0
write(2, "Use of uninitialized value in co"..., 85) = -1 EPIPE (Broken pipe)
--- SIGPIPE (Broken pipe) @ 0 (0) ---
rt_sigreturn(0)                         = -1 EPIPE (Broken pipe)
rt_sigprocmask(SIG_BLOCK, [PIPE], NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [PIPE], NULL, 8) = 0
write(2, "sigpipe at hogehoge line "..., 35) = -1 EPIPE (Broken pipe)
--- SIGPIPE (Broken pipe) @ 0 (0) ---
rt_sigreturn(0)                         = -1 EPIPE (Broken pipe)
exit_group(32)                          = ?
Process 20279 detached

なるほど、sshにSIGINTを送っても、リモートホストにはSIGINTが飛んでないんですね。でも、ssh自体は死んでしまうのでリモート側のsome-commandはSIGPIPEが送られることになり、今回は結果としてそのシグナルで死んでる様です。未確認ですが、おそらくrsyncはSIGPIPEでは死んでくれなかったから、ゾンビで動きつづけたんでしょうね。

とすると、sshでのシグナルのハンドリングを調査しないといけないなーと思ってたんですが、普通にググってたらぴったりの質問がstackoverflowにありました。

host1> ssh -t host2 'some-command'

host2> strace -p 4480
...
read(0, 0x2b73920, 4096)                = ? ERESTARTSYS (To be restarted)
--- SIGINT (Interrupt) @ 0 (0) ---
rt_sigreturn(0)                         = -1 EINTR (Interrupted system call)
rt_sigprocmask(SIG_BLOCK, [INT], NULL, 8) = 0
rt_sigprocmask(SIG_UNBLOCK, [INT], NULL, 8) = 0
write(2, "sigint at hogehoge line 4"..., 34) = 34
exit_group(4)                           = ?
Process 4480 detached

というわけで無事SIGINTがやってきました。

ssh -tというオプションはpseudo-ttyを強制すると書いてあるのですが正直pseudo-ttyがなんなのかよくわからない。。。これもググッてみると分かりやすい例がありました。

なるほどなるほど。-tしない場合、こういうことが起こっちゃうんですね。pseudo-ttyを割り当てることでいい感じに動いてくれます。

というわけで、冒頭のスクリプトも以下の様にsystemで呼ぶコマンドに-tを与えてしまえば無事SIGINTが送られましたとさ!

system("ssh -t remote 'rsync ...'");