xref: /linux/tools/net/ynl/pyynl/ynl_gen_c.py (revision 91ec2035134982b98fab0609a9fd8480e8217dc1)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
3#
4# pylint: disable=line-too-long, missing-class-docstring, missing-function-docstring
5# pylint: disable=too-many-positional-arguments, too-many-arguments, too-many-statements
6# pylint: disable=too-many-branches, too-many-locals, too-many-instance-attributes
7# pylint: disable=too-many-nested-blocks, too-many-lines, too-few-public-methods
8# pylint: disable=broad-exception-raised, broad-exception-caught, protected-access
9
10"""
11ynl_gen_c
12
13A YNL to C code generator for both kernel and userspace protocol stubs.
14"""
15
16import argparse
17import filecmp
18import pathlib
19import os
20import re
21import shutil
22import sys
23import tempfile
24import yaml as pyyaml
25
26# pylint: disable=no-name-in-module,wrong-import-position
27sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())
28from lib import SpecFamily, SpecAttrSet, SpecAttr, SpecOperation, SpecEnumSet, SpecEnumEntry
29from lib import SpecSubMessage
30
31
32def c_upper(name):
33    return name.upper().replace('-', '_')
34
35
36def c_lower(name):
37    return name.lower().replace('-', '_')
38
39
40def limit_to_number(name):
41    """
42    Turn a string limit like u32-max or s64-min into its numerical value
43    """
44    if name[0] == 'u' and name.endswith('-min'):
45        return 0
46    width = int(name[1:-4])
47    if name[0] == 's':
48        width -= 1
49    value = (1 << width) - 1
50    if name[0] == 's' and name.endswith('-min'):
51        value = -value - 1
52    return value
53
54
55class BaseNlLib:
56    def get_family_id(self):
57        return 'ys->family_id'
58
59
60class Type(SpecAttr):
61    def __init__(self, family, attr_set, attr, value):
62        super().__init__(family, attr_set, attr, value)
63
64        self.attr = attr
65        self.attr_set = attr_set
66        self.type = attr['type']
67        self.checks = attr.get('checks', {})
68
69        self.request = False
70        self.reply = False
71
72        self.is_selector = False
73
74        if 'len' in attr:
75            self.len = attr['len']
76
77        if 'nested-attributes' in attr:
78            nested = attr['nested-attributes']
79        elif 'sub-message' in attr:
80            nested = attr['sub-message']
81        else:
82            nested = None
83
84        if nested:
85            self.nested_attrs = nested
86            if self.nested_attrs == family.name:
87                self.nested_render_name = c_lower(f"{family.ident_name}")
88            else:
89                self.nested_render_name = c_lower(f"{family.ident_name}_{self.nested_attrs}")
90
91            if self.nested_attrs in self.family.consts:
92                self.nested_struct_type = 'struct ' + self.nested_render_name + '_'
93            else:
94                self.nested_struct_type = 'struct ' + self.nested_render_name
95
96        self.c_name = c_lower(self.name)
97        if self.c_name in _C_KW:
98            self.c_name += '_'
99        if self.c_name[0].isdigit():
100            self.c_name = '_' + self.c_name
101
102        # Added by resolve():
103        self.enum_name = None
104        delattr(self, "enum_name")
105
106    def _get_real_attr(self):
107        # if the attr is for a subset return the "real" attr (just one down, does not recurse)
108        return self.family.attr_sets[self.attr_set.subset_of][self.name]
109
110    def set_request(self):
111        self.request = True
112        if self.attr_set.subset_of:
113            self._get_real_attr().set_request()
114
115    def set_reply(self):
116        self.reply = True
117        if self.attr_set.subset_of:
118            self._get_real_attr().set_reply()
119
120    def get_limit(self, limit, default=None):
121        value = self.checks.get(limit, default)
122        if value is None:
123            return value
124        if isinstance(value, int):
125            return value
126        if value in self.family.consts:
127            return self.family.consts[value]["value"]
128        return limit_to_number(value)
129
130    def get_limit_str(self, limit, default=None, suffix=''):
131        value = self.checks.get(limit, default)
132        if value is None:
133            return ''
134        if isinstance(value, int):
135            return str(value) + suffix
136        if value in self.family.consts:
137            const = self.family.consts[value]
138            if const.get('header'):
139                return c_upper(value)
140            return c_upper(f"{self.family['name']}-{value}")
141        return c_upper(value)
142
143    def resolve(self):
144        if 'parent-sub-message' in self.attr:
145            enum_name = self.attr['parent-sub-message'].enum_name
146        elif 'name-prefix' in self.attr:
147            enum_name = f"{self.attr['name-prefix']}{self.name}"
148        else:
149            enum_name = f"{self.attr_set.name_prefix}{self.name}"
150        self.enum_name = c_upper(enum_name)
151
152        if self.attr_set.subset_of:
153            if self.checks != self._get_real_attr().checks:
154                raise Exception("Overriding checks not supported by codegen, yet")
155
156    def is_multi_val(self):
157        return None
158
159    def is_scalar(self):
160        return self.type in {'u8', 'u16', 'u32', 'u64', 's32', 's64'}
161
162    def is_recursive(self):
163        return False
164
165    def is_recursive_for_op(self, ri):
166        return self.is_recursive() and not ri.op
167
168    def presence_type(self):
169        return 'present'
170
171    def presence_member(self, space, type_filter):
172        if self.presence_type() != type_filter:
173            return ''
174
175        if self.presence_type() == 'present':
176            pfx = '__' if space == 'user' else ''
177            return f"{pfx}u32 {self.c_name}:1;"
178
179        if self.presence_type() in {'len', 'count'}:
180            pfx = '__' if space == 'user' else ''
181            return f"{pfx}u32 {self.c_name};"
182        return ''
183
184    def _complex_member_type(self, _ri):
185        return None
186
187    def free_needs_iter(self):
188        return False
189
190    def _free_lines(self, _ri, var, ref):
191        if self.is_multi_val() or self.presence_type() in {'count', 'len'}:
192            return [f'free({var}->{ref}{self.c_name});']
193        return []
194
195    def free(self, ri, var, ref):
196        lines = self._free_lines(ri, var, ref)
197        for line in lines:
198            ri.cw.p(line)
199
200    # pylint: disable=assignment-from-none
201    def arg_member(self, ri):
202        member = self._complex_member_type(ri)
203        if member is not None:
204            spc = ' ' if member[-1] != '*' else ''
205            arg = [member + spc + '*' + self.c_name]
206            if self.presence_type() == 'count':
207                arg += ['unsigned int n_' + self.c_name]
208            return arg
209        raise Exception(f"Struct member not implemented for class type {self.type}")
210
211    def struct_member(self, ri):
212        member = self._complex_member_type(ri)
213        if member is not None:
214            ptr = '*' if self.is_multi_val() else ''
215            if self.is_recursive_for_op(ri):
216                ptr = '*'
217            spc = ' ' if member[-1] != '*' else ''
218            ri.cw.p(f"{member}{spc}{ptr}{self.c_name};")
219            return
220        members = self.arg_member(ri)
221        for one in members:
222            ri.cw.p(one + ';')
223
224    def _attr_policy(self, policy):
225        return '{ .type = ' + policy + ', }'
226
227    def attr_policy(self, cw):
228        policy = f'NLA_{c_upper(self.type)}'
229        if self.attr.get('byte-order') == 'big-endian':
230            if self.type in {'u16', 'u32'}:
231                policy = f'NLA_BE{self.type[1:]}'
232
233        spec = self._attr_policy(policy)
234        cw.p(f"\t[{self.enum_name}] = {spec},")
235
236    def _attr_typol(self):
237        raise Exception(f"Type policy not implemented for class type {self.type}")
238
239    def attr_typol(self, cw):
240        typol = self._attr_typol()
241        cw.p(f'[{self.enum_name}] = {"{"} .name = "{self.name}", {typol}{"}"},')
242
243    def _attr_put_line(self, ri, var, line):
244        presence = self.presence_type()
245        if presence in {'present', 'len'}:
246            ri.cw.p(f"if ({var}->_{presence}.{self.c_name})")
247        ri.cw.p(f"{line};")
248
249    def _attr_put_simple(self, ri, var, put_type):
250        line = f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name})"
251        self._attr_put_line(ri, var, line)
252
253    def attr_put(self, ri, var):
254        raise Exception(f"Put not implemented for class type {self.type}")
255
256    def _attr_get(self, ri, var):
257        raise Exception(f"Attr get not implemented for class type {self.type}")
258
259    def attr_get(self, ri, var, first):
260        lines, init_lines, _ = self._attr_get(ri, var)
261        if isinstance(lines, str):
262            lines = [lines]
263        if isinstance(init_lines, str):
264            init_lines = [init_lines]
265
266        kw = 'if' if first else 'else if'
267        ri.cw.block_start(line=f"{kw} (type == {self.enum_name})")
268
269        if not self.is_multi_val():
270            ri.cw.p("if (ynl_attr_validate(yarg, attr))")
271            ri.cw.p("return YNL_PARSE_CB_ERROR;")
272            if self.presence_type() == 'present':
273                ri.cw.p(f"{var}->_present.{self.c_name} = 1;")
274
275        if init_lines:
276            ri.cw.nl()
277            for line in init_lines:
278                ri.cw.p(line)
279
280        for line in lines:
281            ri.cw.p(line)
282        ri.cw.block_end()
283        return True
284
285    def _setter_lines(self, ri, member, presence):
286        raise Exception(f"Setter not implemented for class type {self.type}")
287
288    def setter(self, ri, _space, direction, deref=False, ref=None, var="req"):
289        ref = (ref if ref else []) + [self.c_name]
290        member = f"{var}->{'.'.join(ref)}"
291
292        local_vars = []
293        if self.free_needs_iter():
294            local_vars += ['unsigned int i;']
295
296        code = []
297        presence = ''
298        # pylint: disable=consider-using-enumerate
299        for i in range(0, len(ref)):
300            presence = f"{var}->{'.'.join(ref[:i] + [''])}_present.{ref[i]}"
301            # Every layer below last is a nest, so we know it uses bit presence
302            # last layer is "self" and may be a complex type
303            if i == len(ref) - 1 and self.presence_type() != 'present':
304                presence = f"{var}->{'.'.join(ref[:i] + [''])}_{self.presence_type()}.{ref[i]}"
305                continue
306            code.append(presence + ' = 1;')
307        ref_path = '.'.join(ref[:-1])
308        if ref_path:
309            ref_path += '.'
310        code += self._free_lines(ri, var, ref_path)
311        code += self._setter_lines(ri, member, presence)
312
313        func_name = f"{op_prefix(ri, direction, deref=deref)}_set_{'_'.join(ref)}"
314        free = bool([x for x in code if 'free(' in x])
315        alloc = bool([x for x in code if 'alloc(' in x])
316        if free and not alloc:
317            func_name = '__' + func_name
318        ri.cw.write_func('static inline void', func_name, local_vars=local_vars,
319                         body=code,
320                         args=[f'{type_name(ri, direction, deref=deref)} *{var}'] + self.arg_member(ri))
321
322
323class TypeUnused(Type):
324    def presence_type(self):
325        return ''
326
327    def arg_member(self, ri):
328        return []
329
330    def _attr_get(self, ri, var):
331        return ['return YNL_PARSE_CB_ERROR;'], None, None
332
333    def _attr_typol(self):
334        return '.type = YNL_PT_REJECT, '
335
336    def attr_policy(self, cw):
337        pass
338
339    def attr_put(self, ri, var):
340        pass
341
342    def attr_get(self, ri, var, first):
343        pass
344
345    def setter(self, ri, space, direction, deref=False, ref=None, var=None):
346        pass
347
348
349class TypePad(Type):
350    def presence_type(self):
351        return ''
352
353    def arg_member(self, ri):
354        return []
355
356    def _attr_typol(self):
357        return '.type = YNL_PT_IGNORE, '
358
359    def attr_put(self, ri, var):
360        pass
361
362    def attr_get(self, ri, var, first):
363        pass
364
365    def attr_policy(self, cw):
366        pass
367
368    def setter(self, ri, space, direction, deref=False, ref=None, var=None):
369        pass
370
371
372class TypeScalar(Type):
373    def __init__(self, family, attr_set, attr, value):
374        super().__init__(family, attr_set, attr, value)
375
376        self.byte_order_comment = ''
377        if 'byte-order' in attr:
378            self.byte_order_comment = f" /* {attr['byte-order']} */"
379
380        # Classic families have some funny enums, don't bother
381        # computing checks, since we only need them for kernel policies
382        if not family.is_classic():
383            self._init_checks()
384
385        # Added by resolve():
386        self.is_bitfield = None
387        delattr(self, "is_bitfield")
388        self.type_name = None
389        delattr(self, "type_name")
390
391    def resolve(self):
392        self.resolve_up(super())
393
394        if 'enum-as-flags' in self.attr and self.attr['enum-as-flags']:
395            self.is_bitfield = True
396        elif 'enum' in self.attr:
397            self.is_bitfield = self.family.consts[self.attr['enum']]['type'] == 'flags'
398        else:
399            self.is_bitfield = False
400
401        if not self.is_bitfield and 'enum' in self.attr:
402            self.type_name = self.family.consts[self.attr['enum']].user_type
403        elif self.is_auto_scalar:
404            self.type_name = '__' + self.type[0] + '64'
405        else:
406            self.type_name = '__' + self.type
407
408    def _init_checks(self):
409        if 'enum' in self.attr:
410            enum = self.family.consts[self.attr['enum']]
411            low, high = enum.value_range()
412            if low is None and high is None:
413                self.checks['sparse'] = True
414            else:
415                if 'min' not in self.checks:
416                    if low != 0 or self.type[0] == 's':
417                        self.checks['min'] = low
418                if 'max' not in self.checks:
419                    self.checks['max'] = high
420
421        if 'min' in self.checks and 'max' in self.checks:
422            if self.get_limit('min') > self.get_limit('max'):
423                raise Exception(f'Invalid limit for "{self.name}" min: {self.get_limit("min")} max: {self.get_limit("max")}')
424            self.checks['range'] = True
425
426        low = min(self.get_limit('min', 0), self.get_limit('max', 0))
427        high = max(self.get_limit('min', 0), self.get_limit('max', 0))
428        if low < 0 and self.type[0] == 'u':
429            raise Exception(f'Invalid limit for "{self.name}" negative limit for unsigned type')
430        if low < -32768 or high > 32767:
431            self.checks['full-range'] = True
432
433    # pylint: disable=too-many-return-statements
434    def _attr_policy(self, policy):
435        if 'flags-mask' in self.checks or self.is_bitfield:
436            if self.is_bitfield:
437                enum = self.family.consts[self.attr['enum']]
438                mask = enum.get_mask(as_flags=True)
439            else:
440                flags = self.family.consts[self.checks['flags-mask']]
441                flag_cnt = len(flags['entries'])
442                mask = (1 << flag_cnt) - 1
443            return f"NLA_POLICY_MASK({policy}, 0x{mask:x})"
444        if 'full-range' in self.checks:
445            return f"NLA_POLICY_FULL_RANGE({policy}, &{c_lower(self.enum_name)}_range)"
446        if 'range' in self.checks:
447            return f"NLA_POLICY_RANGE({policy}, {self.get_limit_str('min')}, {self.get_limit_str('max')})"
448        if 'min' in self.checks:
449            return f"NLA_POLICY_MIN({policy}, {self.get_limit_str('min')})"
450        if 'max' in self.checks:
451            return f"NLA_POLICY_MAX({policy}, {self.get_limit_str('max')})"
452        if 'sparse' in self.checks:
453            return f"NLA_POLICY_VALIDATE_FN({policy}, &{c_lower(self.enum_name)}_validate)"
454        return super()._attr_policy(policy)
455
456    def _attr_typol(self):
457        return f'.type = YNL_PT_U{c_upper(self.type[1:])}, '
458
459    def arg_member(self, ri):
460        return [f'{self.type_name} {self.c_name}{self.byte_order_comment}']
461
462    def attr_put(self, ri, var):
463        self._attr_put_simple(ri, var, self.type)
464
465    def _attr_get(self, ri, var):
466        return f"{var}->{self.c_name} = ynl_attr_get_{self.type}(attr);", None, None
467
468    def _setter_lines(self, ri, member, presence):
469        return [f"{member} = {self.c_name};"]
470
471
472class TypeFlag(Type):
473    def arg_member(self, ri):
474        return []
475
476    def _attr_typol(self):
477        return '.type = YNL_PT_FLAG, '
478
479    def attr_put(self, ri, var):
480        self._attr_put_line(ri, var, f"ynl_attr_put(nlh, {self.enum_name}, NULL, 0)")
481
482    def _attr_get(self, ri, var):
483        return [], None, None
484
485    def _setter_lines(self, ri, member, presence):
486        return []
487
488
489class TypeString(Type):
490    def arg_member(self, ri):
491        return [f"const char *{self.c_name}"]
492
493    def presence_type(self):
494        return 'len'
495
496    def struct_member(self, ri):
497        ri.cw.p(f"char *{self.c_name};")
498
499    def _attr_typol(self):
500        typol = '.type = YNL_PT_NUL_STR, '
501        if self.is_selector:
502            typol += '.is_selector = 1, '
503        return typol
504
505    def _attr_policy(self, policy):
506        if 'exact-len' in self.checks:
507            mem = 'NLA_POLICY_EXACT_LEN(' + self.get_limit_str('exact-len') + ')'
508        else:
509            mem = '{ .type = ' + policy
510            if 'max-len' in self.checks:
511                mem += ', .len = ' + self.get_limit_str('max-len')
512            mem += ', }'
513        return mem
514
515    def attr_policy(self, cw):
516        if self.checks.get('unterminated-ok', False):
517            policy = 'NLA_STRING'
518        else:
519            policy = 'NLA_NUL_STRING'
520
521        spec = self._attr_policy(policy)
522        cw.p(f"\t[{self.enum_name}] = {spec},")
523
524    def attr_put(self, ri, var):
525        self._attr_put_simple(ri, var, 'str')
526
527    def _attr_get(self, ri, var):
528        len_mem = var + '->_len.' + self.c_name
529        return [f"{var}->{self.c_name} = malloc(len + 1);",
530                f"if (!{var}->{self.c_name})",
531                "return YNL_PARSE_CB_ERROR;",
532                f"{len_mem} = len;",
533                f"memcpy({var}->{self.c_name}, ynl_attr_get_str(attr), len);",
534                f"{var}->{self.c_name}[len] = 0;"], \
535               ['len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));'], \
536               ['unsigned int len;']
537
538    def _setter_lines(self, ri, member, presence):
539        return [f"{presence} = strlen({self.c_name});",
540                f"{member} = malloc({presence} + 1);",
541                f'memcpy({member}, {self.c_name}, {presence});',
542                f'{member}[{presence}] = 0;']
543
544
545class TypeBinary(Type):
546    def arg_member(self, ri):
547        return [f"const void *{self.c_name}", 'size_t len']
548
549    def presence_type(self):
550        return 'len'
551
552    def struct_member(self, ri):
553        ri.cw.p(f"void *{self.c_name};")
554
555    def _attr_typol(self):
556        return '.type = YNL_PT_BINARY,'
557
558    def _attr_policy(self, policy):
559        if len(self.checks) == 0:
560            pass
561        elif len(self.checks) == 1:
562            check_name = list(self.checks)[0]
563            if check_name not in {'exact-len', 'min-len', 'max-len'}:
564                raise Exception('Unsupported check for binary type: ' + check_name)
565        else:
566            raise Exception('More than one check for binary type not implemented, yet')
567
568        if len(self.checks) == 0:
569            mem = '{ .type = NLA_BINARY, }'
570        elif 'exact-len' in self.checks:
571            mem = 'NLA_POLICY_EXACT_LEN(' + self.get_limit_str('exact-len') + ')'
572        elif 'min-len' in self.checks:
573            mem = 'NLA_POLICY_MIN_LEN(' + self.get_limit_str('min-len') + ')'
574        elif 'max-len' in self.checks:
575            mem = 'NLA_POLICY_MAX_LEN(' + self.get_limit_str('max-len') + ')'
576        else:
577            raise Exception('Failed to process policy check for binary type')
578
579        return mem
580
581    def attr_put(self, ri, var):
582        self._attr_put_line(ri, var, f"ynl_attr_put(nlh, {self.enum_name}, " +
583                            f"{var}->{self.c_name}, {var}->_len.{self.c_name})")
584
585    def _attr_get(self, ri, var):
586        len_mem = var + '->_len.' + self.c_name
587        return [f"{var}->{self.c_name} = malloc(len);",
588                f"if (!{var}->{self.c_name})",
589                "return YNL_PARSE_CB_ERROR;",
590                f"{len_mem} = len;",
591                f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \
592               ['len = ynl_attr_data_len(attr);'], \
593               ['unsigned int len;']
594
595    def _setter_lines(self, ri, member, presence):
596        return [f"{presence} = len;",
597                f"{member} = malloc({presence});",
598                f'memcpy({member}, {self.c_name}, {presence});']
599
600
601class TypeBinaryStruct(TypeBinary):
602    def struct_member(self, ri):
603        ri.cw.p(f'struct {c_lower(self.get("struct"))} *{self.c_name};')
604
605    def _attr_get(self, ri, var):
606        struct_sz = 'sizeof(struct ' + c_lower(self.get("struct")) + ')'
607        len_mem = var + '->_' + self.presence_type() + '.' + self.c_name
608        return [f"if (len < {struct_sz})",
609                f"{var}->{self.c_name} = calloc(1, {struct_sz});",
610                "else",
611                f"{var}->{self.c_name} = malloc(len);",
612                f"if (!{var}->{self.c_name})",
613                "return YNL_PARSE_CB_ERROR;",
614                f"{len_mem} = len;",
615                f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \
616               ['len = ynl_attr_data_len(attr);'], \
617               ['unsigned int len;']
618
619
620class TypeBinaryScalarArray(TypeBinary):
621    def arg_member(self, ri):
622        return [f'__{self.get("sub-type")} *{self.c_name}', 'size_t count']
623
624    def presence_type(self):
625        return 'count'
626
627    def struct_member(self, ri):
628        ri.cw.p(f'__{self.get("sub-type")} *{self.c_name};')
629
630    def attr_put(self, ri, var):
631        presence = self.presence_type()
632        ri.cw.block_start(line=f"if ({var}->_{presence}.{self.c_name})")
633        ri.cw.p(f"i = {var}->_{presence}.{self.c_name} * sizeof(__{self.get('sub-type')});")
634        ri.cw.p(f"ynl_attr_put(nlh, {self.enum_name}, " +
635                f"{var}->{self.c_name}, i);")
636        ri.cw.block_end()
637
638    def _attr_get(self, ri, var):
639        len_mem = var + '->_count.' + self.c_name
640        return [f"len = (len / sizeof(__{self.get('sub-type')})) * sizeof(__{self.get('sub-type')});",
641                f"{var}->{self.c_name} = malloc(len);",
642                f"if (!{var}->{self.c_name})",
643                "return YNL_PARSE_CB_ERROR;",
644                f"{len_mem} = len / sizeof(__{self.get('sub-type')});",
645                f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \
646               ['len = ynl_attr_data_len(attr);'], \
647               ['unsigned int len;']
648
649    def _setter_lines(self, ri, member, presence):
650        return [f"{presence} = count;",
651                f"count *= sizeof(__{self.get('sub-type')});",
652                f"{member} = malloc(count);",
653                f'memcpy({member}, {self.c_name}, count);']
654
655
656class TypeBitfield32(Type):
657    def _complex_member_type(self, _ri):
658        return "struct nla_bitfield32"
659
660    def _attr_typol(self):
661        return '.type = YNL_PT_BITFIELD32, '
662
663    def _attr_policy(self, policy):
664        if 'enum' not in self.attr:
665            raise Exception('Enum required for bitfield32 attr')
666        enum = self.family.consts[self.attr['enum']]
667        mask = enum.get_mask(as_flags=True)
668        return f"NLA_POLICY_BITFIELD32({mask})"
669
670    def attr_put(self, ri, var):
671        line = f"ynl_attr_put(nlh, {self.enum_name}, &{var}->{self.c_name}, sizeof(struct nla_bitfield32))"
672        self._attr_put_line(ri, var, line)
673
674    def _attr_get(self, ri, var):
675        return f"memcpy(&{var}->{self.c_name}, ynl_attr_data(attr), sizeof(struct nla_bitfield32));", None, None
676
677    def _setter_lines(self, ri, member, presence):
678        return [f"memcpy(&{member}, {self.c_name}, sizeof(struct nla_bitfield32));"]
679
680
681class TypeNest(Type):
682    def is_recursive(self):
683        return self.family.pure_nested_structs[self.nested_attrs].recursive
684
685    def _complex_member_type(self, _ri):
686        return self.nested_struct_type
687
688    def _free_lines(self, ri, var, ref):
689        lines = []
690        at = '&'
691        if self.is_recursive_for_op(ri):
692            at = ''
693            lines += [f'if ({var}->{ref}{self.c_name})']
694        lines += [f'{self.nested_render_name}_free({at}{var}->{ref}{self.c_name});']
695        return lines
696
697    def _attr_typol(self):
698        return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, '
699
700    def _attr_policy(self, policy):
701        return 'NLA_POLICY_NESTED(' + self.nested_render_name + '_nl_policy)'
702
703    def attr_put(self, ri, var):
704        at = '' if self.is_recursive_for_op(ri) else '&'
705        self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " +
706                            f"{self.enum_name}, {at}{var}->{self.c_name})")
707
708    def _attr_get(self, ri, var):
709        pns = self.family.pure_nested_structs[self.nested_attrs]
710        args = ["&parg", "attr"]
711        for sel in pns.external_selectors():
712            args.append(f'{var}->{sel.name}')
713        get_lines = [f"if ({self.nested_render_name}_parse({', '.join(args)}))",
714                     "return YNL_PARSE_CB_ERROR;"]
715        init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;",
716                      f"parg.data = &{var}->{self.c_name};"]
717        return get_lines, init_lines, None
718
719    def setter(self, ri, _space, direction, deref=False, ref=None, var="req"):
720        ref = (ref if ref else []) + [self.c_name]
721
722        for _, attr in ri.family.pure_nested_structs[self.nested_attrs].member_list():
723            if attr.is_recursive():
724                continue
725            attr.setter(ri, self.nested_attrs, direction, deref=deref, ref=ref,
726                        var=var)
727
728
729class TypeMultiAttr(Type):
730    def __init__(self, family, attr_set, attr, value, base_type):
731        super().__init__(family, attr_set, attr, value)
732
733        self.base_type = base_type
734
735    def is_multi_val(self):
736        return True
737
738    def presence_type(self):
739        return 'count'
740
741    def _complex_member_type(self, ri):
742        if 'type' not in self.attr or self.attr['type'] == 'nest':
743            return self.nested_struct_type
744        if self.attr['type'] == 'binary' and 'struct' in self.attr:
745            return None  # use arg_member()
746        if self.attr['type'] == 'string':
747            return 'struct ynl_string *'
748        if self.attr['type'] in scalars:
749            scalar_pfx = '__' if ri.ku_space == 'user' else ''
750            if self.is_auto_scalar:
751                name = self.type[0] + '64'
752            else:
753                name = self.attr['type']
754            return scalar_pfx + name
755        raise Exception(f"Sub-type {self.attr['type']} not supported yet")
756
757    def arg_member(self, ri):
758        if self.type == 'binary' and 'struct' in self.attr:
759            return [f'struct {c_lower(self.attr["struct"])} *{self.c_name}',
760                    f'unsigned int n_{self.c_name}']
761        return super().arg_member(ri)
762
763    def free_needs_iter(self):
764        return self.attr['type'] in {'nest', 'string'}
765
766    def _free_lines(self, _ri, var, ref):
767        lines = []
768        if self.attr['type'] in scalars:
769            lines += [f"free({var}->{ref}{self.c_name});"]
770        elif self.attr['type'] == 'binary':
771            lines += [f"free({var}->{ref}{self.c_name});"]
772        elif self.attr['type'] == 'string':
773            lines += [
774                f"for (i = 0; i < {var}->{ref}_count.{self.c_name}; i++)",
775                f"free({var}->{ref}{self.c_name}[i]);",
776                f"free({var}->{ref}{self.c_name});",
777            ]
778        elif 'type' not in self.attr or self.attr['type'] == 'nest':
779            lines += [
780                f"for (i = 0; i < {var}->{ref}_count.{self.c_name}; i++)",
781                f'{self.nested_render_name}_free(&{var}->{ref}{self.c_name}[i]);',
782                f"free({var}->{ref}{self.c_name});",
783            ]
784        else:
785            raise Exception(f"Free of MultiAttr sub-type {self.attr['type']} not supported yet")
786        return lines
787
788    def _attr_policy(self, policy):
789        return self.base_type._attr_policy(policy)
790
791    def _attr_typol(self):
792        return self.base_type._attr_typol()
793
794    def _attr_get(self, ri, var):
795        return f'n_{self.c_name}++;', None, None
796
797    def attr_put(self, ri, var):
798        if self.attr['type'] in scalars:
799            put_type = self.type
800            ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")
801            ri.cw.p(f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name}[i]);")
802        elif self.attr['type'] == 'binary' and 'struct' in self.attr:
803            ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")
804            ri.cw.p(f"ynl_attr_put(nlh, {self.enum_name}, &{var}->{self.c_name}[i], sizeof(struct {c_lower(self.attr['struct'])}));")
805        elif self.attr['type'] == 'string':
806            ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")
807            ri.cw.p(f"ynl_attr_put_str(nlh, {self.enum_name}, {var}->{self.c_name}[i]->str);")
808        elif 'type' not in self.attr or self.attr['type'] == 'nest':
809            ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")
810            self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " +
811                                f"{self.enum_name}, &{var}->{self.c_name}[i])")
812        else:
813            raise Exception(f"Put of MultiAttr sub-type {self.attr['type']} not supported yet")
814
815    def _setter_lines(self, ri, member, presence):
816        return [f"{member} = {self.c_name};",
817                f"{presence} = n_{self.c_name};"]
818
819
820class TypeIndexedArray(Type):
821    def is_multi_val(self):
822        return True
823
824    def presence_type(self):
825        return 'count'
826
827    def _complex_member_type(self, ri):
828        if 'sub-type' not in self.attr or self.attr['sub-type'] == 'nest':
829            return self.nested_struct_type
830        if self.attr['sub-type'] in scalars:
831            scalar_pfx = '__' if ri.ku_space == 'user' else ''
832            return scalar_pfx + self.attr['sub-type']
833        if self.attr['sub-type'] == 'binary' and 'exact-len' in self.checks:
834            return None  # use arg_member()
835        raise Exception(f"Sub-type {self.attr['sub-type']} not supported yet")
836
837    def arg_member(self, ri):
838        if self.sub_type == 'binary' and 'exact-len' in self.checks:
839            return [f'unsigned char (*{self.c_name})[{self.checks["exact-len"]}]',
840                    f'unsigned int n_{self.c_name}']
841        return super().arg_member(ri)
842
843    def _attr_policy(self, policy):
844        if self.attr['sub-type'] == 'nest':
845            return f'NLA_POLICY_NESTED_ARRAY({self.nested_render_name}_nl_policy)'
846        return super()._attr_policy(policy)
847
848    def _attr_typol(self):
849        if self.attr['sub-type'] in scalars:
850            return f'.type = YNL_PT_U{c_upper(self.sub_type[1:])}, '
851        if self.attr['sub-type'] == 'binary' and 'exact-len' in self.checks:
852            return f'.type = YNL_PT_BINARY, .len = {self.checks["exact-len"]}, '
853        if self.attr['sub-type'] == 'nest':
854            return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, '
855        raise Exception(f"Typol for IndexedArray sub-type {self.attr['sub-type']} not supported, yet")
856
857    def _attr_get(self, ri, var):
858        local_vars = ['const struct nlattr *attr2;']
859        get_lines = [f'attr_{self.c_name} = attr;',
860                     'ynl_attr_for_each_nested(attr2, attr) {',
861                     '\tif (__ynl_attr_validate(yarg, attr2, type))',
862                     '\t\treturn YNL_PARSE_CB_ERROR;',
863                     f'\tn_{self.c_name}++;',
864                     '}']
865        return get_lines, None, local_vars
866
867    def attr_put(self, ri, var):
868        ri.cw.p(f'array = ynl_attr_nest_start(nlh, {self.enum_name});')
869        if self.sub_type in scalars:
870            put_type = self.sub_type
871            ri.cw.block_start(line=f'for (i = 0; i < {var}->_count.{self.c_name}; i++)')
872            ri.cw.p(f"ynl_attr_put_{put_type}(nlh, i, {var}->{self.c_name}[i]);")
873            ri.cw.block_end()
874        elif self.sub_type == 'binary' and 'exact-len' in self.checks:
875            ri.cw.p(f'for (i = 0; i < {var}->_count.{self.c_name}; i++)')
876            ri.cw.p(f"ynl_attr_put(nlh, i, {var}->{self.c_name}[i], {self.checks['exact-len']});")
877        elif self.sub_type == 'nest':
878            ri.cw.p(f'for (i = 0; i < {var}->_count.{self.c_name}; i++)')
879            ri.cw.p(f"{self.nested_render_name}_put(nlh, i, &{var}->{self.c_name}[i]);")
880        else:
881            raise Exception(f"Put for IndexedArray sub-type {self.attr['sub-type']} not supported, yet")
882        ri.cw.p('ynl_attr_nest_end(nlh, array);')
883
884    def _setter_lines(self, ri, member, presence):
885        return [f"{member} = {self.c_name};",
886                f"{presence} = n_{self.c_name};"]
887
888    def free_needs_iter(self):
889        return self.sub_type == 'nest'
890
891    def _free_lines(self, _ri, var, ref):
892        lines = []
893        if self.sub_type == 'nest':
894            lines += [
895                f"for (i = 0; i < {var}->{ref}_count.{self.c_name}; i++)",
896                f'{self.nested_render_name}_free(&{var}->{ref}{self.c_name}[i]);',
897            ]
898        lines += (f"free({var}->{ref}{self.c_name});",)
899        return lines
900
901class TypeNestTypeValue(Type):
902    def _complex_member_type(self, _ri):
903        return self.nested_struct_type
904
905    def _attr_typol(self):
906        return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, '
907
908    def _attr_get(self, ri, var):
909        prev = 'attr'
910        tv_args = ''
911        get_lines = []
912        local_vars = []
913        init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;",
914                      f"parg.data = &{var}->{self.c_name};"]
915        if 'type-value' in self.attr:
916            tv_names = [c_lower(x) for x in self.attr["type-value"]]
917            local_vars += [f'const struct nlattr *attr_{", *attr_".join(tv_names)};']
918            local_vars += [f'__u32 {", ".join(tv_names)};']
919            for level in self.attr["type-value"]:
920                level = c_lower(level)
921                get_lines += [f'attr_{level} = ynl_attr_data({prev});']
922                get_lines += [f'{level} = ynl_attr_type(attr_{level});']
923                prev = 'attr_' + level
924
925            tv_args = f", {', '.join(tv_names)}"
926
927        get_lines += [f"{self.nested_render_name}_parse(&parg, {prev}{tv_args});"]
928        return get_lines, init_lines, local_vars
929
930
931class TypeSubMessage(TypeNest):
932    def __init__(self, family, attr_set, attr, value):
933        super().__init__(family, attr_set, attr, value)
934
935        self.selector = Selector(attr, attr_set)
936
937    def _attr_typol(self):
938        typol = f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, '
939        typol += '.is_submsg = 1, '
940        # Reverse-parsing of the policy (ynl_err_walk() in ynl.c) does not
941        # support external selectors. No family uses sub-messages with external
942        # selector for requests so this is fine for now.
943        if not self.selector.is_external():
944            typol += f'.selector_type = {self.attr_set[self["selector"]].value} '
945        return typol
946
947    def _attr_get(self, ri, var):
948        selector = self['selector']
949        sel = c_lower(selector)
950        if self.selector.is_external():
951            sel_var = f"_sel_{sel}"
952        else:
953            sel_var = f"{var}->{sel}"
954        get_lines = [f'if (!{sel_var})',
955                     f'return ynl_submsg_failed(yarg, "{self.name}", "{selector}");',
956                     f"if ({self.nested_render_name}_parse(&parg, {sel_var}, attr))",
957                     "return YNL_PARSE_CB_ERROR;"]
958        init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;",
959                      f"parg.data = &{var}->{self.c_name};"]
960        return get_lines, init_lines, None
961
962
963class Selector:
964    def __init__(self, msg_attr, attr_set):
965        self.name = msg_attr["selector"]
966
967        if self.name in attr_set:
968            self.attr = attr_set[self.name]
969            self.attr.is_selector = True
970            self._external = False
971        else:
972            # The selector will need to get passed down thru the structs
973            self.attr = None
974            self._external = True
975
976    def set_attr(self, attr):
977        self.attr = attr
978
979    def is_external(self):
980        return self._external
981
982
983class Struct:
984    def __init__(self, family, space_name, type_list=None, fixed_header=None,
985                 inherited=None, submsg=None):
986        self.family = family
987        self.space_name = space_name
988        self.attr_set = family.attr_sets[space_name]
989        # Use list to catch comparisons with empty sets
990        self._inherited = inherited if inherited is not None else []
991        self.inherited = []
992        self.fixed_header = None
993        if fixed_header:
994            self.fixed_header = 'struct ' + c_lower(fixed_header)
995        self.submsg = submsg
996
997        self.nested = type_list is None
998        if family.name == c_lower(space_name):
999            self.render_name = c_lower(family.ident_name)
1000        else:
1001            self.render_name = c_lower(family.ident_name + '-' + space_name)
1002        self.struct_name = 'struct ' + self.render_name
1003        if self.nested and space_name in family.consts:
1004            self.struct_name += '_'
1005        self.ptr_name = self.struct_name + ' *'
1006        # All attr sets this one contains, directly or multiple levels down
1007        self.child_nests = set()
1008
1009        self.request = False
1010        self.reply = False
1011        self.recursive = False
1012        self.in_multi_val = False  # used by a MultiAttr or and legacy arrays
1013
1014        self.attr_list = []
1015        self.attrs = {}
1016        if type_list is not None:
1017            for t in type_list:
1018                self.attr_list.append((t, self.attr_set[t]),)
1019        else:
1020            for t in self.attr_set:
1021                self.attr_list.append((t, self.attr_set[t]),)
1022
1023        max_val = 0
1024        self.attr_max_val = None
1025        for name, attr in self.attr_list:
1026            if attr.value >= max_val:
1027                max_val = attr.value
1028                self.attr_max_val = attr
1029            self.attrs[name] = attr
1030
1031    def __iter__(self):
1032        yield from self.attrs
1033
1034    def __getitem__(self, key):
1035        return self.attrs[key]
1036
1037    def member_list(self):
1038        return self.attr_list
1039
1040    def set_inherited(self, new_inherited):
1041        if self._inherited != new_inherited:
1042            raise Exception("Inheriting different members not supported")
1043        self.inherited = [c_lower(x) for x in sorted(self._inherited)]
1044
1045    def external_selectors(self):
1046        sels = []
1047        for _name, attr in self.attr_list:
1048            if isinstance(attr, TypeSubMessage) and attr.selector.is_external():
1049                sels.append(attr.selector)
1050        return sels
1051
1052    def free_needs_iter(self):
1053        for _, attr in self.attr_list:
1054            if attr.free_needs_iter():
1055                return True
1056        return False
1057
1058
1059class EnumEntry(SpecEnumEntry):
1060    def __init__(self, enum_set, yaml, prev, value_start):
1061        super().__init__(enum_set, yaml, prev, value_start)
1062
1063        if prev:
1064            self.value_change = self.value != prev.value + 1
1065        else:
1066            self.value_change = self.value != 0
1067        self.value_change = self.value_change or self.enum_set['type'] == 'flags'
1068
1069        # Added by resolve:
1070        self.c_name = None
1071        delattr(self, "c_name")
1072
1073    def resolve(self):
1074        self.resolve_up(super())
1075
1076        self.c_name = c_upper(self.enum_set.value_pfx + self.name)
1077
1078
1079class EnumSet(SpecEnumSet):
1080    def __init__(self, family, yaml):
1081        self.render_name = c_lower(family.ident_name + '-' + yaml['name'])
1082
1083        if 'enum-name' in yaml:
1084            if yaml['enum-name']:
1085                self.enum_name = 'enum ' + c_lower(yaml['enum-name'])
1086                self.user_type = self.enum_name
1087            else:
1088                self.enum_name = None
1089        else:
1090            self.enum_name = 'enum ' + self.render_name
1091
1092        if self.enum_name:
1093            self.user_type = self.enum_name
1094        else:
1095            self.user_type = 'int'
1096
1097        self.value_pfx = yaml.get('name-prefix', f"{family.ident_name}-{yaml['name']}-")
1098        self.header = yaml.get('header', None)
1099        self.enum_cnt_name = yaml.get('enum-cnt-name', None)
1100
1101        super().__init__(family, yaml)
1102
1103    def new_entry(self, entry, prev_entry, value_start):
1104        return EnumEntry(self, entry, prev_entry, value_start)
1105
1106    def value_range(self):
1107        low = min(x.value for x in self.entries.values())
1108        high = max(x.value for x in self.entries.values())
1109
1110        if high - low + 1 != len(self.entries):
1111            return None, None
1112
1113        return low, high
1114
1115
1116class AttrSet(SpecAttrSet):
1117    def __init__(self, family, yaml):
1118        super().__init__(family, yaml)
1119
1120        if self.subset_of is None:
1121            if 'name-prefix' in yaml:
1122                pfx = yaml['name-prefix']
1123            elif self.name == family.name:
1124                pfx = family.ident_name + '-a-'
1125            else:
1126                pfx = f"{family.ident_name}-a-{self.name}-"
1127            self.name_prefix = c_upper(pfx)
1128            self.max_name = c_upper(self.yaml.get('attr-max-name', f"{self.name_prefix}max"))
1129            self.cnt_name = c_upper(self.yaml.get('attr-cnt-name', f"__{self.name_prefix}max"))
1130        else:
1131            self.name_prefix = family.attr_sets[self.subset_of].name_prefix
1132            self.max_name = family.attr_sets[self.subset_of].max_name
1133            self.cnt_name = family.attr_sets[self.subset_of].cnt_name
1134
1135        # Added by resolve:
1136        self.c_name = None
1137        delattr(self, "c_name")
1138
1139    def resolve(self):
1140        self.c_name = c_lower(self.name)
1141        if self.c_name in _C_KW:
1142            self.c_name += '_'
1143        if self.c_name == self.family.c_name:
1144            self.c_name = ''
1145
1146    def new_attr(self, elem, value):
1147        if elem['type'] in scalars:
1148            t = TypeScalar(self.family, self, elem, value)
1149        elif elem['type'] == 'unused':
1150            t = TypeUnused(self.family, self, elem, value)
1151        elif elem['type'] == 'pad':
1152            t = TypePad(self.family, self, elem, value)
1153        elif elem['type'] == 'flag':
1154            t = TypeFlag(self.family, self, elem, value)
1155        elif elem['type'] == 'string':
1156            t = TypeString(self.family, self, elem, value)
1157        elif elem['type'] == 'binary':
1158            if 'struct' in elem:
1159                t = TypeBinaryStruct(self.family, self, elem, value)
1160            elif elem.get('sub-type') in scalars:
1161                t = TypeBinaryScalarArray(self.family, self, elem, value)
1162            else:
1163                t = TypeBinary(self.family, self, elem, value)
1164        elif elem['type'] == 'bitfield32':
1165            t = TypeBitfield32(self.family, self, elem, value)
1166        elif elem['type'] == 'nest':
1167            t = TypeNest(self.family, self, elem, value)
1168        elif elem['type'] == 'indexed-array' and 'sub-type' in elem:
1169            if elem["sub-type"] in ['binary', 'nest', 'u32']:
1170                t = TypeIndexedArray(self.family, self, elem, value)
1171            else:
1172                raise Exception(f'new_attr: unsupported sub-type {elem["sub-type"]}')
1173        elif elem['type'] == 'nest-type-value':
1174            t = TypeNestTypeValue(self.family, self, elem, value)
1175        elif elem['type'] == 'sub-message':
1176            t = TypeSubMessage(self.family, self, elem, value)
1177        else:
1178            raise Exception(f"No typed class for type {elem['type']}")
1179
1180        if 'multi-attr' in elem and elem['multi-attr']:
1181            t = TypeMultiAttr(self.family, self, elem, value, t)
1182
1183        return t
1184
1185
1186class Operation(SpecOperation):
1187    def __init__(self, family, yaml, req_value, rsp_value):
1188        # Fill in missing operation properties (for fixed hdr-only msgs)
1189        for mode in ['do', 'dump', 'event']:
1190            for direction in ['request', 'reply']:
1191                try:
1192                    yaml[mode][direction].setdefault('attributes', [])
1193                except KeyError:
1194                    pass
1195
1196        super().__init__(family, yaml, req_value, rsp_value)
1197
1198        self.render_name = c_lower(family.ident_name + '_' + self.name)
1199
1200        self.dual_policy = ('do' in yaml and 'request' in yaml['do']) and \
1201                         ('dump' in yaml and 'request' in yaml['dump'])
1202
1203        self.has_ntf = False
1204
1205        # Added by resolve:
1206        self.enum_name = None
1207        delattr(self, "enum_name")
1208
1209    def resolve(self):
1210        self.resolve_up(super())
1211
1212        if not self.is_async:
1213            self.enum_name = self.family.op_prefix + c_upper(self.name)
1214        else:
1215            self.enum_name = self.family.async_op_prefix + c_upper(self.name)
1216
1217    def mark_has_ntf(self):
1218        self.has_ntf = True
1219
1220
1221class SubMessage(SpecSubMessage):
1222    def __init__(self, family, yaml):
1223        super().__init__(family, yaml)
1224
1225        self.render_name = c_lower(family.ident_name + '-' + yaml['name'])
1226
1227    def resolve(self):
1228        self.resolve_up(super())
1229
1230
1231class Family(SpecFamily):
1232    def __init__(self, file_name, exclude_ops, fn_prefix):
1233        # Added by resolve:
1234        self.c_name = None
1235        delattr(self, "c_name")
1236        self.op_prefix = None
1237        delattr(self, "op_prefix")
1238        self.async_op_prefix = None
1239        delattr(self, "async_op_prefix")
1240        self.mcgrps = None
1241        delattr(self, "mcgrps")
1242        self.consts = None
1243        delattr(self, "consts")
1244        self.hooks = None
1245        delattr(self, "hooks")
1246
1247        self.root_sets = {}
1248        self.pure_nested_structs = {}
1249        self.kernel_policy = None
1250        self.global_policy = None
1251        self.global_policy_set = None
1252
1253        super().__init__(file_name, exclude_ops=exclude_ops)
1254
1255        self.fam_key = c_upper(self.yaml.get('c-family-name', self.yaml["name"] + '_FAMILY_NAME'))
1256        self.ver_key = c_upper(self.yaml.get('c-version-name', self.yaml["name"] + '_FAMILY_VERSION'))
1257
1258        if 'definitions' not in self.yaml:
1259            self.yaml['definitions'] = []
1260
1261        if 'uapi-header' in self.yaml:
1262            self.uapi_header = self.yaml['uapi-header']
1263        else:
1264            self.uapi_header = f"linux/{self.ident_name}.h"
1265        if self.uapi_header.startswith("linux/") and self.uapi_header.endswith('.h'):
1266            self.uapi_header_name = self.uapi_header[6:-2]
1267        else:
1268            self.uapi_header_name = self.ident_name
1269
1270        self.fn_prefix = fn_prefix if fn_prefix else f'{self.ident_name}-nl'
1271
1272    def resolve(self):
1273        self.resolve_up(super())
1274
1275        self.c_name = c_lower(self.ident_name)
1276        if 'name-prefix' in self.yaml['operations']:
1277            self.op_prefix = c_upper(self.yaml['operations']['name-prefix'])
1278        else:
1279            self.op_prefix = c_upper(self.yaml['name'] + '-cmd-')
1280        if 'async-prefix' in self.yaml['operations']:
1281            self.async_op_prefix = c_upper(self.yaml['operations']['async-prefix'])
1282        else:
1283            self.async_op_prefix = self.op_prefix
1284
1285        self.mcgrps = self.yaml.get('mcast-groups', {'list': []})
1286
1287        self.hooks = {}
1288        for when in ['pre', 'post']:
1289            self.hooks[when] = {}
1290            for op_mode in ['do', 'dump']:
1291                self.hooks[when][op_mode] = {}
1292                self.hooks[when][op_mode]['set'] = set()
1293                self.hooks[when][op_mode]['list'] = []
1294
1295        # dict space-name -> 'request': set(attrs), 'reply': set(attrs)
1296        self.root_sets = {}
1297        # dict space-name -> Struct
1298        self.pure_nested_structs = {}
1299
1300        self._mark_notify()
1301        self._mock_up_events()
1302
1303        self._load_root_sets()
1304        self._load_nested_sets()
1305        self._load_attr_use()
1306        self._load_selector_passing()
1307        self._load_hooks()
1308
1309        self.kernel_policy = self.yaml.get('kernel-policy', 'split')
1310        if self.kernel_policy == 'global':
1311            self._load_global_policy()
1312
1313    def new_enum(self, elem):
1314        return EnumSet(self, elem)
1315
1316    def new_attr_set(self, elem):
1317        return AttrSet(self, elem)
1318
1319    def new_operation(self, elem, req_value, rsp_value):
1320        return Operation(self, elem, req_value, rsp_value)
1321
1322    def new_sub_message(self, elem):
1323        return SubMessage(self, elem)
1324
1325    def is_classic(self):
1326        return self.proto == 'netlink-raw'
1327
1328    def _mark_notify(self):
1329        for op in self.msgs.values():
1330            if 'notify' in op:
1331                self.ops[op['notify']].mark_has_ntf()
1332
1333    # Fake a 'do' equivalent of all events, so that we can render their response parsing
1334    def _mock_up_events(self):
1335        for op in self.yaml['operations']['list']:
1336            if 'event' in op:
1337                op['do'] = {
1338                    'reply': {
1339                        'attributes': op['event']['attributes']
1340                    }
1341                }
1342
1343    def _load_root_sets(self):
1344        for _op_name, op in self.msgs.items():
1345            if 'attribute-set' not in op:
1346                continue
1347
1348            req_attrs = set()
1349            rsp_attrs = set()
1350            for op_mode in ['do', 'dump']:
1351                if op_mode in op and 'request' in op[op_mode]:
1352                    req_attrs.update(set(op[op_mode]['request']['attributes']))
1353                if op_mode in op and 'reply' in op[op_mode]:
1354                    rsp_attrs.update(set(op[op_mode]['reply']['attributes']))
1355            if 'event' in op:
1356                rsp_attrs.update(set(op['event']['attributes']))
1357
1358            if op['attribute-set'] not in self.root_sets:
1359                self.root_sets[op['attribute-set']] = {'request': req_attrs, 'reply': rsp_attrs}
1360            else:
1361                self.root_sets[op['attribute-set']]['request'].update(req_attrs)
1362                self.root_sets[op['attribute-set']]['reply'].update(rsp_attrs)
1363
1364    def _sort_pure_types(self):
1365        # Try to reorder according to dependencies
1366        pns_key_list = list(self.pure_nested_structs.keys())
1367        pns_key_seen = set()
1368        rounds = len(pns_key_list) ** 2  # it's basically bubble sort
1369        for _ in range(rounds):
1370            if len(pns_key_list) == 0:
1371                break
1372            name = pns_key_list.pop(0)
1373            finished = True
1374            for _, spec in self.attr_sets[name].items():
1375                if 'nested-attributes' in spec:
1376                    nested = spec['nested-attributes']
1377                elif 'sub-message' in spec:
1378                    nested = spec.sub_message
1379                else:
1380                    continue
1381
1382                # If the unknown nest we hit is recursive it's fine, it'll be a pointer
1383                if self.pure_nested_structs[nested].recursive:
1384                    continue
1385                if nested not in pns_key_seen:
1386                    # Dicts are sorted, this will make struct last
1387                    struct = self.pure_nested_structs.pop(name)
1388                    self.pure_nested_structs[name] = struct
1389                    finished = False
1390                    break
1391            if finished:
1392                pns_key_seen.add(name)
1393            else:
1394                pns_key_list.append(name)
1395
1396    def _load_nested_set_nest(self, spec):
1397        inherit = set()
1398        nested = spec['nested-attributes']
1399        if nested not in self.root_sets:
1400            if nested not in self.pure_nested_structs:
1401                self.pure_nested_structs[nested] = \
1402                    Struct(self, nested, inherited=inherit,
1403                           fixed_header=spec.get('fixed-header'))
1404        else:
1405            raise Exception(f'Using attr set as root and nested not supported - {nested}')
1406
1407        if 'type-value' in spec:
1408            if nested in self.root_sets:
1409                raise Exception("Inheriting members to a space used as root not supported")
1410            inherit.update(set(spec['type-value']))
1411        elif spec['type'] == 'indexed-array':
1412            inherit.add('idx')
1413        self.pure_nested_structs[nested].set_inherited(inherit)
1414
1415        return nested
1416
1417    def _load_nested_set_submsg(self, spec):
1418        # Fake the struct type for the sub-message itself
1419        # its not a attr_set but codegen wants attr_sets.
1420        submsg = self.sub_msgs[spec["sub-message"]]
1421        nested = submsg.name
1422
1423        attrs = []
1424        for name, fmt in submsg.formats.items():
1425            attr = {
1426                "name": name,
1427                "parent-sub-message": spec,
1428            }
1429            if 'attribute-set' in fmt:
1430                attr |= {
1431                    "type": "nest",
1432                    "nested-attributes": fmt['attribute-set'],
1433                }
1434                if 'fixed-header' in fmt:
1435                    attr |= { "fixed-header": fmt["fixed-header"] }
1436            elif 'fixed-header' in fmt:
1437                attr |= {
1438                    "type": "binary",
1439                    "struct": fmt["fixed-header"],
1440                }
1441            else:
1442                attr["type"] = "flag"
1443            attrs.append(attr)
1444
1445        self.attr_sets[nested] = AttrSet(self, {
1446            "name": nested,
1447            "name-pfx": self.name + '-' + spec.name + '-',
1448            "attributes": attrs
1449        })
1450
1451        if nested not in self.pure_nested_structs:
1452            self.pure_nested_structs[nested] = Struct(self, nested, submsg=submsg)
1453
1454        return nested
1455
1456    def _load_nested_sets(self):
1457        attr_set_queue = list(self.root_sets.keys())
1458        attr_set_seen = set(self.root_sets.keys())
1459
1460        while attr_set_queue:
1461            a_set = attr_set_queue.pop(0)
1462            for attr, spec in self.attr_sets[a_set].items():
1463                if 'nested-attributes' in spec:
1464                    nested = self._load_nested_set_nest(spec)
1465                elif 'sub-message' in spec:
1466                    nested = self._load_nested_set_submsg(spec)
1467                else:
1468                    continue
1469
1470                if nested not in attr_set_seen:
1471                    attr_set_queue.append(nested)
1472                    attr_set_seen.add(nested)
1473
1474        for root_set, rs_members in self.root_sets.items():
1475            for attr, spec in self.attr_sets[root_set].items():
1476                if 'nested-attributes' in spec:
1477                    nested = spec['nested-attributes']
1478                elif 'sub-message' in spec:
1479                    nested = spec.sub_message
1480                else:
1481                    nested = None
1482
1483                if nested:
1484                    if attr in rs_members['request']:
1485                        self.pure_nested_structs[nested].request = True
1486                    if attr in rs_members['reply']:
1487                        self.pure_nested_structs[nested].reply = True
1488
1489                    if spec.is_multi_val():
1490                        child = self.pure_nested_structs.get(nested)
1491                        child.in_multi_val = True
1492
1493        self._sort_pure_types()
1494
1495        # Propagate the request / reply / recursive
1496        for attr_set, struct in reversed(self.pure_nested_structs.items()):
1497            for _, spec in self.attr_sets[attr_set].items():
1498                if attr_set in struct.child_nests:
1499                    struct.recursive = True
1500
1501                if 'nested-attributes' in spec:
1502                    child_name = spec['nested-attributes']
1503                elif 'sub-message' in spec:
1504                    child_name = spec.sub_message
1505                else:
1506                    continue
1507
1508                struct.child_nests.add(child_name)
1509                child = self.pure_nested_structs.get(child_name)
1510                if child:
1511                    if not child.recursive:
1512                        struct.child_nests.update(child.child_nests)
1513                    child.request |= struct.request
1514                    child.reply |= struct.reply
1515                    if spec.is_multi_val():
1516                        child.in_multi_val = True
1517
1518        self._sort_pure_types()
1519
1520    def _load_attr_use(self):
1521        for _, struct in self.pure_nested_structs.items():
1522            if struct.request:
1523                for _, arg in struct.member_list():
1524                    arg.set_request()
1525            if struct.reply:
1526                for _, arg in struct.member_list():
1527                    arg.set_reply()
1528
1529        for root_set, rs_members in self.root_sets.items():
1530            for attr, spec in self.attr_sets[root_set].items():
1531                if attr in rs_members['request']:
1532                    spec.set_request()
1533                if attr in rs_members['reply']:
1534                    spec.set_reply()
1535
1536    def _load_selector_passing(self):
1537        def all_structs():
1538            for k, v in reversed(self.pure_nested_structs.items()):
1539                yield k, v
1540            for k, _ in self.root_sets.items():
1541                yield k, None  # we don't have a struct, but it must be terminal
1542
1543        for attr_set, _struct in all_structs():
1544            for _, spec in self.attr_sets[attr_set].items():
1545                if 'nested-attributes' in spec:
1546                    child_name = spec['nested-attributes']
1547                elif 'sub-message' in spec:
1548                    child_name = spec.sub_message
1549                else:
1550                    continue
1551
1552                child = self.pure_nested_structs.get(child_name)
1553                for selector in child.external_selectors():
1554                    if selector.name in self.attr_sets[attr_set]:
1555                        sel_attr = self.attr_sets[attr_set][selector.name]
1556                        selector.set_attr(sel_attr)
1557                    else:
1558                        raise Exception("Passing selector thru more than one layer not supported")
1559
1560    def _load_global_policy(self):
1561        global_set = set()
1562        attr_set_name = None
1563        for _op_name, op in self.ops.items():
1564            if not op:
1565                continue
1566            if 'attribute-set' not in op:
1567                continue
1568
1569            if attr_set_name is None:
1570                attr_set_name = op['attribute-set']
1571            if attr_set_name != op['attribute-set']:
1572                raise Exception('For a global policy all ops must use the same set')
1573
1574            for op_mode in ['do', 'dump']:
1575                if op_mode in op:
1576                    req = op[op_mode].get('request')
1577                    if req:
1578                        global_set.update(req.get('attributes', []))
1579
1580        self.global_policy = []
1581        self.global_policy_set = attr_set_name
1582        for attr in self.attr_sets[attr_set_name]:
1583            if attr in global_set:
1584                self.global_policy.append(attr)
1585
1586    def _load_hooks(self):
1587        for op in self.ops.values():
1588            for op_mode in ['do', 'dump']:
1589                if op_mode not in op:
1590                    continue
1591                for when in ['pre', 'post']:
1592                    if when not in op[op_mode]:
1593                        continue
1594                    name = op[op_mode][when]
1595                    if name in self.hooks[when][op_mode]['set']:
1596                        continue
1597                    self.hooks[when][op_mode]['set'].add(name)
1598                    self.hooks[when][op_mode]['list'].append(name)
1599
1600
1601class RenderInfo:
1602    def __init__(self, cw, family, ku_space, op, op_mode, attr_set=None):
1603        self.family = family
1604        self.nl = cw.nlib
1605        self.ku_space = ku_space
1606        self.op_mode = op_mode
1607        self.op = op
1608
1609        fixed_hdr = op.fixed_header if op else None
1610        self.fixed_hdr_len = 'ys->family->hdr_len'
1611        if op and op.fixed_header:
1612            if op.fixed_header != family.fixed_header:
1613                if family.is_classic():
1614                    self.fixed_hdr_len = f"sizeof(struct {c_lower(fixed_hdr)})"
1615                else:
1616                    raise Exception("Per-op fixed header not supported, yet")
1617
1618
1619        # 'do' and 'dump' response parsing is identical
1620        self.type_consistent = True
1621        self.type_oneside = False
1622        if op_mode != 'do' and 'dump' in op:
1623            if 'do' in op:
1624                if ('reply' in op['do']) != ('reply' in op["dump"]):
1625                    self.type_consistent = False
1626                elif 'reply' in op['do'] and op["do"]["reply"] != op["dump"]["reply"]:
1627                    self.type_consistent = False
1628            else:
1629                self.type_consistent = True
1630                self.type_oneside = True
1631
1632        self.attr_set = attr_set
1633        if not self.attr_set:
1634            self.attr_set = op['attribute-set']
1635
1636        self.type_name_conflict = False
1637        if op:
1638            self.type_name = c_lower(op.name)
1639        else:
1640            self.type_name = c_lower(attr_set)
1641            if attr_set in family.consts:
1642                self.type_name_conflict = True
1643
1644        self.cw = cw
1645
1646        self.struct = {}
1647        if op_mode == 'notify':
1648            op_mode = 'do' if 'do' in op else 'dump'
1649        for op_dir in ['request', 'reply']:
1650            if op:
1651                type_list = []
1652                if op_dir in op[op_mode]:
1653                    type_list = op[op_mode][op_dir]['attributes']
1654                self.struct[op_dir] = Struct(family, self.attr_set,
1655                                             fixed_header=fixed_hdr,
1656                                             type_list=type_list)
1657        if op_mode == 'event':
1658            self.struct['reply'] = Struct(family, self.attr_set,
1659                                          fixed_header=fixed_hdr,
1660                                          type_list=op['event']['attributes'])
1661
1662    def type_empty(self, key):
1663        return len(self.struct[key].attr_list) == 0 and \
1664            self.struct['request'].fixed_header is None
1665
1666    def needs_nlflags(self, direction):
1667        return self.op_mode == 'do' and direction == 'request' and self.family.is_classic()
1668
1669
1670class CodeWriter:
1671    def __init__(self, nlib, out_file=None, overwrite=True):
1672        self.nlib = nlib
1673        self._overwrite = overwrite
1674
1675        self._nl = False
1676        self._block_end = False
1677        self._silent_block = False
1678        self._ind = 0
1679        self._ifdef_block = None
1680        if out_file is None:
1681            self._out = os.sys.stdout
1682        else:
1683            # pylint: disable=consider-using-with
1684            self._out = tempfile.NamedTemporaryFile('w+')
1685            self._out_file = out_file
1686
1687    def __del__(self):
1688        self.close_out_file()
1689
1690    def close_out_file(self):
1691        if self._out == os.sys.stdout:
1692            return
1693        # Avoid modifying the file if contents didn't change
1694        self._out.flush()
1695        if not self._overwrite and os.path.isfile(self._out_file):
1696            if filecmp.cmp(self._out.name, self._out_file, shallow=False):
1697                return
1698        with open(self._out_file, 'w+', encoding='utf-8') as out_file:
1699            self._out.seek(0)
1700            shutil.copyfileobj(self._out, out_file)
1701            self._out.close()
1702        self._out = os.sys.stdout
1703
1704    @classmethod
1705    def _is_cond(cls, line):
1706        return line.startswith('if') or line.startswith('while') or line.startswith('for')
1707
1708    def p(self, line, add_ind=0):
1709        if self._block_end:
1710            self._block_end = False
1711            if line.startswith('else'):
1712                line = '} ' + line
1713            else:
1714                self._out.write('\t' * self._ind + '}\n')
1715
1716        if self._nl:
1717            self._out.write('\n')
1718            self._nl = False
1719
1720        ind = self._ind
1721        if line[-1] == ':':
1722            ind -= 1
1723        if self._silent_block:
1724            ind += 1
1725        self._silent_block = line.endswith(')') and CodeWriter._is_cond(line)
1726        self._silent_block |= line.strip() == 'else'
1727        if line[0] == '#':
1728            ind = 0
1729        if add_ind:
1730            ind += add_ind
1731        self._out.write('\t' * ind + line + '\n')
1732
1733    def nl(self):
1734        self._nl = True
1735
1736    def block_start(self, line=''):
1737        if line:
1738            line = line + ' '
1739        self.p(line + '{')
1740        self._ind += 1
1741
1742    def block_end(self, line=''):
1743        if line and line[0] not in {';', ','}:
1744            line = ' ' + line
1745        self._ind -= 1
1746        self._nl = False
1747        if not line:
1748            # Delay printing closing bracket in case "else" comes next
1749            if self._block_end:
1750                self._out.write('\t' * (self._ind + 1) + '}\n')
1751            self._block_end = True
1752        else:
1753            self.p('}' + line)
1754
1755    def write_doc_line(self, doc, indent=True):
1756        words = doc.split()
1757        line = ' *'
1758        for word in words:
1759            if len(line) + len(word) >= 79:
1760                self.p(line)
1761                line = ' *'
1762                if indent:
1763                    line += '  '
1764            line += ' ' + word
1765        self.p(line)
1766
1767    def write_func_prot(self, qual_ret, name, args=None, doc=None, suffix=''):
1768        if not args:
1769            args = ['void']
1770
1771        if doc:
1772            self.p('/*')
1773            self.p(' * ' + doc)
1774            self.p(' */')
1775
1776        oneline = qual_ret
1777        if qual_ret[-1] != '*':
1778            oneline += ' '
1779        oneline += f"{name}({', '.join(args)}){suffix}"
1780
1781        if len(oneline) < 80:
1782            self.p(oneline)
1783            return
1784
1785        v = qual_ret
1786        if len(v) > 3:
1787            self.p(v)
1788            v = ''
1789        elif qual_ret[-1] != '*':
1790            v += ' '
1791        v += name + '('
1792        ind = '\t' * (len(v) // 8) + ' ' * (len(v) % 8)
1793        delta_ind = len(v) - len(ind)
1794        v += args[0]
1795        i = 1
1796        while i < len(args):
1797            next_len = len(v) + len(args[i])
1798            if v[0] == '\t':
1799                next_len += delta_ind
1800            if next_len > 76:
1801                self.p(v + ',')
1802                v = ind
1803            else:
1804                v += ', '
1805            v += args[i]
1806            i += 1
1807        self.p(v + ')' + suffix)
1808
1809    def write_func_lvar(self, local_vars):
1810        if not local_vars:
1811            return
1812
1813        if isinstance(local_vars, str):
1814            local_vars = [local_vars]
1815
1816        local_vars.sort(key=len, reverse=True)
1817        for var in local_vars:
1818            self.p(var)
1819        self.nl()
1820
1821    def write_func(self, qual_ret, name, body, args=None, local_vars=None):
1822        self.write_func_prot(qual_ret=qual_ret, name=name, args=args)
1823        self.block_start()
1824        self.write_func_lvar(local_vars=local_vars)
1825
1826        for line in body:
1827            self.p(line)
1828        self.block_end()
1829
1830    def writes_defines(self, defines):
1831        longest = 0
1832        for define in defines:
1833            longest = max(len(define[0]), longest)
1834        longest = ((longest + 8) // 8) * 8
1835        for define in defines:
1836            line = '#define ' + define[0]
1837            line += '\t' * ((longest - len(define[0]) + 7) // 8)
1838            if isinstance(define[1], int):
1839                line += str(define[1])
1840            elif isinstance(define[1], str):
1841                line += '"' + define[1] + '"'
1842            self.p(line)
1843
1844    def write_struct_init(self, members):
1845        longest = max(len(x[0]) for x in members)
1846        longest += 1  # because we prepend a .
1847        longest = ((longest + 8) // 8) * 8
1848        for one in members:
1849            line = '.' + one[0]
1850            line += '\t' * ((longest - len(one[0]) - 1 + 7) // 8)
1851            line += '= ' + str(one[1]) + ','
1852            self.p(line)
1853
1854    def ifdef_block(self, config):
1855        config_option = None
1856        if config:
1857            config_option = 'CONFIG_' + c_upper(config)
1858        if self._ifdef_block == config_option:
1859            return
1860
1861        if self._ifdef_block:
1862            self.p('#endif /* ' + self._ifdef_block + ' */')
1863        if config_option:
1864            self.p('#ifdef ' + config_option)
1865        self._ifdef_block = config_option
1866
1867
1868scalars = {'u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'uint', 'sint'}
1869
1870direction_to_suffix = {
1871    'reply': '_rsp',
1872    'request': '_req',
1873    '': ''
1874}
1875
1876op_mode_to_wrapper = {
1877    'do': '',
1878    'dump': '_list',
1879    'notify': '_ntf',
1880    'event': '',
1881}
1882
1883_C_KW = {
1884    'auto',
1885    'bool',
1886    'break',
1887    'case',
1888    'char',
1889    'const',
1890    'continue',
1891    'default',
1892    'do',
1893    'double',
1894    'else',
1895    'enum',
1896    'extern',
1897    'float',
1898    'for',
1899    'goto',
1900    'if',
1901    'inline',
1902    'int',
1903    'long',
1904    'register',
1905    'return',
1906    'short',
1907    'signed',
1908    'sizeof',
1909    'static',
1910    'struct',
1911    'switch',
1912    'typedef',
1913    'union',
1914    'unsigned',
1915    'void',
1916    'volatile',
1917    'while'
1918}
1919
1920
1921def rdir(direction):
1922    if direction == 'reply':
1923        return 'request'
1924    if direction == 'request':
1925        return 'reply'
1926    return direction
1927
1928
1929def op_prefix(ri, direction, deref=False):
1930    suffix = f"_{ri.type_name}"
1931
1932    if not ri.op_mode:
1933        pass
1934    elif ri.op_mode == 'do':
1935        suffix += f"{direction_to_suffix[direction]}"
1936    else:
1937        if direction == 'request':
1938            suffix += '_req'
1939            if not ri.type_oneside:
1940                suffix += '_dump'
1941        else:
1942            if ri.type_consistent:
1943                if deref:
1944                    suffix += f"{direction_to_suffix[direction]}"
1945                else:
1946                    suffix += op_mode_to_wrapper[ri.op_mode]
1947            else:
1948                suffix += '_rsp'
1949                suffix += '_dump' if deref else '_list'
1950
1951    return f"{ri.family.c_name}{suffix}"
1952
1953
1954def type_name(ri, direction, deref=False):
1955    return f"struct {op_prefix(ri, direction, deref=deref)}"
1956
1957
1958def print_prototype(ri, direction, terminate=True, doc=None):
1959    suffix = ';' if terminate else ''
1960
1961    fname = ri.op.render_name
1962    if ri.op_mode == 'dump':
1963        fname += '_dump'
1964
1965    args = ['struct ynl_sock *ys']
1966    if 'request' in ri.op[ri.op_mode]:
1967        args.append(f"{type_name(ri, direction)} *" + f"{direction_to_suffix[direction][1:]}")
1968
1969    ret = 'int'
1970    if 'reply' in ri.op[ri.op_mode]:
1971        ret = f"{type_name(ri, rdir(direction))} *"
1972
1973    ri.cw.write_func_prot(ret, fname, args, doc=doc, suffix=suffix)
1974
1975
1976def print_req_prototype(ri):
1977    print_prototype(ri, "request", doc=ri.op['doc'])
1978
1979
1980def print_dump_prototype(ri):
1981    print_prototype(ri, "request")
1982
1983
1984def put_typol_submsg(cw, struct):
1985    cw.block_start(line=f'const struct ynl_policy_attr {struct.render_name}_policy[] =')
1986
1987    i = 0
1988    for name, arg in struct.member_list():
1989        nest = ""
1990        if arg.type == 'nest':
1991            nest = f" .nest = &{arg.nested_render_name}_nest,"
1992        cw.p('[%d] = { .type = YNL_PT_SUBMSG, .name = "%s",%s },' %
1993             (i, name, nest))
1994        i += 1
1995
1996    cw.block_end(line=';')
1997    cw.nl()
1998
1999    cw.block_start(line=f'const struct ynl_policy_nest {struct.render_name}_nest =')
2000    cw.p(f'.max_attr = {i - 1},')
2001    cw.p(f'.table = {struct.render_name}_policy,')
2002    cw.block_end(line=';')
2003    cw.nl()
2004
2005
2006def put_typol_fwd(cw, struct):
2007    cw.p(f'extern const struct ynl_policy_nest {struct.render_name}_nest;')
2008
2009
2010def put_typol(cw, struct):
2011    if struct.submsg:
2012        put_typol_submsg(cw, struct)
2013        return
2014
2015    type_max = struct.attr_set.max_name
2016    cw.block_start(line=f'const struct ynl_policy_attr {struct.render_name}_policy[{type_max} + 1] =')
2017
2018    for _, arg in struct.member_list():
2019        arg.attr_typol(cw)
2020
2021    cw.block_end(line=';')
2022    cw.nl()
2023
2024    cw.block_start(line=f'const struct ynl_policy_nest {struct.render_name}_nest =')
2025    cw.p(f'.max_attr = {type_max},')
2026    cw.p(f'.table = {struct.render_name}_policy,')
2027    cw.block_end(line=';')
2028    cw.nl()
2029
2030
2031def _put_enum_to_str_helper(cw, render_name, map_name, arg_name, enum=None):
2032    args = [f'int {arg_name}']
2033    if enum:
2034        args = [enum.user_type + ' ' + arg_name]
2035    cw.write_func_prot('const char *', f'{render_name}_str', args)
2036    cw.block_start()
2037    if enum and enum.type == 'flags':
2038        cw.p(f'{arg_name} = ffs({arg_name}) - 1;')
2039    cw.p(f'if ({arg_name} < 0 || {arg_name} >= (int)YNL_ARRAY_SIZE({map_name}))')
2040    cw.p('return NULL;')
2041    cw.p(f'return {map_name}[{arg_name}];')
2042    cw.block_end()
2043    cw.nl()
2044
2045
2046def put_op_name_fwd(family, cw):
2047    cw.write_func_prot('const char *', f'{family.c_name}_op_str', ['int op'], suffix=';')
2048
2049
2050def put_op_name(family, cw):
2051    map_name = f'{family.c_name}_op_strmap'
2052    cw.block_start(line=f"static const char * const {map_name}[] =")
2053    for op_name, op in family.msgs.items():
2054        if op.rsp_value:
2055            # Make sure we don't add duplicated entries, if multiple commands
2056            # produce the same response in legacy families.
2057            if family.rsp_by_value[op.rsp_value] != op:
2058                cw.p(f'// skip "{op_name}", duplicate reply value')
2059                continue
2060
2061            if op.req_value == op.rsp_value:
2062                cw.p(f'[{op.enum_name}] = "{op_name}",')
2063            else:
2064                cw.p(f'[{op.rsp_value}] = "{op_name}",')
2065    cw.block_end(line=';')
2066    cw.nl()
2067
2068    _put_enum_to_str_helper(cw, family.c_name + '_op', map_name, 'op')
2069
2070
2071def put_enum_to_str_fwd(_family, cw, enum):
2072    args = [enum.user_type + ' value']
2073    cw.write_func_prot('const char *', f'{enum.render_name}_str', args, suffix=';')
2074
2075
2076def put_enum_to_str(_family, cw, enum):
2077    map_name = f'{enum.render_name}_strmap'
2078    cw.block_start(line=f"static const char * const {map_name}[] =")
2079    for entry in enum.entries.values():
2080        cw.p(f'[{entry.value}] = "{entry.name}",')
2081    cw.block_end(line=';')
2082    cw.nl()
2083
2084    _put_enum_to_str_helper(cw, enum.render_name, map_name, 'value', enum=enum)
2085
2086
2087def put_local_vars(struct):
2088    local_vars = []
2089    has_array = False
2090    has_count = False
2091    for _, arg in struct.member_list():
2092        has_array |= arg.type == 'indexed-array'
2093        has_count |= arg.presence_type() == 'count'
2094    if has_array:
2095        local_vars.append('struct nlattr *array;')
2096    if has_count:
2097        local_vars.append('unsigned int i;')
2098    return local_vars
2099
2100
2101def put_req_nested_prototype(ri, struct, suffix=';'):
2102    func_args = ['struct nlmsghdr *nlh',
2103                 'unsigned int attr_type',
2104                 f'{struct.ptr_name}obj']
2105
2106    ri.cw.write_func_prot('int', f'{struct.render_name}_put', func_args,
2107                          suffix=suffix)
2108
2109
2110def put_req_nested(ri, struct):
2111    local_vars = []
2112    init_lines = []
2113
2114    if struct.submsg is None:
2115        local_vars.append('struct nlattr *nest;')
2116        init_lines.append("nest = ynl_attr_nest_start(nlh, attr_type);")
2117    if struct.fixed_header:
2118        local_vars.append('void *hdr;')
2119        struct_sz = f'sizeof({struct.fixed_header})'
2120        init_lines.append(f"hdr = ynl_nlmsg_put_extra_header(nlh, {struct_sz});")
2121        init_lines.append(f"memcpy(hdr, &obj->_hdr, {struct_sz});")
2122
2123    local_vars += put_local_vars(struct)
2124
2125    put_req_nested_prototype(ri, struct, suffix='')
2126    ri.cw.block_start()
2127    ri.cw.write_func_lvar(local_vars)
2128
2129    for line in init_lines:
2130        ri.cw.p(line)
2131
2132    for _, arg in struct.member_list():
2133        arg.attr_put(ri, "obj")
2134
2135    if struct.submsg is None:
2136        ri.cw.p("ynl_attr_nest_end(nlh, nest);")
2137
2138    ri.cw.nl()
2139    ri.cw.p('return 0;')
2140    ri.cw.block_end()
2141    ri.cw.nl()
2142
2143
2144def _multi_parse(ri, struct, init_lines, local_vars):
2145    if struct.fixed_header:
2146        local_vars += ['void *hdr;']
2147    if struct.nested:
2148        if struct.fixed_header:
2149            iter_line = f"ynl_attr_for_each_nested_off(attr, nested, sizeof({struct.fixed_header}))"
2150        else:
2151            iter_line = "ynl_attr_for_each_nested(attr, nested)"
2152    else:
2153        iter_line = "ynl_attr_for_each(attr, nlh, yarg->ys->family->hdr_len)"
2154        if ri.op.fixed_header != ri.family.fixed_header:
2155            if ri.family.is_classic():
2156                iter_line = f"ynl_attr_for_each(attr, nlh, sizeof({struct.fixed_header}))"
2157            else:
2158                raise Exception("Per-op fixed header not supported, yet")
2159
2160    indexed_arrays = set()
2161    multi_attrs = set()
2162    needs_parg = False
2163    var_set = set()
2164    for arg, aspec in struct.member_list():
2165        if aspec['type'] == 'indexed-array' and 'sub-type' in aspec:
2166            if aspec["sub-type"] in {'binary', 'nest'}:
2167                local_vars.append(f'const struct nlattr *attr_{aspec.c_name} = NULL;')
2168                indexed_arrays.add(arg)
2169            elif aspec['sub-type'] in scalars:
2170                local_vars.append(f'const struct nlattr *attr_{aspec.c_name} = NULL;')
2171                indexed_arrays.add(arg)
2172            else:
2173                raise Exception(f'Not supported sub-type {aspec["sub-type"]}')
2174        if 'multi-attr' in aspec:
2175            multi_attrs.add(arg)
2176        needs_parg |= 'nested-attributes' in aspec
2177        needs_parg |= 'sub-message' in aspec
2178
2179        try:
2180            _, _, l_vars = aspec._attr_get(ri, '')
2181            var_set |= set(l_vars) if l_vars else set()
2182        except Exception:
2183            pass  # _attr_get() not implemented by simple types, ignore
2184    local_vars += list(var_set)
2185    if indexed_arrays or multi_attrs:
2186        local_vars.append('int i;')
2187    if needs_parg:
2188        local_vars.append('struct ynl_parse_arg parg;')
2189        init_lines.append('parg.ys = yarg->ys;')
2190
2191    all_multi = indexed_arrays | multi_attrs
2192
2193    for arg in sorted(all_multi):
2194        local_vars.append(f"unsigned int n_{struct[arg].c_name} = 0;")
2195
2196    ri.cw.block_start()
2197    ri.cw.write_func_lvar(local_vars)
2198
2199    for line in init_lines:
2200        ri.cw.p(line)
2201    ri.cw.nl()
2202
2203    for arg in struct.inherited:
2204        ri.cw.p(f'dst->{arg} = {arg};')
2205
2206    if struct.fixed_header:
2207        if struct.nested:
2208            ri.cw.p('hdr = ynl_attr_data(nested);')
2209        elif ri.family.is_classic():
2210            ri.cw.p('hdr = ynl_nlmsg_data(nlh);')
2211        else:
2212            ri.cw.p('hdr = ynl_nlmsg_data_offset(nlh, sizeof(struct genlmsghdr));')
2213        ri.cw.p(f"memcpy(&dst->_hdr, hdr, sizeof({struct.fixed_header}));")
2214    for arg in sorted(all_multi):
2215        aspec = struct[arg]
2216        ri.cw.p(f"if (dst->{aspec.c_name})")
2217        ri.cw.p(f'return ynl_error_parse(yarg, "attribute already present ({struct.attr_set.name}.{aspec.name})");')
2218
2219    ri.cw.nl()
2220    ri.cw.block_start(line=iter_line)
2221    ri.cw.p('unsigned int type = ynl_attr_type(attr);')
2222    ri.cw.nl()
2223
2224    first = True
2225    for _, arg in struct.member_list():
2226        good = arg.attr_get(ri, 'dst', first=first)
2227        # First may be 'unused' or 'pad', ignore those
2228        first &= not good
2229
2230    ri.cw.block_end()
2231    ri.cw.nl()
2232
2233    for arg in sorted(indexed_arrays):
2234        aspec = struct[arg]
2235
2236        ri.cw.block_start(line=f"if (n_{aspec.c_name})")
2237        ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));")
2238        ri.cw.p(f"if (!dst->{aspec.c_name})")
2239        ri.cw.p("return YNL_PARSE_CB_ERROR;")
2240        ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};")
2241        ri.cw.p('i = 0;')
2242        if 'nested-attributes' in aspec:
2243            ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;")
2244        ri.cw.block_start(line=f"ynl_attr_for_each_nested(attr, attr_{aspec.c_name})")
2245        if 'nested-attributes' in aspec:
2246            ri.cw.p(f"parg.data = &dst->{aspec.c_name}[i];")
2247            ri.cw.p(f"if ({aspec.nested_render_name}_parse(&parg, attr, ynl_attr_type(attr)))")
2248            ri.cw.p('return YNL_PARSE_CB_ERROR;')
2249        elif aspec.sub_type in scalars:
2250            ri.cw.p(f"dst->{aspec.c_name}[i] = ynl_attr_get_{aspec.sub_type}(attr);")
2251        elif aspec.sub_type == 'binary' and 'exact-len' in aspec.checks:
2252            # Length is validated by typol
2253            ri.cw.p(f'memcpy(dst->{aspec.c_name}[i], ynl_attr_data(attr), {aspec.checks["exact-len"]});')
2254        else:
2255            raise Exception(f"Nest parsing type not supported in {aspec['name']}")
2256        ri.cw.p('i++;')
2257        ri.cw.block_end()
2258        ri.cw.block_end()
2259    ri.cw.nl()
2260
2261    for arg in sorted(multi_attrs):
2262        aspec = struct[arg]
2263        ri.cw.block_start(line=f"if (n_{aspec.c_name})")
2264        ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));")
2265        ri.cw.p(f"if (!dst->{aspec.c_name})")
2266        ri.cw.p("return YNL_PARSE_CB_ERROR;")
2267        ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};")
2268        ri.cw.p('i = 0;')
2269        if 'nested-attributes' in aspec:
2270            ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;")
2271        ri.cw.block_start(line=iter_line)
2272        ri.cw.block_start(line=f"if (ynl_attr_type(attr) == {aspec.enum_name})")
2273        if 'nested-attributes' in aspec:
2274            ri.cw.p(f"parg.data = &dst->{aspec.c_name}[i];")
2275            ri.cw.p(f"if ({aspec.nested_render_name}_parse(&parg, attr))")
2276            ri.cw.p('return YNL_PARSE_CB_ERROR;')
2277        elif aspec.type in scalars:
2278            ri.cw.p(f"dst->{aspec.c_name}[i] = ynl_attr_get_{aspec.type}(attr);")
2279        elif aspec.type == 'binary' and 'struct' in aspec:
2280            ri.cw.p('size_t len = ynl_attr_data_len(attr);')
2281            ri.cw.nl()
2282            ri.cw.p(f'if (len > sizeof(dst->{aspec.c_name}[0]))')
2283            ri.cw.p(f'len = sizeof(dst->{aspec.c_name}[0]);')
2284            ri.cw.p(f"memcpy(&dst->{aspec.c_name}[i], ynl_attr_data(attr), len);")
2285        elif aspec.type == 'string':
2286            ri.cw.p('unsigned int len;')
2287            ri.cw.nl()
2288            ri.cw.p('len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));')
2289            ri.cw.p(f'dst->{aspec.c_name}[i] = malloc(sizeof(struct ynl_string) + len + 1);')
2290            ri.cw.p(f"if (!dst->{aspec.c_name}[i])")
2291            ri.cw.p("return YNL_PARSE_CB_ERROR;")
2292            ri.cw.p(f"dst->{aspec.c_name}[i]->len = len;")
2293            ri.cw.p(f"memcpy(dst->{aspec.c_name}[i]->str, ynl_attr_get_str(attr), len);")
2294            ri.cw.p(f"dst->{aspec.c_name}[i]->str[len] = 0;")
2295        else:
2296            raise Exception(f'Nest parsing of type {aspec.type} not supported yet')
2297        ri.cw.p('i++;')
2298        ri.cw.block_end()
2299        ri.cw.block_end()
2300        ri.cw.block_end()
2301    ri.cw.nl()
2302
2303    if struct.nested:
2304        ri.cw.p('return 0;')
2305    else:
2306        ri.cw.p('return YNL_PARSE_CB_OK;')
2307    ri.cw.block_end()
2308    ri.cw.nl()
2309
2310
2311def parse_rsp_submsg(ri, struct):
2312    parse_rsp_nested_prototype(ri, struct, suffix='')
2313
2314    var = 'dst'
2315    local_vars = {'const struct nlattr *attr = nested;',
2316                  f'{struct.ptr_name}{var} = yarg->data;',
2317                  'struct ynl_parse_arg parg;'}
2318
2319    for _, arg in struct.member_list():
2320        _, _, l_vars = arg._attr_get(ri, var)
2321        local_vars |= set(l_vars) if l_vars else set()
2322
2323    ri.cw.block_start()
2324    ri.cw.write_func_lvar(list(local_vars))
2325    ri.cw.p('parg.ys = yarg->ys;')
2326    ri.cw.nl()
2327
2328    first = True
2329    for name, arg in struct.member_list():
2330        kw = 'if' if first else 'else if'
2331        first = False
2332
2333        ri.cw.block_start(line=f'{kw} (!strcmp(sel, "{name}"))')
2334        get_lines, init_lines, _ = arg._attr_get(ri, var)
2335        for line in init_lines or []:
2336            ri.cw.p(line)
2337        for line in get_lines:
2338            ri.cw.p(line)
2339        if arg.presence_type() == 'present':
2340            ri.cw.p(f"{var}->_present.{arg.c_name} = 1;")
2341        ri.cw.block_end()
2342    ri.cw.p('return 0;')
2343    ri.cw.block_end()
2344    ri.cw.nl()
2345
2346
2347def parse_rsp_nested_prototype(ri, struct, suffix=';'):
2348    func_args = ['struct ynl_parse_arg *yarg',
2349                 'const struct nlattr *nested']
2350    for sel in struct.external_selectors():
2351        func_args.append('const char *_sel_' + sel.name)
2352    if struct.submsg:
2353        func_args.insert(1, 'const char *sel')
2354    for arg in struct.inherited:
2355        func_args.append('__u32 ' + arg)
2356
2357    ri.cw.write_func_prot('int', f'{struct.render_name}_parse', func_args,
2358                          suffix=suffix)
2359
2360
2361def parse_rsp_nested(ri, struct):
2362    if struct.submsg:
2363        parse_rsp_submsg(ri, struct)
2364        return
2365
2366    parse_rsp_nested_prototype(ri, struct, suffix='')
2367
2368    local_vars = ['const struct nlattr *attr;',
2369                  f'{struct.ptr_name}dst = yarg->data;']
2370    init_lines = []
2371
2372    if struct.member_list():
2373        _multi_parse(ri, struct, init_lines, local_vars)
2374    else:
2375        # Empty nest
2376        ri.cw.block_start()
2377        ri.cw.p('return 0;')
2378        ri.cw.block_end()
2379        ri.cw.nl()
2380
2381
2382def parse_rsp_msg(ri, deref=False):
2383    if 'reply' not in ri.op[ri.op_mode] and ri.op_mode != 'event':
2384        return
2385
2386    func_args = ['const struct nlmsghdr *nlh',
2387                 'struct ynl_parse_arg *yarg']
2388
2389    local_vars = [f'{type_name(ri, "reply", deref=deref)} *dst;',
2390                  'const struct nlattr *attr;']
2391    init_lines = ['dst = yarg->data;']
2392
2393    ri.cw.write_func_prot('int', f'{op_prefix(ri, "reply", deref=deref)}_parse', func_args)
2394
2395    if ri.struct["reply"].member_list():
2396        _multi_parse(ri, ri.struct["reply"], init_lines, local_vars)
2397    else:
2398        # Empty reply
2399        ri.cw.block_start()
2400        ri.cw.p('return YNL_PARSE_CB_OK;')
2401        ri.cw.block_end()
2402        ri.cw.nl()
2403
2404
2405def print_req(ri):
2406    ret_ok = '0'
2407    ret_err = '-1'
2408    direction = "request"
2409    local_vars = ['struct ynl_req_state yrs = { .yarg = { .ys = ys, }, };',
2410                  'struct nlmsghdr *nlh;',
2411                  'int err;']
2412
2413    if 'reply' in ri.op[ri.op_mode]:
2414        ret_ok = 'rsp'
2415        ret_err = 'NULL'
2416        local_vars += [f'{type_name(ri, rdir(direction))} *rsp;']
2417
2418    if ri.struct["request"].fixed_header:
2419        local_vars += ['size_t hdr_len;',
2420                       'void *hdr;']
2421
2422    local_vars += put_local_vars(ri.struct['request'])
2423
2424    print_prototype(ri, direction, terminate=False)
2425    ri.cw.block_start()
2426    ri.cw.write_func_lvar(local_vars)
2427
2428    if ri.family.is_classic():
2429        ri.cw.p(f"nlh = ynl_msg_start_req(ys, {ri.op.enum_name}, req->_nlmsg_flags);")
2430    else:
2431        ri.cw.p(f"nlh = ynl_gemsg_start_req(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);")
2432
2433    ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;")
2434    ri.cw.p(f"ys->req_hdr_len = {ri.fixed_hdr_len};")
2435    if 'reply' in ri.op[ri.op_mode]:
2436        ri.cw.p(f"yrs.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;")
2437    ri.cw.nl()
2438
2439    if ri.struct['request'].fixed_header:
2440        ri.cw.p("hdr_len = sizeof(req->_hdr);")
2441        ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);")
2442        ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);")
2443        ri.cw.nl()
2444
2445    for _, attr in ri.struct["request"].member_list():
2446        attr.attr_put(ri, "req")
2447    ri.cw.nl()
2448
2449    if 'reply' in ri.op[ri.op_mode]:
2450        ri.cw.p('rsp = calloc(1, sizeof(*rsp));')
2451        ri.cw.p('if (!rsp)')
2452        ri.cw.p(f'return {ret_err};')
2453        ri.cw.p('yrs.yarg.data = rsp;')
2454        ri.cw.p(f"yrs.cb = {op_prefix(ri, 'reply')}_parse;")
2455        if ri.op.value is not None:
2456            ri.cw.p(f'yrs.rsp_cmd = {ri.op.enum_name};')
2457        else:
2458            ri.cw.p(f'yrs.rsp_cmd = {ri.op.rsp_value};')
2459        ri.cw.nl()
2460    ri.cw.p("err = ynl_exec(ys, nlh, &yrs);")
2461    ri.cw.p('if (err < 0)')
2462    if 'reply' in ri.op[ri.op_mode]:
2463        ri.cw.p('goto err_free;')
2464    else:
2465        ri.cw.p('return -1;')
2466    ri.cw.nl()
2467
2468    ri.cw.p(f"return {ret_ok};")
2469    ri.cw.nl()
2470
2471    if 'reply' in ri.op[ri.op_mode]:
2472        ri.cw.p('err_free:')
2473        ri.cw.p(f"{call_free(ri, rdir(direction), 'rsp')}")
2474        ri.cw.p(f"return {ret_err};")
2475
2476    ri.cw.block_end()
2477
2478
2479def print_dump(ri):
2480    direction = "request"
2481    print_prototype(ri, direction, terminate=False)
2482    ri.cw.block_start()
2483    local_vars = ['struct ynl_dump_state yds = {};',
2484                  'struct nlmsghdr *nlh;',
2485                  'int err;']
2486
2487    if ri.struct['request'].fixed_header:
2488        local_vars += ['size_t hdr_len;',
2489                       'void *hdr;']
2490
2491    if 'request' in ri.op[ri.op_mode]:
2492        local_vars += put_local_vars(ri.struct['request'])
2493
2494    ri.cw.write_func_lvar(local_vars)
2495
2496    ri.cw.p('yds.yarg.ys = ys;')
2497    ri.cw.p(f"yds.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;")
2498    ri.cw.p("yds.yarg.data = NULL;")
2499    ri.cw.p(f"yds.alloc_sz = sizeof({type_name(ri, rdir(direction))});")
2500    ri.cw.p(f"yds.cb = {op_prefix(ri, 'reply', deref=True)}_parse;")
2501    if ri.op.value is not None:
2502        ri.cw.p(f'yds.rsp_cmd = {ri.op.enum_name};')
2503    else:
2504        ri.cw.p(f'yds.rsp_cmd = {ri.op.rsp_value};')
2505    ri.cw.nl()
2506    if ri.family.is_classic():
2507        ri.cw.p(f"nlh = ynl_msg_start_dump(ys, {ri.op.enum_name});")
2508    else:
2509        ri.cw.p(f"nlh = ynl_gemsg_start_dump(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);")
2510
2511    if ri.struct['request'].fixed_header:
2512        ri.cw.p("hdr_len = sizeof(req->_hdr);")
2513        ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);")
2514        ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);")
2515        ri.cw.nl()
2516
2517    if "request" in ri.op[ri.op_mode]:
2518        ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;")
2519        ri.cw.p(f"ys->req_hdr_len = {ri.fixed_hdr_len};")
2520        ri.cw.nl()
2521        for _, attr in ri.struct["request"].member_list():
2522            attr.attr_put(ri, "req")
2523    ri.cw.nl()
2524
2525    ri.cw.p('err = ynl_exec_dump(ys, nlh, &yds);')
2526    ri.cw.p('if (err < 0)')
2527    ri.cw.p('goto free_list;')
2528    ri.cw.nl()
2529
2530    ri.cw.p('return yds.first;')
2531    ri.cw.nl()
2532    ri.cw.p('free_list:')
2533    ri.cw.p(call_free(ri, rdir(direction), 'yds.first'))
2534    ri.cw.p('return NULL;')
2535    ri.cw.block_end()
2536
2537
2538def call_free(ri, direction, var):
2539    return f"{op_prefix(ri, direction)}_free({var});"
2540
2541
2542def free_arg_name(direction):
2543    if direction:
2544        return direction_to_suffix[direction][1:]
2545    return 'obj'
2546
2547
2548def print_alloc_wrapper(ri, direction, struct=None):
2549    name = op_prefix(ri, direction)
2550    struct_name = name
2551    if ri.type_name_conflict:
2552        struct_name += '_'
2553
2554    args = ["void"]
2555    cnt = "1"
2556    if struct and struct.in_multi_val:
2557        args = ["unsigned int n"]
2558        cnt = "n"
2559
2560    ri.cw.write_func_prot(f'static inline struct {struct_name} *',
2561                          f"{name}_alloc", args)
2562    ri.cw.block_start()
2563    ri.cw.p(f'return calloc({cnt}, sizeof(struct {struct_name}));')
2564    ri.cw.block_end()
2565
2566
2567def print_free_prototype(ri, direction, suffix=';'):
2568    name = op_prefix(ri, direction)
2569    struct_name = name
2570    if ri.type_name_conflict:
2571        struct_name += '_'
2572    arg = free_arg_name(direction)
2573    ri.cw.write_func_prot('void', f"{name}_free", [f"struct {struct_name} *{arg}"], suffix=suffix)
2574
2575
2576def print_nlflags_set(ri, direction):
2577    name = op_prefix(ri, direction)
2578    ri.cw.write_func_prot('static inline void', f"{name}_set_nlflags",
2579                          [f"struct {name} *req", "__u16 nl_flags"])
2580    ri.cw.block_start()
2581    ri.cw.p('req->_nlmsg_flags = nl_flags;')
2582    ri.cw.block_end()
2583    ri.cw.nl()
2584
2585
2586def _print_type(ri, direction, struct):
2587    suffix = f'_{ri.type_name}{direction_to_suffix[direction]}'
2588    if not direction and ri.type_name_conflict:
2589        suffix += '_'
2590
2591    if ri.op_mode == 'dump' and not ri.type_oneside:
2592        suffix += '_dump'
2593
2594    ri.cw.block_start(line=f"struct {ri.family.c_name}{suffix}")
2595
2596    if ri.needs_nlflags(direction):
2597        ri.cw.p('__u16 _nlmsg_flags;')
2598        ri.cw.nl()
2599    if struct.fixed_header:
2600        ri.cw.p(struct.fixed_header + ' _hdr;')
2601        ri.cw.nl()
2602
2603    for type_filter in ['present', 'len', 'count']:
2604        meta_started = False
2605        for _, attr in struct.member_list():
2606            line = attr.presence_member(ri.ku_space, type_filter)
2607            if line:
2608                if not meta_started:
2609                    ri.cw.block_start(line="struct")
2610                    meta_started = True
2611                ri.cw.p(line)
2612        if meta_started:
2613            ri.cw.block_end(line=f'_{type_filter};')
2614    ri.cw.nl()
2615
2616    for arg in struct.inherited:
2617        ri.cw.p(f"__u32 {arg};")
2618
2619    for _, attr in struct.member_list():
2620        attr.struct_member(ri)
2621
2622    ri.cw.block_end(line=';')
2623    ri.cw.nl()
2624
2625
2626def print_type(ri, direction):
2627    _print_type(ri, direction, ri.struct[direction])
2628
2629
2630def print_type_full(ri, struct):
2631    _print_type(ri, "", struct)
2632
2633    if struct.request and struct.in_multi_val:
2634        print_alloc_wrapper(ri, "", struct)
2635        ri.cw.nl()
2636        free_rsp_nested_prototype(ri)
2637        ri.cw.nl()
2638
2639        # Name conflicts are too hard to deal with with the current code base,
2640        # they are very rare so don't bother printing setters in that case.
2641        if ri.ku_space == 'user' and not ri.type_name_conflict:
2642            for _, attr in struct.member_list():
2643                attr.setter(ri, ri.attr_set, "", var="obj")
2644        ri.cw.nl()
2645
2646
2647def print_type_helpers(ri, direction, deref=False):
2648    print_free_prototype(ri, direction)
2649    ri.cw.nl()
2650
2651    if ri.needs_nlflags(direction):
2652        print_nlflags_set(ri, direction)
2653
2654    if ri.ku_space == 'user' and direction == 'request':
2655        for _, attr in ri.struct[direction].member_list():
2656            attr.setter(ri, ri.attr_set, direction, deref=deref)
2657    ri.cw.nl()
2658
2659
2660def print_req_type_helpers(ri):
2661    if ri.type_empty("request"):
2662        return
2663    print_alloc_wrapper(ri, "request")
2664    print_type_helpers(ri, "request")
2665
2666
2667def print_rsp_type_helpers(ri):
2668    if 'reply' not in ri.op[ri.op_mode]:
2669        return
2670    print_type_helpers(ri, "reply")
2671
2672
2673def print_parse_prototype(ri, direction, terminate=True):
2674    suffix = "_rsp" if direction == "reply" else "_req"
2675    term = ';' if terminate else ''
2676
2677    ri.cw.write_func_prot('void', f"{ri.op.render_name}{suffix}_parse",
2678                          ['const struct nlattr **tb',
2679                           f"struct {ri.op.render_name}{suffix} *req"],
2680                          suffix=term)
2681
2682
2683def print_req_type(ri):
2684    if ri.type_empty("request"):
2685        return
2686    print_type(ri, "request")
2687
2688
2689def print_req_free(ri):
2690    if 'request' not in ri.op[ri.op_mode]:
2691        return
2692    _free_type(ri, 'request', ri.struct['request'])
2693
2694
2695def print_rsp_type(ri):
2696    if ri.op_mode in ('do', 'dump') and 'reply' in ri.op[ri.op_mode]:
2697        direction = 'reply'
2698    elif ri.op_mode == 'event':
2699        direction = 'reply'
2700    else:
2701        return
2702    print_type(ri, direction)
2703
2704
2705def print_wrapped_type(ri):
2706    ri.cw.block_start(line=f"{type_name(ri, 'reply')}")
2707    if ri.op_mode == 'dump':
2708        ri.cw.p(f"{type_name(ri, 'reply')} *next;")
2709    elif ri.op_mode in ('notify', 'event'):
2710        ri.cw.p('__u16 family;')
2711        ri.cw.p('__u8 cmd;')
2712        ri.cw.p('struct ynl_ntf_base_type *next;')
2713        ri.cw.p(f"void (*free)({type_name(ri, 'reply')} *ntf);")
2714    ri.cw.p(f"{type_name(ri, 'reply', deref=True)} obj __attribute__((aligned(8)));")
2715    ri.cw.block_end(line=';')
2716    ri.cw.nl()
2717    print_free_prototype(ri, 'reply')
2718    ri.cw.nl()
2719
2720
2721def _free_type_members_iter(ri, struct):
2722    if struct.free_needs_iter():
2723        ri.cw.p('unsigned int i;')
2724        ri.cw.nl()
2725
2726
2727def _free_type_members(ri, var, struct, ref=''):
2728    for _, attr in struct.member_list():
2729        attr.free(ri, var, ref)
2730
2731
2732def _free_type(ri, direction, struct):
2733    var = free_arg_name(direction)
2734
2735    print_free_prototype(ri, direction, suffix='')
2736    ri.cw.block_start()
2737    _free_type_members_iter(ri, struct)
2738    _free_type_members(ri, var, struct)
2739    if direction:
2740        ri.cw.p(f'free({var});')
2741    ri.cw.block_end()
2742    ri.cw.nl()
2743
2744
2745def free_rsp_nested_prototype(ri):
2746    print_free_prototype(ri, "")
2747
2748
2749def free_rsp_nested(ri, struct):
2750    _free_type(ri, "", struct)
2751
2752
2753def print_rsp_free(ri):
2754    if 'reply' not in ri.op[ri.op_mode]:
2755        return
2756    _free_type(ri, 'reply', ri.struct['reply'])
2757
2758
2759def print_dump_type_free(ri):
2760    sub_type = type_name(ri, 'reply')
2761
2762    print_free_prototype(ri, 'reply', suffix='')
2763    ri.cw.block_start()
2764    ri.cw.p(f"{sub_type} *next = rsp;")
2765    ri.cw.nl()
2766    ri.cw.p('if (!next)')
2767    ri.cw.p('return;')
2768    ri.cw.nl()
2769    ri.cw.block_start(line='while ((void *)next != YNL_LIST_END)')
2770    _free_type_members_iter(ri, ri.struct['reply'])
2771    ri.cw.p('rsp = next;')
2772    ri.cw.p('next = rsp->next;')
2773    ri.cw.nl()
2774
2775    _free_type_members(ri, 'rsp', ri.struct['reply'], ref='obj.')
2776    ri.cw.p('free(rsp);')
2777    ri.cw.block_end()
2778    ri.cw.block_end()
2779    ri.cw.nl()
2780
2781
2782def print_ntf_type_free(ri):
2783    print_free_prototype(ri, 'reply', suffix='')
2784    ri.cw.block_start()
2785    _free_type_members_iter(ri, ri.struct['reply'])
2786    _free_type_members(ri, 'rsp', ri.struct['reply'], ref='obj.')
2787    ri.cw.p('free(rsp);')
2788    ri.cw.block_end()
2789    ri.cw.nl()
2790
2791
2792def print_req_policy_fwd(cw, struct, ri=None, terminate=True):
2793    if terminate and ri and policy_should_be_static(struct.family):
2794        return
2795
2796    if terminate:
2797        prefix = 'extern '
2798    else:
2799        if ri and policy_should_be_static(struct.family):
2800            prefix = 'static '
2801        else:
2802            prefix = ''
2803
2804    suffix = ';' if terminate else ' = {'
2805
2806    max_attr = struct.attr_max_val
2807    if ri:
2808        name = ri.op.render_name
2809        if ri.op.dual_policy:
2810            name += '_' + ri.op_mode
2811    else:
2812        name = struct.render_name
2813    cw.p(f"{prefix}const struct nla_policy {name}_nl_policy[{max_attr.enum_name} + 1]{suffix}")
2814
2815
2816def print_req_policy(cw, struct, ri=None):
2817    if ri and ri.op:
2818        cw.ifdef_block(ri.op.get('config-cond', None))
2819    print_req_policy_fwd(cw, struct, ri=ri, terminate=False)
2820    for _, arg in struct.member_list():
2821        arg.attr_policy(cw)
2822    cw.p("};")
2823    cw.ifdef_block(None)
2824    cw.nl()
2825
2826
2827def kernel_can_gen_family_struct(family):
2828    return family.proto == 'genetlink'
2829
2830
2831def policy_should_be_static(family):
2832    return family.kernel_policy == 'split' or kernel_can_gen_family_struct(family)
2833
2834
2835def print_kernel_policy_ranges(family, cw):
2836    first = True
2837    for _, attr_set in family.attr_sets.items():
2838        if attr_set.subset_of:
2839            continue
2840
2841        for _, attr in attr_set.items():
2842            if not attr.request:
2843                continue
2844            if 'full-range' not in attr.checks:
2845                continue
2846
2847            if first:
2848                cw.p('/* Integer value ranges */')
2849                first = False
2850
2851            sign = '' if attr.type[0] == 'u' else '_signed'
2852            suffix = 'ULL' if attr.type[0] == 'u' else 'LL'
2853            cw.block_start(line=f'static const struct netlink_range_validation{sign} {c_lower(attr.enum_name)}_range =')
2854            members = []
2855            if 'min' in attr.checks:
2856                members.append(('min', attr.get_limit_str('min', suffix=suffix)))
2857            if 'max' in attr.checks:
2858                members.append(('max', attr.get_limit_str('max', suffix=suffix)))
2859            cw.write_struct_init(members)
2860            cw.block_end(line=';')
2861            cw.nl()
2862
2863
2864def print_kernel_policy_sparse_enum_validates(family, cw):
2865    first = True
2866    for _, attr_set in family.attr_sets.items():
2867        if attr_set.subset_of:
2868            continue
2869
2870        for _, attr in attr_set.items():
2871            if not attr.request:
2872                continue
2873            if not attr.enum_name:
2874                continue
2875            if 'sparse' not in attr.checks:
2876                continue
2877
2878            if first:
2879                cw.p('/* Sparse enums validation callbacks */')
2880                first = False
2881
2882            cw.write_func_prot('static int', f'{c_lower(attr.enum_name)}_validate',
2883                               ['const struct nlattr *attr', 'struct netlink_ext_ack *extack'])
2884            cw.block_start()
2885            cw.block_start(line=f'switch (nla_get_{attr["type"]}(attr))')
2886            enum = family.consts[attr['enum']]
2887            first_entry = True
2888            for entry in enum.entries.values():
2889                if first_entry:
2890                    first_entry = False
2891                else:
2892                    cw.p('fallthrough;')
2893                cw.p(f'case {entry.c_name}:')
2894            cw.p('return 0;')
2895            cw.block_end()
2896            cw.p('NL_SET_ERR_MSG_ATTR(extack, attr, "invalid enum value");')
2897            cw.p('return -EINVAL;')
2898            cw.block_end()
2899            cw.nl()
2900
2901
2902def print_kernel_op_table_fwd(family, cw, terminate):
2903    exported = not kernel_can_gen_family_struct(family)
2904
2905    if not terminate or exported:
2906        cw.p(f"/* Ops table for {family.ident_name} */")
2907
2908        pol_to_struct = {'global': 'genl_small_ops',
2909                         'per-op': 'genl_ops',
2910                         'split': 'genl_split_ops'}
2911        struct_type = pol_to_struct[family.kernel_policy]
2912
2913        if not exported:
2914            cnt = ""
2915        elif family.kernel_policy == 'split':
2916            cnt = 0
2917            for op in family.ops.values():
2918                if 'do' in op:
2919                    cnt += 1
2920                if 'dump' in op:
2921                    cnt += 1
2922        else:
2923            cnt = len(family.ops)
2924
2925        qual = 'static const' if not exported else 'const'
2926        line = f"{qual} struct {struct_type} {family.c_name}_nl_ops[{cnt}]"
2927        if terminate:
2928            cw.p(f"extern {line};")
2929        else:
2930            cw.block_start(line=line + ' =')
2931
2932    if not terminate:
2933        return
2934
2935    cw.nl()
2936    for name in family.hooks['pre']['do']['list']:
2937        cw.write_func_prot('int', c_lower(name),
2938                           ['const struct genl_split_ops *ops',
2939                            'struct sk_buff *skb', 'struct genl_info *info'], suffix=';')
2940    for name in family.hooks['post']['do']['list']:
2941        cw.write_func_prot('void', c_lower(name),
2942                           ['const struct genl_split_ops *ops',
2943                            'struct sk_buff *skb', 'struct genl_info *info'], suffix=';')
2944    for name in family.hooks['pre']['dump']['list']:
2945        cw.write_func_prot('int', c_lower(name),
2946                           ['struct netlink_callback *cb'], suffix=';')
2947    for name in family.hooks['post']['dump']['list']:
2948        cw.write_func_prot('int', c_lower(name),
2949                           ['struct netlink_callback *cb'], suffix=';')
2950
2951    cw.nl()
2952
2953    for op_name, op in family.ops.items():
2954        if op.is_async:
2955            continue
2956
2957        if 'do' in op:
2958            name = c_lower(f"{family.fn_prefix}-{op_name}-doit")
2959            cw.write_func_prot('int', name,
2960                               ['struct sk_buff *skb', 'struct genl_info *info'], suffix=';')
2961
2962        if 'dump' in op:
2963            name = c_lower(f"{family.fn_prefix}-{op_name}-dumpit")
2964            cw.write_func_prot('int', name,
2965                               ['struct sk_buff *skb', 'struct netlink_callback *cb'], suffix=';')
2966    cw.nl()
2967
2968
2969def print_kernel_op_table_hdr(family, cw):
2970    print_kernel_op_table_fwd(family, cw, terminate=True)
2971
2972
2973def print_kernel_op_table(family, cw):
2974    print_kernel_op_table_fwd(family, cw, terminate=False)
2975    if family.kernel_policy in ('global', 'per-op'):
2976        for op_name, op in family.ops.items():
2977            if op.is_async:
2978                continue
2979
2980            cw.ifdef_block(op.get('config-cond', None))
2981            cw.block_start()
2982            members = [('cmd', op.enum_name)]
2983            if 'dont-validate' in op:
2984                members.append(('validate',
2985                                ' | '.join([c_upper('genl-dont-validate-' + x)
2986                                            for x in op['dont-validate']])), )
2987            for op_mode in ['do', 'dump']:
2988                if op_mode in op:
2989                    name = c_lower(f"{family.fn_prefix}-{op_name}-{op_mode}it")
2990                    members.append((op_mode + 'it', name))
2991            if family.kernel_policy == 'per-op':
2992                struct = Struct(family, op['attribute-set'],
2993                                type_list=op['do']['request']['attributes'])
2994
2995                name = c_lower(f"{family.ident_name}-{op_name}-nl-policy")
2996                members.append(('policy', name))
2997                members.append(('maxattr', struct.attr_max_val.enum_name))
2998            if 'flags' in op:
2999                members.append(('flags', ' | '.join([c_upper('genl-' + x) for x in op['flags']])))
3000            cw.write_struct_init(members)
3001            cw.block_end(line=',')
3002    elif family.kernel_policy == 'split':
3003        cb_names = {'do':   {'pre': 'pre_doit', 'post': 'post_doit'},
3004                    'dump': {'pre': 'start', 'post': 'done'}}
3005
3006        for op_name, op in family.ops.items():
3007            for op_mode in ['do', 'dump']:
3008                if op.is_async or op_mode not in op:
3009                    continue
3010
3011                cw.ifdef_block(op.get('config-cond', None))
3012                cw.block_start()
3013                members = [('cmd', op.enum_name)]
3014                if 'dont-validate' in op:
3015                    dont_validate = []
3016                    for x in op['dont-validate']:
3017                        if op_mode == 'do' and x in ['dump', 'dump-strict']:
3018                            continue
3019                        if op_mode == "dump" and x == 'strict':
3020                            continue
3021                        dont_validate.append(x)
3022
3023                    if dont_validate:
3024                        members.append(('validate',
3025                                        ' | '.join([c_upper('genl-dont-validate-' + x)
3026                                                    for x in dont_validate])), )
3027                name = c_lower(f"{family.fn_prefix}-{op_name}-{op_mode}it")
3028                if 'pre' in op[op_mode]:
3029                    members.append((cb_names[op_mode]['pre'], c_lower(op[op_mode]['pre'])))
3030                members.append((op_mode + 'it', name))
3031                if 'post' in op[op_mode]:
3032                    members.append((cb_names[op_mode]['post'], c_lower(op[op_mode]['post'])))
3033                if 'request' in op[op_mode]:
3034                    struct = Struct(family, op['attribute-set'],
3035                                    type_list=op[op_mode]['request']['attributes'])
3036
3037                    if op.dual_policy:
3038                        name = c_lower(f"{family.ident_name}-{op_name}-{op_mode}-nl-policy")
3039                    else:
3040                        name = c_lower(f"{family.ident_name}-{op_name}-nl-policy")
3041                    members.append(('policy', name))
3042                    members.append(('maxattr', struct.attr_max_val.enum_name))
3043                flags = (op['flags'] if 'flags' in op else []) + ['cmd-cap-' + op_mode]
3044                members.append(('flags', ' | '.join([c_upper('genl-' + x) for x in flags])))
3045                cw.write_struct_init(members)
3046                cw.block_end(line=',')
3047    cw.ifdef_block(None)
3048
3049    cw.block_end(line=';')
3050    cw.nl()
3051
3052
3053def print_kernel_mcgrp_hdr(family, cw):
3054    if not family.mcgrps['list']:
3055        return
3056
3057    cw.block_start('enum')
3058    for grp in family.mcgrps['list']:
3059        grp_id = c_upper(f"{family.ident_name}-nlgrp-{grp['name']},")
3060        cw.p(grp_id)
3061    cw.block_end(';')
3062    cw.nl()
3063
3064
3065def print_kernel_mcgrp_src(family, cw):
3066    if not family.mcgrps['list']:
3067        return
3068
3069    cw.block_start('static const struct genl_multicast_group ' + family.c_name + '_nl_mcgrps[] =')
3070    for grp in family.mcgrps['list']:
3071        name = grp['name']
3072        grp_id = c_upper(f"{family.ident_name}-nlgrp-{name}")
3073        cw.p('[' + grp_id + '] = { "' + name + '", },')
3074    cw.block_end(';')
3075    cw.nl()
3076
3077
3078def print_kernel_family_struct_hdr(family, cw):
3079    if not kernel_can_gen_family_struct(family):
3080        return
3081
3082    cw.p(f"extern struct genl_family {family.c_name}_nl_family;")
3083    cw.nl()
3084    if 'sock-priv' in family.kernel_family:
3085        cw.p(f'void {family.c_name}_nl_sock_priv_init({family.kernel_family["sock-priv"]} *priv);')
3086        cw.p(f'void {family.c_name}_nl_sock_priv_destroy({family.kernel_family["sock-priv"]} *priv);')
3087        cw.nl()
3088
3089
3090def print_kernel_family_struct_src(family, cw):
3091    if not kernel_can_gen_family_struct(family):
3092        return
3093
3094    if 'sock-priv' in family.kernel_family:
3095        # Generate "trampolines" to make CFI happy
3096        cw.write_func("static void", f"__{family.c_name}_nl_sock_priv_init",
3097                      [f"{family.c_name}_nl_sock_priv_init(priv);"],
3098                      ["void *priv"])
3099        cw.nl()
3100        cw.write_func("static void", f"__{family.c_name}_nl_sock_priv_destroy",
3101                      [f"{family.c_name}_nl_sock_priv_destroy(priv);"],
3102                      ["void *priv"])
3103        cw.nl()
3104
3105    cw.block_start(f"struct genl_family {family.ident_name}_nl_family __ro_after_init =")
3106    cw.p('.name\t\t= ' + family.fam_key + ',')
3107    cw.p('.version\t= ' + family.ver_key + ',')
3108    cw.p('.netnsok\t= true,')
3109    cw.p('.parallel_ops\t= true,')
3110    cw.p('.module\t\t= THIS_MODULE,')
3111    if family.kernel_policy == 'per-op':
3112        cw.p(f'.ops\t\t= {family.c_name}_nl_ops,')
3113        cw.p(f'.n_ops\t\t= ARRAY_SIZE({family.c_name}_nl_ops),')
3114    elif family.kernel_policy == 'split':
3115        cw.p(f'.split_ops\t= {family.c_name}_nl_ops,')
3116        cw.p(f'.n_split_ops\t= ARRAY_SIZE({family.c_name}_nl_ops),')
3117    if family.mcgrps['list']:
3118        cw.p(f'.mcgrps\t\t= {family.c_name}_nl_mcgrps,')
3119        cw.p(f'.n_mcgrps\t= ARRAY_SIZE({family.c_name}_nl_mcgrps),')
3120    if 'sock-priv' in family.kernel_family:
3121        cw.p(f'.sock_priv_size\t= sizeof({family.kernel_family["sock-priv"]}),')
3122        cw.p(f'.sock_priv_init\t= __{family.c_name}_nl_sock_priv_init,')
3123        cw.p(f'.sock_priv_destroy = __{family.c_name}_nl_sock_priv_destroy,')
3124    cw.block_end(';')
3125
3126
3127def uapi_enum_start(family, cw, obj, ckey='', enum_name='enum-name'):
3128    start_line = 'enum'
3129    if enum_name in obj:
3130        if obj[enum_name]:
3131            start_line = 'enum ' + c_lower(obj[enum_name])
3132    elif ckey and ckey in obj:
3133        start_line = 'enum ' + family.c_name + '_' + c_lower(obj[ckey])
3134    cw.block_start(line=start_line)
3135
3136
3137def render_uapi_unified(family, cw, max_by_define, separate_ntf):
3138    max_name = c_upper(family.get('cmd-max-name', f"{family.op_prefix}MAX"))
3139    cnt_name = c_upper(family.get('cmd-cnt-name', f"__{family.op_prefix}MAX"))
3140    max_value = f"({cnt_name} - 1)"
3141
3142    uapi_enum_start(family, cw, family['operations'], 'enum-name')
3143    val = 0
3144    for op in family.msgs.values():
3145        if separate_ntf and ('notify' in op or 'event' in op):
3146            continue
3147
3148        suffix = ','
3149        if op.value != val:
3150            suffix = f" = {op.value},"
3151            val = op.value
3152        cw.p(op.enum_name + suffix)
3153        val += 1
3154    cw.nl()
3155    cw.p(cnt_name + ('' if max_by_define else ','))
3156    if not max_by_define:
3157        cw.p(f"{max_name} = {max_value}")
3158    cw.block_end(line=';')
3159    if max_by_define:
3160        cw.p(f"#define {max_name} {max_value}")
3161    cw.nl()
3162
3163
3164def render_uapi_directional(family, cw, max_by_define):
3165    max_name = f"{family.op_prefix}USER_MAX"
3166    cnt_name = f"__{family.op_prefix}USER_CNT"
3167    max_value = f"({cnt_name} - 1)"
3168
3169    cw.block_start(line='enum')
3170    cw.p(c_upper(f'{family.name}_MSG_USER_NONE = 0,'))
3171    val = 0
3172    for op in family.msgs.values():
3173        if 'do' in op and 'event' not in op:
3174            suffix = ','
3175            if op.value and op.value != val:
3176                suffix = f" = {op.value},"
3177                val = op.value
3178            cw.p(op.enum_name + suffix)
3179            val += 1
3180    cw.nl()
3181    cw.p(cnt_name + ('' if max_by_define else ','))
3182    if not max_by_define:
3183        cw.p(f"{max_name} = {max_value}")
3184    cw.block_end(line=';')
3185    if max_by_define:
3186        cw.p(f"#define {max_name} {max_value}")
3187    cw.nl()
3188
3189    max_name = f"{family.op_prefix}KERNEL_MAX"
3190    cnt_name = f"__{family.op_prefix}KERNEL_CNT"
3191    max_value = f"({cnt_name} - 1)"
3192
3193    cw.block_start(line='enum')
3194    cw.p(c_upper(f'{family.name}_MSG_KERNEL_NONE = 0,'))
3195    val = 0
3196    for op in family.msgs.values():
3197        if ('do' in op and 'reply' in op['do']) or 'notify' in op or 'event' in op:
3198            enum_name = op.enum_name
3199            if 'event' not in op and 'notify' not in op:
3200                enum_name = f'{enum_name}_REPLY'
3201
3202            suffix = ','
3203            if op.value and op.value != val:
3204                suffix = f" = {op.value},"
3205                val = op.value
3206            cw.p(enum_name + suffix)
3207            val += 1
3208    cw.nl()
3209    cw.p(cnt_name + ('' if max_by_define else ','))
3210    if not max_by_define:
3211        cw.p(f"{max_name} = {max_value}")
3212    cw.block_end(line=';')
3213    if max_by_define:
3214        cw.p(f"#define {max_name} {max_value}")
3215    cw.nl()
3216
3217
3218def render_uapi(family, cw):
3219    hdr_prot = f"_UAPI_LINUX_{c_upper(family.uapi_header_name)}_H"
3220    hdr_prot = hdr_prot.replace('/', '_')
3221    cw.p('#ifndef ' + hdr_prot)
3222    cw.p('#define ' + hdr_prot)
3223    cw.nl()
3224
3225    defines = [(family.fam_key, family["name"]),
3226               (family.ver_key, family.get('version', 1))]
3227    cw.writes_defines(defines)
3228    cw.nl()
3229
3230    defines = []
3231    for const in family['definitions']:
3232        if const.get('header'):
3233            continue
3234        if const.get('scope', 'uapi') != 'uapi':
3235            continue
3236
3237        if const['type'] != 'const':
3238            cw.writes_defines(defines)
3239            defines = []
3240            cw.nl()
3241
3242        # Write kdoc for enum and flags (one day maybe also structs)
3243        if const['type'] == 'enum' or const['type'] == 'flags':
3244            enum = family.consts[const['name']]
3245
3246            if enum.header:
3247                continue
3248
3249            if enum.has_doc():
3250                if enum.has_entry_doc():
3251                    cw.p('/**')
3252                    doc = ''
3253                    if 'doc' in enum:
3254                        doc = ' - ' + enum['doc']
3255                    cw.write_doc_line(enum.enum_name + doc)
3256                else:
3257                    cw.p('/*')
3258                    cw.write_doc_line(enum['doc'], indent=False)
3259                for entry in enum.entries.values():
3260                    if entry.has_doc():
3261                        doc = '@' + entry.c_name + ': ' + entry['doc']
3262                        cw.write_doc_line(doc)
3263                cw.p(' */')
3264
3265            uapi_enum_start(family, cw, const, 'name')
3266            name_pfx = const.get('name-prefix', f"{family.ident_name}-{const['name']}-")
3267            for entry in enum.entries.values():
3268                suffix = ','
3269                if entry.value_change:
3270                    suffix = f" = {entry.user_value()}" + suffix
3271                cw.p(entry.c_name + suffix)
3272
3273            if const.get('render-max', False):
3274                cw.nl()
3275                cw.p('/* private: */')
3276                if const['type'] == 'flags':
3277                    max_name = c_upper(name_pfx + 'mask')
3278                    max_val = f' = {enum.get_mask()},'
3279                    cw.p(max_name + max_val)
3280                else:
3281                    cnt_name = enum.enum_cnt_name
3282                    max_name = c_upper(name_pfx + 'max')
3283                    if not cnt_name:
3284                        cnt_name = '__' + name_pfx + 'max'
3285                    cw.p(c_upper(cnt_name) + ',')
3286                    cw.p(max_name + ' = (' + c_upper(cnt_name) + ' - 1)')
3287            cw.block_end(line=';')
3288            cw.nl()
3289        elif const['type'] == 'const':
3290            name_pfx = const.get('name-prefix', f"{family.ident_name}-")
3291            defines.append([c_upper(family.get('c-define-name',
3292                                               f"{name_pfx}{const['name']}")),
3293                            const['value']])
3294
3295    if defines:
3296        cw.writes_defines(defines)
3297        cw.nl()
3298
3299    max_by_define = family.get('max-by-define', False)
3300
3301    for _, attr_set in family.attr_sets.items():
3302        if attr_set.subset_of:
3303            continue
3304
3305        max_value = f"({attr_set.cnt_name} - 1)"
3306
3307        val = 0
3308        uapi_enum_start(family, cw, attr_set.yaml, 'enum-name')
3309        for _, attr in attr_set.items():
3310            suffix = ','
3311            if attr.value != val:
3312                suffix = f" = {attr.value},"
3313                val = attr.value
3314            val += 1
3315            cw.p(attr.enum_name + suffix)
3316        if attr_set.items():
3317            cw.nl()
3318        cw.p(attr_set.cnt_name + ('' if max_by_define else ','))
3319        if not max_by_define:
3320            cw.p(f"{attr_set.max_name} = {max_value}")
3321        cw.block_end(line=';')
3322        if max_by_define:
3323            cw.p(f"#define {attr_set.max_name} {max_value}")
3324        cw.nl()
3325
3326    # Commands
3327    separate_ntf = 'async-prefix' in family['operations']
3328
3329    if family.msg_id_model == 'unified':
3330        render_uapi_unified(family, cw, max_by_define, separate_ntf)
3331    elif family.msg_id_model == 'directional':
3332        render_uapi_directional(family, cw, max_by_define)
3333    else:
3334        raise Exception(f'Unsupported message enum-model {family.msg_id_model}')
3335
3336    if separate_ntf:
3337        uapi_enum_start(family, cw, family['operations'], enum_name='async-enum')
3338        for op in family.msgs.values():
3339            if separate_ntf and not ('notify' in op or 'event' in op):
3340                continue
3341
3342            suffix = ','
3343            if 'value' in op:
3344                suffix = f" = {op['value']},"
3345            cw.p(op.enum_name + suffix)
3346        cw.block_end(line=';')
3347        cw.nl()
3348
3349    # Multicast
3350    defines = []
3351    for grp in family.mcgrps['list']:
3352        name = grp['name']
3353        defines.append([c_upper(grp.get('c-define-name', f"{family.ident_name}-mcgrp-{name}")),
3354                        f'{name}'])
3355    cw.nl()
3356    if defines:
3357        cw.writes_defines(defines)
3358        cw.nl()
3359
3360    cw.p(f'#endif /* {hdr_prot} */')
3361
3362
3363def render_scoped_consts(family, cw, scope):
3364    defines = []
3365    for const in family['definitions']:
3366        if const['type'] != 'const':
3367            continue
3368        if const.get('header'):
3369            continue
3370        if const.get('scope') != scope:
3371            continue
3372        name_pfx = const.get('name-prefix', f"{family.ident_name}-")
3373        defines.append([
3374            c_upper(family.get('c-define-name',
3375                               f"{name_pfx}{const['name']}")),
3376            const['value']])
3377    if defines:
3378        cw.writes_defines(defines)
3379        cw.nl()
3380
3381
3382def _render_user_ntf_entry(ri, op):
3383    if not ri.family.is_classic():
3384        ri.cw.block_start(line=f"[{op.enum_name}] = ")
3385    else:
3386        crud_op = ri.family.req_by_value[op.rsp_value]
3387        ri.cw.block_start(line=f"[{crud_op.enum_name}] = ")
3388    ri.cw.p(f".alloc_sz\t= sizeof({type_name(ri, 'event')}),")
3389    ri.cw.p(f".cb\t\t= {op_prefix(ri, 'reply', deref=True)}_parse,")
3390    ri.cw.p(f".policy\t\t= &{ri.struct['reply'].render_name}_nest,")
3391    ri.cw.p(f".free\t\t= (void *){op_prefix(ri, 'notify')}_free,")
3392    ri.cw.block_end(line=',')
3393
3394
3395def render_user_family(family, cw, prototype):
3396    symbol = f'const struct ynl_family ynl_{family.c_name}_family'
3397    if prototype:
3398        cw.p(f'extern {symbol};')
3399        return
3400
3401    if family.ntfs:
3402        cw.block_start(line=f"static const struct ynl_ntf_info {family.c_name}_ntf_info[] = ")
3403        for ntf_op_name, ntf_op in family.ntfs.items():
3404            if 'notify' in ntf_op:
3405                op = family.ops[ntf_op['notify']]
3406                ri = RenderInfo(cw, family, "user", op, "notify")
3407            elif 'event' in ntf_op:
3408                ri = RenderInfo(cw, family, "user", ntf_op, "event")
3409            else:
3410                raise Exception('Invalid notification ' + ntf_op_name)
3411            _render_user_ntf_entry(ri, ntf_op)
3412        for _op_name, op in family.ops.items():
3413            if 'event' not in op:
3414                continue
3415            ri = RenderInfo(cw, family, "user", op, "event")
3416            _render_user_ntf_entry(ri, op)
3417        cw.block_end(line=";")
3418        cw.nl()
3419
3420    cw.block_start(f'{symbol} = ')
3421    cw.p(f'.name\t\t= "{family.c_name}",')
3422    if family.is_classic():
3423        cw.p('.is_classic\t= true,')
3424        cw.p(f'.classic_id\t= {family.get("protonum")},')
3425    if family.is_classic():
3426        if family.fixed_header:
3427            cw.p(f'.hdr_len\t= sizeof(struct {c_lower(family.fixed_header)}),')
3428    elif family.fixed_header:
3429        cw.p(f'.hdr_len\t= sizeof(struct genlmsghdr) + sizeof(struct {c_lower(family.fixed_header)}),')
3430    else:
3431        cw.p('.hdr_len\t= sizeof(struct genlmsghdr),')
3432    if family.ntfs:
3433        cw.p(f".ntf_info\t= {family.c_name}_ntf_info,")
3434        cw.p(f".ntf_info_size\t= YNL_ARRAY_SIZE({family.c_name}_ntf_info),")
3435    cw.block_end(line=';')
3436
3437
3438def family_contains_bitfield32(family):
3439    for _, attr_set in family.attr_sets.items():
3440        if attr_set.subset_of:
3441            continue
3442        for _, attr in attr_set.items():
3443            if attr.type == "bitfield32":
3444                return True
3445    return False
3446
3447
3448def find_kernel_root(full_path):
3449    sub_path = ''
3450    while True:
3451        sub_path = os.path.join(os.path.basename(full_path), sub_path)
3452        full_path = os.path.dirname(full_path)
3453        maintainers = os.path.join(full_path, "MAINTAINERS")
3454        if os.path.exists(maintainers):
3455            return full_path, sub_path[:-1]
3456
3457
3458def main():
3459    parser = argparse.ArgumentParser(description='Netlink simple parsing generator')
3460    parser.add_argument('--mode', dest='mode', type=str, required=True,
3461                        choices=('user', 'kernel', 'uapi'))
3462    parser.add_argument('--spec', dest='spec', type=str, required=True)
3463    parser.add_argument('--header', dest='header', action='store_true', default=None)
3464    parser.add_argument('--source', dest='header', action='store_false')
3465    parser.add_argument('--user-header', nargs='+', default=[])
3466    parser.add_argument('--cmp-out', action='store_true', default=None,
3467                        help='Do not overwrite the output file if the new output is identical to the old')
3468    parser.add_argument('--exclude-op', action='append', default=[])
3469    parser.add_argument('-o', dest='out_file', type=str, default=None)
3470    parser.add_argument('--function-prefix', dest='fn_prefix', type=str)
3471    args = parser.parse_args()
3472
3473    if args.header is None:
3474        parser.error("--header or --source is required")
3475
3476    exclude_ops = [re.compile(expr) for expr in args.exclude_op]
3477
3478    try:
3479        parsed = Family(args.spec, exclude_ops, args.fn_prefix)
3480        if parsed.license != '((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)':
3481            print('Spec license:', parsed.license)
3482            print('License must be: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)')
3483            os.sys.exit(1)
3484    except pyyaml.YAMLError as exc:
3485        print(exc)
3486        os.sys.exit(1)
3487
3488    cw = CodeWriter(BaseNlLib(), args.out_file, overwrite=not args.cmp_out)
3489
3490    _, spec_kernel = find_kernel_root(args.spec)
3491    if args.mode == 'uapi' or args.header:
3492        cw.p(f'/* SPDX-License-Identifier: {parsed.license} */')
3493    else:
3494        cw.p(f'// SPDX-License-Identifier: {parsed.license}')
3495    cw.p("/* Do not edit directly, auto-generated from: */")
3496    cw.p(f"/*\t{spec_kernel} */")
3497    cw.p(f"/* YNL-GEN {args.mode} {'header' if args.header else 'source'} */")
3498    if args.exclude_op or args.user_header or args.fn_prefix:
3499        line = ''
3500        if args.user_header:
3501            line += ' --user-header '.join([''] + args.user_header)
3502        if args.exclude_op:
3503            line += ' --exclude-op '.join([''] + args.exclude_op)
3504        if args.fn_prefix:
3505            line += f' --function-prefix {args.fn_prefix}'
3506        cw.p(f'/* YNL-ARG{line} */')
3507    cw.p('/* To regenerate run: tools/net/ynl/ynl-regen.sh */')
3508    cw.nl()
3509
3510    if args.mode == 'uapi':
3511        render_uapi(parsed, cw)
3512        return
3513
3514    hdr_prot = f"_LINUX_{parsed.c_name.upper()}_GEN_H"
3515    if args.header:
3516        cw.p('#ifndef ' + hdr_prot)
3517        cw.p('#define ' + hdr_prot)
3518        cw.nl()
3519
3520    if args.out_file:
3521        hdr_file = os.path.basename(args.out_file[:-2]) + ".h"
3522    else:
3523        hdr_file = "generated_header_file.h"
3524
3525    if args.mode == 'kernel':
3526        cw.p('#include <net/netlink.h>')
3527        cw.p('#include <net/genetlink.h>')
3528        cw.nl()
3529        if not args.header:
3530            if args.out_file:
3531                cw.p(f'#include "{hdr_file}"')
3532            cw.nl()
3533        headers = ['uapi/' + parsed.uapi_header]
3534        headers += parsed.kernel_family.get('headers', [])
3535    else:
3536        cw.p('#include <stdlib.h>')
3537        cw.p('#include <string.h>')
3538        if args.header:
3539            cw.p('#include <linux/types.h>')
3540            if family_contains_bitfield32(parsed):
3541                cw.p('#include <linux/netlink.h>')
3542        else:
3543            cw.p(f'#include "{hdr_file}"')
3544            cw.p('#include "ynl.h"')
3545        headers = []
3546    for definition in parsed['definitions'] + parsed['attribute-sets']:
3547        if 'header' not in definition:
3548            continue
3549        scope = definition.get('scope', 'uapi')
3550        if scope != 'uapi' and scope != args.mode:
3551            continue
3552        headers.append(definition['header'])
3553    if args.mode == 'user':
3554        headers.append(parsed.uapi_header)
3555    seen_header = []
3556    for one in headers:
3557        if one not in seen_header:
3558            cw.p(f"#include <{one}>")
3559            seen_header.append(one)
3560    cw.nl()
3561
3562    if args.mode == "user":
3563        if not args.header:
3564            cw.p("#include <linux/genetlink.h>")
3565            cw.nl()
3566            for one in args.user_header:
3567                cw.p(f'#include "{one}"')
3568        else:
3569            render_scoped_consts(parsed, cw, 'user')
3570            cw.p('struct ynl_sock;')
3571            cw.nl()
3572            render_user_family(parsed, cw, True)
3573        cw.nl()
3574
3575    if args.mode == "kernel":
3576        if args.header:
3577            render_scoped_consts(parsed, cw, 'kernel')
3578            for _, struct in sorted(parsed.pure_nested_structs.items()):
3579                if struct.request:
3580                    cw.p('/* Common nested types */')
3581                    break
3582            for attr_set, struct in sorted(parsed.pure_nested_structs.items()):
3583                if struct.request:
3584                    print_req_policy_fwd(cw, struct)
3585            cw.nl()
3586
3587            if parsed.kernel_policy == 'global':
3588                cw.p(f"/* Global operation policy for {parsed.name} */")
3589
3590                struct = Struct(parsed, parsed.global_policy_set, type_list=parsed.global_policy)
3591                print_req_policy_fwd(cw, struct)
3592                cw.nl()
3593
3594            if parsed.kernel_policy in {'per-op', 'split'}:
3595                for _op_name, op in parsed.ops.items():
3596                    if 'do' in op and 'event' not in op:
3597                        ri = RenderInfo(cw, parsed, args.mode, op, "do")
3598                        print_req_policy_fwd(cw, ri.struct['request'], ri=ri)
3599                        cw.nl()
3600
3601            print_kernel_op_table_hdr(parsed, cw)
3602            print_kernel_mcgrp_hdr(parsed, cw)
3603            print_kernel_family_struct_hdr(parsed, cw)
3604        else:
3605            print_kernel_policy_ranges(parsed, cw)
3606            print_kernel_policy_sparse_enum_validates(parsed, cw)
3607
3608            for _, struct in sorted(parsed.pure_nested_structs.items()):
3609                if struct.request:
3610                    cw.p('/* Common nested types */')
3611                    break
3612            for attr_set, struct in sorted(parsed.pure_nested_structs.items()):
3613                if struct.request:
3614                    print_req_policy(cw, struct)
3615            cw.nl()
3616
3617            if parsed.kernel_policy == 'global':
3618                cw.p(f"/* Global operation policy for {parsed.name} */")
3619
3620                struct = Struct(parsed, parsed.global_policy_set, type_list=parsed.global_policy)
3621                print_req_policy(cw, struct)
3622                cw.nl()
3623
3624            for _op_name, op in parsed.ops.items():
3625                if parsed.kernel_policy in {'per-op', 'split'}:
3626                    for op_mode in ['do', 'dump']:
3627                        if op_mode in op and 'request' in op[op_mode]:
3628                            cw.p(f"/* {op.enum_name} - {op_mode} */")
3629                            ri = RenderInfo(cw, parsed, args.mode, op, op_mode)
3630                            print_req_policy(cw, ri.struct['request'], ri=ri)
3631                            cw.nl()
3632
3633            print_kernel_op_table(parsed, cw)
3634            print_kernel_mcgrp_src(parsed, cw)
3635            print_kernel_family_struct_src(parsed, cw)
3636
3637    if args.mode == "user":
3638        if args.header:
3639            cw.p('/* Enums */')
3640            put_op_name_fwd(parsed, cw)
3641
3642            for name, const in parsed.consts.items():
3643                if isinstance(const, EnumSet):
3644                    put_enum_to_str_fwd(parsed, cw, const)
3645            cw.nl()
3646
3647            cw.p('/* Common nested types */')
3648            for attr_set, struct in parsed.pure_nested_structs.items():
3649                ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set)
3650                print_type_full(ri, struct)
3651
3652            for _op_name, op in parsed.ops.items():
3653                cw.p(f"/* ============== {op.enum_name} ============== */")
3654
3655                if 'do' in op and 'event' not in op:
3656                    cw.p(f"/* {op.enum_name} - do */")
3657                    ri = RenderInfo(cw, parsed, args.mode, op, "do")
3658                    print_req_type(ri)
3659                    print_req_type_helpers(ri)
3660                    cw.nl()
3661                    print_rsp_type(ri)
3662                    print_rsp_type_helpers(ri)
3663                    cw.nl()
3664                    print_req_prototype(ri)
3665                    cw.nl()
3666
3667                if 'dump' in op:
3668                    cw.p(f"/* {op.enum_name} - dump */")
3669                    ri = RenderInfo(cw, parsed, args.mode, op, 'dump')
3670                    print_req_type(ri)
3671                    print_req_type_helpers(ri)
3672                    if not ri.type_consistent or ri.type_oneside:
3673                        print_rsp_type(ri)
3674                    print_wrapped_type(ri)
3675                    print_dump_prototype(ri)
3676                    cw.nl()
3677
3678                if op.has_ntf:
3679                    cw.p(f"/* {op.enum_name} - notify */")
3680                    ri = RenderInfo(cw, parsed, args.mode, op, 'notify')
3681                    if not ri.type_consistent:
3682                        raise Exception(f'Only notifications with consistent types supported ({op.name})')
3683                    print_wrapped_type(ri)
3684
3685            for _op_name, op in parsed.ntfs.items():
3686                if 'event' in op:
3687                    ri = RenderInfo(cw, parsed, args.mode, op, 'event')
3688                    cw.p(f"/* {op.enum_name} - event */")
3689                    print_rsp_type(ri)
3690                    cw.nl()
3691                    print_wrapped_type(ri)
3692            cw.nl()
3693        else:
3694            cw.p('/* Enums */')
3695            put_op_name(parsed, cw)
3696
3697            for name, const in parsed.consts.items():
3698                if isinstance(const, EnumSet):
3699                    put_enum_to_str(parsed, cw, const)
3700            cw.nl()
3701
3702            has_recursive_nests = False
3703            cw.p('/* Policies */')
3704            for struct in parsed.pure_nested_structs.values():
3705                if struct.recursive:
3706                    put_typol_fwd(cw, struct)
3707                    has_recursive_nests = True
3708            if has_recursive_nests:
3709                cw.nl()
3710            for struct in parsed.pure_nested_structs.values():
3711                put_typol(cw, struct)
3712            for name in parsed.root_sets:
3713                struct = Struct(parsed, name)
3714                put_typol(cw, struct)
3715
3716            cw.p('/* Common nested types */')
3717            if has_recursive_nests:
3718                for attr_set, struct in parsed.pure_nested_structs.items():
3719                    ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set)
3720                    free_rsp_nested_prototype(ri)
3721                    if struct.request:
3722                        put_req_nested_prototype(ri, struct)
3723                    if struct.reply:
3724                        parse_rsp_nested_prototype(ri, struct)
3725                cw.nl()
3726            for attr_set, struct in parsed.pure_nested_structs.items():
3727                ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set)
3728
3729                free_rsp_nested(ri, struct)
3730                if struct.request:
3731                    put_req_nested(ri, struct)
3732                if struct.reply:
3733                    parse_rsp_nested(ri, struct)
3734
3735            for _op_name, op in parsed.ops.items():
3736                cw.p(f"/* ============== {op.enum_name} ============== */")
3737                if 'do' in op and 'event' not in op:
3738                    cw.p(f"/* {op.enum_name} - do */")
3739                    ri = RenderInfo(cw, parsed, args.mode, op, "do")
3740                    print_req_free(ri)
3741                    print_rsp_free(ri)
3742                    parse_rsp_msg(ri)
3743                    print_req(ri)
3744                    cw.nl()
3745
3746                if 'dump' in op:
3747                    cw.p(f"/* {op.enum_name} - dump */")
3748                    ri = RenderInfo(cw, parsed, args.mode, op, "dump")
3749                    if not ri.type_consistent or ri.type_oneside:
3750                        parse_rsp_msg(ri, deref=True)
3751                    print_req_free(ri)
3752                    print_dump_type_free(ri)
3753                    print_dump(ri)
3754                    cw.nl()
3755
3756                if op.has_ntf:
3757                    cw.p(f"/* {op.enum_name} - notify */")
3758                    ri = RenderInfo(cw, parsed, args.mode, op, 'notify')
3759                    if not ri.type_consistent:
3760                        raise Exception(f'Only notifications with consistent types supported ({op.name})')
3761                    print_ntf_type_free(ri)
3762
3763            for _op_name, op in parsed.ntfs.items():
3764                if 'event' in op:
3765                    cw.p(f"/* {op.enum_name} - event */")
3766
3767                    ri = RenderInfo(cw, parsed, args.mode, op, "do")
3768                    parse_rsp_msg(ri)
3769
3770                    ri = RenderInfo(cw, parsed, args.mode, op, "event")
3771                    print_ntf_type_free(ri)
3772            cw.nl()
3773            render_user_family(parsed, cw, False)
3774
3775    if args.header:
3776        cw.p(f'#endif /* {hdr_prot} */')
3777
3778
3779if __name__ == "__main__":
3780    main()
3781