chiark / gitweb /
fishdescriptor: new 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__(di):
65         di._structs = { }
66         di._saved_errno = None
67         di._result_stream = os.fdopen(3, 'w')
68
69     # assembling structs
70     # sigh, we have to record the order of the arguments!
71     def di._find_fields(typename):
72         try:
73             fields = di._structs[typename]
74         except AttributeError:
75             fields = DonorStructLayout(typename)
76             di._structs[typename] = fields
77         return fields
78
79     def di._make(typename, values):
80         fields = di._find_fields(typename)
81         return fields.substitute(values)
82
83     # calling functions (need to cast the function name to the right
84     # type in case maybe gdb doesn't know the type)
85
86     def _func(di, functype, funcname, realargs):
87         expr = '((%s) %s) %s' % (functype, funcname, realargs)
88         return gdb.parse_and_eval(expr)
89
90     def _must_func(di, functype, funcname, realargs):
91         retval = di._func(functype, funcname, realargs)
92         if retval < 0:
93             errnoval = gdb.parse_and_eval('errno')
94             raise RuntimeError("%s gave errno=%d `%s'" %
95                                (funcname, errnoval, os.strerror(errnoval)))
96
97     # wrappers for the syscalls that do what we want
98
99     def _sendmsg(di, carrier, control_msg):
100         iov_base = _lit_array('int', map(str,fds))
101         iov = di._make('struct iovec', {
102             'iov_base': iov_base,
103             'iov_len' : len(fds),
104         })
105
106         msg = di._make('struct msghdr', {
107             'msg_iov'       : _lit_addressof(iov),
108             'msg_iovlen'    : 1,
109             'msg_control'   : _lit_array('char', control_msg),
110             'msg_controllen': len(control_msg),
111         })
112
113         di._must_func(
114             'ssize_t (*)(int, const struct msghdr*, int flags)',
115             'sendmsg',
116             '(%s, %s, 0)' % (carrier, _lit_addressof(msg))
117         )
118
119     def _socket(di):
120         return di._must_func(
121             'int (*)(int, int, int)',
122             'socket',
123             '(%d, %d, 0)' % (socket.AF_UNIX, socket.SOCK_STREAM)
124         )
125
126     def _connect(di, fd, path):
127         addr = di._make('struct sockaddr_un', {
128             'sun_family' : _lit_integer(socket.AF_UNIX),
129             'sun_path'   : _lit_string_uncasted(path),
130         })
131
132         di._must_func(
133             'int (*)(int, const struct sockaddr*, socklen_t)',
134             'connect',
135             '(%d, (const struct sockaddr*)%s, sizeof(struct sockaddr_un))'
136             % (fd, _lit_addressof(addr)
137         )
138
139     def _close(di, fd):
140         di._must_func('int (*)(int)', 'close', '(%d)' % fd)
141
142     def _mkdir(di, path, mode):
143         r = di._func(
144             'int (*)(const char*, mode_t)',
145             'mkdir',
146             '("%s", %d)' % (_string_escape_for_c(path), mode)
147         )
148         if r < 0:
149             errnoval = gdb.parse_and_eval('errno')
150             if errnoval != os.errno.EEXIST:
151                 raise RuntimeError("mkdir %s failed: `%s'" %
152                                    (repr(path), os.strerror(errnoval)))
153             return 0
154         return 1
155
156     def _errno_save(di):
157         di._saved_errno = gdb.parse_and_eval('errno')
158
159     def _errno_restore(di):
160         to_restore = di._saved_errno
161         di._saved_errno = None
162         if to_restore is not None:
163             gdb.parse_and_eval('errno = %d' % to_restore))
164
165     # main entrypoints
166
167     def result(di, output):
168         di._result_stram.write(output)
169         di._result_stram.flush()
170
171     def donate(di, path, control_msg):
172         # control_msg is an array of integers being the ancillary data
173         # array ("control") for sendmsg, and hence specifies which fds
174         # to pass
175
176         carrier = None
177         try:
178             di._errno_save()
179             carrier = di._socket()
180             di._connect(carrier, path)
181             di._sendmsg(carrier, control_msg)
182             di._close(carrier)
183             carrier = None
184         finally:
185             if carrier is not None:
186                 try: di._close(carrier)
187                 except Exception: pass
188             di._errno_restore()
189
190         di._result('1\n')
191
192     def geteuid(di):
193         try:
194             di._errno_save()
195             val = di._must_func('uid_t (*)(void)', 'geteuid', '()')
196         finally:
197             di._errno_restore()
198         
199         di._result('%d\n' % val)
200
201     def mkdir(di, path):
202         try:
203             di._errno_save()
204             val = di._mkdir(path, '0700')
205         finally:
206             di._errno_restore()
207
208         di._result('%d\n' % val)
209
210     def _protocol_read(di):
211         return sys.stdin.readline().rstrip('\n')
212
213     def eval_loop(di):
214         while True:
215             di._result('!\n')
216             cmd = di._protocol_read()
217             eval(cmd)