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