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