chiark / gitweb /
Bump version to 7.0.1~iwj0
[chiark-utils.git] / fishdescriptor / py / fishdescriptor / indonor.py
1
2 # This file is part of chiark-utils, a collection of useful programs
3 # used on chiark.greenend.org.uk.
4 #
5 # This file is:
6 #  Copyright 2018 Citrix Systems Ltd
7 #
8 # This is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 3, or (at your option) any later version.
11 #
12 # This is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
15 # details.
16 #
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, consult the Free Software Foundation's
19 # website at www.fsf.org, or the GNU Project website at www.gnu.org.
20
21 # class for use inside gdb which is debugging the donor process
22
23 from __future__ import print_function
24
25 import gdb
26 import copy
27 import os
28 import sys
29 import socket
30 import errno
31
32 def _string_bytearray(s):
33     # gets us bytes in py2 and py3
34     if not isinstance(s, bytes):
35         s = s.encode('utf-8') # sigh, python 2/3 compat
36     return bytearray(s)
37
38 def _string_escape_for_c(s):
39     out = ''
40     for c in _string_bytearray(s):
41         if c == ord('\\') or c == ord('"') or c < 32 or c > 126:
42             out += '\\x%02x' % c
43         else:
44             out += chr(c)
45     return out
46
47 # constructing values
48
49 def _lit_integer(v):
50     return '%d' % v
51
52 def _lit_aggregate_uncasted(val_lit_strs):
53     return '{' + ', '.join(['(%s)' % v for v in val_lit_strs]) + ' }'
54
55 def _lit_string_uncasted(s):
56     b = _string_bytearray(s)
57     return _lit_aggregate_uncasted([_lit_integer(x) for x in b] + [ '0' ])
58
59 def _lit_array(elemtype, val_lit_strs):
60     return (
61         '((%s[%d])%s)' %
62         (elemtype, len(val_lit_strs), _lit_aggregate_uncasted(val_lit_strs))
63     )
64
65 def _lit_addressof(v):
66     return '&(char[])(%s)' % v
67
68 def _make_lit(v):
69     if isinstance(v, int):
70         return _lit_integer(v)
71     else:
72         return v # should already be an integer
73
74 def parse_eval(expr):
75     sys.stderr.write("##  EVAL %s\n" % repr(expr))
76     x = gdb.parse_and_eval(expr)
77     sys.stderr.write('##  => %s\n' % x)
78     sys.stderr.flush()
79     return x
80
81 class DonorStructLayout():
82     def __init__(l, typename):
83         x = gdb.lookup_type(typename)
84         l._typename = typename
85         l._template = [ ]
86         l._posns = { }
87         for f in x.fields():
88             l._posns[f.name] = len(l._template)
89             try: f.type.fields();  blank = '{ }'
90             except TypeError:      blank = '0'
91             except AttributeError: blank = '0'
92             l._template.append(blank)
93         sys.stderr.write('##  STRUCT %s template %s fields %s\n'
94                          % (typename, l._template, l._posns))
95
96     def substitute(l, values):
97         build = copy.deepcopy(l._template)
98         for (k,v) in values.items():
99             build[ l._posns[k] ] = _make_lit(v)
100         return '((%s)%s)' % (l._typename, _lit_aggregate_uncasted(build))
101
102 class DonorImplementation():
103     def __init__(di):
104         di._structs = { }
105         di._saved_errno = None
106         di._result_stream = os.fdopen(3, 'w')
107         di._errno_workaround = None
108
109     # assembling structs
110     # sigh, we have to record the order of the arguments!
111     def _find_fields(di, typename):
112         try:
113             fields = di._structs[typename]
114         except KeyError:
115             fields = DonorStructLayout(typename)
116             di._structs[typename] = fields
117         return fields
118
119     def _make(di, typename, values):
120         fields = di._find_fields(typename)
121         return fields.substitute(values)
122
123     # hideous workaround
124
125     def _parse_eval_errno(di, expr_pat):
126         # evaluates  expr_pat % 'errno'
127         if di._errno_workaround is not True:
128             try:
129                 x = parse_eval(expr_pat % 'errno')
130                 di._errno_workaround = False
131                 return x
132             except gdb.error as e:
133                 if di._errno_workaround is False:
134                     raise e
135             di._errno_workaround = True
136         # Incomprehensibly, gdb.parse_and_eval('errno') can sometimes
137         # fail with
138         #   gdb.error: Cannot find thread-local variables on this target
139         # even though plain gdb `print errno' works while `print errno = 25'
140         # doesn't.   OMG.  This may be related to:
141         #  https://github.com/cloudburst/libheap/issues/24
142         # although I can't find it in the gdb bug db (which is half-broken
143         # in my browser).  Also the error is very nonspecific :-/.
144         # This seems to happen on jessie, and is fixed in stretch.
145         # Anyway:
146         return parse_eval(expr_pat % '(*((int*(*)(void))__errno_location)())')
147
148     # calling functions (need to cast the function name to the right
149     # type in case maybe gdb doesn't know the type)
150
151     def _func(di, functype, funcname, realargs):
152         expr = '((%s) %s) %s' % (functype, funcname, realargs)
153         return parse_eval(expr)
154
155     def _must_func(di, functype, funcname, realargs):
156         retval = di._func(functype, funcname, realargs)
157         if retval < 0:
158             errnoval = di._parse_eval_errno('%s')
159             raise RuntimeError("%s gave errno=%d `%s'" %
160                                (funcname, errnoval, os.strerror(errnoval)))
161         return retval
162
163     # wrappers for the syscalls that do what we want
164
165     def _sendmsg(di, carrier, control_msg):
166         iov_base = _lit_array('char', [1])
167         iov = di._make('struct iovec', {
168             'iov_base': iov_base,
169             'iov_len' : 1,
170         })
171
172         msg = di._make('struct msghdr', {
173             'msg_iov'       : _lit_addressof(iov),
174             'msg_iovlen'    : 1,
175             'msg_control'   : _lit_array('char', control_msg),
176             'msg_controllen': len(control_msg),
177         })
178
179         di._must_func(
180             'ssize_t (*)(int, const struct msghdr*, int)',
181             'sendmsg',
182             '(%s, %s, 0)' % (carrier, _lit_addressof(msg))
183         )
184
185     def _socket(di):
186         return di._must_func(
187             'int (*)(int, int, int)',
188             'socket',
189             '(%d, %d, 0)' % (socket.AF_UNIX, socket.SOCK_STREAM)
190         )
191
192     def _connect(di, fd, path):
193         addr = di._make('struct sockaddr_un', {
194             'sun_family' : _lit_integer(socket.AF_UNIX),
195             'sun_path'   : _lit_string_uncasted(path),
196         })
197
198         di._must_func(
199             'int (*)(int, const struct sockaddr*, socklen_t)',
200             'connect',
201             '(%d, (const struct sockaddr*)%s, sizeof(struct sockaddr_un))'
202             % (fd, _lit_addressof(addr))
203         )
204
205     def _close(di, fd):
206         di._must_func('int (*)(int)', 'close', '(%d)' % fd)
207
208     def _mkdir(di, path, mode):
209         r = di._func(
210             'int (*)(const char*, mode_t)',
211             'mkdir',
212             '("%s", %d)' % (_string_escape_for_c(path), mode)
213         )
214         if r < 0:
215             errnoval = di._parse_eval_errno('%s')
216             if errnoval != errno.EEXIST:
217                 raise RuntimeError("mkdir %s failed: `%s'" %
218                                    (repr(path), os.strerror(errnoval)))
219             return 0
220         return 1
221
222     def _errno_save(di):
223         di._saved_errno = di._parse_eval_errno('%s')
224
225     def _errno_restore(di):
226         to_restore = di._saved_errno
227         di._saved_errno = None
228         if to_restore is not None:
229             di._parse_eval_errno('%%s = %d' % to_restore)
230
231     def _result(di, output):
232         sys.stderr.write("#> %s" % output)
233         di._result_stream.write(output)
234         di._result_stream.flush()
235
236     # main entrypoints
237
238     def donate(di, path, control_msg):
239         # control_msg is an array of integers being the ancillary data
240         # array ("control") for sendmsg, and hence specifies which fds
241         # to pass
242
243         carrier = None
244         try:
245             di._errno_save()
246             carrier = di._socket()
247             di._connect(carrier, path)
248             di._sendmsg(carrier, control_msg)
249             di._close(carrier)
250             carrier = None
251         finally:
252             if carrier is not None:
253                 try: di._close(carrier)
254                 except Exception: pass
255             di._errno_restore()
256
257         di._result('1\n')
258
259     def geteuid(di):
260         try:
261             di._errno_save()
262             val = di._must_func('uid_t (*)(void)', 'geteuid', '()')
263         finally:
264             di._errno_restore()
265         
266         di._result('%d\n' % val)
267
268     def mkdir(di, path):
269         try:
270             di._errno_save()
271             val = di._mkdir(path, int('0700', 8))
272         finally:
273             di._errno_restore()
274
275         di._result('%d\n' % val)
276
277     def _protocol_read(di):
278         input = sys.stdin.readline()
279         if input == '': return None
280         input = input.rstrip('\n')
281         sys.stderr.write("#< %s\n" % input)
282         return input
283
284     def eval_loop(di):
285         if not gdb.selected_inferior().was_attached:
286             print('gdb inferior not attached', file=sys.stderr)
287             sys.exit(0)
288         while True:
289             di._result('!\n')
290             cmd = di._protocol_read()
291             if cmd is None: break
292             eval(cmd)