1 | """
|
2 | py_fanos.py: Pure Python implementation of FANOS
|
3 |
|
4 | Python 2 doesn't have native FD passing, but Python 3 does.
|
5 | """
|
6 |
|
7 | import array
|
8 | import os
|
9 | import socket
|
10 | import sys
|
11 |
|
12 |
|
13 | ARGV0 = os.path.basename(sys.argv[0])
|
14 |
|
15 | def log(msg, *args):
|
16 | if args:
|
17 | msg = msg % args
|
18 | print('[%s] %s' %(ARGV0, msg), file=sys.stderr)
|
19 |
|
20 |
|
21 | def send(sock, msg, fds=None):
|
22 | """Send a blob and optional file descriptors."""
|
23 |
|
24 | fds = fds or []
|
25 |
|
26 | sock.send(b'%d:' % len(msg)) # netstring prefix
|
27 |
|
28 | # Send the FILE DESCRIPTOR with the NETSTRING PAYLOAD
|
29 | ancillary = (
|
30 | socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", fds)
|
31 | )
|
32 | result = sock.sendmsg([msg], [ancillary])
|
33 | #log('sendmsg returned %s', result)
|
34 |
|
35 | sock.send(b',') # trailing netstring thing
|
36 |
|
37 |
|
38 | def recv_fds_once(sock, msglen, maxfds, fd_out):
|
39 | """Helper function from Python stdlib docs."""
|
40 | fds = array.array("i") # Array of ints
|
41 | msg, ancdata, flags, addr = sock.recvmsg(msglen,
|
42 | socket.CMSG_LEN(maxfds * fds.itemsize))
|
43 | for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
44 | if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS:
|
45 | # Append data, ignoring any truncated integers at the end.
|
46 | fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
|
47 |
|
48 | fd_out.extend(fds)
|
49 | return msg
|
50 |
|
51 |
|
52 | def recv(sock, fd_out=None):
|
53 | """Receive a blob and optional file descriptors.
|
54 |
|
55 | Returns:
|
56 | The message blob, or None when the other end closes at a valid message
|
57 | boundary.
|
58 |
|
59 | Appends to fd_out.
|
60 | """
|
61 | if fd_out is None:
|
62 | fd_out = [] # Can be thrown away
|
63 |
|
64 | len_buf = []
|
65 | for i in range(10):
|
66 | byte = sock.recv(1)
|
67 | #log('byte = %r', byte)
|
68 |
|
69 | # This happens on close()
|
70 | if len(byte) == 0:
|
71 | if i == 0:
|
72 | return None # that was the last message
|
73 | else:
|
74 | raise ValueError('Unexpected EOF')
|
75 |
|
76 | if b'0' <= byte and byte <= b'9':
|
77 | len_buf.append(byte)
|
78 | else:
|
79 | break
|
80 |
|
81 | if len(len_buf) == 0:
|
82 | raise ValueError('Expected netstring length')
|
83 | if byte != b':':
|
84 | raise ValueError('Expected : after length')
|
85 |
|
86 | num_bytes = int(b''.join(len_buf))
|
87 | #log('num_bytes = %d', num_bytes)
|
88 |
|
89 | msg = b''
|
90 | while True:
|
91 | chunk = recv_fds_once(sock, num_bytes, 3, fd_out)
|
92 | #log("chunk %r FDs %s", chunk, fds)
|
93 |
|
94 | msg += chunk
|
95 | if len(msg) == num_bytes:
|
96 | break
|
97 |
|
98 | byte = sock.recv(1)
|
99 | if byte != b',':
|
100 | raise ValueError('Expected ,')
|
101 |
|
102 | return msg
|