xref: /qemu/scripts/qapi/commands.py (revision b853a79f)
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)
30from .schema import (
31    QAPISchema,
32    QAPISchemaFeature,
33    QAPISchemaObjectType,
34    QAPISchemaType,
35)
36from .source import QAPISourceInfo
37
38
39def gen_command_decl(name: str,
40                     arg_type: Optional[QAPISchemaObjectType],
41                     boxed: bool,
42                     ret_type: Optional[QAPISchemaType]) -> str:
43    return mcgen('''
44%(c_type)s qmp_%(c_name)s(%(params)s);
45''',
46                 c_type=(ret_type and ret_type.c_type()) or 'void',
47                 c_name=c_name(name),
48                 params=build_params(arg_type, boxed, 'Error **errp'))
49
50
51def gen_call(name: str,
52             arg_type: Optional[QAPISchemaObjectType],
53             boxed: bool,
54             ret_type: Optional[QAPISchemaType]) -> str:
55    ret = ''
56
57    argstr = ''
58    if boxed:
59        assert arg_type
60        argstr = '&arg, '
61    elif arg_type:
62        assert not arg_type.variants
63        for memb in arg_type.members:
64            if memb.optional:
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    ret = mcgen('''
73
74    %(lhs)sqmp_%(c_name)s(%(args)s&err);
75    error_propagate(errp, err);
76''',
77                c_name=c_name(name), args=argstr, lhs=lhs)
78    if ret_type:
79        ret += mcgen('''
80    if (err) {
81        goto out;
82    }
83
84    qmp_marshal_output_%(c_name)s(retval, ret, errp);
85''',
86                     c_name=ret_type.c_name())
87    return ret
88
89
90def gen_marshal_output(ret_type: QAPISchemaType) -> str:
91    return mcgen('''
92
93static void qmp_marshal_output_%(c_name)s(%(c_type)s ret_in,
94                                QObject **ret_out, Error **errp)
95{
96    Visitor *v;
97
98    v = qobject_output_visitor_new(ret_out);
99    if (visit_type_%(c_name)s(v, "unused", &ret_in, errp)) {
100        visit_complete(v, ret_out);
101    }
102    visit_free(v);
103    v = qapi_dealloc_visitor_new();
104    visit_type_%(c_name)s(v, "unused", &ret_in, NULL);
105    visit_free(v);
106}
107''',
108                 c_type=ret_type.c_type(), c_name=ret_type.c_name())
109
110
111def build_marshal_proto(name: str) -> str:
112    return ('void qmp_marshal_%s(QDict *args, QObject **ret, Error **errp)'
113            % c_name(name))
114
115
116def gen_marshal_decl(name: str) -> str:
117    return mcgen('''
118%(proto)s;
119''',
120                 proto=build_marshal_proto(name))
121
122
123def gen_marshal(name: str,
124                arg_type: Optional[QAPISchemaObjectType],
125                boxed: bool,
126                ret_type: Optional[QAPISchemaType]) -> str:
127    have_args = boxed or (arg_type and not arg_type.is_empty())
128    if have_args:
129        assert arg_type is not None
130        arg_type_c_name = arg_type.c_name()
131
132    ret = mcgen('''
133
134%(proto)s
135{
136    Error *err = NULL;
137    bool ok = false;
138    Visitor *v;
139''',
140                proto=build_marshal_proto(name))
141
142    if ret_type:
143        ret += mcgen('''
144    %(c_type)s retval;
145''',
146                     c_type=ret_type.c_type())
147
148    if have_args:
149        ret += mcgen('''
150    %(c_name)s arg = {0};
151''',
152                     c_name=arg_type_c_name)
153
154    ret += mcgen('''
155
156    v = qobject_input_visitor_new(QOBJECT(args));
157    if (!visit_start_struct(v, NULL, NULL, 0, errp)) {
158        goto out;
159    }
160''')
161
162    if have_args:
163        ret += mcgen('''
164    if (visit_type_%(c_arg_type)s_members(v, &arg, errp)) {
165        ok = visit_check_struct(v, errp);
166    }
167''',
168                     c_arg_type=arg_type_c_name)
169    else:
170        ret += mcgen('''
171    ok = visit_check_struct(v, errp);
172''')
173
174    ret += mcgen('''
175    visit_end_struct(v, NULL);
176    if (!ok) {
177        goto out;
178    }
179''')
180
181    ret += gen_call(name, arg_type, boxed, ret_type)
182
183    ret += mcgen('''
184
185out:
186    visit_free(v);
187''')
188
189    ret += mcgen('''
190    v = qapi_dealloc_visitor_new();
191    visit_start_struct(v, NULL, NULL, 0, NULL);
192''')
193
194    if have_args:
195        ret += mcgen('''
196    visit_type_%(c_arg_type)s_members(v, &arg, NULL);
197''',
198                     c_arg_type=arg_type_c_name)
199
200    ret += mcgen('''
201    visit_end_struct(v, NULL);
202    visit_free(v);
203''')
204
205    ret += mcgen('''
206}
207''')
208    return ret
209
210
211def gen_register_command(name: str,
212                         success_response: bool,
213                         allow_oob: bool,
214                         allow_preconfig: bool,
215                         coroutine: bool) -> str:
216    options = []
217
218    if not success_response:
219        options += ['QCO_NO_SUCCESS_RESP']
220    if allow_oob:
221        options += ['QCO_ALLOW_OOB']
222    if allow_preconfig:
223        options += ['QCO_ALLOW_PRECONFIG']
224    if coroutine:
225        options += ['QCO_COROUTINE']
226
227    if not options:
228        options = ['QCO_NO_OPTIONS']
229
230    ret = mcgen('''
231    qmp_register_command(cmds, "%(name)s",
232                         qmp_marshal_%(c_name)s, %(opts)s);
233''',
234                name=name, c_name=c_name(name),
235                opts=" | ".join(options))
236    return ret
237
238
239class QAPISchemaGenCommandVisitor(QAPISchemaModularCVisitor):
240    def __init__(self, prefix: str):
241        super().__init__(
242            prefix, 'qapi-commands',
243            ' * Schema-defined QAPI/QMP commands', None, __doc__)
244        self._visited_ret_types: Dict[QAPIGenC, Set[QAPISchemaType]] = {}
245
246    def _begin_user_module(self, name: str) -> None:
247        self._visited_ret_types[self._genc] = set()
248        commands = self._module_basename('qapi-commands', name)
249        types = self._module_basename('qapi-types', name)
250        visit = self._module_basename('qapi-visit', name)
251        self._genc.add(mcgen('''
252#include "qemu/osdep.h"
253#include "qapi/visitor.h"
254#include "qapi/qmp/qdict.h"
255#include "qapi/qobject-output-visitor.h"
256#include "qapi/qobject-input-visitor.h"
257#include "qapi/dealloc-visitor.h"
258#include "qapi/error.h"
259#include "%(visit)s.h"
260#include "%(commands)s.h"
261
262''',
263                             commands=commands, visit=visit))
264        self._genh.add(mcgen('''
265#include "%(types)s.h"
266
267''',
268                             types=types))
269
270    def visit_begin(self, schema: QAPISchema) -> None:
271        self._add_module('./init', ' * QAPI Commands initialization')
272        self._genh.add(mcgen('''
273#include "qapi/qmp/dispatch.h"
274
275void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds);
276''',
277                             c_prefix=c_name(self._prefix, protect=False)))
278        self._genc.add(mcgen('''
279#include "qemu/osdep.h"
280#include "%(prefix)sqapi-commands.h"
281#include "%(prefix)sqapi-init-commands.h"
282
283void %(c_prefix)sqmp_init_marshal(QmpCommandList *cmds)
284{
285    QTAILQ_INIT(cmds);
286
287''',
288                             prefix=self._prefix,
289                             c_prefix=c_name(self._prefix, protect=False)))
290
291    def visit_end(self) -> None:
292        with self._temp_module('./init'):
293            self._genc.add(mcgen('''
294}
295'''))
296
297    def visit_command(self,
298                      name: str,
299                      info: Optional[QAPISourceInfo],
300                      ifcond: List[str],
301                      features: List[QAPISchemaFeature],
302                      arg_type: Optional[QAPISchemaObjectType],
303                      ret_type: Optional[QAPISchemaType],
304                      gen: bool,
305                      success_response: bool,
306                      boxed: bool,
307                      allow_oob: bool,
308                      allow_preconfig: bool,
309                      coroutine: bool) -> None:
310        if not gen:
311            return
312        # FIXME: If T is a user-defined type, the user is responsible
313        # for making this work, i.e. to make T's condition the
314        # conjunction of the T-returning commands' conditions.  If T
315        # is a built-in type, this isn't possible: the
316        # qmp_marshal_output_T() will be generated unconditionally.
317        if ret_type and ret_type not in self._visited_ret_types[self._genc]:
318            self._visited_ret_types[self._genc].add(ret_type)
319            with ifcontext(ret_type.ifcond,
320                           self._genh, self._genc):
321                self._genc.add(gen_marshal_output(ret_type))
322        with ifcontext(ifcond, self._genh, self._genc):
323            self._genh.add(gen_command_decl(name, arg_type, boxed, ret_type))
324            self._genh.add(gen_marshal_decl(name))
325            self._genc.add(gen_marshal(name, arg_type, boxed, ret_type))
326        with self._temp_module('./init'):
327            with ifcontext(ifcond, self._genh, self._genc):
328                self._genc.add(gen_register_command(name, success_response,
329                                                    allow_oob, allow_preconfig,
330                                                    coroutine))
331
332
333def gen_commands(schema: QAPISchema,
334                 output_dir: str,
335                 prefix: str) -> None:
336    vis = QAPISchemaGenCommandVisitor(prefix)
337    schema.visit(vis)
338    vis.write(output_dir)
339