1# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 3import collections 4import importlib 5import os 6import yaml 7 8 9# To be loaded dynamically as needed 10jsonschema = None 11 12 13class SpecElement: 14 """Netlink spec element. 15 16 Abstract element of the Netlink spec. Implements the dictionary interface 17 for access to the raw spec. Supports iterative resolution of dependencies 18 across elements and class inheritance levels. The elements of the spec 19 may refer to each other, and although loops should be very rare, having 20 to maintain correct ordering of instantiation is painful, so the resolve() 21 method should be used to perform parts of init which require access to 22 other parts of the spec. 23 24 Attributes: 25 yaml raw spec as loaded from the spec file 26 family back reference to the full family 27 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 = yaml 33 self.family = family 34 35 if 'name' in self.yaml: 36 self.name = self.yaml['name'] 37 self.ident_name = self.name.replace('-', '_') 38 39 self._super_resolved = False 40 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.yaml 47 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 = True 55 56 def resolve(self): 57 pass 58 59 60class SpecEnumEntry(SpecElement): 61 """ Entry within an enum declared in the Netlink spec. 62 63 Attributes: 64 doc documentation string 65 enum_set back reference to the enum 66 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 flags 70 user_value user value, same as raw value for enums, for flags it's the mask 71 """ 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_set 79 80 if 'value' in yaml: 81 self.value = yaml['value'] 82 elif prev: 83 self.value = prev.value + 1 84 else: 85 self.value = value_start 86 87 def has_doc(self): 88 return bool(self.doc) 89 90 def raw_value(self): 91 return self.value 92 93 def user_value(self, as_flags=None): 94 if self.enum_set['type'] == 'flags' or as_flags: 95 return 1 << self.value 96 else: 97 return self.value 98 99 100class SpecEnumSet(SpecElement): 101 """ Enum type 102 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 flags 108 entries entries by name 109 entries_by_val entries by value 110 Methods: 111 get_mask for flags compute the mask of all defined values 112 """ 113 def __init__(self, family, yaml): 114 super().__init__(family, yaml) 115 116 self.type = yaml['type'] 117 118 prev_entry = None 119 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] = e 125 self.entries_by_val[e.raw_value()] = e 126 prev_entry = e 127 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 True 134 return self.has_entry_doc() 135 136 def has_entry_doc(self): 137 for entry in self.entries.values(): 138 if entry.has_doc(): 139 return True 140 return False 141 142 def get_mask(self, as_flags=None): 143 mask = 0 144 for e in self.entries.values(): 145 mask += e.user_value(as_flags) 146 return mask 147 148 149class SpecAttr(SpecElement): 150 """ Single Netlink attribute type 151 152 Represents a single attribute type within an attr space. 153 154 Attributes: 155 type string, attribute type 156 value numerical ID when serialized 157 attr_set Attribute Set containing this attr 158 is_multi bool, attr may repeat multiple times 159 struct_name string, name of struct definition 160 sub_type string, name of sub type 161 len integer, optional byte length of binary types 162 display_hint string, hint to help choose format specifier 163 when displaying the value 164 sub_message string, name of sub message type 165 selector string, name of attribute used to select 166 sub-message type 167 168 is_auto_scalar bool, attr is a variable-size scalar 169 """ 170 def __init__(self, family, attr_set, yaml, value): 171 super().__init__(family, yaml) 172 173 self.type = yaml['type'] 174 self.value = value 175 self.attr_set = attr_set 176 self.is_multi = yaml.get('multi-attr', False) 177 self.struct_name = yaml.get('struct') 178 self.sub_type = yaml.get('sub-type') 179 self.byte_order = yaml.get('byte-order') 180 self.len = yaml.get('len') 181 self.display_hint = yaml.get('display-hint') 182 self.sub_message = yaml.get('sub-message') 183 self.selector = yaml.get('selector') 184 185 self.is_auto_scalar = self.type == "sint" or self.type == "uint" 186 187 188class SpecAttrSet(SpecElement): 189 """ Netlink Attribute Set class. 190 191 Represents a ID space of attributes within Netlink. 192 193 Note that unlike other elements, which expose contents of the raw spec 194 via the dictionary interface Attribute Set exposes attributes by name. 195 196 Attributes: 197 attrs ordered dict of all attributes (indexed by name) 198 attrs_by_val ordered dict of all attributes (indexed by value) 199 subset_of parent set if this is a subset, otherwise None 200 """ 201 def __init__(self, family, yaml): 202 super().__init__(family, yaml) 203 204 self.subset_of = self.yaml.get('subset-of', None) 205 206 self.attrs = collections.OrderedDict() 207 self.attrs_by_val = collections.OrderedDict() 208 209 if self.subset_of is None: 210 val = 1 211 for elem in self.yaml['attributes']: 212 if 'value' in elem: 213 val = elem['value'] 214 215 attr = self.new_attr(elem, val) 216 self.attrs[attr.name] = attr 217 self.attrs_by_val[attr.value] = attr 218 val += 1 219 else: 220 real_set = family.attr_sets[self.subset_of] 221 for elem in self.yaml['attributes']: 222 attr = real_set[elem['name']] 223 self.attrs[attr.name] = attr 224 self.attrs_by_val[attr.value] = attr 225 226 def new_attr(self, elem, value): 227 return SpecAttr(self.family, self, elem, value) 228 229 def __getitem__(self, key): 230 return self.attrs[key] 231 232 def __contains__(self, key): 233 return key in self.attrs 234 235 def __iter__(self): 236 yield from self.attrs 237 238 def items(self): 239 return self.attrs.items() 240 241 242class SpecStructMember(SpecElement): 243 """Struct member attribute 244 245 Represents a single struct member attribute. 246 247 Attributes: 248 type string, type of the member attribute 249 byte_order string or None for native byte order 250 enum string, name of the enum definition 251 len integer, optional byte length of binary types 252 display_hint string, hint to help choose format specifier 253 when displaying the value 254 struct string, name of nested struct type 255 """ 256 def __init__(self, family, yaml): 257 super().__init__(family, yaml) 258 self.type = yaml['type'] 259 self.byte_order = yaml.get('byte-order') 260 self.enum = yaml.get('enum') 261 self.len = yaml.get('len') 262 self.display_hint = yaml.get('display-hint') 263 self.struct = yaml.get('struct') 264 265 266class SpecStruct(SpecElement): 267 """Netlink struct type 268 269 Represents a C struct definition. 270 271 Attributes: 272 members ordered list of struct members 273 """ 274 def __init__(self, family, yaml): 275 super().__init__(family, yaml) 276 277 self.members = [] 278 for member in yaml.get('members', []): 279 self.members.append(self.new_member(family, member)) 280 281 def new_member(self, family, elem): 282 return SpecStructMember(family, elem) 283 284 def __iter__(self): 285 yield from self.members 286 287 def items(self): 288 return self.members.items() 289 290 291class SpecSubMessage(SpecElement): 292 """ Netlink sub-message definition 293 294 Represents a set of sub-message formats for polymorphic nlattrs 295 that contain type-specific sub messages. 296 297 Attributes: 298 name string, name of sub-message definition 299 formats dict of sub-message formats indexed by match value 300 """ 301 def __init__(self, family, yaml): 302 super().__init__(family, yaml) 303 304 self.formats = collections.OrderedDict() 305 for elem in self.yaml['formats']: 306 format = self.new_format(family, elem) 307 self.formats[format.value] = format 308 309 def new_format(self, family, format): 310 return SpecSubMessageFormat(family, format) 311 312 313class SpecSubMessageFormat(SpecElement): 314 """ Netlink sub-message format definition 315 316 Represents a single format for a sub-message. 317 318 Attributes: 319 value attribute value to match against type selector 320 fixed_header string, name of fixed header, or None 321 attr_set string, name of attribute set, or None 322 """ 323 def __init__(self, family, yaml): 324 super().__init__(family, yaml) 325 326 self.value = yaml.get('value') 327 self.fixed_header = yaml.get('fixed-header') 328 self.attr_set = yaml.get('attribute-set') 329 330 331class SpecOperation(SpecElement): 332 """Netlink Operation 333 334 Information about a single Netlink operation. 335 336 Attributes: 337 value numerical ID when serialized, None if req/rsp values differ 338 339 req_value numerical ID when serialized, user -> kernel 340 rsp_value numerical ID when serialized, user <- kernel 341 modes supported operation modes (do, dump, event etc.) 342 is_call bool, whether the operation is a call 343 is_async bool, whether the operation is a notification 344 is_resv bool, whether the operation does not exist (it's just a reserved ID) 345 attr_set attribute set name 346 fixed_header string, optional name of fixed header struct 347 348 yaml raw spec as loaded from the spec file 349 """ 350 def __init__(self, family, yaml, req_value, rsp_value): 351 super().__init__(family, yaml) 352 353 self.value = req_value if req_value == rsp_value else None 354 self.req_value = req_value 355 self.rsp_value = rsp_value 356 357 self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'} 358 self.is_call = 'do' in yaml or 'dump' in yaml 359 self.is_async = 'notify' in yaml or 'event' in yaml 360 self.is_resv = not self.is_async and not self.is_call 361 self.fixed_header = self.yaml.get('fixed-header', family.fixed_header) 362 363 # Added by resolve: 364 self.attr_set = None 365 delattr(self, "attr_set") 366 367 def resolve(self): 368 self.resolve_up(super()) 369 370 if 'attribute-set' in self.yaml: 371 attr_set_name = self.yaml['attribute-set'] 372 elif 'notify' in self.yaml: 373 msg = self.family.msgs[self.yaml['notify']] 374 attr_set_name = msg['attribute-set'] 375 elif self.is_resv: 376 attr_set_name = '' 377 else: 378 raise Exception(f"Can't resolve attribute set for op '{self.name}'") 379 if attr_set_name: 380 self.attr_set = self.family.attr_sets[attr_set_name] 381 382 383class SpecMcastGroup(SpecElement): 384 """Netlink Multicast Group 385 386 Information about a multicast group. 387 388 Value is only used for classic netlink families that use the 389 netlink-raw schema. Genetlink families use dynamic ID allocation 390 where the ids of multicast groups get resolved at runtime. Value 391 will be None for genetlink families. 392 393 Attributes: 394 name name of the mulitcast group 395 value integer id of this multicast group for netlink-raw or None 396 yaml raw spec as loaded from the spec file 397 """ 398 def __init__(self, family, yaml): 399 super().__init__(family, yaml) 400 self.value = self.yaml.get('value') 401 402 403class SpecFamily(SpecElement): 404 """ Netlink Family Spec class. 405 406 Netlink family information loaded from a spec (e.g. in YAML). 407 Takes care of unfolding implicit information which can be skipped 408 in the spec itself for brevity. 409 410 The class can be used like a dictionary to access the raw spec 411 elements but that's usually a bad idea. 412 413 Attributes: 414 proto protocol type (e.g. genetlink) 415 msg_id_model enum-model for operations (unified, directional etc.) 416 license spec license (loaded from an SPDX tag on the spec) 417 418 attr_sets dict of attribute sets 419 msgs dict of all messages (index by name) 420 sub_msgs dict of all sub messages (index by name) 421 ops dict of all valid requests / responses 422 ntfs dict of all async events 423 consts dict of all constants/enums 424 fixed_header string, optional name of family default fixed header struct 425 mcast_groups dict of all multicast groups (index by name) 426 kernel_family dict of kernel family attributes 427 """ 428 def __init__(self, spec_path, schema_path=None, exclude_ops=None): 429 with open(spec_path, "r") as stream: 430 prefix = '# SPDX-License-Identifier: ' 431 first = stream.readline().strip() 432 if not first.startswith(prefix): 433 raise Exception('SPDX license tag required in the spec') 434 self.license = first[len(prefix):] 435 436 stream.seek(0) 437 spec = yaml.safe_load(stream) 438 439 self._resolution_list = [] 440 441 super().__init__(self, spec) 442 443 self._exclude_ops = exclude_ops if exclude_ops else [] 444 445 self.proto = self.yaml.get('protocol', 'genetlink') 446 self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') 447 448 if schema_path is None: 449 schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' 450 if schema_path: 451 global jsonschema 452 453 with open(schema_path, "r") as stream: 454 schema = yaml.safe_load(stream) 455 456 if jsonschema is None: 457 jsonschema = importlib.import_module("jsonschema") 458 459 jsonschema.validate(self.yaml, schema) 460 461 self.attr_sets = collections.OrderedDict() 462 self.sub_msgs = collections.OrderedDict() 463 self.msgs = collections.OrderedDict() 464 self.req_by_value = collections.OrderedDict() 465 self.rsp_by_value = collections.OrderedDict() 466 self.ops = collections.OrderedDict() 467 self.ntfs = collections.OrderedDict() 468 self.consts = collections.OrderedDict() 469 self.mcast_groups = collections.OrderedDict() 470 self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {})) 471 472 last_exception = None 473 while len(self._resolution_list) > 0: 474 resolved = [] 475 unresolved = self._resolution_list 476 self._resolution_list = [] 477 478 for elem in unresolved: 479 try: 480 elem.resolve() 481 except (KeyError, AttributeError) as e: 482 self._resolution_list.append(elem) 483 last_exception = e 484 continue 485 486 resolved.append(elem) 487 488 if len(resolved) == 0: 489 raise last_exception 490 491 def new_enum(self, elem): 492 return SpecEnumSet(self, elem) 493 494 def new_attr_set(self, elem): 495 return SpecAttrSet(self, elem) 496 497 def new_struct(self, elem): 498 return SpecStruct(self, elem) 499 500 def new_sub_message(self, elem): 501 return SpecSubMessage(self, elem); 502 503 def new_operation(self, elem, req_val, rsp_val): 504 return SpecOperation(self, elem, req_val, rsp_val) 505 506 def new_mcast_group(self, elem): 507 return SpecMcastGroup(self, elem) 508 509 def add_unresolved(self, elem): 510 self._resolution_list.append(elem) 511 512 def _dictify_ops_unified(self): 513 self.fixed_header = self.yaml['operations'].get('fixed-header') 514 val = 1 515 for elem in self.yaml['operations']['list']: 516 if 'value' in elem: 517 val = elem['value'] 518 519 op = self.new_operation(elem, val, val) 520 val += 1 521 522 self.msgs[op.name] = op 523 524 def _dictify_ops_directional(self): 525 self.fixed_header = self.yaml['operations'].get('fixed-header') 526 req_val = rsp_val = 1 527 for elem in self.yaml['operations']['list']: 528 if 'notify' in elem or 'event' in elem: 529 if 'value' in elem: 530 rsp_val = elem['value'] 531 req_val_next = req_val 532 rsp_val_next = rsp_val + 1 533 req_val = None 534 elif 'do' in elem or 'dump' in elem: 535 mode = elem['do'] if 'do' in elem else elem['dump'] 536 537 v = mode.get('request', {}).get('value', None) 538 if v: 539 req_val = v 540 v = mode.get('reply', {}).get('value', None) 541 if v: 542 rsp_val = v 543 544 rsp_inc = 1 if 'reply' in mode else 0 545 req_val_next = req_val + 1 546 rsp_val_next = rsp_val + rsp_inc 547 else: 548 raise Exception("Can't parse directional ops") 549 550 if req_val == req_val_next: 551 req_val = None 552 if rsp_val == rsp_val_next: 553 rsp_val = None 554 555 skip = False 556 for exclude in self._exclude_ops: 557 skip |= bool(exclude.match(elem['name'])) 558 if not skip: 559 op = self.new_operation(elem, req_val, rsp_val) 560 561 req_val = req_val_next 562 rsp_val = rsp_val_next 563 564 self.msgs[op.name] = op 565 566 def find_operation(self, name): 567 """ 568 For a given operation name, find and return operation spec. 569 """ 570 for op in self.yaml['operations']['list']: 571 if name == op['name']: 572 return op 573 return None 574 575 def resolve(self): 576 self.resolve_up(super()) 577 578 definitions = self.yaml.get('definitions', []) 579 for elem in definitions: 580 if elem['type'] == 'enum' or elem['type'] == 'flags': 581 self.consts[elem['name']] = self.new_enum(elem) 582 elif elem['type'] == 'struct': 583 self.consts[elem['name']] = self.new_struct(elem) 584 else: 585 self.consts[elem['name']] = elem 586 587 for elem in self.yaml['attribute-sets']: 588 attr_set = self.new_attr_set(elem) 589 self.attr_sets[elem['name']] = attr_set 590 591 for elem in self.yaml.get('sub-messages', []): 592 sub_message = self.new_sub_message(elem) 593 self.sub_msgs[sub_message.name] = sub_message 594 595 if self.msg_id_model == 'unified': 596 self._dictify_ops_unified() 597 elif self.msg_id_model == 'directional': 598 self._dictify_ops_directional() 599 600 for op in self.msgs.values(): 601 if op.req_value is not None: 602 self.req_by_value[op.req_value] = op 603 if op.rsp_value is not None: 604 self.rsp_by_value[op.rsp_value] = op 605 if not op.is_async and 'attribute-set' in op: 606 self.ops[op.name] = op 607 elif op.is_async: 608 self.ntfs[op.name] = op 609 610 mcgs = self.yaml.get('mcast-groups') 611 if mcgs: 612 for elem in mcgs['list']: 613 mcg = self.new_mcast_group(elem) 614 self.mcast_groups[elem['name']] = mcg 615