Passing file descriptor between processes
How to pass file descriptors between processes in go using Unix domain sockets.
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
}
Comments
Comments use GitHub for sign-in. They load automatically as you reach this section.