From b15ecf9cde10901c2fbdc91f65c84d7378a04b66 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 6 Aug 2018 17:54:25 +0300 Subject: [PATCH] Prefix async job output to prevent external corruption If an external process interferes with the async worker (e.g. writes its pty), the output can become corrupt. To minimize this occurance we now prefix all job output with the NULL character, instead of just separate on NULLs (suffix). --- async.zsh | 8 +++++--- async_test.zsh | 10 ++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/async.zsh b/async.zsh index 1257029..8cd828e 100644 --- a/async.zsh +++ b/async.zsh @@ -36,7 +36,7 @@ _async_job() { read -r -k 1 -p tok || exit 1 # Return output ( ). - print -r -n - ${(q)1} $ret ${(q)stdout} $duration + print -r -n - $'\0'${(q)1} $ret ${(q)stdout} $duration } 2> >(stderr=$(cat) && print -r -n - " "${(q)stderr}$'\0') # Unlock mutex by inserting a token. @@ -245,13 +245,15 @@ async_process_results() { if (( $#items == 5 )); then items+=($has_next) $callback "${(@)items}" # Send all parsed items to the callback. + (( num_processed++ )) + elif [[ -z $items ]]; then + # Empty items occur between results due to double-null ($'\0\0') + # caused by commands being both pre and suffixed with null. else # In case of corrupt data, invoke callback with *async* as job # name, non-zero exit status and an error message on stderr. $callback "async" 1 "" 0 "$0:$LINENO: error: bad format, got ${#items} items (${(q)items})" $has_next fi - - (( num_processed++ )) done done diff --git a/async_test.zsh b/async_test.zsh index 0791fc7..ba55c58 100644 --- a/async_test.zsh +++ b/async_test.zsh @@ -7,8 +7,8 @@ test__async_job_print_hi() { local line local -a out line=$(_async_job print hi) - # Remove trailing null, parse, unquote and interpret as array. - line=$line[1,$#line-1] + # Remove leading/trailing null, parse, unquote and interpret as array. + line=$line[2,$#line-1] out=("${(@Q)${(z)line}}") coproc exit @@ -396,8 +396,10 @@ test_async_flush_jobs() { # TODO: Confirm that they no longer exist in the process tree. local output output="${(Q)$(ASYNC_DEBUG=1 async_flush_jobs test)}" - [[ $output = *'print_four 0 4'* ]] || { - t_error "want discarded output 'print_four 0 4' when ASYNC_DEBUG=1, got ${(Vq-)output}" + # NOTE(mafredri): First 'p' in print_four is lost when null-prefixing + # _async_job output. + [[ $output = *'rint_four 0 4'* ]] || { + t_error "want discarded output 'rint_four 0 4' when ASYNC_DEBUG=1, got ${(Vq-)output}" } # Check that the killed job did not produce output.