xref: /openbmc/linux/tools/net/ynl/ynl-gen-c.py (revision 49c23519)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
3
4import argparse
5import collections
6import os
7import re
8import shutil
9import tempfile
10import yaml
11
12from lib import SpecFamily, SpecAttrSet, SpecAttr, SpecOperation, SpecEnumSet, SpecEnumEntry
13
14
15def c_upper(name):
16    return name.upper().replace('-', '_')
17
18
19def c_lower(name):
20    return name.lower().replace('-', '_')
21
22
23class BaseNlLib:
24    def get_family_id(self):
25        return 'ys->family_id'
26
27    def parse_cb_run(self, cb, data, is_dump=False, indent=1):
28        ind = '\n\t\t' + '\t' * indent + ' '
29        if is_dump:
30            return f"mnl_cb_run2(ys->rx_buf, len, 0, 0, {cb}, {data},{ind}ynl_cb_array, NLMSG_MIN_TYPE)"
31        else:
32            return f"mnl_cb_run2(ys->rx_buf, len, ys->seq, ys->portid,{ind}{cb}, {data},{ind}" + \
33                   "ynl_cb_array, NLMSG_MIN_TYPE)"
34
35
36class Type(SpecAttr):
37    def __init__(self, family, attr_set, attr, value):
38        super().__init__(family, attr_set, attr, value)
39
40        self.attr = attr
41        self.attr_set = attr_set
42        self.type = attr['type']
43        self.checks = attr.get('checks', {})
44
45        if 'len' in attr:
46            self.len = attr['len']
47        if 'nested-attributes' in attr:
48            self.nested_attrs = attr['nested-attributes']
49            if self.nested_attrs == family.name:
50                self.nested_render_name = f"{family.name}"
51            else:
52                self.nested_render_name = f"{family.name}_{c_lower(self.nested_attrs)}"
53
54            if self.nested_attrs in self.family.consts:
55                self.nested_struct_type = 'struct ' + self.nested_render_name + '_'
56            else:
57                self.nested_struct_type = 'struct ' + self.nested_render_name
58
59        self.c_name = c_lower(self.name)
60        if self.c_name in _C_KW:
61            self.c_name += '_'
62
63        # Added by resolve():
64        self.enum_name = None
65        delattr(self, "enum_name")
66
67    def resolve(self):
68        if 'name-prefix' in self.attr:
69            enum_name = f"{self.attr['name-prefix']}{self.name}"
70        else:
71            enum_name = f"{self.attr_set.name_prefix}{self.name}"
72        self.enum_name = c_upper(enum_name)
73
74    def is_multi_val(self):
75        return None
76
77    def is_scalar(self):
78        return self.type in {'u8', 'u16', 'u32', 'u64', 's32', 's64'}
79
80    def presence_type(self):
81        return 'bit'
82
83    def presence_member(self, space, type_filter):
84        if self.presence_type() != type_filter:
85            return
86
87        if self.presence_type() == 'bit':
88            pfx = '__' if space == 'user' else ''
89            return f"{pfx}u32 {self.c_name}:1;"
90
91        if self.presence_type() == 'len':
92            pfx = '__' if space == 'user' else ''
93            return f"{pfx}u32 {self.c_name}_len;"
94
95    def _complex_member_type(self, ri):
96        return None
97
98    def free_needs_iter(self):
99        return False
100
101    def free(self, ri, var, ref):
102        if self.is_multi_val() or self.presence_type() == 'len':
103            ri.cw.p(f'free({var}->{ref}{self.c_name});')
104
105    def arg_member(self, ri):
106        member = self._complex_member_type(ri)
107        if member:
108            arg = [member + ' *' + self.c_name]
109            if self.presence_type() == 'count':
110                arg += ['unsigned int n_' + self.c_name]
111            return arg
112        raise Exception(f"Struct member not implemented for class type {self.type}")
113
114    def struct_member(self, ri):
115        if self.is_multi_val():
116            ri.cw.p(f"unsigned int n_{self.c_name};")
117        member = self._complex_member_type(ri)
118        if member:
119            ptr = '*' if self.is_multi_val() else ''
120            ri.cw.p(f"{member} {ptr}{self.c_name};")
121            return
122        members = self.arg_member(ri)
123        for one in members:
124            ri.cw.p(one + ';')
125
126    def _attr_policy(self, policy):
127        return '{ .type = ' + policy + ', }'
128
129    def attr_policy(self, cw):
130        policy = c_upper('nla-' + self.attr['type'])
131
132        spec = self._attr_policy(policy)
133        cw.p(f"\t[{self.enum_name}] = {spec},")
134
135    def _attr_typol(self):
136        raise Exception(f"Type policy not implemented for class type {self.type}")
137
138    def attr_typol(self, cw):
139        typol = self._attr_typol()
140        cw.p(f'[{self.enum_name}] = {"{"} .name = "{self.name}", {typol}{"}"},')
141
142    def _attr_put_line(self, ri, var, line):
143        if self.presence_type() == 'bit':
144            ri.cw.p(f"if ({var}->_present.{self.c_name})")
145        elif self.presence_type() == 'len':
146            ri.cw.p(f"if ({var}->_present.{self.c_name}_len)")
147        ri.cw.p(f"{line};")
148
149    def _attr_put_simple(self, ri, var, put_type):
150        line = f"mnl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name})"
151        self._attr_put_line(ri, var, line)
152
153    def attr_put(self, ri, var):
154        raise Exception(f"Put not implemented for class type {self.type}")
155
156    def _attr_get(self, ri, var):
157        raise Exception(f"Attr get not implemented for class type {self.type}")
158
159    def attr_get(self, ri, var, first):
160        lines, init_lines, local_vars = self._attr_get(ri, var)
161        if type(lines) is str:
162            lines = [lines]
163        if type(init_lines) is str:
164            init_lines = [init_lines]
165
166        kw = 'if' if first else 'else if'
167        ri.cw.block_start(line=f"{kw} (type == {self.enum_name})")
168        if local_vars:
169            for local in local_vars:
170                ri.cw.p(local)
171            ri.cw.nl()
172
173        if not self.is_multi_val():
174            ri.cw.p("if (ynl_attr_validate(yarg, attr))")
175            ri.cw.p("return MNL_CB_ERROR;")
176            if self.presence_type() == 'bit':
177                ri.cw.p(f"{var}->_present.{self.c_name} = 1;")
178
179        if init_lines:
180            ri.cw.nl()
181            for line in init_lines:
182                ri.cw.p(line)
183
184        for line in lines:
185            ri.cw.p(line)
186        ri.cw.block_end()
187        return True
188
189    def _setter_lines(self, ri, member, presence):
190        raise Exception(f"Setter not implemented for class type {self.type}")
191
192    def setter(self, ri, space, direction, deref=False, ref=None):
193        ref = (ref if ref else []) + [self.c_name]
194        var = "req"
195        member = f"{var}->{'.'.join(ref)}"
196
197        code = []
198        presence = ''
199        for i in range(0, len(ref)):
200            presence = f"{var}->{'.'.join(ref[:i] + [''])}_present.{ref[i]}"
201            # Every layer below last is a nest, so we know it uses bit presence
202            # last layer is "self" and may be a complex type
203            if i == len(ref) - 1 and self.presence_type() != 'bit':
204                continue
205            code.append(presence + ' = 1;')
206        code += self._setter_lines(ri, member, presence)
207
208        func_name = f"{op_prefix(ri, direction, deref=deref)}_set_{'_'.join(ref)}"
209        free = bool([x for x in code if 'free(' in x])
210        alloc = bool([x for x in code if 'alloc(' in x])
211        if free and not alloc:
212            func_name = '__' + func_name
213        ri.cw.write_func('static inline void', func_name, body=code,
214                         args=[f'{type_name(ri, direction, deref=deref)} *{var}'] + self.arg_member(ri))
215
216
217class TypeUnused(Type):
218    def presence_type(self):
219        return ''
220
221    def arg_member(self, ri):
222        return []
223
224    def _attr_get(self, ri, var):
225        return ['return MNL_CB_ERROR;'], None, None
226
227    def _attr_typol(self):
228        return '.type = YNL_PT_REJECT, '
229
230    def attr_policy(self, cw):
231        pass
232
233
234class TypePad(Type):
235    def presence_type(self):
236        return ''
237
238    def arg_member(self, ri):
239        return []
240
241    def _attr_typol(self):
242        return '.type = YNL_PT_IGNORE, '
243
244    def attr_put(self, ri, var):
245        pass
246
247    def attr_get(self, ri, var, first):
248        pass
249
250    def attr_policy(self, cw):
251        pass
252
253    def setter(self, ri, space, direction, deref=False, ref=None):
254        pass
255
256
257class TypeScalar(Type):
258    def __init__(self, family, attr_set, attr, value):
259        super().__init__(family, attr_set, attr, value)
260
261        self.byte_order_comment = ''
262        if 'byte-order' in attr:
263            self.byte_order_comment = f" /* {attr['byte-order']} */"
264
265        # Added by resolve():
266        self.is_bitfield = None
267        delattr(self, "is_bitfield")
268        self.type_name = None
269        delattr(self, "type_name")
270
271    def resolve(self):
272        self.resolve_up(super())
273
274        if 'enum-as-flags' in self.attr and self.attr['enum-as-flags']:
275            self.is_bitfield = True
276        elif 'enum' in self.attr:
277            self.is_bitfield = self.family.consts[self.attr['enum']]['type'] == 'flags'
278        else:
279            self.is_bitfield = False
280
281        maybe_enum = not self.is_bitfield and 'enum' in self.attr
282        if maybe_enum and self.family.consts[self.attr['enum']].enum_name:
283            self.type_name = f"enum {self.family.name}_{c_lower(self.attr['enum'])}"
284        else:
285            self.type_name = '__' + self.type
286
287    def _mnl_type(self):
288        t = self.type
289        # mnl does not have a helper for signed types
290        if t[0] == 's':
291            t = 'u' + t[1:]
292        return t
293
294    def _attr_policy(self, policy):
295        if 'flags-mask' in self.checks or self.is_bitfield:
296            if self.is_bitfield:
297                enum = self.family.consts[self.attr['enum']]
298                mask = enum.get_mask(as_flags=True)
299            else:
300                flags = self.family.consts[self.checks['flags-mask']]
301                flag_cnt = len(flags['entries'])
302                mask = (1 << flag_cnt) - 1
303            return f"NLA_POLICY_MASK({policy}, 0x{mask:x})"
304        elif 'min' in self.checks:
305            return f"NLA_POLICY_MIN({policy}, {self.checks['min']})"
306        elif 'enum' in self.attr:
307            enum = self.family.consts[self.attr['enum']]
308            low, high = enum.value_range()
309            if low == 0:
310                return f"NLA_POLICY_MAX({policy}, {high})"
311            return f"NLA_POLICY_RANGE({policy}, {low}, {high})"
312        return super()._attr_policy(policy)
313
314    def _attr_typol(self):
315        return f'.type = YNL_PT_U{self.type[1:]}, '
316
317    def arg_member(self, ri):
318        return [f'{self.type_name} {self.c_name}{self.byte_order_comment}']
319
320    def attr_put(self, ri, var):
321        self._attr_put_simple(ri, var, self._mnl_type())
322
323    def _attr_get(self, ri, var):
324        return f"{var}->{self.c_name} = mnl_attr_get_{self._mnl_type()}(attr);", None, None
325
326    def _setter_lines(self, ri, member, presence):
327        return [f"{member} = {self.c_name};"]
328
329
330class TypeFlag(Type):
331    def arg_member(self, ri):
332        return []
333
334    def _attr_typol(self):
335        return '.type = YNL_PT_FLAG, '
336
337    def attr_put(self, ri, var):
338        self._attr_put_line(ri, var, f"mnl_attr_put(nlh, {self.enum_name}, 0, NULL)")
339
340    def _attr_get(self, ri, var):
341        return [], None, None
342
343    def _setter_lines(self, ri, member, presence):
344        return []
345
346
347class TypeString(Type):
348    def arg_member(self, ri):
349        return [f"const char *{self.c_name}"]
350
351    def presence_type(self):
352        return 'len'
353
354    def struct_member(self, ri):
355        ri.cw.p(f"char *{self.c_name};")
356
357    def _attr_typol(self):
358        return f'.type = YNL_PT_NUL_STR, '
359
360    def _attr_policy(self, policy):
361        mem = '{ .type = ' + policy
362        if 'max-len' in self.checks:
363            mem += ', .len = ' + str(self.checks['max-len'])
364        mem += ', }'
365        return mem
366
367    def attr_policy(self, cw):
368        if self.checks.get('unterminated-ok', False):
369            policy = 'NLA_STRING'
370        else:
371            policy = 'NLA_NUL_STRING'
372
373        spec = self._attr_policy(policy)
374        cw.p(f"\t[{self.enum_name}] = {spec},")
375
376    def attr_put(self, ri, var):
377        self._attr_put_simple(ri, var, 'strz')
378
379    def _attr_get(self, ri, var):
380        len_mem = var + '->_present.' + self.c_name + '_len'
381        return [f"{len_mem} = len;",
382                f"{var}->{self.c_name} = malloc(len + 1);",
383                f"memcpy({var}->{self.c_name}, mnl_attr_get_str(attr), len);",
384                f"{var}->{self.c_name}[len] = 0;"], \
385               ['len = strnlen(mnl_attr_get_str(attr), mnl_attr_get_payload_len(attr));'], \
386               ['unsigned int len;']
387
388    def _setter_lines(self, ri, member, presence):
389        return [f"free({member});",
390                f"{presence}_len = strlen({self.c_name});",
391                f"{member} = malloc({presence}_len + 1);",
392                f'memcpy({member}, {self.c_name}, {presence}_len);',
393                f'{member}[{presence}_len] = 0;']
394
395
396class TypeBinary(Type):
397    def arg_member(self, ri):
398        return [f"const void *{self.c_name}", 'size_t len']
399
400    def presence_type(self):
401        return 'len'
402
403    def struct_member(self, ri):
404        ri.cw.p(f"void *{self.c_name};")
405
406    def _attr_typol(self):
407        return f'.type = YNL_PT_BINARY,'
408
409    def _attr_policy(self, policy):
410        mem = '{ '
411        if len(self.checks) == 1 and 'min-len' in self.checks:
412            mem += '.len = ' + str(self.checks['min-len'])
413        elif len(self.checks) == 0:
414            mem += '.type = NLA_BINARY'
415        else:
416            raise Exception('One or more of binary type checks not implemented, yet')
417        mem += ', }'
418        return mem
419
420    def attr_put(self, ri, var):
421        self._attr_put_line(ri, var, f"mnl_attr_put(nlh, {self.enum_name}, " +
422                            f"{var}->_present.{self.c_name}_len, {var}->{self.c_name})")
423
424    def _attr_get(self, ri, var):
425        len_mem = var + '->_present.' + self.c_name + '_len'
426        return [f"{len_mem} = len;",
427                f"{var}->{self.c_name} = malloc(len);",
428                f"memcpy({var}->{self.c_name}, mnl_attr_get_payload(attr), len);"], \
429               ['len = mnl_attr_get_payload_len(attr);'], \
430               ['unsigned int len;']
431
432    def _setter_lines(self, ri, member, presence):
433        return [f"free({member});",
434                f"{presence}_len = len;",
435                f"{member} = malloc({presence}_len);",
436                f'memcpy({member}, {self.c_name}, {presence}_len);']
437
438
439class TypeNest(Type):
440    def _complex_member_type(self, ri):
441        return self.nested_struct_type
442
443    def free(self, ri, var, ref):
444        ri.cw.p(f'{self.nested_render_name}_free(&{var}->{ref}{self.c_name});')
445
446    def _attr_typol(self):
447        return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, '
448
449    def _attr_policy(self, policy):
450        return 'NLA_POLICY_NESTED(' + self.nested_render_name + '_nl_policy)'
451
452    def attr_put(self, ri, var):
453        self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " +
454                            f"{self.enum_name}, &{var}->{self.c_name})")
455
456    def _attr_get(self, ri, var):
457        get_lines = [f"if ({self.nested_render_name}_parse(&parg, attr))",
458                     "return MNL_CB_ERROR;"]
459        init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;",
460                      f"parg.data = &{var}->{self.c_name};"]
461        return get_lines, init_lines, None
462
463    def setter(self, ri, space, direction, deref=False, ref=None):
464        ref = (ref if ref else []) + [self.c_name]
465
466        for _, attr in ri.family.pure_nested_structs[self.nested_attrs].member_list():
467            attr.setter(ri, self.nested_attrs, direction, deref=deref, ref=ref)
468
469
470class TypeMultiAttr(Type):
471    def __init__(self, family, attr_set, attr, value, base_type):
472        super().__init__(family, attr_set, attr, value)
473
474        self.base_type = base_type
475
476    def is_multi_val(self):
477        return True
478
479    def presence_type(self):
480        return 'count'
481
482    def _mnl_type(self):
483        t = self.type
484        # mnl does not have a helper for signed types
485        if t[0] == 's':
486            t = 'u' + t[1:]
487        return t
488
489    def _complex_member_type(self, ri):
490        if 'type' not in self.attr or self.attr['type'] == 'nest':
491            return self.nested_struct_type
492        elif self.attr['type'] in scalars:
493            scalar_pfx = '__' if ri.ku_space == 'user' else ''
494            return scalar_pfx + self.attr['type']
495        else:
496            raise Exception(f"Sub-type {self.attr['type']} not supported yet")
497
498    def free_needs_iter(self):
499        return 'type' not in self.attr or self.attr['type'] == 'nest'
500
501    def free(self, ri, var, ref):
502        if self.attr['type'] in scalars:
503            ri.cw.p(f"free({var}->{ref}{self.c_name});")
504        elif 'type' not in self.attr or self.attr['type'] == 'nest':
505            ri.cw.p(f"for (i = 0; i < {var}->{ref}n_{self.c_name}; i++)")
506            ri.cw.p(f'{self.nested_render_name}_free(&{var}->{ref}{self.c_name}[i]);')
507            ri.cw.p(f"free({var}->{ref}{self.c_name});")
508        else:
509            raise Exception(f"Free of MultiAttr sub-type {self.attr['type']} not supported yet")
510
511    def _attr_policy(self, policy):
512        return self.base_type._attr_policy(policy)
513
514    def _attr_typol(self):
515        return self.base_type._attr_typol()
516
517    def _attr_get(self, ri, var):
518        return f'n_{self.c_name}++;', None, None
519
520    def attr_put(self, ri, var):
521        if self.attr['type'] in scalars:
522            put_type = self._mnl_type()
523            ri.cw.p(f"for (unsigned int i = 0; i < {var}->n_{self.c_name}; i++)")
524            ri.cw.p(f"mnl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name}[i]);")
525        elif 'type' not in self.attr or self.attr['type'] == 'nest':
526            ri.cw.p(f"for (unsigned int i = 0; i < {var}->n_{self.c_name}; i++)")
527            self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " +
528                                f"{self.enum_name}, &{var}->{self.c_name}[i])")
529        else:
530            raise Exception(f"Put of MultiAttr sub-type {self.attr['type']} not supported yet")
531
532    def _setter_lines(self, ri, member, presence):
533        # For multi-attr we have a count, not presence, hack up the presence
534        presence = presence[:-(len('_present.') + len(self.c_name))] + "n_" + self.c_name
535        return [f"free({member});",
536                f"{member} = {self.c_name};",
537                f"{presence} = n_{self.c_name};"]
538
539
540class TypeArrayNest(Type):
541    def is_multi_val(self):
542        return True
543
544    def presence_type(self):
545        return 'count'
546
547    def _complex_member_type(self, ri):
548        if 'sub-type' not in self.attr or self.attr['sub-type'] == 'nest':
549            return self.nested_struct_type
550        elif self.attr['sub-type'] in scalars:
551            scalar_pfx = '__' if ri.ku_space == 'user' else ''
552            return scalar_pfx + self.attr['sub-type']
553        else:
554            raise Exception(f"Sub-type {self.attr['sub-type']} not supported yet")
555
556    def _attr_typol(self):
557        return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, '
558
559    def _attr_get(self, ri, var):
560        local_vars = ['const struct nlattr *attr2;']
561        get_lines = [f'attr_{self.c_name} = attr;',
562                     'mnl_attr_for_each_nested(attr2, attr)',
563                     f'\t{var}->n_{self.c_name}++;']
564        return get_lines, None, local_vars
565
566
567class TypeNestTypeValue(Type):
568    def _complex_member_type(self, ri):
569        return self.nested_struct_type
570
571    def _attr_typol(self):
572        return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, '
573
574    def _attr_get(self, ri, var):
575        prev = 'attr'
576        tv_args = ''
577        get_lines = []
578        local_vars = []
579        init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;",
580                      f"parg.data = &{var}->{self.c_name};"]
581        if 'type-value' in self.attr:
582            tv_names = [c_lower(x) for x in self.attr["type-value"]]
583            local_vars += [f'const struct nlattr *attr_{", *attr_".join(tv_names)};']
584            local_vars += [f'__u32 {", ".join(tv_names)};']
585            for level in self.attr["type-value"]:
586                level = c_lower(level)
587                get_lines += [f'attr_{level} = mnl_attr_get_payload({prev});']
588                get_lines += [f'{level} = mnl_attr_get_type(attr_{level});']
589                prev = 'attr_' + level
590
591            tv_args = f", {', '.join(tv_names)}"
592
593        get_lines += [f"{self.nested_render_name}_parse(&parg, {prev}{tv_args});"]
594        return get_lines, init_lines, local_vars
595
596
597class Struct:
598    def __init__(self, family, space_name, type_list=None, inherited=None):
599        self.family = family
600        self.space_name = space_name
601        self.attr_set = family.attr_sets[space_name]
602        # Use list to catch comparisons with empty sets
603        self._inherited = inherited if inherited is not None else []
604        self.inherited = []
605
606        self.nested = type_list is None
607        if family.name == c_lower(space_name):
608            self.render_name = f"{family.name}"
609        else:
610            self.render_name = f"{family.name}_{c_lower(space_name)}"
611        self.struct_name = 'struct ' + self.render_name
612        if self.nested and space_name in family.consts:
613            self.struct_name += '_'
614        self.ptr_name = self.struct_name + ' *'
615
616        self.request = False
617        self.reply = False
618
619        self.attr_list = []
620        self.attrs = dict()
621        if type_list is not None:
622            for t in type_list:
623                self.attr_list.append((t, self.attr_set[t]),)
624        else:
625            for t in self.attr_set:
626                self.attr_list.append((t, self.attr_set[t]),)
627
628        max_val = 0
629        self.attr_max_val = None
630        for name, attr in self.attr_list:
631            if attr.value >= max_val:
632                max_val = attr.value
633                self.attr_max_val = attr
634            self.attrs[name] = attr
635
636    def __iter__(self):
637        yield from self.attrs
638
639    def __getitem__(self, key):
640        return self.attrs[key]
641
642    def member_list(self):
643        return self.attr_list
644
645    def set_inherited(self, new_inherited):
646        if self._inherited != new_inherited:
647            raise Exception("Inheriting different members not supported")
648        self.inherited = [c_lower(x) for x in sorted(self._inherited)]
649
650
651class EnumEntry(SpecEnumEntry):
652    def __init__(self, enum_set, yaml, prev, value_start):
653        super().__init__(enum_set, yaml, prev, value_start)
654
655        if prev:
656            self.value_change = (self.value != prev.value + 1)
657        else:
658            self.value_change = (self.value != 0)
659        self.value_change = self.value_change or self.enum_set['type'] == 'flags'
660
661        # Added by resolve:
662        self.c_name = None
663        delattr(self, "c_name")
664
665    def resolve(self):
666        self.resolve_up(super())
667
668        self.c_name = c_upper(self.enum_set.value_pfx + self.name)
669
670
671class EnumSet(SpecEnumSet):
672    def __init__(self, family, yaml):
673        self.render_name = c_lower(family.name + '-' + yaml['name'])
674
675        if 'enum-name' in yaml:
676            if yaml['enum-name']:
677                self.enum_name = 'enum ' + c_lower(yaml['enum-name'])
678            else:
679                self.enum_name = None
680        else:
681            self.enum_name = 'enum ' + self.render_name
682
683        self.value_pfx = yaml.get('name-prefix', f"{family.name}-{yaml['name']}-")
684
685        super().__init__(family, yaml)
686
687    def new_entry(self, entry, prev_entry, value_start):
688        return EnumEntry(self, entry, prev_entry, value_start)
689
690    def value_range(self):
691        low = min([x.value for x in self.entries.values()])
692        high = max([x.value for x in self.entries.values()])
693
694        if high - low + 1 != len(self.entries):
695            raise Exception("Can't get value range for a noncontiguous enum")
696
697        return low, high
698
699
700class AttrSet(SpecAttrSet):
701    def __init__(self, family, yaml):
702        super().__init__(family, yaml)
703
704        if self.subset_of is None:
705            if 'name-prefix' in yaml:
706                pfx = yaml['name-prefix']
707            elif self.name == family.name:
708                pfx = family.name + '-a-'
709            else:
710                pfx = f"{family.name}-a-{self.name}-"
711            self.name_prefix = c_upper(pfx)
712            self.max_name = c_upper(self.yaml.get('attr-max-name', f"{self.name_prefix}max"))
713        else:
714            self.name_prefix = family.attr_sets[self.subset_of].name_prefix
715            self.max_name = family.attr_sets[self.subset_of].max_name
716
717        # Added by resolve:
718        self.c_name = None
719        delattr(self, "c_name")
720
721    def resolve(self):
722        self.c_name = c_lower(self.name)
723        if self.c_name in _C_KW:
724            self.c_name += '_'
725        if self.c_name == self.family.c_name:
726            self.c_name = ''
727
728    def new_attr(self, elem, value):
729        if elem['type'] in scalars:
730            t = TypeScalar(self.family, self, elem, value)
731        elif elem['type'] == 'unused':
732            t = TypeUnused(self.family, self, elem, value)
733        elif elem['type'] == 'pad':
734            t = TypePad(self.family, self, elem, value)
735        elif elem['type'] == 'flag':
736            t = TypeFlag(self.family, self, elem, value)
737        elif elem['type'] == 'string':
738            t = TypeString(self.family, self, elem, value)
739        elif elem['type'] == 'binary':
740            t = TypeBinary(self.family, self, elem, value)
741        elif elem['type'] == 'nest':
742            t = TypeNest(self.family, self, elem, value)
743        elif elem['type'] == 'array-nest':
744            t = TypeArrayNest(self.family, self, elem, value)
745        elif elem['type'] == 'nest-type-value':
746            t = TypeNestTypeValue(self.family, self, elem, value)
747        else:
748            raise Exception(f"No typed class for type {elem['type']}")
749
750        if 'multi-attr' in elem and elem['multi-attr']:
751            t = TypeMultiAttr(self.family, self, elem, value, t)
752
753        return t
754
755
756class Operation(SpecOperation):
757    def __init__(self, family, yaml, req_value, rsp_value):
758        super().__init__(family, yaml, req_value, rsp_value)
759
760        self.render_name = family.name + '_' + c_lower(self.name)
761
762        self.dual_policy = ('do' in yaml and 'request' in yaml['do']) and \
763                         ('dump' in yaml and 'request' in yaml['dump'])
764
765        self.has_ntf = False
766
767        # Added by resolve:
768        self.enum_name = None
769        delattr(self, "enum_name")
770
771    def resolve(self):
772        self.resolve_up(super())
773
774        if not self.is_async:
775            self.enum_name = self.family.op_prefix + c_upper(self.name)
776        else:
777            self.enum_name = self.family.async_op_prefix + c_upper(self.name)
778
779    def mark_has_ntf(self):
780        self.has_ntf = True
781
782
783class Family(SpecFamily):
784    def __init__(self, file_name, exclude_ops):
785        # Added by resolve:
786        self.c_name = None
787        delattr(self, "c_name")
788        self.op_prefix = None
789        delattr(self, "op_prefix")
790        self.async_op_prefix = None
791        delattr(self, "async_op_prefix")
792        self.mcgrps = None
793        delattr(self, "mcgrps")
794        self.consts = None
795        delattr(self, "consts")
796        self.hooks = None
797        delattr(self, "hooks")
798
799        super().__init__(file_name, exclude_ops=exclude_ops)
800
801        self.fam_key = c_upper(self.yaml.get('c-family-name', self.yaml["name"] + '_FAMILY_NAME'))
802        self.ver_key = c_upper(self.yaml.get('c-version-name', self.yaml["name"] + '_FAMILY_VERSION'))
803
804        if 'definitions' not in self.yaml:
805            self.yaml['definitions'] = []
806
807        if 'uapi-header' in self.yaml:
808            self.uapi_header = self.yaml['uapi-header']
809        else:
810            self.uapi_header = f"linux/{self.name}.h"
811
812    def resolve(self):
813        self.resolve_up(super())
814
815        if self.yaml.get('protocol', 'genetlink') not in {'genetlink', 'genetlink-c', 'genetlink-legacy'}:
816            raise Exception("Codegen only supported for genetlink")
817
818        self.c_name = c_lower(self.name)
819        if 'name-prefix' in self.yaml['operations']:
820            self.op_prefix = c_upper(self.yaml['operations']['name-prefix'])
821        else:
822            self.op_prefix = c_upper(self.yaml['name'] + '-cmd-')
823        if 'async-prefix' in self.yaml['operations']:
824            self.async_op_prefix = c_upper(self.yaml['operations']['async-prefix'])
825        else:
826            self.async_op_prefix = self.op_prefix
827
828        self.mcgrps = self.yaml.get('mcast-groups', {'list': []})
829
830        self.hooks = dict()
831        for when in ['pre', 'post']:
832            self.hooks[when] = dict()
833            for op_mode in ['do', 'dump']:
834                self.hooks[when][op_mode] = dict()
835                self.hooks[when][op_mode]['set'] = set()
836                self.hooks[when][op_mode]['list'] = []
837
838        # dict space-name -> 'request': set(attrs), 'reply': set(attrs)
839        self.root_sets = dict()
840        # dict space-name -> set('request', 'reply')
841        self.pure_nested_structs = dict()
842
843        self._mark_notify()
844        self._mock_up_events()
845
846        self._load_root_sets()
847        self._load_nested_sets()
848        self._load_hooks()
849
850        self.kernel_policy = self.yaml.get('kernel-policy', 'split')
851        if self.kernel_policy == 'global':
852            self._load_global_policy()
853
854    def new_enum(self, elem):
855        return EnumSet(self, elem)
856
857    def new_attr_set(self, elem):
858        return AttrSet(self, elem)
859
860    def new_operation(self, elem, req_value, rsp_value):
861        return Operation(self, elem, req_value, rsp_value)
862
863    def _mark_notify(self):
864        for op in self.msgs.values():
865            if 'notify' in op:
866                self.ops[op['notify']].mark_has_ntf()
867
868    # Fake a 'do' equivalent of all events, so that we can render their response parsing
869    def _mock_up_events(self):
870        for op in self.yaml['operations']['list']:
871            if 'event' in op:
872                op['do'] = {
873                    'reply': {
874                        'attributes': op['event']['attributes']
875                    }
876                }
877
878    def _load_root_sets(self):
879        for op_name, op in self.msgs.items():
880            if 'attribute-set' not in op:
881                continue
882
883            req_attrs = set()
884            rsp_attrs = set()
885            for op_mode in ['do', 'dump']:
886                if op_mode in op and 'request' in op[op_mode]:
887                    req_attrs.update(set(op[op_mode]['request']['attributes']))
888                if op_mode in op and 'reply' in op[op_mode]:
889                    rsp_attrs.update(set(op[op_mode]['reply']['attributes']))
890            if 'event' in op:
891                rsp_attrs.update(set(op['event']['attributes']))
892
893            if op['attribute-set'] not in self.root_sets:
894                self.root_sets[op['attribute-set']] = {'request': req_attrs, 'reply': rsp_attrs}
895            else:
896                self.root_sets[op['attribute-set']]['request'].update(req_attrs)
897                self.root_sets[op['attribute-set']]['reply'].update(rsp_attrs)
898
899    def _load_nested_sets(self):
900        attr_set_queue = list(self.root_sets.keys())
901        attr_set_seen = set(self.root_sets.keys())
902
903        while len(attr_set_queue):
904            a_set = attr_set_queue.pop(0)
905            for attr, spec in self.attr_sets[a_set].items():
906                if 'nested-attributes' not in spec:
907                    continue
908
909                nested = spec['nested-attributes']
910                if nested not in attr_set_seen:
911                    attr_set_queue.append(nested)
912                    attr_set_seen.add(nested)
913
914                inherit = set()
915                if nested not in self.root_sets:
916                    if nested not in self.pure_nested_structs:
917                        self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit)
918                else:
919                    raise Exception(f'Using attr set as root and nested not supported - {nested}')
920
921                if 'type-value' in spec:
922                    if nested in self.root_sets:
923                        raise Exception("Inheriting members to a space used as root not supported")
924                    inherit.update(set(spec['type-value']))
925                elif spec['type'] == 'array-nest':
926                    inherit.add('idx')
927                self.pure_nested_structs[nested].set_inherited(inherit)
928
929        for root_set, rs_members in self.root_sets.items():
930            for attr, spec in self.attr_sets[root_set].items():
931                if 'nested-attributes' in spec:
932                    nested = spec['nested-attributes']
933                    if attr in rs_members['request']:
934                        self.pure_nested_structs[nested].request = True
935                    if attr in rs_members['reply']:
936                        self.pure_nested_structs[nested].reply = True
937
938        # Try to reorder according to dependencies
939        pns_key_list = list(self.pure_nested_structs.keys())
940        pns_key_seen = set()
941        rounds = len(pns_key_list)**2  # it's basically bubble sort
942        for _ in range(rounds):
943            if len(pns_key_list) == 0:
944                break
945            name = pns_key_list.pop(0)
946            finished = True
947            for _, spec in self.attr_sets[name].items():
948                if 'nested-attributes' in spec:
949                    if spec['nested-attributes'] not in pns_key_seen:
950                        # Dicts are sorted, this will make struct last
951                        struct = self.pure_nested_structs.pop(name)
952                        self.pure_nested_structs[name] = struct
953                        finished = False
954                        break
955            if finished:
956                pns_key_seen.add(name)
957            else:
958                pns_key_list.append(name)
959        # Propagate the request / reply
960        for attr_set, struct in reversed(self.pure_nested_structs.items()):
961            for _, spec in self.attr_sets[attr_set].items():
962                if 'nested-attributes' in spec:
963                    child = self.pure_nested_structs.get(spec['nested-attributes'])
964                    if child:
965                        child.request |= struct.request
966                        child.reply |= struct.reply
967
968    def _load_global_policy(self):
969        global_set = set()
970        attr_set_name = None
971        for op_name, op in self.ops.items():
972            if not op:
973                continue
974            if 'attribute-set' not in op:
975                continue
976
977            if attr_set_name is None:
978                attr_set_name = op['attribute-set']
979            if attr_set_name != op['attribute-set']:
980                raise Exception('For a global policy all ops must use the same set')
981
982            for op_mode in ['do', 'dump']:
983                if op_mode in op:
984                    req = op[op_mode].get('request')
985                    if req:
986                        global_set.update(req.get('attributes', []))
987
988        self.global_policy = []
989        self.global_policy_set = attr_set_name
990        for attr in self.attr_sets[attr_set_name]:
991            if attr in global_set:
992                self.global_policy.append(attr)
993
994    def _load_hooks(self):
995        for op in self.ops.values():
996            for op_mode in ['do', 'dump']:
997                if op_mode not in op:
998                    continue
999                for when in ['pre', 'post']:
1000                    if when not in op[op_mode]:
1001                        continue
1002                    name = op[op_mode][when]
1003                    if name in self.hooks[when][op_mode]['set']:
1004                        continue
1005                    self.hooks[when][op_mode]['set'].add(name)
1006                    self.hooks[when][op_mode]['list'].append(name)
1007
1008
1009class RenderInfo:
1010    def __init__(self, cw, family, ku_space, op, op_mode, attr_set=None):
1011        self.family = family
1012        self.nl = cw.nlib
1013        self.ku_space = ku_space
1014        self.op_mode = op_mode
1015        self.op = op
1016
1017        # 'do' and 'dump' response parsing is identical
1018        self.type_consistent = True
1019        if op_mode != 'do' and 'dump' in op and 'do' in op:
1020            if ('reply' in op['do']) != ('reply' in op["dump"]):
1021                self.type_consistent = False
1022            elif 'reply' in op['do'] and op["do"]["reply"] != op["dump"]["reply"]:
1023                self.type_consistent = False
1024
1025        self.attr_set = attr_set
1026        if not self.attr_set:
1027            self.attr_set = op['attribute-set']
1028
1029        self.type_name_conflict = False
1030        if op:
1031            self.type_name = c_lower(op.name)
1032        else:
1033            self.type_name = c_lower(attr_set)
1034            if attr_set in family.consts:
1035                self.type_name_conflict = True
1036
1037        self.cw = cw
1038
1039        self.struct = dict()
1040        if op_mode == 'notify':
1041            op_mode = 'do'
1042        for op_dir in ['request', 'reply']:
1043            if op and op_dir in op[op_mode]:
1044                self.struct[op_dir] = Struct(family, self.attr_set,
1045                                             type_list=op[op_mode][op_dir]['attributes'])
1046        if op_mode == 'event':
1047            self.struct['reply'] = Struct(family, self.attr_set, type_list=op['event']['attributes'])
1048
1049
1050class CodeWriter:
1051    def __init__(self, nlib, out_file=None):
1052        self.nlib = nlib
1053
1054        self._nl = False
1055        self._block_end = False
1056        self._silent_block = False
1057        self._ind = 0
1058        if out_file is None:
1059            self._out = os.sys.stdout
1060        else:
1061            self._out = tempfile.TemporaryFile('w+')
1062            self._out_file = out_file
1063
1064    def __del__(self):
1065        self.close_out_file()
1066
1067    def close_out_file(self):
1068        if self._out == os.sys.stdout:
1069            return
1070        with open(self._out_file, 'w+') as out_file:
1071            self._out.seek(0)
1072            shutil.copyfileobj(self._out, out_file)
1073            self._out.close()
1074        self._out = os.sys.stdout
1075
1076    @classmethod
1077    def _is_cond(cls, line):
1078        return line.startswith('if') or line.startswith('while') or line.startswith('for')
1079
1080    def p(self, line, add_ind=0):
1081        if self._block_end:
1082            self._block_end = False
1083            if line.startswith('else'):
1084                line = '} ' + line
1085            else:
1086                self._out.write('\t' * self._ind + '}\n')
1087
1088        if self._nl:
1089            self._out.write('\n')
1090            self._nl = False
1091
1092        ind = self._ind
1093        if line[-1] == ':':
1094            ind -= 1
1095        if self._silent_block:
1096            ind += 1
1097        self._silent_block = line.endswith(')') and CodeWriter._is_cond(line)
1098        if add_ind:
1099            ind += add_ind
1100        self._out.write('\t' * ind + line + '\n')
1101
1102    def nl(self):
1103        self._nl = True
1104
1105    def block_start(self, line=''):
1106        if line:
1107            line = line + ' '
1108        self.p(line + '{')
1109        self._ind += 1
1110
1111    def block_end(self, line=''):
1112        if line and line[0] not in {';', ','}:
1113            line = ' ' + line
1114        self._ind -= 1
1115        self._nl = False
1116        if not line:
1117            # Delay printing closing bracket in case "else" comes next
1118            if self._block_end:
1119                self._out.write('\t' * (self._ind + 1) + '}\n')
1120            self._block_end = True
1121        else:
1122            self.p('}' + line)
1123
1124    def write_doc_line(self, doc, indent=True):
1125        words = doc.split()
1126        line = ' *'
1127        for word in words:
1128            if len(line) + len(word) >= 79:
1129                self.p(line)
1130                line = ' *'
1131                if indent:
1132                    line += '  '
1133            line += ' ' + word
1134        self.p(line)
1135
1136    def write_func_prot(self, qual_ret, name, args=None, doc=None, suffix=''):
1137        if not args:
1138            args = ['void']
1139
1140        if doc:
1141            self.p('/*')
1142            self.p(' * ' + doc)
1143            self.p(' */')
1144
1145        oneline = qual_ret
1146        if qual_ret[-1] != '*':
1147            oneline += ' '
1148        oneline += f"{name}({', '.join(args)}){suffix}"
1149
1150        if len(oneline) < 80:
1151            self.p(oneline)
1152            return
1153
1154        v = qual_ret
1155        if len(v) > 3:
1156            self.p(v)
1157            v = ''
1158        elif qual_ret[-1] != '*':
1159            v += ' '
1160        v += name + '('
1161        ind = '\t' * (len(v) // 8) + ' ' * (len(v) % 8)
1162        delta_ind = len(v) - len(ind)
1163        v += args[0]
1164        i = 1
1165        while i < len(args):
1166            next_len = len(v) + len(args[i])
1167            if v[0] == '\t':
1168                next_len += delta_ind
1169            if next_len > 76:
1170                self.p(v + ',')
1171                v = ind
1172            else:
1173                v += ', '
1174            v += args[i]
1175            i += 1
1176        self.p(v + ')' + suffix)
1177
1178    def write_func_lvar(self, local_vars):
1179        if not local_vars:
1180            return
1181
1182        if type(local_vars) is str:
1183            local_vars = [local_vars]
1184
1185        local_vars.sort(key=len, reverse=True)
1186        for var in local_vars:
1187            self.p(var)
1188        self.nl()
1189
1190    def write_func(self, qual_ret, name, body, args=None, local_vars=None):
1191        self.write_func_prot(qual_ret=qual_ret, name=name, args=args)
1192        self.write_func_lvar(local_vars=local_vars)
1193
1194        self.block_start()
1195        for line in body:
1196            self.p(line)
1197        self.block_end()
1198
1199    def writes_defines(self, defines):
1200        longest = 0
1201        for define in defines:
1202            if len(define[0]) > longest:
1203                longest = len(define[0])
1204        longest = ((longest + 8) // 8) * 8
1205        for define in defines:
1206            line = '#define ' + define[0]
1207            line += '\t' * ((longest - len(define[0]) + 7) // 8)
1208            if type(define[1]) is int:
1209                line += str(define[1])
1210            elif type(define[1]) is str:
1211                line += '"' + define[1] + '"'
1212            self.p(line)
1213
1214    def write_struct_init(self, members):
1215        longest = max([len(x[0]) for x in members])
1216        longest += 1  # because we prepend a .
1217        longest = ((longest + 8) // 8) * 8
1218        for one in members:
1219            line = '.' + one[0]
1220            line += '\t' * ((longest - len(one[0]) - 1 + 7) // 8)
1221            line += '= ' + one[1] + ','
1222            self.p(line)
1223
1224
1225scalars = {'u8', 'u16', 'u32', 'u64', 's32', 's64'}
1226
1227direction_to_suffix = {
1228    'reply': '_rsp',
1229    'request': '_req',
1230    '': ''
1231}
1232
1233op_mode_to_wrapper = {
1234    'do': '',
1235    'dump': '_list',
1236    'notify': '_ntf',
1237    'event': '',
1238}
1239
1240_C_KW = {
1241    'auto',
1242    'bool',
1243    'break',
1244    'case',
1245    'char',
1246    'const',
1247    'continue',
1248    'default',
1249    'do',
1250    'double',
1251    'else',
1252    'enum',
1253    'extern',
1254    'float',
1255    'for',
1256    'goto',
1257    'if',
1258    'inline',
1259    'int',
1260    'long',
1261    'register',
1262    'return',
1263    'short',
1264    'signed',
1265    'sizeof',
1266    'static',
1267    'struct',
1268    'switch',
1269    'typedef',
1270    'union',
1271    'unsigned',
1272    'void',
1273    'volatile',
1274    'while'
1275}
1276
1277
1278def rdir(direction):
1279    if direction == 'reply':
1280        return 'request'
1281    if direction == 'request':
1282        return 'reply'
1283    return direction
1284
1285
1286def op_prefix(ri, direction, deref=False):
1287    suffix = f"_{ri.type_name}"
1288
1289    if not ri.op_mode or ri.op_mode == 'do':
1290        suffix += f"{direction_to_suffix[direction]}"
1291    else:
1292        if direction == 'request':
1293            suffix += '_req_dump'
1294        else:
1295            if ri.type_consistent:
1296                if deref:
1297                    suffix += f"{direction_to_suffix[direction]}"
1298                else:
1299                    suffix += op_mode_to_wrapper[ri.op_mode]
1300            else:
1301                suffix += '_rsp'
1302                suffix += '_dump' if deref else '_list'
1303
1304    return f"{ri.family['name']}{suffix}"
1305
1306
1307def type_name(ri, direction, deref=False):
1308    return f"struct {op_prefix(ri, direction, deref=deref)}"
1309
1310
1311def print_prototype(ri, direction, terminate=True, doc=None):
1312    suffix = ';' if terminate else ''
1313
1314    fname = ri.op.render_name
1315    if ri.op_mode == 'dump':
1316        fname += '_dump'
1317
1318    args = ['struct ynl_sock *ys']
1319    if 'request' in ri.op[ri.op_mode]:
1320        args.append(f"{type_name(ri, direction)} *" + f"{direction_to_suffix[direction][1:]}")
1321
1322    ret = 'int'
1323    if 'reply' in ri.op[ri.op_mode]:
1324        ret = f"{type_name(ri, rdir(direction))} *"
1325
1326    ri.cw.write_func_prot(ret, fname, args, doc=doc, suffix=suffix)
1327
1328
1329def print_req_prototype(ri):
1330    print_prototype(ri, "request", doc=ri.op['doc'])
1331
1332
1333def print_dump_prototype(ri):
1334    print_prototype(ri, "request")
1335
1336
1337def put_typol(cw, struct):
1338    type_max = struct.attr_set.max_name
1339    cw.block_start(line=f'struct ynl_policy_attr {struct.render_name}_policy[{type_max} + 1] =')
1340
1341    for _, arg in struct.member_list():
1342        arg.attr_typol(cw)
1343
1344    cw.block_end(line=';')
1345    cw.nl()
1346
1347    cw.block_start(line=f'struct ynl_policy_nest {struct.render_name}_nest =')
1348    cw.p(f'.max_attr = {type_max},')
1349    cw.p(f'.table = {struct.render_name}_policy,')
1350    cw.block_end(line=';')
1351    cw.nl()
1352
1353
1354def _put_enum_to_str_helper(cw, render_name, map_name, arg_name, enum=None):
1355    args = [f'int {arg_name}']
1356    if enum and not ('enum-name' in enum and not enum['enum-name']):
1357        args = [f'enum {render_name} {arg_name}']
1358    cw.write_func_prot('const char *', f'{render_name}_str', args)
1359    cw.block_start()
1360    if enum and enum.type == 'flags':
1361        cw.p(f'{arg_name} = ffs({arg_name}) - 1;')
1362    cw.p(f'if ({arg_name} < 0 || {arg_name} >= (int)MNL_ARRAY_SIZE({map_name}))')
1363    cw.p('return NULL;')
1364    cw.p(f'return {map_name}[{arg_name}];')
1365    cw.block_end()
1366    cw.nl()
1367
1368
1369def put_op_name_fwd(family, cw):
1370    cw.write_func_prot('const char *', f'{family.name}_op_str', ['int op'], suffix=';')
1371
1372
1373def put_op_name(family, cw):
1374    map_name = f'{family.name}_op_strmap'
1375    cw.block_start(line=f"static const char * const {map_name}[] =")
1376    for op_name, op in family.msgs.items():
1377        if op.rsp_value:
1378            if op.req_value == op.rsp_value:
1379                cw.p(f'[{op.enum_name}] = "{op_name}",')
1380            else:
1381                cw.p(f'[{op.rsp_value}] = "{op_name}",')
1382    cw.block_end(line=';')
1383    cw.nl()
1384
1385    _put_enum_to_str_helper(cw, family.name + '_op', map_name, 'op')
1386
1387
1388def put_enum_to_str_fwd(family, cw, enum):
1389    args = [f'enum {enum.render_name} value']
1390    if 'enum-name' in enum and not enum['enum-name']:
1391        args = ['int value']
1392    cw.write_func_prot('const char *', f'{enum.render_name}_str', args, suffix=';')
1393
1394
1395def put_enum_to_str(family, cw, enum):
1396    map_name = f'{enum.render_name}_strmap'
1397    cw.block_start(line=f"static const char * const {map_name}[] =")
1398    for entry in enum.entries.values():
1399        cw.p(f'[{entry.value}] = "{entry.name}",')
1400    cw.block_end(line=';')
1401    cw.nl()
1402
1403    _put_enum_to_str_helper(cw, enum.render_name, map_name, 'value', enum=enum)
1404
1405
1406def put_req_nested(ri, struct):
1407    func_args = ['struct nlmsghdr *nlh',
1408                 'unsigned int attr_type',
1409                 f'{struct.ptr_name}obj']
1410
1411    ri.cw.write_func_prot('int', f'{struct.render_name}_put', func_args)
1412    ri.cw.block_start()
1413    ri.cw.write_func_lvar('struct nlattr *nest;')
1414
1415    ri.cw.p("nest = mnl_attr_nest_start(nlh, attr_type);")
1416
1417    for _, arg in struct.member_list():
1418        arg.attr_put(ri, "obj")
1419
1420    ri.cw.p("mnl_attr_nest_end(nlh, nest);")
1421
1422    ri.cw.nl()
1423    ri.cw.p('return 0;')
1424    ri.cw.block_end()
1425    ri.cw.nl()
1426
1427
1428def _multi_parse(ri, struct, init_lines, local_vars):
1429    if struct.nested:
1430        iter_line = "mnl_attr_for_each_nested(attr, nested)"
1431    else:
1432        iter_line = "mnl_attr_for_each(attr, nlh, sizeof(struct genlmsghdr))"
1433
1434    array_nests = set()
1435    multi_attrs = set()
1436    needs_parg = False
1437    for arg, aspec in struct.member_list():
1438        if aspec['type'] == 'array-nest':
1439            local_vars.append(f'const struct nlattr *attr_{aspec.c_name};')
1440            array_nests.add(arg)
1441        if 'multi-attr' in aspec:
1442            multi_attrs.add(arg)
1443        needs_parg |= 'nested-attributes' in aspec
1444    if array_nests or multi_attrs:
1445        local_vars.append('int i;')
1446    if needs_parg:
1447        local_vars.append('struct ynl_parse_arg parg;')
1448        init_lines.append('parg.ys = yarg->ys;')
1449
1450    all_multi = array_nests | multi_attrs
1451
1452    for anest in sorted(all_multi):
1453        local_vars.append(f"unsigned int n_{struct[anest].c_name} = 0;")
1454
1455    ri.cw.block_start()
1456    ri.cw.write_func_lvar(local_vars)
1457
1458    for line in init_lines:
1459        ri.cw.p(line)
1460    ri.cw.nl()
1461
1462    for arg in struct.inherited:
1463        ri.cw.p(f'dst->{arg} = {arg};')
1464
1465    for anest in sorted(all_multi):
1466        aspec = struct[anest]
1467        ri.cw.p(f"if (dst->{aspec.c_name})")
1468        ri.cw.p(f'return ynl_error_parse(yarg, "attribute already present ({struct.attr_set.name}.{aspec.name})");')
1469
1470    ri.cw.nl()
1471    ri.cw.block_start(line=iter_line)
1472    ri.cw.p('unsigned int type = mnl_attr_get_type(attr);')
1473    ri.cw.nl()
1474
1475    first = True
1476    for _, arg in struct.member_list():
1477        good = arg.attr_get(ri, 'dst', first=first)
1478        # First may be 'unused' or 'pad', ignore those
1479        first &= not good
1480
1481    ri.cw.block_end()
1482    ri.cw.nl()
1483
1484    for anest in sorted(array_nests):
1485        aspec = struct[anest]
1486
1487        ri.cw.block_start(line=f"if (n_{aspec.c_name})")
1488        ri.cw.p(f"dst->{aspec.c_name} = calloc({aspec.c_name}, sizeof(*dst->{aspec.c_name}));")
1489        ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};")
1490        ri.cw.p('i = 0;')
1491        ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;")
1492        ri.cw.block_start(line=f"mnl_attr_for_each_nested(attr, attr_{aspec.c_name})")
1493        ri.cw.p(f"parg.data = &dst->{aspec.c_name}[i];")
1494        ri.cw.p(f"if ({aspec.nested_render_name}_parse(&parg, attr, mnl_attr_get_type(attr)))")
1495        ri.cw.p('return MNL_CB_ERROR;')
1496        ri.cw.p('i++;')
1497        ri.cw.block_end()
1498        ri.cw.block_end()
1499    ri.cw.nl()
1500
1501    for anest in sorted(multi_attrs):
1502        aspec = struct[anest]
1503        ri.cw.block_start(line=f"if (n_{aspec.c_name})")
1504        ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));")
1505        ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};")
1506        ri.cw.p('i = 0;')
1507        if 'nested-attributes' in aspec:
1508            ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;")
1509        ri.cw.block_start(line=iter_line)
1510        ri.cw.block_start(line=f"if (mnl_attr_get_type(attr) == {aspec.enum_name})")
1511        if 'nested-attributes' in aspec:
1512            ri.cw.p(f"parg.data = &dst->{aspec.c_name}[i];")
1513            ri.cw.p(f"if ({aspec.nested_render_name}_parse(&parg, attr))")
1514            ri.cw.p('return MNL_CB_ERROR;')
1515        elif aspec['type'] in scalars:
1516            t = aspec['type']
1517            if t[0] == 's':
1518                t = 'u' + t[1:]
1519            ri.cw.p(f"dst->{aspec.c_name}[i] = mnl_attr_get_{t}(attr);")
1520        else:
1521            raise Exception('Nest parsing type not supported yet')
1522        ri.cw.p('i++;')
1523        ri.cw.block_end()
1524        ri.cw.block_end()
1525        ri.cw.block_end()
1526    ri.cw.nl()
1527
1528    if struct.nested:
1529        ri.cw.p('return 0;')
1530    else:
1531        ri.cw.p('return MNL_CB_OK;')
1532    ri.cw.block_end()
1533    ri.cw.nl()
1534
1535
1536def parse_rsp_nested(ri, struct):
1537    func_args = ['struct ynl_parse_arg *yarg',
1538                 'const struct nlattr *nested']
1539    for arg in struct.inherited:
1540        func_args.append('__u32 ' + arg)
1541
1542    local_vars = ['const struct nlattr *attr;',
1543                  f'{struct.ptr_name}dst = yarg->data;']
1544    init_lines = []
1545
1546    ri.cw.write_func_prot('int', f'{struct.render_name}_parse', func_args)
1547
1548    _multi_parse(ri, struct, init_lines, local_vars)
1549
1550
1551def parse_rsp_msg(ri, deref=False):
1552    if 'reply' not in ri.op[ri.op_mode] and ri.op_mode != 'event':
1553        return
1554
1555    func_args = ['const struct nlmsghdr *nlh',
1556                 'void *data']
1557
1558    local_vars = [f'{type_name(ri, "reply", deref=deref)} *dst;',
1559                  'struct ynl_parse_arg *yarg = data;',
1560                  'const struct nlattr *attr;']
1561    init_lines = ['dst = yarg->data;']
1562
1563    ri.cw.write_func_prot('int', f'{op_prefix(ri, "reply", deref=deref)}_parse', func_args)
1564
1565    if ri.struct["reply"].member_list():
1566        _multi_parse(ri, ri.struct["reply"], init_lines, local_vars)
1567    else:
1568        # Empty reply
1569        ri.cw.block_start()
1570        ri.cw.p('return MNL_CB_OK;')
1571        ri.cw.block_end()
1572        ri.cw.nl()
1573
1574
1575def print_req(ri):
1576    ret_ok = '0'
1577    ret_err = '-1'
1578    direction = "request"
1579    local_vars = ['struct nlmsghdr *nlh;',
1580                  'int err;']
1581
1582    if 'reply' in ri.op[ri.op_mode]:
1583        ret_ok = 'rsp'
1584        ret_err = 'NULL'
1585        local_vars += [f'{type_name(ri, rdir(direction))} *rsp;',
1586                       'struct ynl_req_state yrs = { .yarg = { .ys = ys, }, };']
1587
1588    print_prototype(ri, direction, terminate=False)
1589    ri.cw.block_start()
1590    ri.cw.write_func_lvar(local_vars)
1591
1592    ri.cw.p(f"nlh = ynl_gemsg_start_req(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);")
1593
1594    ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;")
1595    if 'reply' in ri.op[ri.op_mode]:
1596        ri.cw.p(f"yrs.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;")
1597    ri.cw.nl()
1598    for _, attr in ri.struct["request"].member_list():
1599        attr.attr_put(ri, "req")
1600    ri.cw.nl()
1601
1602    parse_arg = "NULL"
1603    if 'reply' in ri.op[ri.op_mode]:
1604        ri.cw.p('rsp = calloc(1, sizeof(*rsp));')
1605        ri.cw.p('yrs.yarg.data = rsp;')
1606        ri.cw.p(f"yrs.cb = {op_prefix(ri, 'reply')}_parse;")
1607        if ri.op.value is not None:
1608            ri.cw.p(f'yrs.rsp_cmd = {ri.op.enum_name};')
1609        else:
1610            ri.cw.p(f'yrs.rsp_cmd = {ri.op.rsp_value};')
1611        ri.cw.nl()
1612        parse_arg = '&yrs'
1613    ri.cw.p(f"err = ynl_exec(ys, nlh, {parse_arg});")
1614    ri.cw.p('if (err < 0)')
1615    if 'reply' in ri.op[ri.op_mode]:
1616        ri.cw.p('goto err_free;')
1617    else:
1618        ri.cw.p('return -1;')
1619    ri.cw.nl()
1620
1621    ri.cw.p(f"return {ret_ok};")
1622    ri.cw.nl()
1623
1624    if 'reply' in ri.op[ri.op_mode]:
1625        ri.cw.p('err_free:')
1626        ri.cw.p(f"{call_free(ri, rdir(direction), 'rsp')}")
1627        ri.cw.p(f"return {ret_err};")
1628
1629    ri.cw.block_end()
1630
1631
1632def print_dump(ri):
1633    direction = "request"
1634    print_prototype(ri, direction, terminate=False)
1635    ri.cw.block_start()
1636    local_vars = ['struct ynl_dump_state yds = {};',
1637                  'struct nlmsghdr *nlh;',
1638                  'int err;']
1639
1640    for var in local_vars:
1641        ri.cw.p(f'{var}')
1642    ri.cw.nl()
1643
1644    ri.cw.p('yds.ys = ys;')
1645    ri.cw.p(f"yds.alloc_sz = sizeof({type_name(ri, rdir(direction))});")
1646    ri.cw.p(f"yds.cb = {op_prefix(ri, 'reply', deref=True)}_parse;")
1647    if ri.op.value is not None:
1648        ri.cw.p(f'yds.rsp_cmd = {ri.op.enum_name};')
1649    else:
1650        ri.cw.p(f'yds.rsp_cmd = {ri.op.rsp_value};')
1651    ri.cw.p(f"yds.rsp_policy = &{ri.struct['reply'].render_name}_nest;")
1652    ri.cw.nl()
1653    ri.cw.p(f"nlh = ynl_gemsg_start_dump(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);")
1654
1655    if "request" in ri.op[ri.op_mode]:
1656        ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;")
1657        ri.cw.nl()
1658        for _, attr in ri.struct["request"].member_list():
1659            attr.attr_put(ri, "req")
1660    ri.cw.nl()
1661
1662    ri.cw.p('err = ynl_exec_dump(ys, nlh, &yds);')
1663    ri.cw.p('if (err < 0)')
1664    ri.cw.p('goto free_list;')
1665    ri.cw.nl()
1666
1667    ri.cw.p('return yds.first;')
1668    ri.cw.nl()
1669    ri.cw.p('free_list:')
1670    ri.cw.p(call_free(ri, rdir(direction), 'yds.first'))
1671    ri.cw.p('return NULL;')
1672    ri.cw.block_end()
1673
1674
1675def call_free(ri, direction, var):
1676    return f"{op_prefix(ri, direction)}_free({var});"
1677
1678
1679def free_arg_name(direction):
1680    if direction:
1681        return direction_to_suffix[direction][1:]
1682    return 'obj'
1683
1684
1685def print_alloc_wrapper(ri, direction):
1686    name = op_prefix(ri, direction)
1687    ri.cw.write_func_prot(f'static inline struct {name} *', f"{name}_alloc", [f"void"])
1688    ri.cw.block_start()
1689    ri.cw.p(f'return calloc(1, sizeof(struct {name}));')
1690    ri.cw.block_end()
1691
1692
1693def print_free_prototype(ri, direction, suffix=';'):
1694    name = op_prefix(ri, direction)
1695    struct_name = name
1696    if ri.type_name_conflict:
1697        struct_name += '_'
1698    arg = free_arg_name(direction)
1699    ri.cw.write_func_prot('void', f"{name}_free", [f"struct {struct_name} *{arg}"], suffix=suffix)
1700
1701
1702def _print_type(ri, direction, struct):
1703    suffix = f'_{ri.type_name}{direction_to_suffix[direction]}'
1704    if not direction and ri.type_name_conflict:
1705        suffix += '_'
1706
1707    if ri.op_mode == 'dump':
1708        suffix += '_dump'
1709
1710    ri.cw.block_start(line=f"struct {ri.family['name']}{suffix}")
1711
1712    meta_started = False
1713    for _, attr in struct.member_list():
1714        for type_filter in ['len', 'bit']:
1715            line = attr.presence_member(ri.ku_space, type_filter)
1716            if line:
1717                if not meta_started:
1718                    ri.cw.block_start(line=f"struct")
1719                    meta_started = True
1720                ri.cw.p(line)
1721    if meta_started:
1722        ri.cw.block_end(line='_present;')
1723        ri.cw.nl()
1724
1725    for arg in struct.inherited:
1726        ri.cw.p(f"__u32 {arg};")
1727
1728    for _, attr in struct.member_list():
1729        attr.struct_member(ri)
1730
1731    ri.cw.block_end(line=';')
1732    ri.cw.nl()
1733
1734
1735def print_type(ri, direction):
1736    _print_type(ri, direction, ri.struct[direction])
1737
1738
1739def print_type_full(ri, struct):
1740    _print_type(ri, "", struct)
1741
1742
1743def print_type_helpers(ri, direction, deref=False):
1744    print_free_prototype(ri, direction)
1745    ri.cw.nl()
1746
1747    if ri.ku_space == 'user' and direction == 'request':
1748        for _, attr in ri.struct[direction].member_list():
1749            attr.setter(ri, ri.attr_set, direction, deref=deref)
1750    ri.cw.nl()
1751
1752
1753def print_req_type_helpers(ri):
1754    print_alloc_wrapper(ri, "request")
1755    print_type_helpers(ri, "request")
1756
1757
1758def print_rsp_type_helpers(ri):
1759    if 'reply' not in ri.op[ri.op_mode]:
1760        return
1761    print_type_helpers(ri, "reply")
1762
1763
1764def print_parse_prototype(ri, direction, terminate=True):
1765    suffix = "_rsp" if direction == "reply" else "_req"
1766    term = ';' if terminate else ''
1767
1768    ri.cw.write_func_prot('void', f"{ri.op.render_name}{suffix}_parse",
1769                          ['const struct nlattr **tb',
1770                           f"struct {ri.op.render_name}{suffix} *req"],
1771                          suffix=term)
1772
1773
1774def print_req_type(ri):
1775    print_type(ri, "request")
1776
1777
1778def print_req_free(ri):
1779    if 'request' not in ri.op[ri.op_mode]:
1780        return
1781    _free_type(ri, 'request', ri.struct['request'])
1782
1783
1784def print_rsp_type(ri):
1785    if (ri.op_mode == 'do' or ri.op_mode == 'dump') and 'reply' in ri.op[ri.op_mode]:
1786        direction = 'reply'
1787    elif ri.op_mode == 'event':
1788        direction = 'reply'
1789    else:
1790        return
1791    print_type(ri, direction)
1792
1793
1794def print_wrapped_type(ri):
1795    ri.cw.block_start(line=f"{type_name(ri, 'reply')}")
1796    if ri.op_mode == 'dump':
1797        ri.cw.p(f"{type_name(ri, 'reply')} *next;")
1798    elif ri.op_mode == 'notify' or ri.op_mode == 'event':
1799        ri.cw.p('__u16 family;')
1800        ri.cw.p('__u8 cmd;')
1801        ri.cw.p('struct ynl_ntf_base_type *next;')
1802        ri.cw.p(f"void (*free)({type_name(ri, 'reply')} *ntf);")
1803    ri.cw.p(f"{type_name(ri, 'reply', deref=True)} obj __attribute__ ((aligned (8)));")
1804    ri.cw.block_end(line=';')
1805    ri.cw.nl()
1806    print_free_prototype(ri, 'reply')
1807    ri.cw.nl()
1808
1809
1810def _free_type_members_iter(ri, struct):
1811    for _, attr in struct.member_list():
1812        if attr.free_needs_iter():
1813            ri.cw.p('unsigned int i;')
1814            ri.cw.nl()
1815            break
1816
1817
1818def _free_type_members(ri, var, struct, ref=''):
1819    for _, attr in struct.member_list():
1820        attr.free(ri, var, ref)
1821
1822
1823def _free_type(ri, direction, struct):
1824    var = free_arg_name(direction)
1825
1826    print_free_prototype(ri, direction, suffix='')
1827    ri.cw.block_start()
1828    _free_type_members_iter(ri, struct)
1829    _free_type_members(ri, var, struct)
1830    if direction:
1831        ri.cw.p(f'free({var});')
1832    ri.cw.block_end()
1833    ri.cw.nl()
1834
1835
1836def free_rsp_nested(ri, struct):
1837    _free_type(ri, "", struct)
1838
1839
1840def print_rsp_free(ri):
1841    if 'reply' not in ri.op[ri.op_mode]:
1842        return
1843    _free_type(ri, 'reply', ri.struct['reply'])
1844
1845
1846def print_dump_type_free(ri):
1847    sub_type = type_name(ri, 'reply')
1848
1849    print_free_prototype(ri, 'reply', suffix='')
1850    ri.cw.block_start()
1851    ri.cw.p(f"{sub_type} *next = rsp;")
1852    ri.cw.nl()
1853    ri.cw.block_start(line='while ((void *)next != YNL_LIST_END)')
1854    _free_type_members_iter(ri, ri.struct['reply'])
1855    ri.cw.p('rsp = next;')
1856    ri.cw.p('next = rsp->next;')
1857    ri.cw.nl()
1858
1859    _free_type_members(ri, 'rsp', ri.struct['reply'], ref='obj.')
1860    ri.cw.p(f'free(rsp);')
1861    ri.cw.block_end()
1862    ri.cw.block_end()
1863    ri.cw.nl()
1864
1865
1866def print_ntf_type_free(ri):
1867    print_free_prototype(ri, 'reply', suffix='')
1868    ri.cw.block_start()
1869    _free_type_members_iter(ri, ri.struct['reply'])
1870    _free_type_members(ri, 'rsp', ri.struct['reply'], ref='obj.')
1871    ri.cw.p(f'free(rsp);')
1872    ri.cw.block_end()
1873    ri.cw.nl()
1874
1875
1876def print_req_policy_fwd(cw, struct, ri=None, terminate=True):
1877    if terminate and ri and policy_should_be_static(struct.family):
1878        return
1879
1880    if terminate:
1881        prefix = 'extern '
1882    else:
1883        if ri and policy_should_be_static(struct.family):
1884            prefix = 'static '
1885        else:
1886            prefix = ''
1887
1888    suffix = ';' if terminate else ' = {'
1889
1890    max_attr = struct.attr_max_val
1891    if ri:
1892        name = ri.op.render_name
1893        if ri.op.dual_policy:
1894            name += '_' + ri.op_mode
1895    else:
1896        name = struct.render_name
1897    cw.p(f"{prefix}const struct nla_policy {name}_nl_policy[{max_attr.enum_name} + 1]{suffix}")
1898
1899
1900def print_req_policy(cw, struct, ri=None):
1901    print_req_policy_fwd(cw, struct, ri=ri, terminate=False)
1902    for _, arg in struct.member_list():
1903        arg.attr_policy(cw)
1904    cw.p("};")
1905    cw.nl()
1906
1907
1908def kernel_can_gen_family_struct(family):
1909    return family.proto == 'genetlink'
1910
1911
1912def policy_should_be_static(family):
1913    return family.kernel_policy == 'split' or kernel_can_gen_family_struct(family)
1914
1915
1916def print_kernel_op_table_fwd(family, cw, terminate):
1917    exported = not kernel_can_gen_family_struct(family)
1918
1919    if not terminate or exported:
1920        cw.p(f"/* Ops table for {family.name} */")
1921
1922        pol_to_struct = {'global': 'genl_small_ops',
1923                         'per-op': 'genl_ops',
1924                         'split': 'genl_split_ops'}
1925        struct_type = pol_to_struct[family.kernel_policy]
1926
1927        if not exported:
1928            cnt = ""
1929        elif family.kernel_policy == 'split':
1930            cnt = 0
1931            for op in family.ops.values():
1932                if 'do' in op:
1933                    cnt += 1
1934                if 'dump' in op:
1935                    cnt += 1
1936        else:
1937            cnt = len(family.ops)
1938
1939        qual = 'static const' if not exported else 'const'
1940        line = f"{qual} struct {struct_type} {family.name}_nl_ops[{cnt}]"
1941        if terminate:
1942            cw.p(f"extern {line};")
1943        else:
1944            cw.block_start(line=line + ' =')
1945
1946    if not terminate:
1947        return
1948
1949    cw.nl()
1950    for name in family.hooks['pre']['do']['list']:
1951        cw.write_func_prot('int', c_lower(name),
1952                           ['const struct genl_split_ops *ops',
1953                            'struct sk_buff *skb', 'struct genl_info *info'], suffix=';')
1954    for name in family.hooks['post']['do']['list']:
1955        cw.write_func_prot('void', c_lower(name),
1956                           ['const struct genl_split_ops *ops',
1957                            'struct sk_buff *skb', 'struct genl_info *info'], suffix=';')
1958    for name in family.hooks['pre']['dump']['list']:
1959        cw.write_func_prot('int', c_lower(name),
1960                           ['struct netlink_callback *cb'], suffix=';')
1961    for name in family.hooks['post']['dump']['list']:
1962        cw.write_func_prot('int', c_lower(name),
1963                           ['struct netlink_callback *cb'], suffix=';')
1964
1965    cw.nl()
1966
1967    for op_name, op in family.ops.items():
1968        if op.is_async:
1969            continue
1970
1971        if 'do' in op:
1972            name = c_lower(f"{family.name}-nl-{op_name}-doit")
1973            cw.write_func_prot('int', name,
1974                               ['struct sk_buff *skb', 'struct genl_info *info'], suffix=';')
1975
1976        if 'dump' in op:
1977            name = c_lower(f"{family.name}-nl-{op_name}-dumpit")
1978            cw.write_func_prot('int', name,
1979                               ['struct sk_buff *skb', 'struct netlink_callback *cb'], suffix=';')
1980    cw.nl()
1981
1982
1983def print_kernel_op_table_hdr(family, cw):
1984    print_kernel_op_table_fwd(family, cw, terminate=True)
1985
1986
1987def print_kernel_op_table(family, cw):
1988    print_kernel_op_table_fwd(family, cw, terminate=False)
1989    if family.kernel_policy == 'global' or family.kernel_policy == 'per-op':
1990        for op_name, op in family.ops.items():
1991            if op.is_async:
1992                continue
1993
1994            cw.block_start()
1995            members = [('cmd', op.enum_name)]
1996            if 'dont-validate' in op:
1997                members.append(('validate',
1998                                ' | '.join([c_upper('genl-dont-validate-' + x)
1999                                            for x in op['dont-validate']])), )
2000            for op_mode in ['do', 'dump']:
2001                if op_mode in op:
2002                    name = c_lower(f"{family.name}-nl-{op_name}-{op_mode}it")
2003                    members.append((op_mode + 'it', name))
2004            if family.kernel_policy == 'per-op':
2005                struct = Struct(family, op['attribute-set'],
2006                                type_list=op['do']['request']['attributes'])
2007
2008                name = c_lower(f"{family.name}-{op_name}-nl-policy")
2009                members.append(('policy', name))
2010                members.append(('maxattr', struct.attr_max_val.enum_name))
2011            if 'flags' in op:
2012                members.append(('flags', ' | '.join([c_upper('genl-' + x) for x in op['flags']])))
2013            cw.write_struct_init(members)
2014            cw.block_end(line=',')
2015    elif family.kernel_policy == 'split':
2016        cb_names = {'do':   {'pre': 'pre_doit', 'post': 'post_doit'},
2017                    'dump': {'pre': 'start', 'post': 'done'}}
2018
2019        for op_name, op in family.ops.items():
2020            for op_mode in ['do', 'dump']:
2021                if op.is_async or op_mode not in op:
2022                    continue
2023
2024                cw.block_start()
2025                members = [('cmd', op.enum_name)]
2026                if 'dont-validate' in op:
2027                    dont_validate = []
2028                    for x in op['dont-validate']:
2029                        if op_mode == 'do' and x in ['dump', 'dump-strict']:
2030                            continue
2031                        if op_mode == "dump" and x == 'strict':
2032                            continue
2033                        dont_validate.append(x)
2034
2035                    if dont_validate:
2036                        members.append(('validate',
2037                                        ' | '.join([c_upper('genl-dont-validate-' + x)
2038                                                    for x in dont_validate])), )
2039                name = c_lower(f"{family.name}-nl-{op_name}-{op_mode}it")
2040                if 'pre' in op[op_mode]:
2041                    members.append((cb_names[op_mode]['pre'], c_lower(op[op_mode]['pre'])))
2042                members.append((op_mode + 'it', name))
2043                if 'post' in op[op_mode]:
2044                    members.append((cb_names[op_mode]['post'], c_lower(op[op_mode]['post'])))
2045                if 'request' in op[op_mode]:
2046                    struct = Struct(family, op['attribute-set'],
2047                                    type_list=op[op_mode]['request']['attributes'])
2048
2049                    if op.dual_policy:
2050                        name = c_lower(f"{family.name}-{op_name}-{op_mode}-nl-policy")
2051                    else:
2052                        name = c_lower(f"{family.name}-{op_name}-nl-policy")
2053                    members.append(('policy', name))
2054                    members.append(('maxattr', struct.attr_max_val.enum_name))
2055                flags = (op['flags'] if 'flags' in op else []) + ['cmd-cap-' + op_mode]
2056                members.append(('flags', ' | '.join([c_upper('genl-' + x) for x in flags])))
2057                cw.write_struct_init(members)
2058                cw.block_end(line=',')
2059
2060    cw.block_end(line=';')
2061    cw.nl()
2062
2063
2064def print_kernel_mcgrp_hdr(family, cw):
2065    if not family.mcgrps['list']:
2066        return
2067
2068    cw.block_start('enum')
2069    for grp in family.mcgrps['list']:
2070        grp_id = c_upper(f"{family.name}-nlgrp-{grp['name']},")
2071        cw.p(grp_id)
2072    cw.block_end(';')
2073    cw.nl()
2074
2075
2076def print_kernel_mcgrp_src(family, cw):
2077    if not family.mcgrps['list']:
2078        return
2079
2080    cw.block_start('static const struct genl_multicast_group ' + family.name + '_nl_mcgrps[] =')
2081    for grp in family.mcgrps['list']:
2082        name = grp['name']
2083        grp_id = c_upper(f"{family.name}-nlgrp-{name}")
2084        cw.p('[' + grp_id + '] = { "' + name + '", },')
2085    cw.block_end(';')
2086    cw.nl()
2087
2088
2089def print_kernel_family_struct_hdr(family, cw):
2090    if not kernel_can_gen_family_struct(family):
2091        return
2092
2093    cw.p(f"extern struct genl_family {family.name}_nl_family;")
2094    cw.nl()
2095
2096
2097def print_kernel_family_struct_src(family, cw):
2098    if not kernel_can_gen_family_struct(family):
2099        return
2100
2101    cw.block_start(f"struct genl_family {family.name}_nl_family __ro_after_init =")
2102    cw.p('.name\t\t= ' + family.fam_key + ',')
2103    cw.p('.version\t= ' + family.ver_key + ',')
2104    cw.p('.netnsok\t= true,')
2105    cw.p('.parallel_ops\t= true,')
2106    cw.p('.module\t\t= THIS_MODULE,')
2107    if family.kernel_policy == 'per-op':
2108        cw.p(f'.ops\t\t= {family.name}_nl_ops,')
2109        cw.p(f'.n_ops\t\t= ARRAY_SIZE({family.name}_nl_ops),')
2110    elif family.kernel_policy == 'split':
2111        cw.p(f'.split_ops\t= {family.name}_nl_ops,')
2112        cw.p(f'.n_split_ops\t= ARRAY_SIZE({family.name}_nl_ops),')
2113    if family.mcgrps['list']:
2114        cw.p(f'.mcgrps\t\t= {family.name}_nl_mcgrps,')
2115        cw.p(f'.n_mcgrps\t= ARRAY_SIZE({family.name}_nl_mcgrps),')
2116    cw.block_end(';')
2117
2118
2119def uapi_enum_start(family, cw, obj, ckey='', enum_name='enum-name'):
2120    start_line = 'enum'
2121    if enum_name in obj:
2122        if obj[enum_name]:
2123            start_line = 'enum ' + c_lower(obj[enum_name])
2124    elif ckey and ckey in obj:
2125        start_line = 'enum ' + family.name + '_' + c_lower(obj[ckey])
2126    cw.block_start(line=start_line)
2127
2128
2129def render_uapi(family, cw):
2130    hdr_prot = f"_UAPI_LINUX_{family.name.upper()}_H"
2131    cw.p('#ifndef ' + hdr_prot)
2132    cw.p('#define ' + hdr_prot)
2133    cw.nl()
2134
2135    defines = [(family.fam_key, family["name"]),
2136               (family.ver_key, family.get('version', 1))]
2137    cw.writes_defines(defines)
2138    cw.nl()
2139
2140    defines = []
2141    for const in family['definitions']:
2142        if const['type'] != 'const':
2143            cw.writes_defines(defines)
2144            defines = []
2145            cw.nl()
2146
2147        # Write kdoc for enum and flags (one day maybe also structs)
2148        if const['type'] == 'enum' or const['type'] == 'flags':
2149            enum = family.consts[const['name']]
2150
2151            if enum.has_doc():
2152                cw.p('/**')
2153                doc = ''
2154                if 'doc' in enum:
2155                    doc = ' - ' + enum['doc']
2156                cw.write_doc_line(enum.enum_name + doc)
2157                for entry in enum.entries.values():
2158                    if entry.has_doc():
2159                        doc = '@' + entry.c_name + ': ' + entry['doc']
2160                        cw.write_doc_line(doc)
2161                cw.p(' */')
2162
2163            uapi_enum_start(family, cw, const, 'name')
2164            name_pfx = const.get('name-prefix', f"{family.name}-{const['name']}-")
2165            for entry in enum.entries.values():
2166                suffix = ','
2167                if entry.value_change:
2168                    suffix = f" = {entry.user_value()}" + suffix
2169                cw.p(entry.c_name + suffix)
2170
2171            if const.get('render-max', False):
2172                cw.nl()
2173                cw.p('/* private: */')
2174                if const['type'] == 'flags':
2175                    max_name = c_upper(name_pfx + 'mask')
2176                    max_val = f' = {enum.get_mask()},'
2177                    cw.p(max_name + max_val)
2178                else:
2179                    max_name = c_upper(name_pfx + 'max')
2180                    cw.p('__' + max_name + ',')
2181                    cw.p(max_name + ' = (__' + max_name + ' - 1)')
2182            cw.block_end(line=';')
2183            cw.nl()
2184        elif const['type'] == 'const':
2185            defines.append([c_upper(family.get('c-define-name',
2186                                               f"{family.name}-{const['name']}")),
2187                            const['value']])
2188
2189    if defines:
2190        cw.writes_defines(defines)
2191        cw.nl()
2192
2193    max_by_define = family.get('max-by-define', False)
2194
2195    for _, attr_set in family.attr_sets.items():
2196        if attr_set.subset_of:
2197            continue
2198
2199        cnt_name = c_upper(family.get('attr-cnt-name', f"__{attr_set.name_prefix}MAX"))
2200        max_value = f"({cnt_name} - 1)"
2201
2202        val = 0
2203        uapi_enum_start(family, cw, attr_set.yaml, 'enum-name')
2204        for _, attr in attr_set.items():
2205            suffix = ','
2206            if attr.value != val:
2207                suffix = f" = {attr.value},"
2208                val = attr.value
2209            val += 1
2210            cw.p(attr.enum_name + suffix)
2211        cw.nl()
2212        cw.p(cnt_name + ('' if max_by_define else ','))
2213        if not max_by_define:
2214            cw.p(f"{attr_set.max_name} = {max_value}")
2215        cw.block_end(line=';')
2216        if max_by_define:
2217            cw.p(f"#define {attr_set.max_name} {max_value}")
2218        cw.nl()
2219
2220    # Commands
2221    separate_ntf = 'async-prefix' in family['operations']
2222
2223    max_name = c_upper(family.get('cmd-max-name', f"{family.op_prefix}MAX"))
2224    cnt_name = c_upper(family.get('cmd-cnt-name', f"__{family.op_prefix}MAX"))
2225    max_value = f"({cnt_name} - 1)"
2226
2227    uapi_enum_start(family, cw, family['operations'], 'enum-name')
2228    val = 0
2229    for op in family.msgs.values():
2230        if separate_ntf and ('notify' in op or 'event' in op):
2231            continue
2232
2233        suffix = ','
2234        if op.value != val:
2235            suffix = f" = {op.value},"
2236            val = op.value
2237        cw.p(op.enum_name + suffix)
2238        val += 1
2239    cw.nl()
2240    cw.p(cnt_name + ('' if max_by_define else ','))
2241    if not max_by_define:
2242        cw.p(f"{max_name} = {max_value}")
2243    cw.block_end(line=';')
2244    if max_by_define:
2245        cw.p(f"#define {max_name} {max_value}")
2246    cw.nl()
2247
2248    if separate_ntf:
2249        uapi_enum_start(family, cw, family['operations'], enum_name='async-enum')
2250        for op in family.msgs.values():
2251            if separate_ntf and not ('notify' in op or 'event' in op):
2252                continue
2253
2254            suffix = ','
2255            if 'value' in op:
2256                suffix = f" = {op['value']},"
2257            cw.p(op.enum_name + suffix)
2258        cw.block_end(line=';')
2259        cw.nl()
2260
2261    # Multicast
2262    defines = []
2263    for grp in family.mcgrps['list']:
2264        name = grp['name']
2265        defines.append([c_upper(grp.get('c-define-name', f"{family.name}-mcgrp-{name}")),
2266                        f'{name}'])
2267    cw.nl()
2268    if defines:
2269        cw.writes_defines(defines)
2270        cw.nl()
2271
2272    cw.p(f'#endif /* {hdr_prot} */')
2273
2274
2275def _render_user_ntf_entry(ri, op):
2276    ri.cw.block_start(line=f"[{op.enum_name}] = ")
2277    ri.cw.p(f".alloc_sz\t= sizeof({type_name(ri, 'event')}),")
2278    ri.cw.p(f".cb\t\t= {op_prefix(ri, 'reply', deref=True)}_parse,")
2279    ri.cw.p(f".policy\t\t= &{ri.struct['reply'].render_name}_nest,")
2280    ri.cw.p(f".free\t\t= (void *){op_prefix(ri, 'notify')}_free,")
2281    ri.cw.block_end(line=',')
2282
2283
2284def render_user_family(family, cw, prototype):
2285    symbol = f'const struct ynl_family ynl_{family.c_name}_family'
2286    if prototype:
2287        cw.p(f'extern {symbol};')
2288        return
2289
2290    if family.ntfs:
2291        cw.block_start(line=f"static const struct ynl_ntf_info {family['name']}_ntf_info[] = ")
2292        for ntf_op_name, ntf_op in family.ntfs.items():
2293            if 'notify' in ntf_op:
2294                op = family.ops[ntf_op['notify']]
2295                ri = RenderInfo(cw, family, "user", op, "notify")
2296            elif 'event' in ntf_op:
2297                ri = RenderInfo(cw, family, "user", ntf_op, "event")
2298            else:
2299                raise Exception('Invalid notification ' + ntf_op_name)
2300            _render_user_ntf_entry(ri, ntf_op)
2301        for op_name, op in family.ops.items():
2302            if 'event' not in op:
2303                continue
2304            ri = RenderInfo(cw, family, "user", op, "event")
2305            _render_user_ntf_entry(ri, op)
2306        cw.block_end(line=";")
2307        cw.nl()
2308
2309    cw.block_start(f'{symbol} = ')
2310    cw.p(f'.name\t\t= "{family.name}",')
2311    if family.ntfs:
2312        cw.p(f".ntf_info\t= {family['name']}_ntf_info,")
2313        cw.p(f".ntf_info_size\t= MNL_ARRAY_SIZE({family['name']}_ntf_info),")
2314    cw.block_end(line=';')
2315
2316
2317def find_kernel_root(full_path):
2318    sub_path = ''
2319    while True:
2320        sub_path = os.path.join(os.path.basename(full_path), sub_path)
2321        full_path = os.path.dirname(full_path)
2322        maintainers = os.path.join(full_path, "MAINTAINERS")
2323        if os.path.exists(maintainers):
2324            return full_path, sub_path[:-1]
2325
2326
2327def main():
2328    parser = argparse.ArgumentParser(description='Netlink simple parsing generator')
2329    parser.add_argument('--mode', dest='mode', type=str, required=True)
2330    parser.add_argument('--spec', dest='spec', type=str, required=True)
2331    parser.add_argument('--header', dest='header', action='store_true', default=None)
2332    parser.add_argument('--source', dest='header', action='store_false')
2333    parser.add_argument('--user-header', nargs='+', default=[])
2334    parser.add_argument('--exclude-op', action='append', default=[])
2335    parser.add_argument('-o', dest='out_file', type=str, default=None)
2336    args = parser.parse_args()
2337
2338    if args.header is None:
2339        parser.error("--header or --source is required")
2340
2341    exclude_ops = [re.compile(expr) for expr in args.exclude_op]
2342
2343    try:
2344        parsed = Family(args.spec, exclude_ops)
2345        if parsed.license != '((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)':
2346            print('Spec license:', parsed.license)
2347            print('License must be: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)')
2348            os.sys.exit(1)
2349    except yaml.YAMLError as exc:
2350        print(exc)
2351        os.sys.exit(1)
2352        return
2353
2354    supported_models = ['unified']
2355    if args.mode in ['user', 'kernel']:
2356        supported_models += ['directional']
2357    if parsed.msg_id_model not in supported_models:
2358        print(f'Message enum-model {parsed.msg_id_model} not supported for {args.mode} generation')
2359        os.sys.exit(1)
2360
2361    cw = CodeWriter(BaseNlLib(), args.out_file)
2362
2363    _, spec_kernel = find_kernel_root(args.spec)
2364    if args.mode == 'uapi' or args.header:
2365        cw.p(f'/* SPDX-License-Identifier: {parsed.license} */')
2366    else:
2367        cw.p(f'// SPDX-License-Identifier: {parsed.license}')
2368    cw.p("/* Do not edit directly, auto-generated from: */")
2369    cw.p(f"/*\t{spec_kernel} */")
2370    cw.p(f"/* YNL-GEN {args.mode} {'header' if args.header else 'source'} */")
2371    if args.exclude_op or args.user_header:
2372        line = ''
2373        line += ' --user-header '.join([''] + args.user_header)
2374        line += ' --exclude-op '.join([''] + args.exclude_op)
2375        cw.p(f'/* YNL-ARG{line} */')
2376    cw.nl()
2377
2378    if args.mode == 'uapi':
2379        render_uapi(parsed, cw)
2380        return
2381
2382    hdr_prot = f"_LINUX_{parsed.name.upper()}_GEN_H"
2383    if args.header:
2384        cw.p('#ifndef ' + hdr_prot)
2385        cw.p('#define ' + hdr_prot)
2386        cw.nl()
2387
2388    if args.mode == 'kernel':
2389        cw.p('#include <net/netlink.h>')
2390        cw.p('#include <net/genetlink.h>')
2391        cw.nl()
2392        if not args.header:
2393            if args.out_file:
2394                cw.p(f'#include "{os.path.basename(args.out_file[:-2])}.h"')
2395            cw.nl()
2396        headers = ['uapi/' + parsed.uapi_header]
2397    else:
2398        cw.p('#include <stdlib.h>')
2399        cw.p('#include <string.h>')
2400        if args.header:
2401            cw.p('#include <linux/types.h>')
2402        else:
2403            cw.p(f'#include "{parsed.name}-user.h"')
2404            cw.p('#include "ynl.h"')
2405        headers = [parsed.uapi_header]
2406    for definition in parsed['definitions']:
2407        if 'header' in definition:
2408            headers.append(definition['header'])
2409    for one in headers:
2410        cw.p(f"#include <{one}>")
2411    cw.nl()
2412
2413    if args.mode == "user":
2414        if not args.header:
2415            cw.p("#include <libmnl/libmnl.h>")
2416            cw.p("#include <linux/genetlink.h>")
2417            cw.nl()
2418            for one in args.user_header:
2419                cw.p(f'#include "{one}"')
2420        else:
2421            cw.p('struct ynl_sock;')
2422            cw.nl()
2423            render_user_family(parsed, cw, True)
2424        cw.nl()
2425
2426    if args.mode == "kernel":
2427        if args.header:
2428            for _, struct in sorted(parsed.pure_nested_structs.items()):
2429                if struct.request:
2430                    cw.p('/* Common nested types */')
2431                    break
2432            for attr_set, struct in sorted(parsed.pure_nested_structs.items()):
2433                if struct.request:
2434                    print_req_policy_fwd(cw, struct)
2435            cw.nl()
2436
2437            if parsed.kernel_policy == 'global':
2438                cw.p(f"/* Global operation policy for {parsed.name} */")
2439
2440                struct = Struct(parsed, parsed.global_policy_set, type_list=parsed.global_policy)
2441                print_req_policy_fwd(cw, struct)
2442                cw.nl()
2443
2444            if parsed.kernel_policy in {'per-op', 'split'}:
2445                for op_name, op in parsed.ops.items():
2446                    if 'do' in op and 'event' not in op:
2447                        ri = RenderInfo(cw, parsed, args.mode, op, "do")
2448                        print_req_policy_fwd(cw, ri.struct['request'], ri=ri)
2449                        cw.nl()
2450
2451            print_kernel_op_table_hdr(parsed, cw)
2452            print_kernel_mcgrp_hdr(parsed, cw)
2453            print_kernel_family_struct_hdr(parsed, cw)
2454        else:
2455            for _, struct in sorted(parsed.pure_nested_structs.items()):
2456                if struct.request:
2457                    cw.p('/* Common nested types */')
2458                    break
2459            for attr_set, struct in sorted(parsed.pure_nested_structs.items()):
2460                if struct.request:
2461                    print_req_policy(cw, struct)
2462            cw.nl()
2463
2464            if parsed.kernel_policy == 'global':
2465                cw.p(f"/* Global operation policy for {parsed.name} */")
2466
2467                struct = Struct(parsed, parsed.global_policy_set, type_list=parsed.global_policy)
2468                print_req_policy(cw, struct)
2469                cw.nl()
2470
2471            for op_name, op in parsed.ops.items():
2472                if parsed.kernel_policy in {'per-op', 'split'}:
2473                    for op_mode in ['do', 'dump']:
2474                        if op_mode in op and 'request' in op[op_mode]:
2475                            cw.p(f"/* {op.enum_name} - {op_mode} */")
2476                            ri = RenderInfo(cw, parsed, args.mode, op, op_mode)
2477                            print_req_policy(cw, ri.struct['request'], ri=ri)
2478                            cw.nl()
2479
2480            print_kernel_op_table(parsed, cw)
2481            print_kernel_mcgrp_src(parsed, cw)
2482            print_kernel_family_struct_src(parsed, cw)
2483
2484    if args.mode == "user":
2485        if args.header:
2486            cw.p('/* Enums */')
2487            put_op_name_fwd(parsed, cw)
2488
2489            for name, const in parsed.consts.items():
2490                if isinstance(const, EnumSet):
2491                    put_enum_to_str_fwd(parsed, cw, const)
2492            cw.nl()
2493
2494            cw.p('/* Common nested types */')
2495            for attr_set, struct in parsed.pure_nested_structs.items():
2496                ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set)
2497                print_type_full(ri, struct)
2498
2499            for op_name, op in parsed.ops.items():
2500                cw.p(f"/* ============== {op.enum_name} ============== */")
2501
2502                if 'do' in op and 'event' not in op:
2503                    cw.p(f"/* {op.enum_name} - do */")
2504                    ri = RenderInfo(cw, parsed, args.mode, op, "do")
2505                    print_req_type(ri)
2506                    print_req_type_helpers(ri)
2507                    cw.nl()
2508                    print_rsp_type(ri)
2509                    print_rsp_type_helpers(ri)
2510                    cw.nl()
2511                    print_req_prototype(ri)
2512                    cw.nl()
2513
2514                if 'dump' in op:
2515                    cw.p(f"/* {op.enum_name} - dump */")
2516                    ri = RenderInfo(cw, parsed, args.mode, op, 'dump')
2517                    if 'request' in op['dump']:
2518                        print_req_type(ri)
2519                        print_req_type_helpers(ri)
2520                    if not ri.type_consistent:
2521                        print_rsp_type(ri)
2522                    print_wrapped_type(ri)
2523                    print_dump_prototype(ri)
2524                    cw.nl()
2525
2526                if op.has_ntf:
2527                    cw.p(f"/* {op.enum_name} - notify */")
2528                    ri = RenderInfo(cw, parsed, args.mode, op, 'notify')
2529                    if not ri.type_consistent:
2530                        raise Exception(f'Only notifications with consistent types supported ({op.name})')
2531                    print_wrapped_type(ri)
2532
2533            for op_name, op in parsed.ntfs.items():
2534                if 'event' in op:
2535                    ri = RenderInfo(cw, parsed, args.mode, op, 'event')
2536                    cw.p(f"/* {op.enum_name} - event */")
2537                    print_rsp_type(ri)
2538                    cw.nl()
2539                    print_wrapped_type(ri)
2540            cw.nl()
2541        else:
2542            cw.p('/* Enums */')
2543            put_op_name(parsed, cw)
2544
2545            for name, const in parsed.consts.items():
2546                if isinstance(const, EnumSet):
2547                    put_enum_to_str(parsed, cw, const)
2548            cw.nl()
2549
2550            cw.p('/* Policies */')
2551            for name in parsed.pure_nested_structs:
2552                struct = Struct(parsed, name)
2553                put_typol(cw, struct)
2554            for name in parsed.root_sets:
2555                struct = Struct(parsed, name)
2556                put_typol(cw, struct)
2557
2558            cw.p('/* Common nested types */')
2559            for attr_set, struct in parsed.pure_nested_structs.items():
2560                ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set)
2561
2562                free_rsp_nested(ri, struct)
2563                if struct.request:
2564                    put_req_nested(ri, struct)
2565                if struct.reply:
2566                    parse_rsp_nested(ri, struct)
2567
2568            for op_name, op in parsed.ops.items():
2569                cw.p(f"/* ============== {op.enum_name} ============== */")
2570                if 'do' in op and 'event' not in op:
2571                    cw.p(f"/* {op.enum_name} - do */")
2572                    ri = RenderInfo(cw, parsed, args.mode, op, "do")
2573                    print_req_free(ri)
2574                    print_rsp_free(ri)
2575                    parse_rsp_msg(ri)
2576                    print_req(ri)
2577                    cw.nl()
2578
2579                if 'dump' in op:
2580                    cw.p(f"/* {op.enum_name} - dump */")
2581                    ri = RenderInfo(cw, parsed, args.mode, op, "dump")
2582                    if not ri.type_consistent:
2583                        parse_rsp_msg(ri, deref=True)
2584                    print_dump_type_free(ri)
2585                    print_dump(ri)
2586                    cw.nl()
2587
2588                if op.has_ntf:
2589                    cw.p(f"/* {op.enum_name} - notify */")
2590                    ri = RenderInfo(cw, parsed, args.mode, op, 'notify')
2591                    if not ri.type_consistent:
2592                        raise Exception(f'Only notifications with consistent types supported ({op.name})')
2593                    print_ntf_type_free(ri)
2594
2595            for op_name, op in parsed.ntfs.items():
2596                if 'event' in op:
2597                    cw.p(f"/* {op.enum_name} - event */")
2598
2599                    ri = RenderInfo(cw, parsed, args.mode, op, "do")
2600                    parse_rsp_msg(ri)
2601
2602                    ri = RenderInfo(cw, parsed, args.mode, op, "event")
2603                    print_ntf_type_free(ri)
2604            cw.nl()
2605            render_user_family(parsed, cw, False)
2606
2607    if args.header:
2608        cw.p(f'#endif /* {hdr_prot} */')
2609
2610
2611if __name__ == "__main__":
2612    main()
2613