Skip to content

Commit 6728009

Browse files
chrboekawasaki
authored andcommitted
tools: ynl-gen-c: optionally emit structs and helpers
Add a "emit-structs" option to the genetlink-legacy spec. Enabling "emit-structs" adds struct declarations for nested attribute sets to the generated kernel headers. It also adds some useful serialization helpers: - from_attrs() with 'required' attribute enforcement - to_skb() for struct-to-netlink serialization - set_defaults() driven by the 'default' YAML key The motivation is to replace the existing deprecated genl_magic system. Some genl_magic features are dropped entirely because they had no significant users, some are carried over to YNL (the genetlink-legacy spec). The new flags in the genetlink-legacy spec that are required for existing consumers to keep working are: "default": a literal value or C define that sets the default value for an attribute, consumed by set_defaults(). "required": if true, from_attrs() returns an error when this attribute is missing from the request message. "nla-policy-type": can be used to override the NLA type used in policy arrays. This is needed when the semantic type differs from the wire type for backward compatibility: genl_magic maps s32 fields to NLA_U32/nla_get_u32, and existing userspace might depend on this encoding. The immediate motivation is DRBD, whose genl spec definition predates the addition of signed types in genl. However, this is a generic issue that potentially affects multiple families: for example, nftables has NFTA_HOOK_PRIORITY as s32 in the spec but NLA_U32 in the actual kernel policy. All new properties are backward-compatible; existing specs that do not use them are unaffected. Signed-off-by: Christoph Böhmwalder <christoph.boehmwalder@linbit.com>
1 parent 6e7de26 commit 6728009

2 files changed

Lines changed: 313 additions & 3 deletions

File tree

Documentation/netlink/genetlink-legacy.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,23 @@ properties:
270270
For string attributes, do not check whether attribute
271271
contains the terminating null character.
272272
type: boolean
273+
default:
274+
description: |
275+
Default value expression (C macro or literal) for this attribute.
276+
Used to generate set_defaults() initialization functions.
277+
type: [ string, integer ]
278+
required:
279+
description: |
280+
If true, from_attrs() returns an error when this attribute is
281+
missing from the request message.
282+
type: boolean
283+
nla-policy-type:
284+
description: |
285+
Override the NLA type used in kernel policy arrays. Use this when
286+
the semantic type differs from the wire type for backward compat
287+
(e.g., s32 fields that must use NLA_U32 on the wire because
288+
userspace predates NLA_S32 support).
289+
enum: [ u8, u16, u32, u64, s8, s16, s32, s64 ]
273290
sub-type: *attr-type
274291
display-hint: *display-hint
275292
# Start genetlink-c
@@ -471,3 +488,9 @@ properties:
471488
to store the socket state. The type / structure is internal
472489
to the kernel, and is not defined in the spec.
473490
type: string
491+
emit-structs:
492+
description: |
493+
Generate C struct declarations and serialization helpers
494+
(from_attrs, to_skb, set_defaults) in the kernel header
495+
and source.
496+
type: boolean

tools/net/ynl/pyynl/ynl_gen_c.py

Lines changed: 290 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,11 @@ def _attr_policy(self, policy):
225225
return '{ .type = ' + policy + ', }'
226226

227227
def attr_policy(self, cw):
228-
policy = f'NLA_{c_upper(self.type)}'
228+
policy_type = self.attr.get('nla-policy-type', self.type)
229+
policy = f'NLA_{c_upper(policy_type)}'
229230
if self.attr.get('byte-order') == 'big-endian':
230-
if self.type in {'u16', 'u32'}:
231-
policy = f'NLA_BE{self.type[1:]}'
231+
if policy_type in {'u16', 'u32'}:
232+
policy = f'NLA_BE{policy_type[1:]}'
232233

233234
spec = self._attr_policy(policy)
234235
cw.p(f"\t[{self.enum_name}] = {spec},")
@@ -3415,6 +3416,255 @@ def find_kernel_root(full_path):
34153416
return full_path, sub_path[:-1]
34163417

34173418

3419+
def _struct_c_type(attr_type):
3420+
"""Map YNL attribute type to C type for struct field declarations."""
3421+
type_map = {
3422+
'u8': 'unsigned char', 'u16': '__u16', 'u32': '__u32', 'u64': '__u64',
3423+
's8': '__s8', 's16': '__s16', 's32': '__s32', 's64': '__s64',
3424+
}
3425+
return type_map.get(attr_type)
3426+
3427+
3428+
def _nested_attr_sets(family):
3429+
"""Yield (name, attr_set) for non-root attr-sets in spec order.
3430+
3431+
The root attr-set (same name as family) contains nest-type attributes
3432+
that point to the nested sets. Only the nested sets have scalar/array
3433+
fields that translate to struct members.
3434+
"""
3435+
root_name = family['name']
3436+
for name, attr_set in family.attr_sets.items():
3437+
if name == root_name or attr_set.subset_of:
3438+
continue
3439+
yield name, attr_set
3440+
3441+
3442+
def render_struct_decl(family, cw):
3443+
"""Generate C struct declarations from nested attribute sets."""
3444+
for set_name, attr_set in _nested_attr_sets(family):
3445+
s_name = c_lower(set_name)
3446+
cw.p(f"struct {s_name} {{")
3447+
for _, attr in attr_set.items():
3448+
c_name = c_lower(attr.name)
3449+
c_type = _struct_c_type(attr['type'])
3450+
if c_type:
3451+
cw.p(f"\t{c_type} {c_name};")
3452+
elif attr['type'] in ('string', 'binary'):
3453+
maxlen = attr.get('checks', {}).get('max-len', 0)
3454+
cw.p(f"\tchar {c_name}[{maxlen}];")
3455+
cw.p(f"\t__u32 {c_name}_len;")
3456+
cw.p('};')
3457+
cw.nl()
3458+
3459+
3460+
def _nla_get_fn(attr_type):
3461+
"""Return the nla_get function name for a scalar type."""
3462+
fn_map = {
3463+
'u8': 'nla_get_u8', 'u16': 'nla_get_u16',
3464+
'u32': 'nla_get_u32', 'u64': 'nla_get_u64',
3465+
's8': 'nla_get_s8', 's16': 'nla_get_s16',
3466+
's32': 'nla_get_s32', 's64': 'nla_get_s64',
3467+
}
3468+
return fn_map.get(attr_type)
3469+
3470+
3471+
def _nla_put_fn(attr_type):
3472+
"""Return (function_name, extra_args) for a scalar nla_put."""
3473+
fn_map = {
3474+
'u8': ('nla_put_u8', ''),
3475+
'u16': ('nla_put_u16', ''),
3476+
'u32': ('nla_put_u32', ''),
3477+
'u64': ('nla_put_u64_64bit', ', 0'),
3478+
's8': ('nla_put_s8', ''),
3479+
's16': ('nla_put_s16', ''),
3480+
's32': ('nla_put_s32', ''),
3481+
's64': ('nla_put_s64', ''),
3482+
}
3483+
return fn_map.get(attr_type)
3484+
3485+
3486+
def render_from_attrs(family, cw):
3487+
"""Generate from_attrs() deserialization functions."""
3488+
root_set = family.attr_sets.get(family['name'])
3489+
3490+
for set_name, attr_set in _nested_attr_sets(family):
3491+
s_name = c_lower(set_name)
3492+
struct = family.pure_nested_structs.get(set_name)
3493+
if not struct or not struct.request:
3494+
continue
3495+
tla_name = None
3496+
if root_set:
3497+
for _, tla_attr in root_set.items():
3498+
if tla_attr.attr.get('nested-attributes') == set_name:
3499+
tla_name = tla_attr.enum_name
3500+
break
3501+
if tla_name is None:
3502+
continue
3503+
3504+
policy_name = f"{struct.render_name}_nl_policy"
3505+
max_attr = struct.attr_max_val.enum_name
3506+
cw.p(f"static int __{s_name}_from_attrs(struct {s_name} *s,")
3507+
cw.p(f"\t\tstruct nlattr ***ret_nested_attribute_table,")
3508+
cw.p(f"\t\tstruct genl_info *info)")
3509+
cw.block_start()
3510+
cw.p(f"const int maxtype = {max_attr};")
3511+
cw.p(f"struct nlattr *tla = info->attrs[{tla_name}];")
3512+
cw.p('struct nlattr **ntb;')
3513+
cw.p('struct nlattr *nla;')
3514+
cw.p('int err = 0;')
3515+
cw.nl()
3516+
cw.p('if (ret_nested_attribute_table)')
3517+
cw.p('*ret_nested_attribute_table = NULL;')
3518+
cw.p('if (!tla)')
3519+
cw.p('return -ENOMSG;')
3520+
cw.p(f"ntb = kcalloc({max_attr} + 1, sizeof(*ntb), GFP_KERNEL);")
3521+
cw.p('if (!ntb)')
3522+
cw.p('return -ENOMEM;')
3523+
cw.p(f"err = nla_parse_nested_deprecated(ntb, maxtype, tla, {policy_name}, NULL);")
3524+
cw.p('if (err)')
3525+
cw.p('goto out;')
3526+
cw.nl()
3527+
3528+
for _, attr in attr_set.items():
3529+
c_name = c_lower(attr.name)
3530+
is_required = attr.attr.get('required', False)
3531+
get_fn = _nla_get_fn(attr['type'])
3532+
3533+
cw.p(f"nla = ntb[{attr.enum_name}];")
3534+
if is_required:
3535+
cw.block_start(line='if (nla)')
3536+
if get_fn:
3537+
cw.p('if (s)')
3538+
cw.p(f"s->{c_name} = {get_fn}(nla);")
3539+
elif attr['type'] == 'string':
3540+
maxlen = attr.get('checks', {}).get('max-len', 0)
3541+
cw.p('if (s)')
3542+
cw.p(f"s->{c_name}_len = nla_strscpy(s->{c_name}, nla, {maxlen});")
3543+
elif attr['type'] == 'binary':
3544+
maxlen = attr.get('checks', {}).get('max-len', 0)
3545+
cw.p('if (s)')
3546+
cw.p(f"s->{c_name}_len = nla_memcpy(s->{c_name}, nla, {maxlen});")
3547+
cw.block_end()
3548+
cw.block_start(line='else')
3549+
cw.p(f'pr_info("<< missing required attr: {c_name}\\n");')
3550+
cw.p('err = -ENOMSG;')
3551+
cw.block_end()
3552+
else:
3553+
if get_fn:
3554+
cw.p('if (nla && s)')
3555+
cw.p(f"s->{c_name} = {get_fn}(nla);")
3556+
elif attr['type'] == 'string':
3557+
maxlen = attr.get('checks', {}).get('max-len', 0)
3558+
cw.p('if (nla && s)')
3559+
cw.p(f"s->{c_name}_len = nla_strscpy(s->{c_name}, nla, {maxlen});")
3560+
elif attr['type'] == 'binary':
3561+
maxlen = attr.get('checks', {}).get('max-len', 0)
3562+
cw.p('if (nla && s)')
3563+
cw.p(f"s->{c_name}_len = nla_memcpy(s->{c_name}, nla, {maxlen});")
3564+
cw.nl()
3565+
3566+
cw.p('out:')
3567+
cw.p('if (ret_nested_attribute_table && (!err || err == -ENOMSG))')
3568+
cw.p('*ret_nested_attribute_table = ntb;')
3569+
cw.p('else')
3570+
cw.p('kfree(ntb);')
3571+
cw.p('return err;')
3572+
cw.block_end()
3573+
cw.nl()
3574+
3575+
cw.p(f"int {s_name}_from_attrs(struct {s_name} *s,")
3576+
cw.p(f"\t\t\t\tstruct genl_info *info)")
3577+
cw.block_start()
3578+
cw.p(f"return __{s_name}_from_attrs(s, NULL, info);")
3579+
cw.block_end()
3580+
cw.nl()
3581+
3582+
cw.p(f"int {s_name}_ntb_from_attrs(")
3583+
cw.p(f"\t\t\tstruct nlattr ***ret_nested_attribute_table,")
3584+
cw.p(f"\t\t\tstruct genl_info *info)")
3585+
cw.block_start()
3586+
cw.p(f"return __{s_name}_from_attrs(NULL, ret_nested_attribute_table, info);")
3587+
cw.block_end()
3588+
cw.nl()
3589+
3590+
3591+
def render_to_skb(family, cw):
3592+
"""Generate to_skb() serialization functions."""
3593+
root_set = family.attr_sets.get(family['name'])
3594+
3595+
for set_name, attr_set in _nested_attr_sets(family):
3596+
s_name = c_lower(set_name)
3597+
tla_name = None
3598+
if root_set:
3599+
for _, tla_attr in root_set.items():
3600+
if tla_attr.attr.get('nested-attributes') == set_name:
3601+
tla_name = tla_attr.enum_name
3602+
break
3603+
if tla_name is None:
3604+
continue
3605+
3606+
cw.p(f"int {s_name}_to_skb(struct sk_buff *skb, struct {s_name} *s)")
3607+
cw.block_start()
3608+
cw.p(f"struct nlattr *tla = nla_nest_start(skb, {tla_name});")
3609+
cw.nl()
3610+
cw.p('if (!tla)')
3611+
cw.p('goto nla_put_failure;')
3612+
cw.nl()
3613+
3614+
for _, attr in attr_set.items():
3615+
c_name = c_lower(attr.name)
3616+
put = _nla_put_fn(attr['type'])
3617+
3618+
if put:
3619+
fn, extra = put
3620+
cw.p(f"if ({fn}(skb, {attr.enum_name}, s->{c_name}{extra}))")
3621+
cw.p('goto nla_put_failure;')
3622+
elif attr['type'] in ('string', 'binary'):
3623+
maxlen = attr.get('checks', {}).get('max-len', 0)
3624+
nul_adj = f" + (s->{c_name}_len < {maxlen})" if attr['type'] == 'string' else ''
3625+
cw.p(f"if (nla_put(skb, {attr.enum_name}, min_t(int, {maxlen},")
3626+
cw.p(f"\t\ts->{c_name}_len{nul_adj}), s->{c_name}))")
3627+
cw.p('goto nla_put_failure;', add_ind=1)
3628+
3629+
cw.nl()
3630+
cw.p('nla_nest_end(skb, tla);')
3631+
cw.p('return 0;')
3632+
cw.nl()
3633+
cw.p('nla_put_failure:')
3634+
cw.p('if (tla)')
3635+
cw.p('nla_nest_cancel(skb, tla);')
3636+
cw.p('return -EMSGSIZE;')
3637+
cw.block_end()
3638+
cw.nl()
3639+
3640+
3641+
def render_set_defaults(family, cw):
3642+
"""Generate set_defaults() initialization functions."""
3643+
for set_name, attr_set in _nested_attr_sets(family):
3644+
s_name = c_lower(set_name)
3645+
has_defaults = any(
3646+
'default' in attr.attr for _, attr in attr_set.items()
3647+
)
3648+
if not has_defaults:
3649+
continue
3650+
3651+
cw.p(f"void set_{s_name}_defaults(struct {s_name} *x)")
3652+
cw.block_start()
3653+
for _, attr in attr_set.items():
3654+
c_name = c_lower(attr.name)
3655+
default = attr.attr.get('default')
3656+
if default is None:
3657+
continue
3658+
3659+
if attr['type'] in ('string', 'binary'):
3660+
cw.p(f"memset(x->{c_name}, 0, sizeof(x->{c_name}));")
3661+
cw.p(f"x->{c_name}_len = 0;")
3662+
else:
3663+
cw.p(f"x->{c_name} = {default};")
3664+
cw.block_end()
3665+
cw.nl()
3666+
3667+
34183668
def main():
34193669
parser = argparse.ArgumentParser(description='Netlink simple parsing generator')
34203670
parser.add_argument('--mode', dest='mode', type=str, required=True,
@@ -3487,6 +3737,9 @@ def main():
34873737
cw.p('#include <net/genetlink.h>')
34883738
cw.nl()
34893739
if not args.header:
3740+
if parsed.kernel_family.get('emit-structs'):
3741+
cw.p('#include <linux/kernel.h>')
3742+
cw.p('#include <linux/slab.h>')
34903743
if args.out_file:
34913744
cw.p(f'#include "{hdr_file}"')
34923745
cw.nl()
@@ -3555,6 +3808,31 @@ def main():
35553808
print_kernel_op_table_hdr(parsed, cw)
35563809
print_kernel_mcgrp_hdr(parsed, cw)
35573810
print_kernel_family_struct_hdr(parsed, cw)
3811+
3812+
if parsed.kernel_family.get('emit-structs'):
3813+
cw.nl()
3814+
render_struct_decl(parsed, cw)
3815+
# Function prototypes
3816+
root_set = parsed.attr_sets.get(parsed['name'])
3817+
for set_name, attr_set in _nested_attr_sets(parsed):
3818+
s_name = c_lower(set_name)
3819+
struct = parsed.pure_nested_structs.get(set_name)
3820+
has_tla = False
3821+
if root_set:
3822+
for _, tla_attr in root_set.items():
3823+
if tla_attr.attr.get('nested-attributes') == set_name:
3824+
has_tla = True
3825+
break
3826+
if not has_tla:
3827+
continue
3828+
if struct and struct.request:
3829+
cw.p(f"int {s_name}_from_attrs(struct {s_name} *s, struct genl_info *info);")
3830+
cw.p(f"int {s_name}_ntb_from_attrs(struct nlattr ***ret_nested_attribute_table, struct genl_info *info);")
3831+
cw.p(f"int {s_name}_to_skb(struct sk_buff *skb, struct {s_name} *s);")
3832+
has_defaults = any('default' in a.attr for _, a in attr_set.items())
3833+
if has_defaults:
3834+
cw.p(f"void set_{s_name}_defaults(struct {s_name} *x);")
3835+
cw.nl()
35583836
else:
35593837
print_kernel_policy_ranges(parsed, cw)
35603838
print_kernel_policy_sparse_enum_validates(parsed, cw)
@@ -3588,6 +3866,15 @@ def main():
35883866
print_kernel_mcgrp_src(parsed, cw)
35893867
print_kernel_family_struct_src(parsed, cw)
35903868

3869+
if parsed.kernel_family.get('emit-structs'):
3870+
cw.nl()
3871+
render_from_attrs(parsed, cw)
3872+
render_to_skb(parsed, cw)
3873+
render_set_defaults(parsed, cw)
3874+
if cw._block_end:
3875+
cw._block_end = False
3876+
cw._out.write('}\n')
3877+
35913878
if args.mode == "user":
35923879
if args.header:
35933880
cw.p('/* Enums */')

0 commit comments

Comments
 (0)