Back to postsPOST 04

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.

Search posts

Search post titles and text.

Enter a word or phrase to begin.