3

This code terminates with error :

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

This is a minimal code to reproduce the write error. The point is not to change the subprocess / pipe architecture to make it work, but to understand why this error occurs when fd 1 is assigned to a pipe that is reused later to write large output.

Why does SSH client leave stdout file descriptor dirty ? Is it a design drawback ? Is there an option to make it behave as another process would do ?

EDIT: thanks to clues in comments, I suspect that it may be related to OpenSSH versions 7.9 to 8.4

2
  • 1
    Cannot reproduce (Kubuntu 22.10). May 26 at 9:14
  • 1
    I can reproduce this with OpenSSH 8.4 (on Debian 11) but not 8.0 (on RHEL 8). May 26 at 10:13

1 Answer 1

2

Given the strong clues suggesting that it is due to a version-specific regression, here are three ways to get code that doesn't depend on OpenSSH version.

Verification made that only fd 1 is affected and fd 0 and 2 are left clean, permutation between fd 1 and 2 is a possible workaround. In following code, fd 2 become dirty but the defect would not appear unless a sufficient amount of data is written on it, while fd 1 is clean:

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

Or using an additional pipe, wrap ssh in a subprocess to isolate the affected file descriptor:

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

May be an acceptable drawback if you have to write an SSH invocation that may share stdout file descriptor with no dependency on OpenSSH affected version.

A 3rd way is to assign the affected fd to a file instead of directly to a pipe. Here is a function to do that :

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

EDIT: https://bugzilla.mindrot.org/show_bug.cgi?id=3280 has been reported on version 8.5 (but existed in 7.9 at least) and closed on version 8.9. If you cannot upgrade your binaries, you may want to put this script with basename ssh in front of /usr/bin in your user PATH :

#!/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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .