forked from rook/rook
executeCommandWithTimeout wires a single bytes.Buffer to both cmd.Stdout and cmd.Stderr and joins the command in a goroutine via cmd.Wait(). On the timeout kill path it called cmd.Process.Kill() and then read that buffer without waiting for cmd.Wait() to return. Kill() only signals the process; it does not wait for os/exec's output-copier goroutines (joined only by cmd.Wait) to finish, so reading the buffer there races with those writers, which the race detector flags. Only read the buffer once the copier goroutines have drained, signaled by cmd.Wait() on the done channel. Because a killed process can leave an orphaned descendant holding the output pipe open -- a D-state cryptsetup/dmsetup child, or the downstream of a "sh -c '... | head'" pipeline -- cmd.Wait() may never return, so bound the drain with a short grace period and give up on the captured output rather than blocking the caller forever. This keeps the bounded return the timeout path is meant to guarantee. On a failed Kill() the process may still be writing, so return without reading the buffer. Add a regression test that drives the kill path with a child that ignores SIGINT while writing to stdout; it fails under -race before this change. Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> Signed-off-by: Joshua Hoblitt <josh@hoblitt.com>