blob: a5ced4ba3a3ce74c628166d2bd0bba96eb132750 [file] [log] [blame]
Ryan Brownfed691e2014-09-15 13:09:40 -07001// Package unixfd provides provides support for Dialing and Listening
2// on already connected file descriptors (like those returned by socketpair).
3package unixfd
4
5import (
6 "errors"
7 "fmt"
8 "io"
9 "net"
10 "os"
11 "strconv"
12 "sync"
13 "syscall"
Cosmos Nicolaou87c0a552014-12-02 23:05:49 -080014 "time"
Ryan Brown50b473a2014-09-23 14:23:00 -070015 "unsafe"
Cosmos Nicolaou87c0a552014-12-02 23:05:49 -080016
Jiri Simsa519c5072014-09-17 21:37:57 -070017 "veyron.io/veyron/veyron2/ipc/stream"
Ryan Brownfed691e2014-09-15 13:09:40 -070018)
19
20const Network string = "unixfd"
21
22// singleConnListener implements net.Listener for an already-connected socket.
23// This is different from net.FileListener, which calls syscall.Listen
24// on an unconnected socket.
25type singleConnListener struct {
26 c chan net.Conn
27 addr net.Addr
28 sync.Mutex
29}
30
31func (l *singleConnListener) getChan() chan net.Conn {
32 l.Lock()
33 defer l.Unlock()
34 return l.c
35}
36
37func (l *singleConnListener) Accept() (net.Conn, error) {
38 c := l.getChan()
39 if c == nil {
40 return nil, errors.New("listener closed")
41 }
42 if conn, ok := <-c; ok {
43 return conn, nil
44 }
45 return nil, io.EOF
46}
47
48func (l *singleConnListener) Close() error {
49 l.Lock()
50 defer l.Unlock()
51 lc := l.c
52 if lc == nil {
53 return errors.New("listener already closed")
54 }
55 close(l.c)
56 l.c = nil
57 // If the socket was never Accept'ed we need to close it.
58 if c, ok := <-lc; ok {
59 return c.Close()
60 }
61 return nil
62}
63
64func (l *singleConnListener) Addr() net.Addr {
65 return l.addr
66}
67
Cosmos Nicolaou87c0a552014-12-02 23:05:49 -080068func unixFDConn(protocol, address string, timeout time.Duration) (net.Conn, error) {
69 // TODO(cnicolaou): have this respect the timeout. Possibly have a helper
70 // function that can be generally used for this, but in practice, I think
71 // it'll be cleaner to use the underlying protocol's deadline support of it
72 // has it.
Ryan Brownfed691e2014-09-15 13:09:40 -070073 fd, err := strconv.ParseInt(address, 10, 32)
74 if err != nil {
75 return nil, err
76 }
77 file := os.NewFile(uintptr(fd), "tmp")
78 conn, err := net.FileConn(file)
79 // 'file' is not used after this point, but we keep it open
80 // so that 'address' remains valid.
81 if err != nil {
82 file.Close()
83 return nil, err
84 }
85 // We wrap 'conn' so we can customize the address, and also
86 // to close 'file'.
Matt Rosencrantz0610a232014-12-04 10:26:39 -080087 return &fdConn{addr: addr(address), sock: file, Conn: conn}, nil
Ryan Brownfed691e2014-09-15 13:09:40 -070088}
89
90type fdConn struct {
91 addr net.Addr
92 sock *os.File
93 net.Conn
Matt Rosencrantz0610a232014-12-04 10:26:39 -080094
95 mu sync.Mutex
96 closed bool
Ryan Brownfed691e2014-09-15 13:09:40 -070097}
98
99func (c *fdConn) Close() (err error) {
Matt Rosencrantz0610a232014-12-04 10:26:39 -0800100 c.mu.Lock()
101 defer c.mu.Unlock()
102
103 if c.closed {
104 return nil
105 }
106
107 c.closed = true
Ryan Brownfed691e2014-09-15 13:09:40 -0700108 defer c.sock.Close()
109 return c.Conn.Close()
110}
111
112func (c *fdConn) LocalAddr() net.Addr {
113 return c.addr
114}
115
116func (c *fdConn) RemoteAddr() net.Addr {
117 return c.addr
118}
119
Cosmos Nicolaou87c0a552014-12-02 23:05:49 -0800120func unixFDListen(protocol, address string) (net.Listener, error) {
121 conn, err := unixFDConn(protocol, address, 0)
Ryan Brownfed691e2014-09-15 13:09:40 -0700122 if err != nil {
123 return nil, err
124 }
125 c := make(chan net.Conn, 1)
126 c <- conn
127 return &singleConnListener{c, conn.LocalAddr(), sync.Mutex{}}, nil
128}
129
130type addr string
131
132func (a addr) Network() string { return Network }
133func (a addr) String() string { return string(a) }
134
135// Addr returns a net.Addr for the unixfd network for the given file descriptor.
136func Addr(fd uintptr) net.Addr {
137 return addr(fmt.Sprintf("%d", fd))
138}
139
Ryan Brown50b473a2014-09-23 14:23:00 -0700140type fileDescriptor struct {
141 fd chan int
142 name string
143}
144
145func newFd(fd int, name string) *fileDescriptor {
146 ch := make(chan int, 1)
147 ch <- fd
148 close(ch)
149 d := &fileDescriptor{ch, name}
150 return d
151}
152
153func (f *fileDescriptor) releaseAddr() net.Addr {
154 if fd, ok := <-f.fd; ok {
155 return Addr(uintptr(fd))
156 }
157 return nil
158}
159
160func (f *fileDescriptor) releaseFile() *os.File {
161 if fd, ok := <-f.fd; ok {
162 return os.NewFile(uintptr(fd), f.name)
163 }
164 return nil
165}
166
167// maybeClose closes the file descriptor, if it hasn't been released.
168func (f *fileDescriptor) maybeClose() {
169 if file := f.releaseFile(); file != nil {
170 file.Close()
171 }
172}
173
174// Socketpair returns a pair of connected sockets for communicating with a child process.
175func Socketpair() (*net.UnixConn, *os.File, error) {
176 lfd, rfd, err := socketpair(false)
177 if err != nil {
178 return nil, nil, err
179 }
180 defer rfd.maybeClose()
181 file := lfd.releaseFile()
182 // FileConn dups the fd, so we still want to close the original one.
183 defer file.Close()
184 conn, err := net.FileConn(file)
185 if err != nil {
186 return nil, nil, err
187 }
188 return conn.(*net.UnixConn), rfd.releaseFile(), nil
189}
190
191func socketpair(closeRemoteOnExec bool) (local, remote *fileDescriptor, err error) {
192 syscall.ForkLock.RLock()
Ryan Brownfed691e2014-09-15 13:09:40 -0700193 fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
Ryan Brown50b473a2014-09-23 14:23:00 -0700194 if err == nil {
195 syscall.CloseOnExec(fds[0])
196 if closeRemoteOnExec {
197 syscall.CloseOnExec(fds[1])
198 }
199 }
200 syscall.ForkLock.RUnlock()
201 if err != nil {
202 return nil, nil, err
203 }
204 return newFd(fds[0], "local"), newFd(fds[1], "remote"), nil
205}
206
207// SendConnection creates a new connected socket and sends
208// one end over 'conn', along with 'data'. It returns the address for
209// the local end of the socketpair.
Ryan Brown81789442014-10-30 13:23:53 -0700210// Note that the returned address is an open file descriptor,
Ryan Brown50b473a2014-09-23 14:23:00 -0700211// which you must close if you do not Dial or Listen to the address.
Ryan Brown81789442014-10-30 13:23:53 -0700212func SendConnection(conn *net.UnixConn, data []byte, closeOnExec bool) (addr net.Addr, err error) {
Ryan Brown50b473a2014-09-23 14:23:00 -0700213 if len(data) < 1 {
214 return nil, errors.New("cannot send a socket without data.")
215 }
Ryan Brown81789442014-10-30 13:23:53 -0700216 remote, local, err := socketpair(closeOnExec)
Ryan Brownfed691e2014-09-15 13:09:40 -0700217 if err != nil {
218 return nil, err
219 }
Ryan Brown50b473a2014-09-23 14:23:00 -0700220 defer local.maybeClose()
221 rfile := remote.releaseFile()
Ryan Brown50b473a2014-09-23 14:23:00 -0700222
223 rights := syscall.UnixRights(int(rfile.Fd()))
224 n, oobn, err := conn.WriteMsgUnix(data, rights, nil)
225 if err != nil {
Ryan Brownfac212e2014-11-13 12:23:47 -0800226 rfile.Close()
Ryan Brown50b473a2014-09-23 14:23:00 -0700227 return nil, err
228 } else if n != len(data) || oobn != len(rights) {
Ryan Brownfac212e2014-11-13 12:23:47 -0800229 rfile.Close()
Ryan Brown50b473a2014-09-23 14:23:00 -0700230 return nil, fmt.Errorf("expected to send %d, %d bytes, sent %d, %d", len(data), len(rights), n, oobn)
231 }
Ryan Brownfac212e2014-11-13 12:23:47 -0800232 // Wait for the other side to acknowledge.
233 // This is to work around a race on OS X where it appears we can close
234 // the file descriptor before it gets transfered over the socket.
235 f := local.releaseFile()
236 fd, err := syscall.Dup(int(f.Fd()))
237 if err != nil {
238 f.Close()
239 rfile.Close()
240 return nil, err
241 }
242 newConn, err := net.FileConn(f)
243 f.Close()
244 if err != nil {
245 rfile.Close()
246 return nil, err
247 }
248 newConn.Read(make([]byte, 1))
249 newConn.Close()
250 rfile.Close()
251
252 return Addr(uintptr(fd)), nil
Ryan Brown50b473a2014-09-23 14:23:00 -0700253}
254
255const cmsgDataLength = int(unsafe.Sizeof(int(1)))
256
257// ReadConnection reads a connection and additional data sent on 'conn' via a call to SendConnection.
258// 'buf' must be large enough to hold the data.
259func ReadConnection(conn *net.UnixConn, buf []byte) (net.Addr, int, error) {
260 oob := make([]byte, syscall.CmsgLen(cmsgDataLength))
261 n, oobn, _, _, err := conn.ReadMsgUnix(buf, oob)
262 if err != nil {
263 return nil, n, err
264 }
265 if oobn > len(oob) {
266 return nil, n, fmt.Errorf("received too large oob data (%d, max %d)", oobn, len(oob))
267 }
268 scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
269 if err != nil {
270 return nil, n, err
271 }
272 fd := -1
Ryan Brownfac212e2014-11-13 12:23:47 -0800273 // Loop through any file descriptors we are sent, and close
274 // all extras.
Ryan Brown50b473a2014-09-23 14:23:00 -0700275 for _, scm := range scms {
276 fds, err := syscall.ParseUnixRights(&scm)
277 if err != nil {
278 return nil, n, err
279 }
280 for _, f := range fds {
Ryan Brownfac212e2014-11-13 12:23:47 -0800281 if fd == -1 {
282 fd = f
283 } else if f != -1 {
284 syscall.Close(f)
Ryan Brown50b473a2014-09-23 14:23:00 -0700285 }
Ryan Brown50b473a2014-09-23 14:23:00 -0700286 }
287 }
288 if fd == -1 {
289 return nil, n, nil
290 }
Ryan Brownfac212e2014-11-13 12:23:47 -0800291 result := Addr(uintptr(fd))
292 fd, err = syscall.Dup(fd)
293 if err != nil {
294 CloseUnixAddr(result)
295 return nil, n, err
296 }
297 file := os.NewFile(uintptr(fd), "newconn")
298 newconn, err := net.FileConn(file)
299 file.Close()
300 if err != nil {
301 CloseUnixAddr(result)
302 return nil, n, err
303 }
304 newconn.Write(make([]byte, 1))
305 newconn.Close()
306 return result, n, nil
Ryan Brownfed691e2014-09-15 13:09:40 -0700307}
308
Ryan Brown81789442014-10-30 13:23:53 -0700309func CloseUnixAddr(addr net.Addr) error {
310 if addr.Network() != Network {
311 return errors.New("invalid network")
312 }
313 fd, err := strconv.ParseInt(addr.String(), 10, 32)
314 if err != nil {
315 return err
316 }
317 return syscall.Close(int(fd))
318}
319
Ryan Brownfed691e2014-09-15 13:09:40 -0700320func init() {
321 stream.RegisterProtocol(Network, unixFDConn, unixFDListen)
322}