chiark / gitweb /
fishdescriptor: wip reconsider approach
[chiark-utils.git] / fishdescriptor / py / fishdescriptor / indonor.py
1
2 # class for use inside gdb which is debugging the donor process
3
4 import gdb
5 import copy
6 import os
7
8 def _string_escape_for_c(s):
9     out = ''
10     for c in bytearray(s): # gets us bytes in py2 and py3
11         if c == ord('\\') or c == ord('=') or c < 32 or c > 126:
12             out += '\\x%02x' % c
13         else:
14             out += chr(c)
15     return out
16
17 # constructing values
18
19 def _lit_integer(v):
20     return '%d' % v
21
22 def _lit_aggregate_uncasted(val_lit_strs):
23     return '{' + ', '.join(['(%s)' % v for v in val_lit_strs]) + ' }'
24
25 def _lit_string_uncasted(s):
26     if not isinstance(s, bytes):
27         s = s.encode('utf-8') # sigh, python 2/3 compat
28         b = bytearray(s)
29     return _lit_aggregate_uncasted(map(_lit_integer, b) + [ '0' ])
30
31 def _lit_array(elemtype, val_lit_strs):
32     return (
33         '((%s[%d])%s)' %
34         (elem_type, len(val_lit_strs), _lit_aggregate_uncasted(val_lit_strs))
35     )
36
37 def _lit_addressof(v):
38     return '&(%s)' % v
39
40 def _make_lit(v):
41     if isinstance(v, int):
42         return _lit_integer(v)
43     else:
44         return v # should already be an integer
45
46 class DonorStructLayout():
47     def __init__(l, typename):
48         x = gdb.parse_and_eval('(%s){ }' % typename)
49         l._typename = typename
50         l._template = [ ]
51         l._posns = { }
52         for f in x.type.fields():
53             l._posns[f.name] = len(l._template)
54             try f.type.fields(): blank = '{ }'
55             except AttributeError: blank = '0'
56             l._template.append(blank)
57     def substitute(l, values):
58         build = copy.deepcopy(l._template)
59         for (k,v) in values.items():
60             build[ l._posns[k] ] = _make_lit(v)
61         return '((%s)%s)' % (l._typename, _lit_aggregate_uncasted(build))
62
63 class DonorImplementation():
64     def __init__(d):
65         d._structs = { }
66
67     # assembling structs
68     # sigh, we have to record the order of the arguments!
69     def d._find_fields(typename):
70         try:
71             fields = d._structs[typename]
72         except AttributeError:
73             fields = DonorStructLayout(typename)
74             d._structs[typename] = fields
75         return fields
76
77     def d._make(typename, values):
78         fields = d._find_fields(typename)
79         return fieldd.substitute(values)
80
81     # calling functions (need to cast the function name to the right
82     # type in case maybe gdb doesn't know the type)
83
84     def _func(d, functype, funcname, realargs):
85         expr = '((%s) %s) %s' % (functype, funcname, realargs)
86         return gdb.parse_and_eval(expr)
87
88     def _must_func(d, functype, funcname, realargs):
89         retval = d._func(functype, funcname, realargs)
90         if retval < 0:
91             errnoval = gdb.parse_and_eval('errno')
92             raise RuntimeError("%s gave errno=%d `%s'" %
93                                (funcname, errnoval, od.strerror(errnoval)))
94
95     # wrappers for the syscalls that do what we want
96
97     def _sendmsg(d, carrier, control_msg):
98         iov_base = _lit_array('int', map(str,fds))
99         iov = d._make('struct iovec', {
100             'iov_base': iov_base,
101             'iov_len' : len(fds),
102         })
103
104         msg = d._make('struct msghdr', {
105             'msg_iov'       : _lit_addressof(iov),
106             'msg_iovlen'    : 1,
107             'msg_control'   : _lit_array('char', control_msg),
108             'msg_controllen': len(control_msg),
109         })
110
111         d._must_func(
112             'ssize_t (*)(int, const struct msghdr*, int flags)',
113             'sendmsg',
114             '(%s, %s, 0)' % (carrier, _lit_addressof(msg))
115         )
116
117     def _socket(d):
118         return d._must_func(
119             'int (*)(int, int, int)',
120             'socket',
121             '(%d, %d, 0)' % (socket.AF_UNIX, socket.SOCK_STREAM)
122         )
123
124     def _connect(d, fd, path):
125         addr = d._make('struct sockaddr_un', {
126             'sun_family' : _lit_integer(socket.AF_UNIX),
127             'sun_path'   : _lit_string_uncasted(path),
128         })
129
130         d._must_func(
131             'int (*)(int, const struct sockaddr*, socklen_t)',
132             'connect',
133             '(%d, (const struct sockaddr*)%s, sizeof(struct sockaddr_un))'
134             % (fd, _lit_addressof(addr)
135         )
136
137     def _close(d, fd):
138         d._must_func('int (*)(int)', 'close', '(%d)' % fd)
139
140     # main entrypoints
141
142     def donate(d, path, control_msg):
143         # control_msg is an array of integers being the ancillary data
144         # array ("control") for sendmsg, and hence specifies which fds
145         # to pass
146
147         carrier = None
148         errnoval = None
149         try:
150             errnoval = gdb.parse_and_eval('errno')
151             carrier = d._socket()
152             d._connect(carrier, path)
153             d._sendmsg(carrier, control_msg)
154             d._close(carrier)
155             carrier = None
156         finally:
157             if carrier is not None:
158                 try: d._close(carrier)
159                 except Exception: pass
160             if errnoval is not None:
161                 gdb.parse_and_eval('errno = %d' % errnoval)