Here shows how to pass file descriptors between processes in go.
package main
import (
"fmt"
"log"
"net"
"os"
"os/exec"
"syscall"
)
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
if os.Getenv("SUBPROCESS") == "true" {
subProcess()
return
}
mainProcess()
}
func mainProcess() {
localFile, remoteFile, err := unixPair()
if err != nil {
log.Fatal(err)
}
defer localFile.Close()
defer remoteFile.Close()
cmd := exec.Command(os.Args[0], os.Args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.ExtraFiles = []*os.File{remoteFile}
cmd.Env = append(os.Environ(), "SUBPROCESS=true")
err = cmd.Start()
if err != nil {
log.Fatal(err)
}
local, err := net.FileConn(localFile)
if err != nil {
log.Fatal(err)
}
defer local.Close()
unixConn, ok := local.(*net.UnixConn)
if !ok {
log.Fatal("not a unix conn")
}
file, err := recvFd(unixConn)
if err != nil {
log.Fatal(err)
}
defer file.Close()
fmt.Println("the fd in main process is ", file.Fd())
fmt.Fprintln(file, "hello from main process")
err = cmd.Wait()
if err != nil {
log.Println(err)
}
}
func subProcess() {
file, err := os.Create("hello.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
fmt.Println("the fd in sub process is ", file.Fd())
fmt.Fprintln(file, "hello from sub process")
remoteFile := os.NewFile(3, "unix-remote")
defer remoteFile.Close()
conn, err := net.FileConn(remoteFile)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
unixConn, ok := conn.(*net.UnixConn)
if !ok {
log.Fatal("sub process: ", "not a unix conn")
}
err = sendFd(unixConn, file)
if err != nil {
log.Fatal(err)
}
}
func unixPair() (*os.File, *os.File, error) {
fd, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, nil, err
}
localFile := os.NewFile(uintptr(fd[0]), "unix-local")
remoteFile := os.NewFile(uintptr(fd[1]), "unix-remote")
return localFile, remoteFile, nil
}
func sendFd(unixLocal *net.UnixConn, file *os.File) error {
oob := syscall.UnixRights(int(file.Fd()))
_, _, err := unixLocal.WriteMsgUnix(nil, oob, nil)
return err
}
func recvFd(unixConn *net.UnixConn) (*os.File, error) {
var (
b [32]byte
oob [32]byte
)
_, oobn, _, _, err := unixConn.ReadMsgUnix(b[:], oob[:])
if err != nil {
return nil, err
}
messages, err := syscall.ParseSocketControlMessage(oob[:oobn])
if err != nil {
return nil, err
}
if len(messages) != 1 {
return nil, fmt.Errorf("expect 1 message, got %#v", messages)
}
message := messages[0]
fds, err := syscall.ParseUnixRights(&message)
if err != nil {
return nil, err
}
if len(fds) != 1 {
return nil, fmt.Errorf("expect 1 fd, got %#v", fds)
}
return os.NewFile(uintptr(fds[0]), "remote-file"), nil
}