goread-write

Golang ReadWriteCloser as io.File


In golang having an instance of io.ReadWriteCloser how to obtain its representation (kind of proxy) as an instance of *os.File (E.g. to be provided for exec.Cmd.ExtraFiles)

The first is coming to a mind is to use os.Pipe approach. Still such a stub would be unidirectional (read or write only).

So how to completely represent io.ReadWriteCloser as an *os.File E.g.

func something(stream io.ReadWriteCloser) {
  cmd := exec.Command("comm")

  var wrapped *os.File = wrappAsFile(stream) // How this could be achieved?

  cmd.ExtraFiles := []*File{wrapped}
}

Simplified solution I can see would be something like the following. Still (as I mentioned it before) it covers only a Reader flow.

func something(stream io.ReadWriteCloser) {
  cmd := exec.Command("comm")

  r, w, _ := os.Pipe()

  go func() {
     io.Copy(w, stream)
     w.Close()
  }()

  cmd.ExtraFiles := []*File{r}
}

Thx


Solution

  • If you can be constrained to POSIX systems, you can use a socketpair to create a full duplex pipe with syscall.Socketpair

    func socketpair() (*os.File, *os.File, error) {
        fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
        if err != nil {
            return nil, nil, err
        }
    
        // Setnonblock may not be necessary if only using these to pass to
        // another process, but the runtime will handled it either way.
        if err := syscall.SetNonblock(int(fds[0]), true); err != nil {
            return nil, nil, err
        }
        if err := syscall.SetNonblock(int(fds[1]), true); err != nil {
            return nil, nil, err
        }
    
        f0 := os.NewFile(uintptr(fds[0]), "socket-0")
        f1 := os.NewFile(uintptr(fds[1]), "socket-1")
    
        return f0, f1, nil
    }
    

    Which you could then connect to an io.ReadWriteCloser with a couple io.Copy calls:

        // We'll use f0 as the local side of the pipe, and pass along f1 to the child process
        f0, f1, err := socketpair()
        if err != nil {
            log.Fatal(err)
        }
    
        go func() {
            io.Copy(f0, rw)
            f0.Close()
        }()
    
        go func() {
            io.Copy(rw, f0)
        }()
    

    Windows now also supports unix domain sockets too, so while not quite as convenient as a socketpair, you can also create unix socket listener, and then dial another socket to create the connection and get the underlying files from the File() method