master
py 393 lines 10.6 KB
Raw
1 """
2 QAPI command marshaller generator
3
4 Copyright IBM, Corp. 2011
5 Copyright (C) 2014-2018 Red Hat, Inc.
6
7 Authors:
8 Anthony Liguori <aliguori@us.ibm.com>
9 Michael Roth <mdroth@linux.vnet.ibm.com>
10 Markus Armbruster <armbru@redhat.com>
11
12 This work is licensed under the terms of the GNU GPL, version 2.
13 See the COPYING file in the top-level directory.
14 """
15
16 from typing import List, Optional
17
18 from .common import c_name, mcgen
19 from .gen import (
20 QAPISchemaModularCVisitor,
21 build_params,
22 gen_features,
23 ifcontext,
24 )
25 from .schema import (
26 QAPISchema,
27 QAPISchemaFeature,
28 QAPISchemaIfCond,
29 QAPISchemaObjectType,
30 QAPISchemaType,
31 )
32 from .source import QAPISourceInfo
33
34
35 def gen_command_decl(name: str,
36 arg_type: Optional[QAPISchemaObjectType],
37 boxed: bool,
38 ret_type: Optional[QAPISchemaType],
39 coroutine: bool) -> str:
40 return mcgen('''
41 %(c_type)s %(coroutine_fn)sqmp_%(c_name)s(%(params)s);
42 ''',
43 c_type=(ret_type and ret_type.c_type()) or 'void',
44 coroutine_fn='coroutine_fn ' if coroutine else '',
45 c_name=c_name(name),
46 params=build_params(arg_type, boxed, 'Error **errp'))
47
48
49 def gen_call(name: str,
50 arg_type: Optional[QAPISchemaObjectType],
51 boxed: bool,
52 ret_type: Optional[QAPISchemaType],
53 gen_tracing: bool) -> str:
54 ret = ''
55
56 argstr = ''
57 if boxed:
58 assert arg_type
59 argstr = '&arg, '
60 elif arg_type:
61 assert not arg_type.branches
62 for memb in arg_type.members:
63 assert not memb.ifcond.is_present()
64 if memb.need_has():
65 argstr += 'arg.has_%s, ' % c_name(memb.name)
66 argstr += 'arg.%s, ' % c_name(memb.name)
67
68 lhs = ''
69 if ret_type:
70 lhs = 'retval = '
71
72 name = c_name(name)
73 upper = name.upper()
74
75 if gen_tracing:
76 ret += mcgen('''
77
78 if (trace_event_get_state_backends(TRACE_QMP_ENTER_%(upper)s)) {
79 g_autoptr(GString) req_json = qobject_to_json(QOBJECT(args));
80
81 trace_qmp_enter_%(name)s(req_json->str);
82 }
83 ''',
84 upper=upper, name=name)
85
86 ret += mcgen('''
87
88 %(lhs)sqmp_%(name)s(%(args)s&err);
89 ''',
90 name=name, args=argstr, lhs=lhs)
91
92 ret += mcgen('''
93 if (err) {
94 ''')
95
96 if gen_tracing:
97 ret += mcgen('''
98 trace_qmp_exit_%(name)s(error_get_pretty(err), false);
99 ''',
100 name=name)
101
102 ret += mcgen('''
103 error_propagate(errp, err);
104 goto out;
105 }
106 ''')
107
108 if ret_type:
109 ret += gen_marshal_output(ret_type)
110
111 if gen_tracing:
112 if ret_type:
113 ret += mcgen('''
114
115 if (trace_event_get_state_backends(TRACE_QMP_EXIT_%(upper)s)) {
116 g_autoptr(GString) ret_json = qobject_to_json(*ret);
117
118 trace_qmp_exit_%(name)s(ret_json->str, true);
119 }
120 ''',
121 upper=upper, name=name)
122 else:
123 ret += mcgen('''
124
125 trace_qmp_exit_%(name)s("{}", true);
126 ''',
127 name=name)
128
129 return ret
130
131
132 def gen_marshal_output(ret_type: QAPISchemaType) -> str:
133 return mcgen('''
134
135 ov = qobject_output_visitor_new_qmp(ret);
136 if (visit_type_%(c_name)s(ov, "unused", &retval, errp)) {
137 visit_complete(ov, ret);
138 }
139 visit_free(ov);
140 ov = qapi_dealloc_visitor_new();
141 visit_type_%(c_name)s(ov, "unused", &retval, NULL);
142 visit_free(ov);
143 ''',
144 c_name=ret_type.c_name())
145
146
147 def build_marshal_proto(name: str,
148 coroutine: bool) -> str:
149 return ('void %(coroutine_fn)sqmp_marshal_%(c_name)s(%(params)s)' % {
150 'coroutine_fn': 'coroutine_fn ' if coroutine else '',
151 'c_name': c_name(name),
152 'params': 'QDict *args, QObject **ret, Error **errp',
153 })
154
155
156 def gen_marshal_decl(name: str,
157 coroutine: bool) -> str:
158 return mcgen('''
159 %(proto)s;
160 ''',
161 proto=build_marshal_proto(name, coroutine))
162
163
164 def gen_trace(name: str) -> str:
165 return mcgen('''
166 qmp_enter_%(name)s(const char *json) "%%s"
167 qmp_exit_%(name)s(const char *result, bool succeeded) "%%s %%d"
168 ''',
169 name=c_name(name))
170
171
172 def gen_marshal(name: str,
173 arg_type: Optional[QAPISchemaObjectType],
174 boxed: bool,
175 ret_type: Optional[QAPISchemaType],
176 gen_tracing: bool,
177 coroutine: bool) -> str:
178 have_args = boxed or (arg_type and not arg_type.is_empty())
179 if have_args:
180 assert arg_type is not None
181 arg_type_c_name = arg_type.c_name()
182
183 ret = mcgen('''
184
185 %(proto)s
186 {
187 Error *err = NULL;
188 bool ok = false;
189 Visitor *v;
190 ''',
191 proto=build_marshal_proto(name, coroutine))
192
193 if ret_type:
194 ret += mcgen('''
195 %(c_type)s retval;
196 Visitor *ov;
197 ''',
198 c_type=ret_type.c_type())
199
200 if have_args:
201 ret += mcgen('''
202 %(c_name)s arg = {0};
203 ''',
204 c_name=arg_type_c_name)
205
206 ret += mcgen('''
207
208 v = qobject_input_visitor_new_qmp(QOBJECT(args));
209 if (!visit_start_struct(v, NULL, NULL, 0, errp)) {
210 goto out;
211 }
212 ''')
213
214 if have_args:
215 ret += mcgen('''
216 if (visit_type_%(c_arg_type)s_members(v, &arg, errp)) {
217 ok = visit_check_struct(v, errp);
218 }
219 ''',
220 c_arg_type=arg_type_c_name)
221 else:
222 ret += mcgen('''
223 ok = visit_check_struct(v, errp);
224 ''')
225
226 ret += mcgen('''
227 visit_end_struct(v, NULL);
228 if (!ok) {
229 goto out;
230 }
231 ''')
232
233 ret += gen_call(name, arg_type, boxed, ret_type, gen_tracing)
234
235 ret += mcgen('''
236
237 out:
238 visit_free(v);
239 ''')
240
241 ret += mcgen('''
242 v = qapi_dealloc_visitor_new();
243 visit_start_struct(v, NULL, NULL, 0, NULL);
244 ''')
245
246 if have_args:
247 ret += mcgen('''
248 visit_type_%(c_arg_type)s_members(v, &arg, NULL);
249 ''',
250 c_arg_type=arg_type_c_name)
251
252 ret += mcgen('''
253 visit_end_struct(v, NULL);
254 visit_free(v);
255 ''')
256
257 ret += mcgen('''
258 }
259 ''')
260 return ret
261
262
263 def gen_register_command(name: str,
264 features: List[QAPISchemaFeature],
265 success_response: bool,
266 allow_oob: bool,
267 allow_preconfig: bool,
268 coroutine: bool) -> str:
269 options = []
270
271 if not success_response:
272 options += ['QCO_NO_SUCCESS_RESP']
273 if allow_oob:
274 options += ['QCO_ALLOW_OOB']
275 if allow_preconfig:
276 options += ['QCO_ALLOW_PRECONFIG']
277 if coroutine:
278 options += ['QCO_COROUTINE']
279
280 ret = mcgen('''
281 qmp_register_command(cmds, "%(name)s",
282 qmp_marshal_%(c_name)s, %(opts)s, %(feats)s);
283 ''',
284 name=name, c_name=c_name(name),
285 opts=' | '.join(options) or 0,
286 feats=gen_features(features))
287 return ret
288
289
290 class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
291 def __init__(self, prefix: str, gen_tracing: bool):
292 super().__init__(
293 prefix, 'qapi-commands',
294 ' * Schema-defined QAPI/QMP commands', None, __doc__,
295 gen_tracing=gen_tracing)
296 self._gen_tracing = gen_tracing
297
298 def _begin_user_module(self, name: str) -> None:
299 commands = self._module_basename('qapi-commands', name)
300 types = self._module_basename('qapi-types', name)
301 visit = self._module_basename('qapi-visit', name)
302 self._genc.add(mcgen('''
303 #include "qemu/osdep.h"
304 #include "qapi/compat-policy.h"
305 #include "qapi/visitor.h"
306 #include "qobject/qdict.h"
307 #include "qapi/dealloc-visitor.h"
308 #include "qapi/error.h"
309 #include "%(visit)s.h"
310 #include "%(commands)s.h"
311 ''',
312 commands=commands, visit=visit))
313
314 if self._gen_tracing and commands != 'qapi-commands':
315 self._genc.add(mcgen('''
316 #include "qobject/qjson.h"
317 #include "trace/trace-%(nm)s_trace_events.h"
318 ''',
319 nm=c_name(commands, protect=False)))
320 # We use c_name(commands, protect=False) to turn '-' into '_', to
321 # match .underscorify() in trace/meson.build
322
323 self._genh.add(mcgen('''
324 #include "%(types)s.h"
325
326 ''',
327 types=types))
328
329 def visit_begin(self, schema: QAPISchema) -> None:
330 self._add_module('./init', ' * QAPI Commands initialization')
331 self._genh.add(mcgen('''
332 #include "qapi/qmp-registry.h"
333
334 void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
335 ''',
336 c_prefix=c_name(self._prefix, protect=False)))
337 self._genc.add(mcgen('''
338 #include "qemu/osdep.h"
339 #include "%(prefix)sqapi-commands.h"
340 #include "%(prefix)sqapi-init-commands.h"
341 #include "%(prefix)sqapi-features.h"
342
343 void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
344 {
345 QTAILQ_INIT(cmds);
346
347 ''',
348 prefix=self._prefix,
349 c_prefix=c_name(self._prefix, protect=False)))
350
351 def visit_end(self) -> None:
352 with self._temp_module('./init'):
353 self._genc.add(mcgen('''
354 }
355 '''))
356
357 def visit_command(self,
358 name: str,
359 info: Optional[QAPISourceInfo],
360 ifcond: QAPISchemaIfCond,
361 features: List[QAPISchemaFeature],
362 arg_type: Optional[QAPISchemaObjectType],
363 ret_type: Optional[QAPISchemaType],
364 gen: bool,
365 success_response: bool,
366 boxed: bool,
367 allow_oob: bool,
368 allow_preconfig: bool,
369 coroutine: bool) -> None:
370 if not gen:
371 return
372 with ifcontext(ifcond, self._genh, self._genc):
373 self._genh.add(gen_command_decl(name, arg_type, boxed,
374 ret_type, coroutine))
375 self._genh.add(gen_marshal_decl(name, coroutine))
376 self._genc.add(gen_marshal(name, arg_type, boxed, ret_type,
377 self._gen_tracing, coroutine))
378 if self._gen_tracing:
379 self._gen_trace_events.add(gen_trace(name))
380 with self._temp_module('./init'):
381 with ifcontext(ifcond, self._genh, self._genc):
382 self._genc.add(gen_register_command(
383 name, features, success_response, allow_oob,
384 allow_preconfig, coroutine))
385
386
387 def gen_commands(schema: QAPISchema,
388 output_dir: str,
389 prefix: str,
390 gen_tracing: bool) -> None:
391 vis = QAPISchemaGenCommandVisitor(prefix, gen_tracing)
392 schema.visit(vis)
393 vis.write(output_dir)