This repository has been archived by the owner on May 9, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
/
fd.go
104 lines (91 loc) · 2.76 KB
/
fd.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Package fd provides a simple API to pass file descriptors
// between different OS processes.
//
// It can be useful if you want to inherit network connections
// from another process without closing them.
//
// Example scenario:
//
// 1) Running server receives a "let's upgrade" message
// 2) Server opens a Unix domain socket for the "upgrade"
// 3) Server starts a new copy of itself and passes Unix domain socket name
// 4) New copy starts reading for the socket
// 5) Server sends its state over the socket, also sending the number
// of network connections to inherit, then it sends those connections
// using fd.Put()
// 6) New copy reads the state and inherits connections using fd.Get(),
// checks that everything is OK and sends the "OK" message to the socket
// 7) Server receives "OK" message and kills itself
package fd
import (
"net"
"os"
"syscall"
)
// Get receives file descriptors from a Unix domain socket.
//
// Num specifies the expected number of file descriptors in one message.
// Internal files' names to be assigned are specified via optional filenames
// argument.
//
// You need to close all files in the returned slice. The slice can be
// non-empty even if this function returns an error.
//
// Use net.FileConn() if you're receiving a network connection.
func Get(via *net.UnixConn, num int, filenames []string) ([]*os.File, error) {
if num < 1 {
return nil, nil
}
// get the underlying socket
viaf, err := via.File()
if err != nil {
return nil, err
}
socket := int(viaf.Fd())
defer viaf.Close()
// recvmsg
buf := make([]byte, syscall.CmsgSpace(num*4))
_, _, _, _, err = syscall.Recvmsg(socket, nil, buf, 0)
if err != nil {
return nil, err
}
// parse control msgs
var msgs []syscall.SocketControlMessage
msgs, err = syscall.ParseSocketControlMessage(buf)
// convert fds to files
res := make([]*os.File, 0, len(msgs))
for i := 0; i < len(msgs) && err == nil; i++ {
var fds []int
fds, err = syscall.ParseUnixRights(&msgs[i])
for fi, fd := range fds {
var filename string
if fi < len(filenames) {
filename = filenames[fi]
}
res = append(res, os.NewFile(uintptr(fd), filename))
}
}
return res, err
}
// Put sends file descriptors to Unix domain socket.
//
// Please note that the number of descriptors in one message is limited
// and is rather small.
// Use conn.File() to get a file if you want to put a network connection.
func Put(via *net.UnixConn, files ...*os.File) error {
if len(files) == 0 {
return nil
}
viaf, err := via.File()
if err != nil {
return err
}
socket := int(viaf.Fd())
defer viaf.Close()
fds := make([]int, len(files))
for i := range files {
fds[i] = int(files[i].Fd())
}
rights := syscall.UnixRights(fds...)
return syscall.Sendmsg(socket, nil, rights, nil, 0)
}