diff options
Diffstat (limited to 'tools/net/ynl/pyynl/ynl_gen_c.py')
| -rwxr-xr-x | tools/net/ynl/pyynl/ynl_gen_c.py | 848 | 
1 files changed, 688 insertions, 160 deletions
diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py index 30c0a34b2784..76032e01c2e7 100755 --- a/tools/net/ynl/pyynl/ynl_gen_c.py +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -14,6 +14,7 @@ import yaml  sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix())  from lib import SpecFamily, SpecAttrSet, SpecAttr, SpecOperation, SpecEnumSet, SpecEnumEntry +from lib import SpecSubMessage, SpecSubMessageFormat  def c_upper(name): @@ -56,11 +57,20 @@ class Type(SpecAttr):          self.request = False          self.reply = False +        self.is_selector = False +          if 'len' in attr:              self.len = attr['len']          if 'nested-attributes' in attr: -            self.nested_attrs = attr['nested-attributes'] +            nested = attr['nested-attributes'] +        elif 'sub-message' in attr: +            nested = attr['sub-message'] +        else: +            nested = None + +        if nested: +            self.nested_attrs = nested              if self.nested_attrs == family.name:                  self.nested_render_name = c_lower(f"{family.ident_name}")              else: @@ -119,7 +129,9 @@ class Type(SpecAttr):          return c_upper(value)      def resolve(self): -        if 'name-prefix' in self.attr: +        if 'parent-sub-message' in self.attr: +            enum_name = self.attr['parent-sub-message'].enum_name +        elif 'name-prefix' in self.attr:              enum_name = f"{self.attr['name-prefix']}{self.name}"          else:              enum_name = f"{self.attr_set.name_prefix}{self.name}" @@ -142,19 +154,19 @@ class Type(SpecAttr):          return self.is_recursive() and not ri.op      def presence_type(self): -        return 'bit' +        return 'present'      def presence_member(self, space, type_filter):          if self.presence_type() != type_filter:              return -        if self.presence_type() == 'bit': +        if self.presence_type() == 'present':              pfx = '__' if space == 'user' else ''              return f"{pfx}u32 {self.c_name}:1;" -        if self.presence_type() == 'len': +        if self.presence_type() in {'len', 'count'}:              pfx = '__' if space == 'user' else '' -            return f"{pfx}u32 {self.c_name}_len;" +            return f"{pfx}u32 {self.c_name};"      def _complex_member_type(self, ri):          return None @@ -163,7 +175,7 @@ class Type(SpecAttr):          return False      def _free_lines(self, ri, var, ref): -        if self.is_multi_val() or self.presence_type() == 'len': +        if self.is_multi_val() or self.presence_type() in {'count', 'len'}:              return [f'free({var}->{ref}{self.c_name});']          return [] @@ -175,21 +187,21 @@ class Type(SpecAttr):      def arg_member(self, ri):          member = self._complex_member_type(ri)          if member: -            arg = [member + ' *' + self.c_name] +            spc = ' ' if member[-1] != '*' else '' +            arg = [member + spc + '*' + self.c_name]              if self.presence_type() == 'count':                  arg += ['unsigned int n_' + self.c_name]              return arg          raise Exception(f"Struct member not implemented for class type {self.type}")      def struct_member(self, ri): -        if self.is_multi_val(): -            ri.cw.p(f"unsigned int n_{self.c_name};")          member = self._complex_member_type(ri)          if member:              ptr = '*' if self.is_multi_val() else ''              if self.is_recursive_for_op(ri):                  ptr = '*' -            ri.cw.p(f"{member} {ptr}{self.c_name};") +            spc = ' ' if member[-1] != '*' else '' +            ri.cw.p(f"{member}{spc}{ptr}{self.c_name};")              return          members = self.arg_member(ri)          for one in members: @@ -215,10 +227,9 @@ class Type(SpecAttr):          cw.p(f'[{self.enum_name}] = {"{"} .name = "{self.name}", {typol}{"}"},')      def _attr_put_line(self, ri, var, line): -        if self.presence_type() == 'bit': -            ri.cw.p(f"if ({var}->_present.{self.c_name})") -        elif self.presence_type() == 'len': -            ri.cw.p(f"if ({var}->_present.{self.c_name}_len)") +        presence = self.presence_type() +        if presence in {'present', 'len'}: +            ri.cw.p(f"if ({var}->_{presence}.{self.c_name})")          ri.cw.p(f"{line};")      def _attr_put_simple(self, ri, var, put_type): @@ -248,7 +259,7 @@ class Type(SpecAttr):          if not self.is_multi_val():              ri.cw.p("if (ynl_attr_validate(yarg, attr))")              ri.cw.p("return YNL_PARSE_CB_ERROR;") -            if self.presence_type() == 'bit': +            if self.presence_type() == 'present':                  ri.cw.p(f"{var}->_present.{self.c_name} = 1;")          if init_lines: @@ -279,7 +290,8 @@ class Type(SpecAttr):              presence = f"{var}->{'.'.join(ref[:i] + [''])}_present.{ref[i]}"              # Every layer below last is a nest, so we know it uses bit presence              # last layer is "self" and may be a complex type -            if i == len(ref) - 1 and self.presence_type() != 'bit': +            if i == len(ref) - 1 and self.presence_type() != 'present': +                presence = f"{var}->{'.'.join(ref[:i] + [''])}_{self.presence_type()}.{ref[i]}"                  continue              code.append(presence + ' = 1;')          ref_path = '.'.join(ref[:-1]) @@ -355,26 +367,10 @@ class TypeScalar(Type):          if 'byte-order' in attr:              self.byte_order_comment = f" /* {attr['byte-order']} */" -        if 'enum' in self.attr: -            enum = self.family.consts[self.attr['enum']] -            low, high = enum.value_range() -            if 'min' not in self.checks: -                if low != 0 or self.type[0] == 's': -                    self.checks['min'] = low -            if 'max' not in self.checks: -                self.checks['max'] = high - -        if 'min' in self.checks and 'max' in self.checks: -            if self.get_limit('min') > self.get_limit('max'): -                raise Exception(f'Invalid limit for "{self.name}" min: {self.get_limit("min")} max: {self.get_limit("max")}') -            self.checks['range'] = True - -        low = min(self.get_limit('min', 0), self.get_limit('max', 0)) -        high = max(self.get_limit('min', 0), self.get_limit('max', 0)) -        if low < 0 and self.type[0] == 'u': -            raise Exception(f'Invalid limit for "{self.name}" negative limit for unsigned type') -        if low < -32768 or high > 32767: -            self.checks['full-range'] = True +        # Classic families have some funny enums, don't bother +        # computing checks, since we only need them for kernel policies +        if not family.is_classic(): +            self._init_checks()          # Added by resolve():          self.is_bitfield = None @@ -399,6 +395,31 @@ class TypeScalar(Type):          else:              self.type_name = '__' + self.type +    def _init_checks(self): +        if 'enum' in self.attr: +            enum = self.family.consts[self.attr['enum']] +            low, high = enum.value_range() +            if low == None and high == None: +                self.checks['sparse'] = True +            else: +                if 'min' not in self.checks: +                    if low != 0 or self.type[0] == 's': +                        self.checks['min'] = low +                if 'max' not in self.checks: +                    self.checks['max'] = high + +        if 'min' in self.checks and 'max' in self.checks: +            if self.get_limit('min') > self.get_limit('max'): +                raise Exception(f'Invalid limit for "{self.name}" min: {self.get_limit("min")} max: {self.get_limit("max")}') +            self.checks['range'] = True + +        low = min(self.get_limit('min', 0), self.get_limit('max', 0)) +        high = max(self.get_limit('min', 0), self.get_limit('max', 0)) +        if low < 0 and self.type[0] == 'u': +            raise Exception(f'Invalid limit for "{self.name}" negative limit for unsigned type') +        if low < -32768 or high > 32767: +            self.checks['full-range'] = True +      def _attr_policy(self, policy):          if 'flags-mask' in self.checks or self.is_bitfield:              if self.is_bitfield: @@ -417,6 +438,8 @@ class TypeScalar(Type):              return f"NLA_POLICY_MIN({policy}, {self.get_limit_str('min')})"          elif 'max' in self.checks:              return f"NLA_POLICY_MAX({policy}, {self.get_limit_str('max')})" +        elif 'sparse' in self.checks: +            return f"NLA_POLICY_VALIDATE_FN({policy}, &{c_lower(self.enum_name)}_validate)"          return super()._attr_policy(policy)      def _attr_typol(self): @@ -463,7 +486,10 @@ class TypeString(Type):          ri.cw.p(f"char *{self.c_name};")      def _attr_typol(self): -        return f'.type = YNL_PT_NUL_STR, ' +        typol = f'.type = YNL_PT_NUL_STR, ' +        if self.is_selector: +            typol += '.is_selector = 1, ' +        return typol      def _attr_policy(self, policy):          if 'exact-len' in self.checks: @@ -488,7 +514,7 @@ class TypeString(Type):          self._attr_put_simple(ri, var, 'str')      def _attr_get(self, ri, var): -        len_mem = var + '->_present.' + self.c_name + '_len' +        len_mem = var + '->_len.' + self.c_name          return [f"{len_mem} = len;",                  f"{var}->{self.c_name} = malloc(len + 1);",                  f"memcpy({var}->{self.c_name}, ynl_attr_get_str(attr), len);", @@ -497,10 +523,10 @@ class TypeString(Type):                 ['unsigned int len;']      def _setter_lines(self, ri, member, presence): -        return [f"{presence}_len = strlen({self.c_name});", -                f"{member} = malloc({presence}_len + 1);", -                f'memcpy({member}, {self.c_name}, {presence}_len);', -                f'{member}[{presence}_len] = 0;'] +        return [f"{presence} = strlen({self.c_name});", +                f"{member} = malloc({presence} + 1);", +                f'memcpy({member}, {self.c_name}, {presence});', +                f'{member}[{presence}] = 0;']  class TypeBinary(Type): @@ -539,20 +565,71 @@ class TypeBinary(Type):      def attr_put(self, ri, var):          self._attr_put_line(ri, var, f"ynl_attr_put(nlh, {self.enum_name}, " + -                            f"{var}->{self.c_name}, {var}->_present.{self.c_name}_len)") +                            f"{var}->{self.c_name}, {var}->_len.{self.c_name})") + +    def _attr_get(self, ri, var): +        len_mem = var + '->_len.' + self.c_name +        return [f"{len_mem} = len;", +                f"{var}->{self.c_name} = malloc(len);", +                f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ +               ['len = ynl_attr_data_len(attr);'], \ +               ['unsigned int len;'] + +    def _setter_lines(self, ri, member, presence): +        return [f"{presence} = len;", +                f"{member} = malloc({presence});", +                f'memcpy({member}, {self.c_name}, {presence});'] + + +class TypeBinaryStruct(TypeBinary): +    def struct_member(self, ri): +        ri.cw.p(f'struct {c_lower(self.get("struct"))} *{self.c_name};')      def _attr_get(self, ri, var): -        len_mem = var + '->_present.' + self.c_name + '_len' +        struct_sz = 'sizeof(struct ' + c_lower(self.get("struct")) + ')' +        len_mem = var + '->_' + self.presence_type() + '.' + self.c_name          return [f"{len_mem} = len;", +                f"if (len < {struct_sz})", +                f"{var}->{self.c_name} = calloc(1, {struct_sz});", +                "else", +                f"{var}->{self.c_name} = malloc(len);", +                f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ +               ['len = ynl_attr_data_len(attr);'], \ +               ['unsigned int len;'] + + +class TypeBinaryScalarArray(TypeBinary): +    def arg_member(self, ri): +        return [f'__{self.get("sub-type")} *{self.c_name}', 'size_t count'] + +    def presence_type(self): +        return 'count' + +    def struct_member(self, ri): +        ri.cw.p(f'__{self.get("sub-type")} *{self.c_name};') + +    def attr_put(self, ri, var): +        presence = self.presence_type() +        ri.cw.block_start(line=f"if ({var}->_{presence}.{self.c_name})") +        ri.cw.p(f"i = {var}->_{presence}.{self.c_name} * sizeof(__{self.get('sub-type')});") +        ri.cw.p(f"ynl_attr_put(nlh, {self.enum_name}, " + +                f"{var}->{self.c_name}, i);") +        ri.cw.block_end() + +    def _attr_get(self, ri, var): +        len_mem = var + '->_count.' + self.c_name +        return [f"{len_mem} = len / sizeof(__{self.get('sub-type')});", +                f"len = {len_mem} * sizeof(__{self.get('sub-type')});",                  f"{var}->{self.c_name} = malloc(len);",                  f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \                 ['len = ynl_attr_data_len(attr);'], \                 ['unsigned int len;']      def _setter_lines(self, ri, member, presence): -        return [f"{presence}_len = len;", -                f"{member} = malloc({presence}_len);", -                f'memcpy({member}, {self.c_name}, {presence}_len);'] +        return [f"{presence} = count;", +                f"count *= sizeof(__{self.get('sub-type')});", +                f"{member} = malloc(count);", +                f'memcpy({member}, {self.c_name}, count);']  class TypeBitfield32(Type): @@ -608,7 +685,11 @@ class TypeNest(Type):                              f"{self.enum_name}, {at}{var}->{self.c_name})")      def _attr_get(self, ri, var): -        get_lines = [f"if ({self.nested_render_name}_parse(&parg, attr))", +        pns = self.family.pure_nested_structs[self.nested_attrs] +        args = ["&parg", "attr"] +        for sel in pns.external_selectors(): +            args.append(f'{var}->{sel.name}') +        get_lines = [f"if ({self.nested_render_name}_parse({', '.join(args)}))",                       "return YNL_PARSE_CB_ERROR;"]          init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;",                        f"parg.data = &{var}->{self.c_name};"] @@ -638,22 +719,40 @@ class TypeMultiAttr(Type):      def _complex_member_type(self, ri):          if 'type' not in self.attr or self.attr['type'] == 'nest':              return self.nested_struct_type +        elif self.attr['type'] == 'binary' and 'struct' in self.attr: +            return None  # use arg_member() +        elif self.attr['type'] == 'string': +            return 'struct ynl_string *'          elif self.attr['type'] in scalars:              scalar_pfx = '__' if ri.ku_space == 'user' else ''              return scalar_pfx + self.attr['type']          else:              raise Exception(f"Sub-type {self.attr['type']} not supported yet") +    def arg_member(self, ri): +        if self.type == 'binary' and 'struct' in self.attr: +            return [f'struct {c_lower(self.attr["struct"])} *{self.c_name}', +                    f'unsigned int n_{self.c_name}'] +        return super().arg_member(ri) +      def free_needs_iter(self): -        return 'type' not in self.attr or self.attr['type'] == 'nest' +        return self.attr['type'] in {'nest', 'string'}      def _free_lines(self, ri, var, ref):          lines = []          if self.attr['type'] in scalars:              lines += [f"free({var}->{ref}{self.c_name});"] +        elif self.attr['type'] == 'binary': +            lines += [f"free({var}->{ref}{self.c_name});"] +        elif self.attr['type'] == 'string': +            lines += [ +                f"for (i = 0; i < {var}->{ref}_count.{self.c_name}; i++)", +                f"free({var}->{ref}{self.c_name}[i]);", +                f"free({var}->{ref}{self.c_name});", +            ]          elif 'type' not in self.attr or self.attr['type'] == 'nest':              lines += [ -                f"for (i = 0; i < {var}->{ref}n_{self.c_name}; i++)", +                f"for (i = 0; i < {var}->{ref}_count.{self.c_name}; i++)",                  f'{self.nested_render_name}_free(&{var}->{ref}{self.c_name}[i]);',                  f"free({var}->{ref}{self.c_name});",              ] @@ -673,18 +772,22 @@ class TypeMultiAttr(Type):      def attr_put(self, ri, var):          if self.attr['type'] in scalars:              put_type = self.type -            ri.cw.p(f"for (i = 0; i < {var}->n_{self.c_name}; i++)") +            ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")              ri.cw.p(f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name}[i]);") +        elif self.attr['type'] == 'binary' and 'struct' in self.attr: +            ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)") +            ri.cw.p(f"ynl_attr_put(nlh, {self.enum_name}, &{var}->{self.c_name}[i], sizeof(struct {c_lower(self.attr['struct'])}));") +        elif self.attr['type'] == 'string': +            ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)") +            ri.cw.p(f"ynl_attr_put_str(nlh, {self.enum_name}, {var}->{self.c_name}[i]->str);")          elif 'type' not in self.attr or self.attr['type'] == 'nest': -            ri.cw.p(f"for (i = 0; i < {var}->n_{self.c_name}; i++)") +            ri.cw.p(f"for (i = 0; i < {var}->_count.{self.c_name}; i++)")              self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " +                                  f"{self.enum_name}, &{var}->{self.c_name}[i])")          else:              raise Exception(f"Put of MultiAttr sub-type {self.attr['type']} not supported yet")      def _setter_lines(self, ri, member, presence): -        # For multi-attr we have a count, not presence, hack up the presence -        presence = presence[:-(len('_present.') + len(self.c_name))] + "n_" + self.c_name          return [f"{member} = {self.c_name};",                  f"{presence} = n_{self.c_name};"] @@ -702,12 +805,22 @@ class TypeArrayNest(Type):          elif self.attr['sub-type'] in scalars:              scalar_pfx = '__' if ri.ku_space == 'user' else ''              return scalar_pfx + self.attr['sub-type'] +        elif self.attr['sub-type'] == 'binary' and 'exact-len' in self.checks: +            return None  # use arg_member()          else:              raise Exception(f"Sub-type {self.attr['sub-type']} not supported yet") +    def arg_member(self, ri): +        if self.sub_type == 'binary' and 'exact-len' in self.checks: +            return [f'unsigned char (*{self.c_name})[{self.checks["exact-len"]}]', +                    f'unsigned int n_{self.c_name}'] +        return super().arg_member(ri) +      def _attr_typol(self):          if self.attr['sub-type'] in scalars:              return f'.type = YNL_PT_U{c_upper(self.sub_type[1:])}, ' +        elif self.attr['sub-type'] == 'binary' and 'exact-len' in self.checks: +            return f'.type = YNL_PT_BINARY, .len = {self.checks["exact-len"]}, '          else:              return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' @@ -717,10 +830,31 @@ class TypeArrayNest(Type):                       'ynl_attr_for_each_nested(attr2, attr) {',                       '\tif (ynl_attr_validate(yarg, attr2))',                       '\t\treturn YNL_PARSE_CB_ERROR;', -                     f'\t{var}->n_{self.c_name}++;', +                     f'\t{var}->_count.{self.c_name}++;',                       '}']          return get_lines, None, local_vars +    def attr_put(self, ri, var): +        ri.cw.p(f'array = ynl_attr_nest_start(nlh, {self.enum_name});') +        if self.sub_type in scalars: +            put_type = self.sub_type +            ri.cw.block_start(line=f'for (i = 0; i < {var}->_count.{self.c_name}; i++)') +            ri.cw.p(f"ynl_attr_put_{put_type}(nlh, i, {var}->{self.c_name}[i]);") +            ri.cw.block_end() +        elif self.sub_type == 'binary' and 'exact-len' in self.checks: +            ri.cw.p(f'for (i = 0; i < {var}->_count.{self.c_name}; i++)') +            ri.cw.p(f"ynl_attr_put(nlh, i, {var}->{self.c_name}[i], {self.checks['exact-len']});") +        elif self.sub_type == 'nest': +            ri.cw.p(f'for (i = 0; i < {var}->_count.{self.c_name}; i++)') +            ri.cw.p(f"{self.nested_render_name}_put(nlh, i, &{var}->{self.c_name}[i]);") +        else: +            raise Exception(f"Put for ArrayNest sub-type {self.attr['sub-type']} not supported, yet") +        ri.cw.p('ynl_attr_nest_end(nlh, array);') + +    def _setter_lines(self, ri, member, presence): +        return [f"{member} = {self.c_name};", +                f"{presence} = n_{self.c_name};"] +  class TypeNestTypeValue(Type):      def _complex_member_type(self, ri): @@ -752,14 +886,71 @@ class TypeNestTypeValue(Type):          return get_lines, init_lines, local_vars +class TypeSubMessage(TypeNest): +    def __init__(self, family, attr_set, attr, value): +        super().__init__(family, attr_set, attr, value) + +        self.selector = Selector(attr, attr_set) + +    def _attr_typol(self): +        typol = f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' +        typol += '.is_submsg = 1, ' +        # Reverse-parsing of the policy (ynl_err_walk() in ynl.c) does not +        # support external selectors. No family uses sub-messages with external +        # selector for requests so this is fine for now. +        if not self.selector.is_external(): +            typol += f'.selector_type = {self.attr_set[self["selector"]].value} ' +        return typol + +    def _attr_get(self, ri, var): +        sel = c_lower(self['selector']) +        if self.selector.is_external(): +            sel_var = f"_sel_{sel}" +        else: +            sel_var = f"{var}->{sel}" +        get_lines = [f'if (!{sel_var})', +                     f'return ynl_submsg_failed(yarg, "%s", "%s");' % +                        (self.name, self['selector']), +                    f"if ({self.nested_render_name}_parse(&parg, {sel_var}, attr))", +                     "return YNL_PARSE_CB_ERROR;"] +        init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;", +                      f"parg.data = &{var}->{self.c_name};"] +        return get_lines, init_lines, None + + +class Selector: +    def __init__(self, msg_attr, attr_set): +        self.name = msg_attr["selector"] + +        if self.name in attr_set: +            self.attr = attr_set[self.name] +            self.attr.is_selector = True +            self._external = False +        else: +            # The selector will need to get passed down thru the structs +            self.attr = None +            self._external = True + +    def set_attr(self, attr): +        self.attr = attr + +    def is_external(self): +        return self._external + +  class Struct: -    def __init__(self, family, space_name, type_list=None, inherited=None): +    def __init__(self, family, space_name, type_list=None, fixed_header=None, +                 inherited=None, submsg=None):          self.family = family          self.space_name = space_name          self.attr_set = family.attr_sets[space_name]          # Use list to catch comparisons with empty sets          self._inherited = inherited if inherited is not None else []          self.inherited = [] +        self.fixed_header = None +        if fixed_header: +            self.fixed_header = 'struct ' + c_lower(fixed_header) +        self.submsg = submsg          self.nested = type_list is None          if family.name == c_lower(space_name): @@ -809,6 +1000,19 @@ class Struct:              raise Exception("Inheriting different members not supported")          self.inherited = [c_lower(x) for x in sorted(self._inherited)] +    def external_selectors(self): +        sels = [] +        for name, attr in self.attr_list: +            if isinstance(attr, TypeSubMessage) and attr.selector.is_external(): +                sels.append(attr.selector) +        return sels + +    def free_needs_iter(self): +        for _, attr in self.attr_list: +            if attr.free_needs_iter(): +                return True +        return False +  class EnumEntry(SpecEnumEntry):      def __init__(self, enum_set, yaml, prev, value_start): @@ -862,7 +1066,7 @@ class EnumSet(SpecEnumSet):          high = max([x.value for x in self.entries.values()])          if high - low + 1 != len(self.entries): -            raise Exception("Can't get value range for a noncontiguous enum") +            return None, None          return low, high @@ -909,18 +1113,25 @@ class AttrSet(SpecAttrSet):          elif elem['type'] == 'string':              t = TypeString(self.family, self, elem, value)          elif elem['type'] == 'binary': -            t = TypeBinary(self.family, self, elem, value) +            if 'struct' in elem: +                t = TypeBinaryStruct(self.family, self, elem, value) +            elif elem.get('sub-type') in scalars: +                t = TypeBinaryScalarArray(self.family, self, elem, value) +            else: +                t = TypeBinary(self.family, self, elem, value)          elif elem['type'] == 'bitfield32':              t = TypeBitfield32(self.family, self, elem, value)          elif elem['type'] == 'nest':              t = TypeNest(self.family, self, elem, value)          elif elem['type'] == 'indexed-array' and 'sub-type' in elem: -            if elem["sub-type"] in ['nest', 'u32']: +            if elem["sub-type"] in ['binary', 'nest', 'u32']:                  t = TypeArrayNest(self.family, self, elem, value)              else:                  raise Exception(f'new_attr: unsupported sub-type {elem["sub-type"]}')          elif elem['type'] == 'nest-type-value':              t = TypeNestTypeValue(self.family, self, elem, value) +        elif elem['type'] == 'sub-message': +            t = TypeSubMessage(self.family, self, elem, value)          else:              raise Exception(f"No typed class for type {elem['type']}") @@ -932,6 +1143,14 @@ class AttrSet(SpecAttrSet):  class Operation(SpecOperation):      def __init__(self, family, yaml, req_value, rsp_value): +        # Fill in missing operation properties (for fixed hdr-only msgs) +        for mode in ['do', 'dump', 'event']: +            for direction in ['request', 'reply']: +                try: +                    yaml[mode][direction].setdefault('attributes', []) +                except KeyError: +                    pass +          super().__init__(family, yaml, req_value, rsp_value)          self.render_name = c_lower(family.ident_name + '_' + self.name) @@ -957,6 +1176,16 @@ class Operation(SpecOperation):          self.has_ntf = True +class SubMessage(SpecSubMessage): +    def __init__(self, family, yaml): +        super().__init__(family, yaml) + +        self.render_name = c_lower(family.ident_name + '-' + yaml['name']) + +    def resolve(self): +        self.resolve_up(super()) + +  class Family(SpecFamily):      def __init__(self, file_name, exclude_ops):          # Added by resolve: @@ -993,9 +1222,6 @@ class Family(SpecFamily):      def resolve(self):          self.resolve_up(super()) -        if self.yaml.get('protocol', 'genetlink') not in {'genetlink', 'genetlink-c', 'genetlink-legacy'}: -            raise Exception("Codegen only supported for genetlink") -          self.c_name = c_lower(self.ident_name)          if 'name-prefix' in self.yaml['operations']:              self.op_prefix = c_upper(self.yaml['operations']['name-prefix']) @@ -1018,7 +1244,7 @@ class Family(SpecFamily):          # dict space-name -> 'request': set(attrs), 'reply': set(attrs)          self.root_sets = dict() -        # dict space-name -> set('request', 'reply') +        # dict space-name -> Struct          self.pure_nested_structs = dict()          self._mark_notify() @@ -1027,6 +1253,7 @@ class Family(SpecFamily):          self._load_root_sets()          self._load_nested_sets()          self._load_attr_use() +        self._load_selector_passing()          self._load_hooks()          self.kernel_policy = self.yaml.get('kernel-policy', 'split') @@ -1042,6 +1269,12 @@ class Family(SpecFamily):      def new_operation(self, elem, req_value, rsp_value):          return Operation(self, elem, req_value, rsp_value) +    def new_sub_message(self, elem): +        return SubMessage(self, elem) + +    def is_classic(self): +        return self.proto == 'netlink-raw' +      def _mark_notify(self):          for op in self.msgs.values():              if 'notify' in op: @@ -1091,20 +1324,85 @@ class Family(SpecFamily):              for _, spec in self.attr_sets[name].items():                  if 'nested-attributes' in spec:                      nested = spec['nested-attributes'] -                    # If the unknown nest we hit is recursive it's fine, it'll be a pointer -                    if self.pure_nested_structs[nested].recursive: -                        continue -                    if nested not in pns_key_seen: -                        # Dicts are sorted, this will make struct last -                        struct = self.pure_nested_structs.pop(name) -                        self.pure_nested_structs[name] = struct -                        finished = False -                        break +                elif 'sub-message' in spec: +                    nested = spec.sub_message +                else: +                    continue + +                # If the unknown nest we hit is recursive it's fine, it'll be a pointer +                if self.pure_nested_structs[nested].recursive: +                    continue +                if nested not in pns_key_seen: +                    # Dicts are sorted, this will make struct last +                    struct = self.pure_nested_structs.pop(name) +                    self.pure_nested_structs[name] = struct +                    finished = False +                    break              if finished:                  pns_key_seen.add(name)              else:                  pns_key_list.append(name) +    def _load_nested_set_nest(self, spec): +        inherit = set() +        nested = spec['nested-attributes'] +        if nested not in self.root_sets: +            if nested not in self.pure_nested_structs: +                self.pure_nested_structs[nested] = \ +                    Struct(self, nested, inherited=inherit, +                           fixed_header=spec.get('fixed-header')) +        else: +            raise Exception(f'Using attr set as root and nested not supported - {nested}') + +        if 'type-value' in spec: +            if nested in self.root_sets: +                raise Exception("Inheriting members to a space used as root not supported") +            inherit.update(set(spec['type-value'])) +        elif spec['type'] == 'indexed-array': +            inherit.add('idx') +        self.pure_nested_structs[nested].set_inherited(inherit) + +        return nested + +    def _load_nested_set_submsg(self, spec): +        # Fake the struct type for the sub-message itself +        # its not a attr_set but codegen wants attr_sets. +        submsg = self.sub_msgs[spec["sub-message"]] +        nested = submsg.name + +        attrs = [] +        for name, fmt in submsg.formats.items(): +            attr = { +                "name": name, +                "parent-sub-message": spec, +            } +            if 'attribute-set' in fmt: +                attr |= { +                    "type": "nest", +                    "nested-attributes": fmt['attribute-set'], +                } +                if 'fixed-header' in fmt: +                    attr |= { "fixed-header": fmt["fixed-header"] } +            elif 'fixed-header' in fmt: +                attr |= { +                    "type": "binary", +                    "struct": fmt["fixed-header"], +                } +            else: +                attr["type"] = "flag" +            attrs.append(attr) + +        self.attr_sets[nested] = AttrSet(self, { +            "name": nested, +            "name-pfx": self.name + '-' + spec.name + '-', +            "attributes": attrs +        }) + +        if nested not in self.pure_nested_structs: +            self.pure_nested_structs[nested] = Struct(self, nested, submsg=submsg) + +        return nested +      def _load_nested_sets(self):          attr_set_queue = list(self.root_sets.keys())          attr_set_seen = set(self.root_sets.keys()) @@ -1112,61 +1410,61 @@ class Family(SpecFamily):          while len(attr_set_queue):              a_set = attr_set_queue.pop(0)              for attr, spec in self.attr_sets[a_set].items(): -                if 'nested-attributes' not in spec: +                if 'nested-attributes' in spec: +                    nested = self._load_nested_set_nest(spec) +                elif 'sub-message' in spec: +                    nested = self._load_nested_set_submsg(spec) +                else:                      continue -                nested = spec['nested-attributes']                  if nested not in attr_set_seen:                      attr_set_queue.append(nested)                      attr_set_seen.add(nested) -                inherit = set() -                if nested not in self.root_sets: -                    if nested not in self.pure_nested_structs: -                        self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit) -                else: -                    raise Exception(f'Using attr set as root and nested not supported - {nested}') - -                if 'type-value' in spec: -                    if nested in self.root_sets: -                        raise Exception("Inheriting members to a space used as root not supported") -                    inherit.update(set(spec['type-value'])) -                elif spec['type'] == 'indexed-array': -                    inherit.add('idx') -                self.pure_nested_structs[nested].set_inherited(inherit) -          for root_set, rs_members in self.root_sets.items():              for attr, spec in self.attr_sets[root_set].items():                  if 'nested-attributes' in spec:                      nested = spec['nested-attributes'] +                elif 'sub-message' in spec: +                    nested = spec.sub_message +                else: +                    nested = None + +                if nested:                      if attr in rs_members['request']:                          self.pure_nested_structs[nested].request = True                      if attr in rs_members['reply']:                          self.pure_nested_structs[nested].reply = True -                if spec.is_multi_val(): -                    child = self.pure_nested_structs.get(nested) -                    child.in_multi_val = True +                    if spec.is_multi_val(): +                        child = self.pure_nested_structs.get(nested) +                        child.in_multi_val = True          self._sort_pure_types()          # Propagate the request / reply / recursive          for attr_set, struct in reversed(self.pure_nested_structs.items()):              for _, spec in self.attr_sets[attr_set].items(): -                if 'nested-attributes' in spec: -                    child_name = spec['nested-attributes'] -                    struct.child_nests.add(child_name) -                    child = self.pure_nested_structs.get(child_name) -                    if child: -                        if not child.recursive: -                            struct.child_nests.update(child.child_nests) -                        child.request |= struct.request -                        child.reply |= struct.reply -                        if spec.is_multi_val(): -                            child.in_multi_val = True                  if attr_set in struct.child_nests:                      struct.recursive = True +                if 'nested-attributes' in spec: +                    child_name = spec['nested-attributes'] +                elif 'sub-message' in spec: +                    child_name = spec.sub_message +                else: +                    continue + +                struct.child_nests.add(child_name) +                child = self.pure_nested_structs.get(child_name) +                if child: +                    if not child.recursive: +                        struct.child_nests.update(child.child_nests) +                    child.request |= struct.request +                    child.reply |= struct.reply +                    if spec.is_multi_val(): +                        child.in_multi_val = True +          self._sort_pure_types()      def _load_attr_use(self): @@ -1185,6 +1483,30 @@ class Family(SpecFamily):                  if attr in rs_members['reply']:                      spec.set_reply() +    def _load_selector_passing(self): +        def all_structs(): +            for k, v in reversed(self.pure_nested_structs.items()): +                yield k, v +            for k, _ in self.root_sets.items(): +                yield k, None  # we don't have a struct, but it must be terminal + +        for attr_set, struct in all_structs(): +            for _, spec in self.attr_sets[attr_set].items(): +                if 'nested-attributes' in spec: +                    child_name = spec['nested-attributes'] +                elif 'sub-message' in spec: +                    child_name = spec.sub_message +                else: +                    continue + +                child = self.pure_nested_structs.get(child_name) +                for selector in child.external_selectors(): +                    if selector.name in self.attr_sets[attr_set]: +                        sel_attr = self.attr_sets[attr_set][selector.name] +                        selector.set_attr(sel_attr) +                    else: +                        raise Exception("Passing selector thru more than one layer not supported") +      def _load_global_policy(self):          global_set = set()          attr_set_name = None @@ -1234,12 +1556,19 @@ class RenderInfo:          self.op_mode = op_mode          self.op = op -        self.fixed_hdr = None +        fixed_hdr = op.fixed_header if op else None +        self.fixed_hdr_len = 'ys->family->hdr_len'          if op and op.fixed_header: -            self.fixed_hdr = 'struct ' + c_lower(op.fixed_header) +            if op.fixed_header != family.fixed_header: +                if family.is_classic(): +                    self.fixed_hdr_len = f"sizeof(struct {c_lower(fixed_hdr)})" +                else: +                    raise Exception(f"Per-op fixed header not supported, yet") +          # 'do' and 'dump' response parsing is identical          self.type_consistent = True +        self.type_oneside = False          if op_mode != 'do' and 'dump' in op:              if 'do' in op:                  if ('reply' in op['do']) != ('reply' in op["dump"]): @@ -1247,7 +1576,8 @@ class RenderInfo:                  elif 'reply' in op['do'] and op["do"]["reply"] != op["dump"]["reply"]:                      self.type_consistent = False              else: -                self.type_consistent = False +                self.type_consistent = True +                self.type_oneside = True          self.attr_set = attr_set          if not self.attr_set: @@ -1265,15 +1595,26 @@ class RenderInfo:          self.struct = dict()          if op_mode == 'notify': -            op_mode = 'do' +            op_mode = 'do' if 'do' in op else 'dump'          for op_dir in ['request', 'reply']:              if op:                  type_list = []                  if op_dir in op[op_mode]:                      type_list = op[op_mode][op_dir]['attributes'] -                self.struct[op_dir] = Struct(family, self.attr_set, type_list=type_list) +                self.struct[op_dir] = Struct(family, self.attr_set, +                                             fixed_header=fixed_hdr, +                                             type_list=type_list)          if op_mode == 'event': -            self.struct['reply'] = Struct(family, self.attr_set, type_list=op['event']['attributes']) +            self.struct['reply'] = Struct(family, self.attr_set, +                                          fixed_header=fixed_hdr, +                                          type_list=op['event']['attributes']) + +    def type_empty(self, key): +        return len(self.struct[key].attr_list) == 0 and \ +            self.struct['request'].fixed_header is None + +    def needs_nlflags(self, direction): +        return self.op_mode == 'do' and direction == 'request' and self.family.is_classic()  class CodeWriter: @@ -1331,6 +1672,7 @@ class CodeWriter:          if self._silent_block:              ind += 1          self._silent_block = line.endswith(')') and CodeWriter._is_cond(line) +        self._silent_block |= line.strip() == 'else'          if line[0] == '#':              ind = 0          if add_ind: @@ -1541,7 +1883,9 @@ def op_prefix(ri, direction, deref=False):          suffix += f"{direction_to_suffix[direction]}"      else:          if direction == 'request': -            suffix += '_req_dump' +            suffix += '_req' +            if not ri.type_oneside: +                suffix += '_dump'          else:              if ri.type_consistent:                  if deref: @@ -1585,11 +1929,37 @@ def print_dump_prototype(ri):      print_prototype(ri, "request") +def put_typol_submsg(cw, struct): +    cw.block_start(line=f'const struct ynl_policy_attr {struct.render_name}_policy[] =') + +    i = 0 +    for name, arg in struct.member_list(): +        nest = "" +        if arg.type == 'nest': +            nest = f" .nest = &{arg.nested_render_name}_nest," +        cw.p('[%d] = { .type = YNL_PT_SUBMSG, .name = "%s",%s },' % +             (i, name, nest)) +        i += 1 + +    cw.block_end(line=';') +    cw.nl() + +    cw.block_start(line=f'const struct ynl_policy_nest {struct.render_name}_nest =') +    cw.p(f'.max_attr = {i - 1},') +    cw.p(f'.table = {struct.render_name}_policy,') +    cw.block_end(line=';') +    cw.nl() + +  def put_typol_fwd(cw, struct):      cw.p(f'extern const struct ynl_policy_nest {struct.render_name}_nest;')  def put_typol(cw, struct): +    if struct.submsg: +        put_typol_submsg(cw, struct) +        return +      type_max = struct.attr_set.max_name      cw.block_start(line=f'const struct ynl_policy_attr {struct.render_name}_policy[{type_max} + 1] =') @@ -1675,13 +2045,24 @@ def put_req_nested(ri, struct):      local_vars = []      init_lines = [] -    local_vars.append('struct nlattr *nest;') -    init_lines.append("nest = ynl_attr_nest_start(nlh, attr_type);") - +    if struct.submsg is None: +        local_vars.append('struct nlattr *nest;') +        init_lines.append("nest = ynl_attr_nest_start(nlh, attr_type);") +    if struct.fixed_header: +        local_vars.append('void *hdr;') +        struct_sz = f'sizeof({struct.fixed_header})' +        init_lines.append(f"hdr = ynl_nlmsg_put_extra_header(nlh, {struct_sz});") +        init_lines.append(f"memcpy(hdr, &obj->_hdr, {struct_sz});") + +    has_anest = False +    has_count = False      for _, arg in struct.member_list(): -        if arg.presence_type() == 'count': -            local_vars.append('unsigned int i;') -            break +        has_anest |= arg.type == 'indexed-array' +        has_count |= arg.presence_type() == 'count' +    if has_anest: +        local_vars.append('struct nlattr *array;') +    if has_count: +        local_vars.append('unsigned int i;')      put_req_nested_prototype(ri, struct, suffix='')      ri.cw.block_start() @@ -1693,7 +2074,8 @@ def put_req_nested(ri, struct):      for _, arg in struct.member_list():          arg.attr_put(ri, "obj") -    ri.cw.p("ynl_attr_nest_end(nlh, nest);") +    if struct.submsg is None: +        ri.cw.p("ynl_attr_nest_end(nlh, nest);")      ri.cw.nl()      ri.cw.p('return 0;') @@ -1702,19 +2084,27 @@ def put_req_nested(ri, struct):  def _multi_parse(ri, struct, init_lines, local_vars): +    if struct.fixed_header: +        local_vars += ['void *hdr;']      if struct.nested: -        iter_line = "ynl_attr_for_each_nested(attr, nested)" +        if struct.fixed_header: +            iter_line = f"ynl_attr_for_each_nested_off(attr, nested, sizeof({struct.fixed_header}))" +        else: +            iter_line = "ynl_attr_for_each_nested(attr, nested)"      else: -        if ri.fixed_hdr: -            local_vars += ['void *hdr;']          iter_line = "ynl_attr_for_each(attr, nlh, yarg->ys->family->hdr_len)" +        if ri.op.fixed_header != ri.family.fixed_header: +            if ri.family.is_classic(): +                iter_line = f"ynl_attr_for_each(attr, nlh, sizeof({struct.fixed_header}))" +            else: +                raise Exception(f"Per-op fixed header not supported, yet")      array_nests = set()      multi_attrs = set()      needs_parg = False      for arg, aspec in struct.member_list():          if aspec['type'] == 'indexed-array' and 'sub-type' in aspec: -            if aspec["sub-type"] == 'nest': +            if aspec["sub-type"] in {'binary', 'nest'}:                  local_vars.append(f'const struct nlattr *attr_{aspec.c_name};')                  array_nests.add(arg)              elif aspec['sub-type'] in scalars: @@ -1725,6 +2115,7 @@ def _multi_parse(ri, struct, init_lines, local_vars):          if 'multi-attr' in aspec:              multi_attrs.add(arg)          needs_parg |= 'nested-attributes' in aspec +        needs_parg |= 'sub-message' in aspec      if array_nests or multi_attrs:          local_vars.append('int i;')      if needs_parg: @@ -1746,9 +2137,14 @@ def _multi_parse(ri, struct, init_lines, local_vars):      for arg in struct.inherited:          ri.cw.p(f'dst->{arg} = {arg};') -    if ri.fixed_hdr: -        ri.cw.p('hdr = ynl_nlmsg_data_offset(nlh, sizeof(struct genlmsghdr));') -        ri.cw.p(f"memcpy(&dst->_hdr, hdr, sizeof({ri.fixed_hdr}));") +    if struct.fixed_header: +        if struct.nested: +            ri.cw.p('hdr = ynl_attr_data(nested);') +        elif ri.family.is_classic(): +            ri.cw.p('hdr = ynl_nlmsg_data(nlh);') +        else: +            ri.cw.p('hdr = ynl_nlmsg_data_offset(nlh, sizeof(struct genlmsghdr));') +        ri.cw.p(f"memcpy(&dst->_hdr, hdr, sizeof({struct.fixed_header}));")      for anest in sorted(all_multi):          aspec = struct[anest]          ri.cw.p(f"if (dst->{aspec.c_name})") @@ -1773,7 +2169,7 @@ def _multi_parse(ri, struct, init_lines, local_vars):          ri.cw.block_start(line=f"if (n_{aspec.c_name})")          ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") -        ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};") +        ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};")          ri.cw.p('i = 0;')          if 'nested-attributes' in aspec:              ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;") @@ -1784,6 +2180,9 @@ def _multi_parse(ri, struct, init_lines, local_vars):              ri.cw.p('return YNL_PARSE_CB_ERROR;')          elif aspec.sub_type in scalars:              ri.cw.p(f"dst->{aspec.c_name}[i] = ynl_attr_get_{aspec.sub_type}(attr);") +        elif aspec.sub_type == 'binary' and 'exact-len' in aspec.checks: +            # Length is validated by typol +            ri.cw.p(f'memcpy(dst->{aspec.c_name}[i], ynl_attr_data(attr), {aspec.checks["exact-len"]});')          else:              raise Exception(f"Nest parsing type not supported in {aspec['name']}")          ri.cw.p('i++;') @@ -1795,7 +2194,7 @@ def _multi_parse(ri, struct, init_lines, local_vars):          aspec = struct[anest]          ri.cw.block_start(line=f"if (n_{aspec.c_name})")          ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") -        ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};") +        ri.cw.p(f"dst->_count.{aspec.c_name} = n_{aspec.c_name};")          ri.cw.p('i = 0;')          if 'nested-attributes' in aspec:              ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;") @@ -1807,8 +2206,22 @@ def _multi_parse(ri, struct, init_lines, local_vars):              ri.cw.p('return YNL_PARSE_CB_ERROR;')          elif aspec.type in scalars:              ri.cw.p(f"dst->{aspec.c_name}[i] = ynl_attr_get_{aspec.type}(attr);") +        elif aspec.type == 'binary' and 'struct' in aspec: +            ri.cw.p('size_t len = ynl_attr_data_len(attr);') +            ri.cw.nl() +            ri.cw.p(f'if (len > sizeof(dst->{aspec.c_name}[0]))') +            ri.cw.p(f'len = sizeof(dst->{aspec.c_name}[0]);') +            ri.cw.p(f"memcpy(&dst->{aspec.c_name}[i], ynl_attr_data(attr), len);") +        elif aspec.type == 'string': +            ri.cw.p('unsigned int len;') +            ri.cw.nl() +            ri.cw.p('len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));') +            ri.cw.p(f'dst->{aspec.c_name}[i] = malloc(sizeof(struct ynl_string) + len + 1);') +            ri.cw.p(f"dst->{aspec.c_name}[i]->len = len;") +            ri.cw.p(f"memcpy(dst->{aspec.c_name}[i]->str, ynl_attr_get_str(attr), len);") +            ri.cw.p(f"dst->{aspec.c_name}[i]->str[len] = 0;")          else: -            raise Exception('Nest parsing type not supported yet') +            raise Exception(f'Nest parsing of type {aspec.type} not supported yet')          ri.cw.p('i++;')          ri.cw.block_end()          ri.cw.block_end() @@ -1823,9 +2236,49 @@ def _multi_parse(ri, struct, init_lines, local_vars):      ri.cw.nl() +def parse_rsp_submsg(ri, struct): +    parse_rsp_nested_prototype(ri, struct, suffix='') + +    var = 'dst' +    local_vars = {'const struct nlattr *attr = nested;', +                  f'{struct.ptr_name}{var} = yarg->data;', +                  'struct ynl_parse_arg parg;'} + +    for _, arg in struct.member_list(): +        _, _, l_vars = arg._attr_get(ri, var) +        local_vars |= set(l_vars) if l_vars else set() + +    ri.cw.block_start() +    ri.cw.write_func_lvar(list(local_vars)) +    ri.cw.p('parg.ys = yarg->ys;') +    ri.cw.nl() + +    first = True +    for name, arg in struct.member_list(): +        kw = 'if' if first else 'else if' +        first = False + +        ri.cw.block_start(line=f'{kw} (!strcmp(sel, "{name}"))') +        get_lines, init_lines, _ = arg._attr_get(ri, var) +        for line in init_lines or []: +            ri.cw.p(line) +        for line in get_lines: +            ri.cw.p(line) +        if arg.presence_type() == 'present': +            ri.cw.p(f"{var}->_present.{arg.c_name} = 1;") +        ri.cw.block_end() +    ri.cw.p('return 0;') +    ri.cw.block_end() +    ri.cw.nl() + +  def parse_rsp_nested_prototype(ri, struct, suffix=';'):      func_args = ['struct ynl_parse_arg *yarg',                   'const struct nlattr *nested'] +    for sel in struct.external_selectors(): +        func_args.append('const char *_sel_' + sel.name) +    if struct.submsg: +        func_args.insert(1, 'const char *sel')      for arg in struct.inherited:          func_args.append('__u32 ' + arg) @@ -1834,6 +2287,9 @@ def parse_rsp_nested_prototype(ri, struct, suffix=';'):  def parse_rsp_nested(ri, struct): +    if struct.submsg: +        return parse_rsp_submsg(ri, struct) +      parse_rsp_nested_prototype(ri, struct, suffix='')      local_vars = ['const struct nlattr *attr;', @@ -1886,7 +2342,7 @@ def print_req(ri):          ret_err = 'NULL'          local_vars += [f'{type_name(ri, rdir(direction))} *rsp;'] -    if ri.fixed_hdr: +    if ri.struct["request"].fixed_header:          local_vars += ['size_t hdr_len;',                         'void *hdr;'] @@ -1899,14 +2355,18 @@ def print_req(ri):      ri.cw.block_start()      ri.cw.write_func_lvar(local_vars) -    ri.cw.p(f"nlh = ynl_gemsg_start_req(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);") +    if ri.family.is_classic(): +        ri.cw.p(f"nlh = ynl_msg_start_req(ys, {ri.op.enum_name}, req->_nlmsg_flags);") +    else: +        ri.cw.p(f"nlh = ynl_gemsg_start_req(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);")      ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;") +    ri.cw.p(f"ys->req_hdr_len = {ri.fixed_hdr_len};")      if 'reply' in ri.op[ri.op_mode]:          ri.cw.p(f"yrs.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;")      ri.cw.nl() -    if ri.fixed_hdr: +    if ri.struct['request'].fixed_header:          ri.cw.p("hdr_len = sizeof(req->_hdr);")          ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);")          ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);") @@ -1952,7 +2412,7 @@ def print_dump(ri):                    'struct nlmsghdr *nlh;',                    'int err;'] -    if ri.fixed_hdr: +    if ri.struct['request'].fixed_header:          local_vars += ['size_t hdr_len;',                         'void *hdr;'] @@ -1968,9 +2428,12 @@ def print_dump(ri):      else:          ri.cw.p(f'yds.rsp_cmd = {ri.op.rsp_value};')      ri.cw.nl() -    ri.cw.p(f"nlh = ynl_gemsg_start_dump(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);") +    if ri.family.is_classic(): +        ri.cw.p(f"nlh = ynl_msg_start_dump(ys, {ri.op.enum_name});") +    else: +        ri.cw.p(f"nlh = ynl_gemsg_start_dump(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);") -    if ri.fixed_hdr: +    if ri.struct['request'].fixed_header:          ri.cw.p("hdr_len = sizeof(req->_hdr);")          ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);")          ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);") @@ -1978,6 +2441,7 @@ def print_dump(ri):      if "request" in ri.op[ri.op_mode]:          ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;") +        ri.cw.p(f"ys->req_hdr_len = {ri.fixed_hdr_len};")          ri.cw.nl()          for _, attr in ri.struct["request"].member_list():              attr.attr_put(ri, "req") @@ -2023,32 +2487,45 @@ def print_free_prototype(ri, direction, suffix=';'):      ri.cw.write_func_prot('void', f"{name}_free", [f"struct {struct_name} *{arg}"], suffix=suffix) +def print_nlflags_set(ri, direction): +    name = op_prefix(ri, direction) +    ri.cw.write_func_prot(f'static inline void', f"{name}_set_nlflags", +                          [f"struct {name} *req", "__u16 nl_flags"]) +    ri.cw.block_start() +    ri.cw.p('req->_nlmsg_flags = nl_flags;') +    ri.cw.block_end() +    ri.cw.nl() + +  def _print_type(ri, direction, struct):      suffix = f'_{ri.type_name}{direction_to_suffix[direction]}'      if not direction and ri.type_name_conflict:          suffix += '_' -    if ri.op_mode == 'dump': +    if ri.op_mode == 'dump' and not ri.type_oneside:          suffix += '_dump'      ri.cw.block_start(line=f"struct {ri.family.c_name}{suffix}") -    if ri.fixed_hdr: -        ri.cw.p(ri.fixed_hdr + ' _hdr;') +    if ri.needs_nlflags(direction): +        ri.cw.p('__u16 _nlmsg_flags;') +        ri.cw.nl() +    if struct.fixed_header: +        ri.cw.p(struct.fixed_header + ' _hdr;')          ri.cw.nl() -    meta_started = False -    for _, attr in struct.member_list(): -        for type_filter in ['len', 'bit']: +    for type_filter in ['present', 'len', 'count']: +        meta_started = False +        for _, attr in struct.member_list():              line = attr.presence_member(ri.ku_space, type_filter)              if line:                  if not meta_started:                      ri.cw.block_start(line=f"struct")                      meta_started = True                  ri.cw.p(line) -    if meta_started: -        ri.cw.block_end(line='_present;') -        ri.cw.nl() +        if meta_started: +            ri.cw.block_end(line=f'_{type_filter};') +    ri.cw.nl()      for arg in struct.inherited:          ri.cw.p(f"__u32 {arg};") @@ -2072,6 +2549,9 @@ def print_type_helpers(ri, direction, deref=False):      print_free_prototype(ri, direction)      ri.cw.nl() +    if ri.needs_nlflags(direction): +        print_nlflags_set(ri, direction) +      if ri.ku_space == 'user' and direction == 'request':          for _, attr in ri.struct[direction].member_list():              attr.setter(ri, ri.attr_set, direction, deref=deref) @@ -2079,7 +2559,7 @@ def print_type_helpers(ri, direction, deref=False):  def print_req_type_helpers(ri): -    if len(ri.struct["request"].attr_list) == 0: +    if ri.type_empty("request"):          return      print_alloc_wrapper(ri, "request")      print_type_helpers(ri, "request") @@ -2102,7 +2582,7 @@ def print_parse_prototype(ri, direction, terminate=True):  def print_req_type(ri): -    if len(ri.struct["request"].attr_list) == 0: +    if ri.type_empty("request"):          return      print_type(ri, "request") @@ -2140,11 +2620,9 @@ def print_wrapped_type(ri):  def _free_type_members_iter(ri, struct): -    for _, attr in struct.member_list(): -        if attr.free_needs_iter(): -            ri.cw.p('unsigned int i;') -            ri.cw.nl() -            break +    if struct.free_needs_iter(): +        ri.cw.p('unsigned int i;') +        ri.cw.nl()  def _free_type_members(ri, var, struct, ref=''): @@ -2281,6 +2759,46 @@ def print_kernel_policy_ranges(family, cw):              cw.nl() +def print_kernel_policy_sparse_enum_validates(family, cw): +    first = True +    for _, attr_set in family.attr_sets.items(): +        if attr_set.subset_of: +            continue + +        for _, attr in attr_set.items(): +            if not attr.request: +                continue +            if not attr.enum_name: +                continue +            if 'sparse' not in attr.checks: +                continue + +            if first: +                cw.p('/* Sparse enums validation callbacks */') +                first = False + +            sign = '' if attr.type[0] == 'u' else '_signed' +            suffix = 'ULL' if attr.type[0] == 'u' else 'LL' +            cw.write_func_prot('static int', f'{c_lower(attr.enum_name)}_validate', +                               ['const struct nlattr *attr', 'struct netlink_ext_ack *extack']) +            cw.block_start() +            cw.block_start(line=f'switch (nla_get_{attr["type"]}(attr))') +            enum = family.consts[attr['enum']] +            first_entry = True +            for entry in enum.entries.values(): +                if first_entry: +                    first_entry = False +                else: +                    cw.p('fallthrough;') +                cw.p(f'case {entry.c_name}:') +            cw.p('return 0;') +            cw.block_end() +            cw.p('NL_SET_ERR_MSG_ATTR(extack, attr, "invalid enum value");') +            cw.p('return -EINVAL;') +            cw.block_end() +            cw.nl() + +  def print_kernel_op_table_fwd(family, cw, terminate):      exported = not kernel_can_gen_family_struct(family) @@ -2740,7 +3258,11 @@ def render_uapi(family, cw):  def _render_user_ntf_entry(ri, op): -    ri.cw.block_start(line=f"[{op.enum_name}] = ") +    if not ri.family.is_classic(): +        ri.cw.block_start(line=f"[{op.enum_name}] = ") +    else: +        crud_op = ri.family.req_by_value[op.rsp_value] +        ri.cw.block_start(line=f"[{crud_op.enum_name}] = ")      ri.cw.p(f".alloc_sz\t= sizeof({type_name(ri, 'event')}),")      ri.cw.p(f".cb\t\t= {op_prefix(ri, 'reply', deref=True)}_parse,")      ri.cw.p(f".policy\t\t= &{ri.struct['reply'].render_name}_nest,") @@ -2755,7 +3277,7 @@ def render_user_family(family, cw, prototype):          return      if family.ntfs: -        cw.block_start(line=f"static const struct ynl_ntf_info {family['name']}_ntf_info[] = ") +        cw.block_start(line=f"static const struct ynl_ntf_info {family.c_name}_ntf_info[] = ")          for ntf_op_name, ntf_op in family.ntfs.items():              if 'notify' in ntf_op:                  op = family.ops[ntf_op['notify']] @@ -2775,13 +3297,19 @@ def render_user_family(family, cw, prototype):      cw.block_start(f'{symbol} = ')      cw.p(f'.name\t\t= "{family.c_name}",') -    if family.fixed_header: +    if family.is_classic(): +        cw.p(f'.is_classic\t= true,') +        cw.p(f'.classic_id\t= {family.get("protonum")},') +    if family.is_classic(): +        if family.fixed_header: +            cw.p(f'.hdr_len\t= sizeof(struct {c_lower(family.fixed_header)}),') +    elif family.fixed_header:          cw.p(f'.hdr_len\t= sizeof(struct genlmsghdr) + sizeof(struct {c_lower(family.fixed_header)}),')      else:          cw.p('.hdr_len\t= sizeof(struct genlmsghdr),')      if family.ntfs: -        cw.p(f".ntf_info\t= {family['name']}_ntf_info,") -        cw.p(f".ntf_info_size\t= YNL_ARRAY_SIZE({family['name']}_ntf_info),") +        cw.p(f".ntf_info\t= {family.c_name}_ntf_info,") +        cw.p(f".ntf_info_size\t= YNL_ARRAY_SIZE({family.c_name}_ntf_info),")      cw.block_end(line=';') @@ -2888,7 +3416,7 @@ def main():              cw.p(f'#include "{hdr_file}"')              cw.p('#include "ynl.h"')          headers = [] -    for definition in parsed['definitions']: +    for definition in parsed['definitions'] + parsed['attribute-sets']:          if 'header' in definition:              headers.append(definition['header'])      if args.mode == 'user': @@ -2942,6 +3470,7 @@ def main():              print_kernel_family_struct_hdr(parsed, cw)          else:              print_kernel_policy_ranges(parsed, cw) +            print_kernel_policy_sparse_enum_validates(parsed, cw)              for _, struct in sorted(parsed.pure_nested_structs.items()):                  if struct.request: @@ -3010,7 +3539,7 @@ def main():                      ri = RenderInfo(cw, parsed, args.mode, op, 'dump')                      print_req_type(ri)                      print_req_type_helpers(ri) -                    if not ri.type_consistent: +                    if not ri.type_consistent or ri.type_oneside:                          print_rsp_type(ri)                      print_wrapped_type(ri)                      print_dump_prototype(ri) @@ -3048,8 +3577,7 @@ def main():                      has_recursive_nests = True              if has_recursive_nests:                  cw.nl() -            for name in parsed.pure_nested_structs: -                struct = Struct(parsed, name) +            for struct in parsed.pure_nested_structs.values():                  put_typol(cw, struct)              for name in parsed.root_sets:                  struct = Struct(parsed, name) @@ -3088,7 +3616,7 @@ def main():                  if 'dump' in op:                      cw.p(f"/* {op.enum_name} - dump */")                      ri = RenderInfo(cw, parsed, args.mode, op, "dump") -                    if not ri.type_consistent: +                    if not ri.type_consistent or ri.type_oneside:                          parse_rsp_msg(ri, deref=True)                      print_req_free(ri)                      print_dump_type_free(ri)  | 
