chiark / gitweb /
fishdescriptor: debugging
[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 import sys
8
9 def _string_escape_for_c(s):
10     out = ''
11     for c in bytearray(s): # gets us bytes in py2 and py3
12         if c == ord('\\') or c == ord('"') or c < 32 or c > 126:
13             out += '\\x%02x' % c
14         else:
15             out += chr(c)
16     return out
17
18 # constructing values
19
20 def _lit_integer(v):
21     return '%d' % v
22
23 def _lit_aggregate_uncasted(val_lit_strs):
24     return '{' + ', '.join(['(%s)' % v for v in val_lit_strs]) + ' }'
25
26 def _lit_string_uncasted(s):
27     if not isinstance(s, bytes):
28         s = s.encode('utf-8') # sigh, python 2/3 compat
29         b = bytearray(s)
30     return _lit_aggregate_uncasted(map(_lit_integer, b) + [ '0' ])
31
32 def _lit_array(elemtype, val_lit_strs):
33     return (
34         '((%s[%d])%s)' %
35         (elem_type, len(val_lit_strs), _lit_aggregate_uncasted(val_lit_strs))
36     )
37
38 def _lit_addressof(v):
39     return '&(%s)' % v
40
41 def _make_lit(v):
42     if isinstance(v, int):
43         return _lit_integer(v)
44     else:
45         return v # should already be an integer
46
47 def parse_eval(expr):
48     sys.stderr.write("##  EVAL %s\n" % repr(expr))
49     x = gdb.parse_and_eval(expr)
50     sys.stderr.write('##  => %s\n' % x)
51     sys.stderr.flush()
52     return x
53
54 class DonorStructLayout():
55     def __init__(l, typename):
56         x = parse_eval('(%s){ }' % typename)
57         l._typename = typename
58         l._template = [ ]
59         l._posns = { }
60         for f in x.type.fields():
61             l._posns[f.name] = len(l._template)
62             try: f.type.fields(); blank = '{ }'
63             except AttributeError: blank = '0'
64             l._template.append(blank)
65
66     def substitute(l, values):
67         build = copy.deepcopy(l._template)
68         for (k,v) in values.items():
69             build[ l._posns[k] ] = _make_lit(v)
70         return '((%s)%s)' % (l._typename, _lit_aggregate_uncasted(build))
71
72 class DonorImplementation():
73     def __init__(di):
74         di._structs = { }
75         di._saved_errno = None
76         di._result_stream = os.fdopen(3, 'w')
77
78     # assembling structs
79     # sigh, we have to record the order of the arguments!
80     def _find_fields(typename):
81         try:
82             fields = di._structs[typename]
83         except AttributeError:
84             fields = DonorStructLayout(typename)
85             di._structs[typename] = fields
86         return fields
87
88     def _make(typename, values):
89         fields = di._find_fields(typename)
90         return fields.substitute(values)
91
92     # calling functions (need to cast the function name to the right
93     # type in case maybe gdb doesn't know the type)
94
95     def _func(di, functype, funcname, realargs):
96         expr = '((%s) %s) %s' % (functype, funcname, realargs)
97         return parse_eval(expr)
98
99     def _must_func(di, functype, funcname, realargs):
100         retval = di._func(functype, funcname, realargs)
101         if retval < 0:
102             errnoval = parse_eval('errno')
103             raise RuntimeError("%s gave errno=%d `%s'" %
104                                (funcname, errnoval, os.strerror(errnoval)))
105         return retval
106
107     # wrappers for the syscalls that do what we want
108
109     def _sendmsg(di, carrier, control_msg):
110         iov_base = _lit_array('int', map(str,fds))
111         iov = di._make('struct iovec', {
112             'iov_base': iov_base,
113             'iov_len' : len(fds),
114         })
115
116         msg = di._make('struct msghdr', {
117             'msg_iov'       : _lit_addressof(iov),
118             'msg_iovlen'    : 1,
119             'msg_control'   : _lit_array('char', control_msg),
120             'msg_controllen': len(control_msg),
121         })
122
123         di._must_func(
124             'ssize_t (*)(int, const struct msghdr*, int flags)',
125             'sendmsg',
126             '(%s, %s, 0)' % (carrier, _lit_addressof(msg))
127         )
128
129     def _socket(di):
130         return di._must_func(
131             'int (*)(int, int, int)',
132             'socket',
133             '(%d, %d, 0)' % (socket.AF_UNIX, socket.SOCK_STREAM)
134         )
135
136     def _connect(di, fd, path):
137         addr = di._make('struct sockaddr_un', {
138             'sun_family' : _lit_integer(socket.AF_UNIX),
139             'sun_path'   : _lit_string_uncasted(path),
140         })
141
142         di._must_func(
143             'int (*)(int, const struct sockaddr*, socklen_t)',
144             'connect',
145             '(%d, (const struct sockaddr*)%s, sizeof(struct sockaddr_un))'
146             % (fd, _lit_addressof(addr))
147         )
148
149     def _close(di, fd):
150         di._must_func('int (*)(int)', 'close', '(%d)' % fd)
151
152     def _mkdir(di, path, mode):
153         r = di._func(
154             'int (*)(const char*, mode_t)',
155             'mkdir',
156             '("%s", %d)' % (_string_escape_for_c(path), mode)
157         )
158         if r < 0:
159             errnoval = parse_eval('errno')
160             if errnoval != os.errno.EEXIST:
161                 raise RuntimeError("mkdir %s failed: `%s'" %
162                                    (repr(path), os.strerror(errnoval)))
163             return 0
164         return 1
165
166     def _errno_save(di):
167         di._saved_errno = parse_eval('errno')
168
169     def _errno_restore(di):
170         to_restore = di._saved_errno
171         di._saved_errno = None
172         if to_restore is not None:
173             parse_eval('errno = %d' % to_restore)
174
175     def _result(di, output):
176         sys.stderr.write("#> %s" % output)
177         di._result_stream.write(output)
178         di._result_stream.flush()
179
180     # main entrypoints
181
182     def donate(di, path, control_msg):
183         # control_msg is an array of integers being the ancillary data
184         # array ("control") for sendmsg, and hence specifies which fds
185         # to pass
186
187         carrier = None
188         try:
189             di._errno_save()
190             carrier = di._socket()
191             di._connect(carrier, path)
192             di._sendmsg(carrier, control_msg)
193             di._close(carrier)
194             carrier = None
195         finally:
196             if carrier is not None:
197                 try: di._close(carrier)
198                 except Exception: pass
199             di._errno_restore()
200
201         di._result('1\n')
202
203     def geteuid(di):
204         try:
205             di._errno_save()
206             val = di._must_func('uid_t (*)(void)', 'geteuid', '()')
207         finally:
208             di._errno_restore()
209         
210         di._result('%d\n' % val)
211
212     def mkdir(di, path):
213         try:
214             di._errno_save()
215             val = di._mkdir(path, int('0700', 8))
216         finally:
217             di._errno_restore()
218
219         di._result('%d\n' % val)
220
221     def _protocol_read(di):
222         input = sys.stdin.readline().rstrip('\n')
223         sys.stderr.write("#< %s\n" % input)
224         return input
225
226     def eval_loop(di):
227         while True:
228             di._result('!\n')
229             cmd = di._protocol_read()
230             eval(cmd)