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