612 lines · python
1# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause2 3import collections4import importlib5import os6import yaml7 8 9# To be loaded dynamically as needed10jsonschema = None11 12 13class SpecElement:14 """Netlink spec element.15 16 Abstract element of the Netlink spec. Implements the dictionary interface17 for access to the raw spec. Supports iterative resolution of dependencies18 across elements and class inheritance levels. The elements of the spec19 may refer to each other, and although loops should be very rare, having20 to maintain correct ordering of instantiation is painful, so the resolve()21 method should be used to perform parts of init which require access to22 other parts of the spec.23 24 Attributes:25 yaml raw spec as loaded from the spec file26 family back reference to the full family27 28 name name of the entity as listed in the spec (optional)29 ident_name name which can be safely used as identifier in code (optional)30 """31 def __init__(self, family, yaml):32 self.yaml = yaml33 self.family = family34 35 if 'name' in self.yaml:36 self.name = self.yaml['name']37 self.ident_name = self.name.replace('-', '_')38 39 self._super_resolved = False40 family.add_unresolved(self)41 42 def __getitem__(self, key):43 return self.yaml[key]44 45 def __contains__(self, key):46 return key in self.yaml47 48 def get(self, key, default=None):49 return self.yaml.get(key, default)50 51 def resolve_up(self, up):52 if not self._super_resolved:53 up.resolve()54 self._super_resolved = True55 56 def resolve(self):57 pass58 59 60class SpecEnumEntry(SpecElement):61 """ Entry within an enum declared in the Netlink spec.62 63 Attributes:64 doc documentation string65 enum_set back reference to the enum66 value numerical value of this enum (use accessors in most situations!)67 68 Methods:69 raw_value raw value, i.e. the id in the enum, unlike user value which is a mask for flags70 user_value user value, same as raw value for enums, for flags it's the mask71 """72 def __init__(self, enum_set, yaml, prev, value_start):73 if isinstance(yaml, str):74 yaml = {'name': yaml}75 super().__init__(enum_set.family, yaml)76 77 self.doc = yaml.get('doc', '')78 self.enum_set = enum_set79 80 if 'value' in yaml:81 self.value = yaml['value']82 elif prev:83 self.value = prev.value + 184 else:85 self.value = value_start86 87 def has_doc(self):88 return bool(self.doc)89 90 def raw_value(self):91 return self.value92 93 def user_value(self, as_flags=None):94 if self.enum_set['type'] == 'flags' or as_flags:95 return 1 << self.value96 else:97 return self.value98 99 100class SpecEnumSet(SpecElement):101 """ Enum type102 103 Represents an enumeration (list of numerical constants)104 as declared in the "definitions" section of the spec.105 106 Attributes:107 type enum or flags108 entries entries by name109 entries_by_val entries by value110 Methods:111 get_mask for flags compute the mask of all defined values112 """113 def __init__(self, family, yaml):114 super().__init__(family, yaml)115 116 self.type = yaml['type']117 118 prev_entry = None119 value_start = self.yaml.get('value-start', 0)120 self.entries = dict()121 self.entries_by_val = dict()122 for entry in self.yaml['entries']:123 e = self.new_entry(entry, prev_entry, value_start)124 self.entries[e.name] = e125 self.entries_by_val[e.raw_value()] = e126 prev_entry = e127 128 def new_entry(self, entry, prev_entry, value_start):129 return SpecEnumEntry(self, entry, prev_entry, value_start)130 131 def has_doc(self):132 if 'doc' in self.yaml:133 return True134 for entry in self.entries.values():135 if entry.has_doc():136 return True137 return False138 139 def get_mask(self, as_flags=None):140 mask = 0141 for e in self.entries.values():142 mask += e.user_value(as_flags)143 return mask144 145 146class SpecAttr(SpecElement):147 """ Single Netlink attribute type148 149 Represents a single attribute type within an attr space.150 151 Attributes:152 type string, attribute type153 value numerical ID when serialized154 attr_set Attribute Set containing this attr155 is_multi bool, attr may repeat multiple times156 struct_name string, name of struct definition157 sub_type string, name of sub type158 len integer, optional byte length of binary types159 display_hint string, hint to help choose format specifier160 when displaying the value161 sub_message string, name of sub message type162 selector string, name of attribute used to select163 sub-message type164 165 is_auto_scalar bool, attr is a variable-size scalar166 """167 def __init__(self, family, attr_set, yaml, value):168 super().__init__(family, yaml)169 170 self.type = yaml['type']171 self.value = value172 self.attr_set = attr_set173 self.is_multi = yaml.get('multi-attr', False)174 self.struct_name = yaml.get('struct')175 self.sub_type = yaml.get('sub-type')176 self.byte_order = yaml.get('byte-order')177 self.len = yaml.get('len')178 self.display_hint = yaml.get('display-hint')179 self.sub_message = yaml.get('sub-message')180 self.selector = yaml.get('selector')181 182 self.is_auto_scalar = self.type == "sint" or self.type == "uint"183 184 185class SpecAttrSet(SpecElement):186 """ Netlink Attribute Set class.187 188 Represents a ID space of attributes within Netlink.189 190 Note that unlike other elements, which expose contents of the raw spec191 via the dictionary interface Attribute Set exposes attributes by name.192 193 Attributes:194 attrs ordered dict of all attributes (indexed by name)195 attrs_by_val ordered dict of all attributes (indexed by value)196 subset_of parent set if this is a subset, otherwise None197 """198 def __init__(self, family, yaml):199 super().__init__(family, yaml)200 201 self.subset_of = self.yaml.get('subset-of', None)202 203 self.attrs = collections.OrderedDict()204 self.attrs_by_val = collections.OrderedDict()205 206 if self.subset_of is None:207 val = 1208 for elem in self.yaml['attributes']:209 if 'value' in elem:210 val = elem['value']211 212 attr = self.new_attr(elem, val)213 self.attrs[attr.name] = attr214 self.attrs_by_val[attr.value] = attr215 val += 1216 else:217 real_set = family.attr_sets[self.subset_of]218 for elem in self.yaml['attributes']:219 attr = real_set[elem['name']]220 self.attrs[attr.name] = attr221 self.attrs_by_val[attr.value] = attr222 223 def new_attr(self, elem, value):224 return SpecAttr(self.family, self, elem, value)225 226 def __getitem__(self, key):227 return self.attrs[key]228 229 def __contains__(self, key):230 return key in self.attrs231 232 def __iter__(self):233 yield from self.attrs234 235 def items(self):236 return self.attrs.items()237 238 239class SpecStructMember(SpecElement):240 """Struct member attribute241 242 Represents a single struct member attribute.243 244 Attributes:245 type string, type of the member attribute246 byte_order string or None for native byte order247 enum string, name of the enum definition248 len integer, optional byte length of binary types249 display_hint string, hint to help choose format specifier250 when displaying the value251 struct string, name of nested struct type252 """253 def __init__(self, family, yaml):254 super().__init__(family, yaml)255 self.type = yaml['type']256 self.byte_order = yaml.get('byte-order')257 self.enum = yaml.get('enum')258 self.len = yaml.get('len')259 self.display_hint = yaml.get('display-hint')260 self.struct = yaml.get('struct')261 262 263class SpecStruct(SpecElement):264 """Netlink struct type265 266 Represents a C struct definition.267 268 Attributes:269 members ordered list of struct members270 """271 def __init__(self, family, yaml):272 super().__init__(family, yaml)273 274 self.members = []275 for member in yaml.get('members', []):276 self.members.append(self.new_member(family, member))277 278 def new_member(self, family, elem):279 return SpecStructMember(family, elem)280 281 def __iter__(self):282 yield from self.members283 284 def items(self):285 return self.members.items()286 287 288class SpecSubMessage(SpecElement):289 """ Netlink sub-message definition290 291 Represents a set of sub-message formats for polymorphic nlattrs292 that contain type-specific sub messages.293 294 Attributes:295 name string, name of sub-message definition296 formats dict of sub-message formats indexed by match value297 """298 def __init__(self, family, yaml):299 super().__init__(family, yaml)300 301 self.formats = collections.OrderedDict()302 for elem in self.yaml['formats']:303 format = self.new_format(family, elem)304 self.formats[format.value] = format305 306 def new_format(self, family, format):307 return SpecSubMessageFormat(family, format)308 309 310class SpecSubMessageFormat(SpecElement):311 """ Netlink sub-message format definition312 313 Represents a single format for a sub-message.314 315 Attributes:316 value attribute value to match against type selector317 fixed_header string, name of fixed header, or None318 attr_set string, name of attribute set, or None319 """320 def __init__(self, family, yaml):321 super().__init__(family, yaml)322 323 self.value = yaml.get('value')324 self.fixed_header = yaml.get('fixed-header')325 self.attr_set = yaml.get('attribute-set')326 327 328class SpecOperation(SpecElement):329 """Netlink Operation330 331 Information about a single Netlink operation.332 333 Attributes:334 value numerical ID when serialized, None if req/rsp values differ335 336 req_value numerical ID when serialized, user -> kernel337 rsp_value numerical ID when serialized, user <- kernel338 modes supported operation modes (do, dump, event etc.)339 is_call bool, whether the operation is a call340 is_async bool, whether the operation is a notification341 is_resv bool, whether the operation does not exist (it's just a reserved ID)342 attr_set attribute set name343 fixed_header string, optional name of fixed header struct344 345 yaml raw spec as loaded from the spec file346 """347 def __init__(self, family, yaml, req_value, rsp_value):348 super().__init__(family, yaml)349 350 self.value = req_value if req_value == rsp_value else None351 self.req_value = req_value352 self.rsp_value = rsp_value353 354 self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'}355 self.is_call = 'do' in yaml or 'dump' in yaml356 self.is_async = 'notify' in yaml or 'event' in yaml357 self.is_resv = not self.is_async and not self.is_call358 self.fixed_header = self.yaml.get('fixed-header', family.fixed_header)359 360 # Added by resolve:361 self.attr_set = None362 delattr(self, "attr_set")363 364 def resolve(self):365 self.resolve_up(super())366 367 if 'attribute-set' in self.yaml:368 attr_set_name = self.yaml['attribute-set']369 elif 'notify' in self.yaml:370 msg = self.family.msgs[self.yaml['notify']]371 attr_set_name = msg['attribute-set']372 elif self.is_resv:373 attr_set_name = ''374 else:375 raise Exception(f"Can't resolve attribute set for op '{self.name}'")376 if attr_set_name:377 self.attr_set = self.family.attr_sets[attr_set_name]378 379 380class SpecMcastGroup(SpecElement):381 """Netlink Multicast Group382 383 Information about a multicast group.384 385 Value is only used for classic netlink families that use the386 netlink-raw schema. Genetlink families use dynamic ID allocation387 where the ids of multicast groups get resolved at runtime. Value388 will be None for genetlink families.389 390 Attributes:391 name name of the mulitcast group392 value integer id of this multicast group for netlink-raw or None393 yaml raw spec as loaded from the spec file394 """395 def __init__(self, family, yaml):396 super().__init__(family, yaml)397 self.value = self.yaml.get('value')398 399 400class SpecFamily(SpecElement):401 """ Netlink Family Spec class.402 403 Netlink family information loaded from a spec (e.g. in YAML).404 Takes care of unfolding implicit information which can be skipped405 in the spec itself for brevity.406 407 The class can be used like a dictionary to access the raw spec408 elements but that's usually a bad idea.409 410 Attributes:411 proto protocol type (e.g. genetlink)412 msg_id_model enum-model for operations (unified, directional etc.)413 license spec license (loaded from an SPDX tag on the spec)414 415 attr_sets dict of attribute sets416 msgs dict of all messages (index by name)417 sub_msgs dict of all sub messages (index by name)418 ops dict of all valid requests / responses419 ntfs dict of all async events420 consts dict of all constants/enums421 fixed_header string, optional name of family default fixed header struct422 mcast_groups dict of all multicast groups (index by name)423 kernel_family dict of kernel family attributes424 """425 def __init__(self, spec_path, schema_path=None, exclude_ops=None):426 with open(spec_path, "r") as stream:427 prefix = '# SPDX-License-Identifier: '428 first = stream.readline().strip()429 if not first.startswith(prefix):430 raise Exception('SPDX license tag required in the spec')431 self.license = first[len(prefix):]432 433 stream.seek(0)434 spec = yaml.safe_load(stream)435 436 self._resolution_list = []437 438 super().__init__(self, spec)439 440 self._exclude_ops = exclude_ops if exclude_ops else []441 442 self.proto = self.yaml.get('protocol', 'genetlink')443 self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified')444 445 if schema_path is None:446 schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml'447 if schema_path:448 global jsonschema449 450 with open(schema_path, "r") as stream:451 schema = yaml.safe_load(stream)452 453 if jsonschema is None:454 jsonschema = importlib.import_module("jsonschema")455 456 jsonschema.validate(self.yaml, schema)457 458 self.attr_sets = collections.OrderedDict()459 self.sub_msgs = collections.OrderedDict()460 self.msgs = collections.OrderedDict()461 self.req_by_value = collections.OrderedDict()462 self.rsp_by_value = collections.OrderedDict()463 self.ops = collections.OrderedDict()464 self.ntfs = collections.OrderedDict()465 self.consts = collections.OrderedDict()466 self.mcast_groups = collections.OrderedDict()467 self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {}))468 469 last_exception = None470 while len(self._resolution_list) > 0:471 resolved = []472 unresolved = self._resolution_list473 self._resolution_list = []474 475 for elem in unresolved:476 try:477 elem.resolve()478 except (KeyError, AttributeError) as e:479 self._resolution_list.append(elem)480 last_exception = e481 continue482 483 resolved.append(elem)484 485 if len(resolved) == 0:486 raise last_exception487 488 def new_enum(self, elem):489 return SpecEnumSet(self, elem)490 491 def new_attr_set(self, elem):492 return SpecAttrSet(self, elem)493 494 def new_struct(self, elem):495 return SpecStruct(self, elem)496 497 def new_sub_message(self, elem):498 return SpecSubMessage(self, elem);499 500 def new_operation(self, elem, req_val, rsp_val):501 return SpecOperation(self, elem, req_val, rsp_val)502 503 def new_mcast_group(self, elem):504 return SpecMcastGroup(self, elem)505 506 def add_unresolved(self, elem):507 self._resolution_list.append(elem)508 509 def _dictify_ops_unified(self):510 self.fixed_header = self.yaml['operations'].get('fixed-header')511 val = 1512 for elem in self.yaml['operations']['list']:513 if 'value' in elem:514 val = elem['value']515 516 op = self.new_operation(elem, val, val)517 val += 1518 519 self.msgs[op.name] = op520 521 def _dictify_ops_directional(self):522 self.fixed_header = self.yaml['operations'].get('fixed-header')523 req_val = rsp_val = 1524 for elem in self.yaml['operations']['list']:525 if 'notify' in elem or 'event' in elem:526 if 'value' in elem:527 rsp_val = elem['value']528 req_val_next = req_val529 rsp_val_next = rsp_val + 1530 req_val = None531 elif 'do' in elem or 'dump' in elem:532 mode = elem['do'] if 'do' in elem else elem['dump']533 534 v = mode.get('request', {}).get('value', None)535 if v:536 req_val = v537 v = mode.get('reply', {}).get('value', None)538 if v:539 rsp_val = v540 541 rsp_inc = 1 if 'reply' in mode else 0542 req_val_next = req_val + 1543 rsp_val_next = rsp_val + rsp_inc544 else:545 raise Exception("Can't parse directional ops")546 547 if req_val == req_val_next:548 req_val = None549 if rsp_val == rsp_val_next:550 rsp_val = None551 552 skip = False553 for exclude in self._exclude_ops:554 skip |= bool(exclude.match(elem['name']))555 if not skip:556 op = self.new_operation(elem, req_val, rsp_val)557 558 req_val = req_val_next559 rsp_val = rsp_val_next560 561 self.msgs[op.name] = op562 563 def find_operation(self, name):564 """565 For a given operation name, find and return operation spec.566 """567 for op in self.yaml['operations']['list']:568 if name == op['name']:569 return op570 return None571 572 def resolve(self):573 self.resolve_up(super())574 575 definitions = self.yaml.get('definitions', [])576 for elem in definitions:577 if elem['type'] == 'enum' or elem['type'] == 'flags':578 self.consts[elem['name']] = self.new_enum(elem)579 elif elem['type'] == 'struct':580 self.consts[elem['name']] = self.new_struct(elem)581 else:582 self.consts[elem['name']] = elem583 584 for elem in self.yaml['attribute-sets']:585 attr_set = self.new_attr_set(elem)586 self.attr_sets[elem['name']] = attr_set587 588 for elem in self.yaml.get('sub-messages', []):589 sub_message = self.new_sub_message(elem)590 self.sub_msgs[sub_message.name] = sub_message591 592 if self.msg_id_model == 'unified':593 self._dictify_ops_unified()594 elif self.msg_id_model == 'directional':595 self._dictify_ops_directional()596 597 for op in self.msgs.values():598 if op.req_value is not None:599 self.req_by_value[op.req_value] = op600 if op.rsp_value is not None:601 self.rsp_by_value[op.rsp_value] = op602 if not op.is_async and 'attribute-set' in op:603 self.ops[op.name] = op604 elif op.is_async:605 self.ntfs[op.name] = op606 607 mcgs = self.yaml.get('mcast-groups')608 if mcgs:609 for elem in mcgs['list']:610 mcg = self.new_mcast_group(elem)611 self.mcast_groups[elem['name']] = mcg612