Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

tools: ynl: submsg: reverse parse / error reporting

Reverse parsing lets YNL convert bad and missing attr pointers
from extack into a string like "missing attribute nest1.nest2.attr_name".
It's a feature that's unique to YNL C AFAIU (even the Python YNL
can't do nested reverse parsing). Add support for reverse-parsing
of sub-messages.

To simplify the logic and the code annotate the type policies
with extra metadata. Mark the selectors and the messages with
the information we need. We assume that key / selector always
precedes the sub-message while parsing (and also if there are
multiple sub-messages like in rt-link they are interleaved
selector 1 ... submsg 1 ... selector 2 .. submsg 2, not
selector 1 ... selector 2 ... submsg 1 ... submsg 2).

The rt-link sample in a subsequent changes shows reverse parsing
of sub-messages in action.

Reviewed-by: Donald Hunter <donald.hunter@gmail.com>
Link: https://patch.msgid.link/20250515231650.1325372-8-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

+107 -11
+4 -1
tools/net/ynl/lib/ynl-priv.h
··· 43 43 struct ynl_parse_arg *yarg); 44 44 45 45 struct ynl_policy_attr { 46 - enum ynl_policy_type type; 46 + enum ynl_policy_type type:8; 47 + __u8 is_submsg:1; 48 + __u8 is_selector:1; 49 + __u16 selector_type; 47 50 unsigned int len; 48 51 const char *name; 49 52 const struct ynl_policy_nest *nest;
+75 -9
tools/net/ynl/lib/ynl.c
··· 45 45 #define perr(_ys, _msg) __yerr(&(_ys)->err, errno, _msg) 46 46 47 47 /* -- Netlink boiler plate */ 48 + static bool 49 + ynl_err_walk_is_sel(const struct ynl_policy_nest *policy, 50 + const struct nlattr *attr) 51 + { 52 + unsigned int type = ynl_attr_type(attr); 53 + 54 + return policy && type <= policy->max_attr && 55 + policy->table[type].is_selector; 56 + } 57 + 58 + static const struct ynl_policy_nest * 59 + ynl_err_walk_sel_policy(const struct ynl_policy_attr *policy_attr, 60 + const struct nlattr *selector) 61 + { 62 + const struct ynl_policy_nest *policy = policy_attr->nest; 63 + const char *sel; 64 + unsigned int i; 65 + 66 + if (!policy_attr->is_submsg) 67 + return policy; 68 + 69 + sel = ynl_attr_get_str(selector); 70 + for (i = 0; i <= policy->max_attr; i++) { 71 + if (!strcmp(sel, policy->table[i].name)) 72 + return policy->table[i].nest; 73 + } 74 + 75 + return NULL; 76 + } 77 + 48 78 static int 49 - ynl_err_walk_report_one(const struct ynl_policy_nest *policy, unsigned int type, 79 + ynl_err_walk_report_one(const struct ynl_policy_nest *policy, 80 + const struct nlattr *selector, unsigned int type, 50 81 char *str, int str_sz, int *n) 51 82 { 52 83 if (!policy) { ··· 98 67 return 1; 99 68 } 100 69 101 - if (*n < str_sz) 102 - *n += snprintf(str, str_sz - *n, 103 - ".%s", policy->table[type].name); 70 + if (*n < str_sz) { 71 + int sz; 72 + 73 + sz = snprintf(str, str_sz - *n, 74 + ".%s", policy->table[type].name); 75 + *n += sz; 76 + str += sz; 77 + } 78 + 79 + if (policy->table[type].is_submsg) { 80 + if (!selector) { 81 + if (*n < str_sz) 82 + *n += snprintf(str, str_sz, "(!selector)"); 83 + return 1; 84 + } 85 + 86 + if (ynl_attr_type(selector) != 87 + policy->table[type].selector_type) { 88 + if (*n < str_sz) 89 + *n += snprintf(str, str_sz, "(!=selector)"); 90 + return 1; 91 + } 92 + 93 + if (*n < str_sz) 94 + *n += snprintf(str, str_sz - *n, "(%s)", 95 + ynl_attr_get_str(selector)); 96 + } 97 + 104 98 return 0; 105 99 } 106 100 ··· 134 78 const struct ynl_policy_nest *policy, char *str, int str_sz, 135 79 const struct ynl_policy_nest **nest_pol) 136 80 { 81 + const struct ynl_policy_nest *next_pol; 82 + const struct nlattr *selector = NULL; 137 83 unsigned int astart_off, aend_off; 138 84 const struct nlattr *attr; 139 85 unsigned int data_len; ··· 154 96 ynl_attr_for_each_payload(start, data_len, attr) { 155 97 astart_off = (char *)attr - (char *)start; 156 98 aend_off = (char *)ynl_attr_data_end(attr) - (char *)start; 99 + 100 + if (ynl_err_walk_is_sel(policy, attr)) 101 + selector = attr; 102 + 157 103 if (aend_off <= off) 158 104 continue; 159 105 ··· 171 109 172 110 type = ynl_attr_type(attr); 173 111 174 - if (ynl_err_walk_report_one(policy, type, str, str_sz, &n)) 112 + if (ynl_err_walk_report_one(policy, selector, type, str, str_sz, &n)) 113 + return n; 114 + 115 + next_pol = ynl_err_walk_sel_policy(&policy->table[type], selector); 116 + if (!next_pol) 175 117 return n; 176 118 177 119 if (!off) { 178 120 if (nest_pol) 179 - *nest_pol = policy->table[type].nest; 121 + *nest_pol = next_pol; 180 122 return n; 181 123 } 182 124 183 - if (!policy->table[type].nest) { 125 + if (!next_pol) { 184 126 if (n < str_sz) 185 127 n += snprintf(str, str_sz, "!nest"); 186 128 return n; ··· 194 128 start = ynl_attr_data(attr); 195 129 end = start + ynl_attr_data_len(attr); 196 130 197 - return n + ynl_err_walk(ys, start, end, off, policy->table[type].nest, 131 + return n + ynl_err_walk(ys, start, end, off, next_pol, 198 132 &str[n], str_sz - n, nest_pol); 199 133 } 200 134 ··· 297 231 } 298 232 299 233 n2 = 0; 300 - ynl_err_walk_report_one(nest_pol, type, &miss_attr[n], 234 + ynl_err_walk_report_one(nest_pol, NULL, type, &miss_attr[n], 301 235 sizeof(miss_attr) - n, &n2); 302 236 n += n2; 303 237
+28 -1
tools/net/ynl/pyynl/ynl_gen_c.py
··· 57 57 self.request = False 58 58 self.reply = False 59 59 60 + self.is_selector = False 61 + 60 62 if 'len' in attr: 61 63 self.len = attr['len'] 62 64 ··· 486 484 ri.cw.p(f"char *{self.c_name};") 487 485 488 486 def _attr_typol(self): 489 - return f'.type = YNL_PT_NUL_STR, ' 487 + typol = f'.type = YNL_PT_NUL_STR, ' 488 + if self.is_selector: 489 + typol += '.is_selector = 1, ' 490 + return typol 490 491 491 492 def _attr_policy(self, policy): 492 493 if 'exact-len' in self.checks: ··· 883 878 884 879 885 880 class TypeSubMessage(TypeNest): 881 + def __init__(self, family, attr_set, attr, value): 882 + super().__init__(family, attr_set, attr, value) 883 + 884 + self.selector = Selector(attr, attr_set) 885 + 886 + def _attr_typol(self): 887 + typol = f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' 888 + typol += f'.is_submsg = 1, .selector_type = {self.attr_set[self["selector"]].value} ' 889 + return typol 890 + 886 891 def _attr_get(self, ri, var): 887 892 sel = c_lower(self['selector']) 888 893 get_lines = [f'if (!{var}->{sel})', ··· 903 888 init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;", 904 889 f"parg.data = &{var}->{self.c_name};"] 905 890 return get_lines, init_lines, None 891 + 892 + 893 + class Selector: 894 + def __init__(self, msg_attr, attr_set): 895 + self.name = msg_attr["selector"] 896 + 897 + if self.name in attr_set: 898 + self.attr = attr_set[self.name] 899 + self.attr.is_selector = True 900 + self._external = False 901 + else: 902 + raise Exception("Passing selectors from external nests not supported") 906 903 907 904 908 905 class Struct: