Skip to content

Instantly share code, notes, and snippets.

@smoser
Created August 27, 2020 16:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smoser/b5925ab57e87423a46056e9d47e6afc8 to your computer and use it in GitHub Desktop.
Save smoser/b5925ab57e87423a46056e9d47e6afc8 to your computer and use it in GitHub Desktop.
golang (go) cmd.ExtraFiles example

This is an example of using cmd.ExtraFiles in golang with a os.Pipe()

There are 2 things that might be a bit tricky:

  • the order of closing the read and write side of the pipe. I got this right by reading the implementation of cmd.StderrPipe.
  • the number of the file descriptor for the child. I'm not sure of a good way to cleanly determine what that is. Golang cmd processes do not inherit all open filehandles, so you can't just use pipeWrite.Fd().

Ultimately, we did this because we were working with tssnvread which is possibly the worst user interface I've ever seen. We wanted to read the contents without writing to a file on the filesystem. To do so, we were able to tell it to write to /proc/self/fd/.

package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
)
func main() {
var stdout, stderr bytes.Buffer
// the idea is to 'secret' to the path provided
// in argument 1 ("testprog0" is arg0 when sh -c).
// we write to /proc/self/fd/<fd> to trick a program (tssnvread)
// to avoid writing to a file on the filesystem.
// so our 'sh' program here mimics a call to 'tssnvread -of <path>'
//
// One of the hacky parts here is that we just "know" the
// inherited fd will be number 3, as golang closes all but
// stdin, stdout, stderr.
cmd := exec.Command("sh", "-ec", `
out=$1; shift;
echo "message on stdout: will write to $out"
echo "message on stderr: will write to $out" 1>&2
ls -l /proc/self/fd/
echo "secret" >"$out"
exit 0
`,
"testprog0",
fmt.Sprintf("/proc/self/fd/%d", 3),
)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
pipeRead, pipeWrite, err := os.Pipe()
if err != nil {
log.Fatalf("os.Pipe() failed: %s\n", err)
}
defer pipeRead.Close()
defer pipeWrite.Close()
cmd.ExtraFiles = []*os.File{pipeWrite}
// the order of the Close calls is important.
// copy the implementation of cmd.StderrPipe() which does the
// close of the pipeWrite after Start and close of pipeRead after Wait.
if err := cmd.Start(); err != nil {
log.Fatalf("start failed: %s\n", err)
}
pipeWrite.Close()
data, err := ioutil.ReadAll(pipeRead)
if err != nil {
log.Fatalf("read failed: %s\n", err)
}
if err := cmd.Wait(); err != nil {
fmt.Printf("stdout: %s", string(stdout.Bytes()))
fmt.Printf("stderr: %s", string(stderr.Bytes()))
log.Fatalf("Wait failed: %s", err)
}
pipeRead.Close()
fmt.Printf("got the data!: %s", data)
fmt.Printf("stdout: %s", string(stdout.Bytes()))
fmt.Printf("stderr: %s", string(stderr.Bytes()))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment