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 버전에 의존하지 않는 코드를 얻는 세 가지 방법이 있습니다.

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 호출을 작성해야 하는 경우 이는 허용되는 단점이 될 수 있습니다.

세 번째 접근 방식은 영향을 받은 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

관련 정보