SSHクライアントが再利用のために標準出力ファイル記述子をきれいに保つことを確認する方法は?

SSHクライアントが再利用のために標準出力ファイル記述子をきれいに保つことを確認する方法は?

このコードは次のエラーで終了します。

 (
  ssh localhost seq 100000
  seq 100000
 ) | wc
 #-> seq: write error: Resource temporarily unavailable

書き込みエラーを再現するための最小限のコードです。要点は、適切に機能するように子プロセス/パイプラインアーキテクチャを変更するのではなく、後で大規模出力を作成するために再利用されるパイプにfd 1を割り当てるときにこのエラーが発生する理由を理解することです。

SSHクライアントがstdoutファイル記述子を汚すのはなぜですか?これは設計上の欠陥ですか?他のプロセスのように動作させるオプションはありますか?

編集する:コメントの手がかりによると、OpenSSHバージョン7.9〜8.4と関連があると疑われます。

答え1

これがバージョンごとの回帰による強力な手がかりに基づいて、OpenSSHバージョンに依存しないコードを取得する3つの方法があります。

fd 1のみが影響を受け、fd 0と2はクリーンなままであることが確認されており、fd 1と2の間の配置が可能な解決策です。以下のコードでは、fd 2は汚れていますが、十分なデータが書き込まれていないと欠陥は発生しませんが、fd 1はきれいです。

(
 ssh localhost '(seq 100000) {safe}>&2 2>&1 >&$safe' \
                             {safe}>&2 2>&1 >&$safe
 seq 100000
) | wc

または、追加のパイプを使用して子プロセスでsshをラップして、影響を受けるファイル記述子を分離します。

(
 ssh localhost seq 100000 | cat
 seq 100000
) | wc

stdoutファイル記述子を共有でき、影響を受けるOpenSSHバージョンに依存しないSSH呼び出しを作成する必要がある場合、これは許容される欠点になる可能性があります。

3番目のアプローチは、影響を受けたfdをパイプに直接割り当てるのではなく、ファイルに割り当てることです。これを行う関数は次のとおりです。

repipe(){
  tmp=$(mktemp)
  trap "rm -f $tmp" RETURN
  "$@" > $tmp &
  tail --pid $! -n+0 -f $tmp
}
(
  repipe ssh localhost seq 100000
  seq 100000
) | wc

編集する: https://bugzilla.mindrot.org/show_bug.cgi?id=3280 バージョン8.5(ただし少なくとも7.9にはあります)で報告され、バージョン8.9で終了しました。バイナリをアップグレードできない場合は、sshユーザーのPATHの前にデフォルトの名前でこのスクリプトを配置する必要があります。/usr/bin

#!/bin/bash
native=/usr/bin/ssh
if [[ ${vnum:=$($native -V 2>&1 | (IFS="[_. ]" read osef maj min osef && printf "%02d.%s" $maj $min))} > 07.9 && $vnum < 08.9 ]]
then
  exec $native "$@" | cat
else
  [[ $vnum > 08.9 ]] && echo "INFO: $0 may be removed on this host">&2
  exec $native "$@"
fi

関連情報