saltstack / printf IO-error / debianutils / which

saltstack / printf IO-error / debianutils / which

  • Written by Walter Doekes

  • Published on: 29/04/2020

After some upgrades, I suddenly noticed unexpected sh: printf: I/O error output. Some debugging later, it turns out that it’s the Dash way of informing us of a PIPE error. Apparently salt’s cmd.run can cause so little output buffering, that the debianutils which command can be aborted mid-output.

Not-broken example:

$ which python3 python false | head -n1
/usr/bin/python3

Broken example, through a salt cmd.run call:

$ salt 'example.com' cmd.run 'which python3 python false | head -n1'
example.com:
    /usr/bin/python3
    sh: printf: I/O error

It finds and prints /usr/bin/python3. Then head -n1 is so fast in closing stdin, that dash notices and reports an I/O error when the dash builtin printf tries to write the line /usr/bin/python to stdout — which is the stdin that head closed already.

$ salt 'example.com' cmd.run 'sh -x /bin/which python3 python false | head -n1' 2>&1 | tail -n5
    + [ -f /usr/bin/python ]
    + [ -x /usr/bin/python ]
    + puts /usr/bin/python
    + printf %s\n /usr/bin/python
    sh: printf: I/O error

bash reports something similar, but is more pronounced:

$ salt 'example.com' cmd.run 'bash -x /bin/which python3 python false | head -n1' 2>&1 | tail -n5
    + '[' -x /usr/bin/python ']'
    /usr/bin/python3
    + puts /usr/bin/python
    + printf '%s\n' /usr/bin/python
    /bin/which: line 10: printf: write error: Broken pipe

While I haven’t delved into the reason why this happens in these salt calls only, I think the problem lies with debianutils which.

Something like this would fix things:

--- /bin/which  2020-04-29 14:40:26.032397533 +0200
+++ /bin/which-fixed  2020-04-29 14:56:11.518941600 +0200
@@ -7,7 +7,7 @@ if test -n "$KSH_VERSION"; then
  }
 else
  puts() {
-   printf '%s\n' "$*"
+   printf '%s\n' "$*" 2>/dev/null
  }
 fi

Trapping the PIPE signal would not work, as the signal is handled already — by a handler that prints “write error: Broken pipe”.

$ sh -c 'trap exit PIPE; echo foo; sleep 1; echo bar' | true
sh: echo: I/O error

$ bash -c 'trap exit PIPE; echo foo; sleep 1; echo bar' | true
bash: line 0: echo: write error: Broken pipe

Oh well. At least this explains the odd messages.

Back to overview