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