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 real_attr = real_set[elem['name']] 223 combined_elem = real_attr.yaml | elem 224 attr = self.new_attr(combined_elem, real_attr.value) 225 226 self.attrs[attr.name] = attr 227 self.attrs_by_val[attr.value] = attr 228 229 def new_attr(self, elem, value): 230 return SpecAttr(self.family, self, elem, value) 231 232 def __getitem__(self, key): 233 return self.attrs[key] 234 235 def __contains__(self, key): 236 return key in self.attrs 237 238 def __iter__(self): 239 yield from self.attrs 240 241 def items(self): 242 return self.attrs.items() 243 244 245class SpecStructMember(SpecElement): 246 """Struct member attribute 247 248 Represents a single struct member attribute. 249 250 Attributes: 251 type string, type of the member attribute 252 byte_order string or None for native byte order 253 enum string, name of the enum definition 254 len integer, optional byte length of binary types 255 display_hint string, hint to help choose format specifier 256 when displaying the value 257 struct string, name of nested struct type 258 """ 259 def __init__(self, family, yaml): 260 super().__init__(family, yaml) 261 self.type = yaml['type'] 262 self.byte_order = yaml.get('byte-order') 263 self.enum = yaml.get('enum') 264 self.len = yaml.get('len') 265 self.display_hint = yaml.get('display-hint') 266 self.struct = yaml.get('struct') 267 268 269class SpecStruct(SpecElement): 270 """Netlink struct type 271 272 Represents a C struct definition. 273 274 Attributes: 275 members ordered list of struct members 276 """ 277 def __init__(self, family, yaml): 278 super().__init__(family, yaml) 279 280 self.members = [] 281 for member in yaml.get('members', []): 282 self.members.append(self.new_member(family, member)) 283 284 def new_member(self, family, elem): 285 return SpecStructMember(family, elem) 286 287 def __iter__(self): 288 yield from self.members 289 290 def items(self): 291 return self.members.items() 292 293 294class SpecSubMessage(SpecElement): 295 """ Netlink sub-message definition 296 297 Represents a set of sub-message formats for polymorphic nlattrs 298 that contain type-specific sub messages. 299 300 Attributes: 301 name string, name of sub-message definition 302 formats dict of sub-message formats indexed by match value 303 """ 304 def __init__(self, family, yaml): 305 super().__init__(family, yaml) 306 307 self.formats = collections.OrderedDict() 308 for elem in self.yaml['formats']: 309 format = self.new_format(family, elem) 310 self.formats[format.value] = format 311 312 def new_format(self, family, format): 313 return SpecSubMessageFormat(family, format) 314 315 316class SpecSubMessageFormat(SpecElement): 317 """ Netlink sub-message format definition 318 319 Represents a single format for a sub-message. 320 321 Attributes: 322 value attribute value to match against type selector 323 fixed_header string, name of fixed header, or None 324 attr_set string, name of attribute set, or None 325 """ 326 def __init__(self, family, yaml): 327 super().__init__(family, yaml) 328 329 self.value = yaml.get('value') 330 self.fixed_header = yaml.get('fixed-header') 331 self.attr_set = yaml.get('attribute-set') 332 333 334class SpecOperation(SpecElement): 335 """Netlink Operation 336 337 Information about a single Netlink operation. 338 339 Attributes: 340 value numerical ID when serialized, None if req/rsp values differ 341 342 req_value numerical ID when serialized, user -> kernel 343 rsp_value numerical ID when serialized, user <- kernel 344 modes supported operation modes (do, dump, event etc.) 345 is_call bool, whether the operation is a call 346 is_async bool, whether the operation is a notification 347 is_resv bool, whether the operation does not exist (it's just a reserved ID) 348 attr_set attribute set name 349 fixed_header string, optional name of fixed header struct 350 351 yaml raw spec as loaded from the spec file 352 """ 353 def __init__(self, family, yaml, req_value, rsp_value): 354 super().__init__(family, yaml) 355 356 self.value = req_value if req_value == rsp_value else None 357 self.req_value = req_value 358 self.rsp_value = rsp_value 359 360 self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'} 361 self.is_call = 'do' in yaml or 'dump' in yaml 362 self.is_async = 'notify' in yaml or 'event' in yaml 363 self.is_resv = not self.is_async and not self.is_call 364 self.fixed_header = self.yaml.get('fixed-header', family.fixed_header) 365 366 # Added by resolve: 367 self.attr_set = None 368 delattr(self, "attr_set") 369 370 def resolve(self): 371 self.resolve_up(super()) 372 373 if 'attribute-set' in self.yaml: 374 attr_set_name = self.yaml['attribute-set'] 375 elif 'notify' in self.yaml: 376 msg = self.family.msgs[self.yaml['notify']] 377 attr_set_name = msg['attribute-set'] 378 elif self.is_resv: 379 attr_set_name = '' 380 else: 381 raise Exception(f"Can't resolve attribute set for op '{self.name}'") 382 if attr_set_name: 383 self.attr_set = self.family.attr_sets[attr_set_name] 384 385 386class SpecMcastGroup(SpecElement): 387 """Netlink Multicast Group 388 389 Information about a multicast group. 390 391 Value is only used for classic netlink families that use the 392 netlink-raw schema. Genetlink families use dynamic ID allocation 393 where the ids of multicast groups get resolved at runtime. Value 394 will be None for genetlink families. 395 396 Attributes: 397 name name of the mulitcast group 398 value integer id of this multicast group for netlink-raw or None 399 yaml raw spec as loaded from the spec file 400 """ 401 def __init__(self, family, yaml): 402 super().__init__(family, yaml) 403 self.value = self.yaml.get('value') 404 405 406class SpecFamily(SpecElement): 407 """ Netlink Family Spec class. 408 409 Netlink family information loaded from a spec (e.g. in YAML). 410 Takes care of unfolding implicit information which can be skipped 411 in the spec itself for brevity. 412 413 The class can be used like a dictionary to access the raw spec 414 elements but that's usually a bad idea. 415 416 Attributes: 417 proto protocol type (e.g. genetlink) 418 msg_id_model enum-model for operations (unified, directional etc.) 419 license spec license (loaded from an SPDX tag on the spec) 420 421 attr_sets dict of attribute sets 422 msgs dict of all messages (index by name) 423 sub_msgs dict of all sub messages (index by name) 424 ops dict of all valid requests / responses 425 ntfs dict of all async events 426 consts dict of all constants/enums 427 fixed_header string, optional name of family default fixed header struct 428 mcast_groups dict of all multicast groups (index by name) 429 kernel_family dict of kernel family attributes 430 """ 431 def __init__(self, spec_path, schema_path=None, exclude_ops=None): 432 with open(spec_path, "r") as stream: 433 prefix = '# SPDX-License-Identifier: ' 434 first = stream.readline().strip() 435 if not first.startswith(prefix): 436 raise Exception('SPDX license tag required in the spec') 437 self.license = first[len(prefix):] 438 439 stream.seek(0) 440 spec = yaml.safe_load(stream) 441 442 self._resolution_list = [] 443 444 super().__init__(self, spec) 445 446 self._exclude_ops = exclude_ops if exclude_ops else [] 447 448 self.proto = self.yaml.get('protocol', 'genetlink') 449 self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') 450 451 if schema_path is None: 452 schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' 453 if schema_path: 454 global jsonschema 455 456 with open(schema_path, "r") as stream: 457 schema = yaml.safe_load(stream) 458 459 if jsonschema is None: 460 jsonschema = importlib.import_module("jsonschema") 461 462 jsonschema.validate(self.yaml, schema) 463 464 self.attr_sets = collections.OrderedDict() 465 self.sub_msgs = collections.OrderedDict() 466 self.msgs = collections.OrderedDict() 467 self.req_by_value = collections.OrderedDict() 468 self.rsp_by_value = collections.OrderedDict() 469 self.ops = collections.OrderedDict() 470 self.ntfs = collections.OrderedDict() 471 self.consts = collections.OrderedDict() 472 self.mcast_groups = collections.OrderedDict() 473 self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {})) 474 475 last_exception = None 476 while len(self._resolution_list) > 0: 477 resolved = [] 478 unresolved = self._resolution_list 479 self._resolution_list = [] 480 481 for elem in unresolved: 482 try: 483 elem.resolve() 484 except (KeyError, AttributeError) as e: 485 self._resolution_list.append(elem) 486 last_exception = e 487 continue 488 489 resolved.append(elem) 490 491 if len(resolved) == 0: 492 raise last_exception 493 494 def new_enum(self, elem): 495 return SpecEnumSet(self, elem) 496 497 def new_attr_set(self, elem): 498 return SpecAttrSet(self, elem) 499 500 def new_struct(self, elem): 501 return SpecStruct(self, elem) 502 503 def new_sub_message(self, elem): 504 return SpecSubMessage(self, elem); 505 506 def new_operation(self, elem, req_val, rsp_val): 507 return SpecOperation(self, elem, req_val, rsp_val) 508 509 def new_mcast_group(self, elem): 510 return SpecMcastGroup(self, elem) 511 512 def add_unresolved(self, elem): 513 self._resolution_list.append(elem) 514 515 def _dictify_ops_unified(self): 516 self.fixed_header = self.yaml['operations'].get('fixed-header') 517 val = 1 518 for elem in self.yaml['operations']['list']: 519 if 'value' in elem: 520 val = elem['value'] 521 522 op = self.new_operation(elem, val, val) 523 val += 1 524 525 self.msgs[op.name] = op 526 527 def _dictify_ops_directional(self): 528 self.fixed_header = self.yaml['operations'].get('fixed-header') 529 req_val = rsp_val = 1 530 for elem in self.yaml['operations']['list']: 531 if 'notify' in elem or 'event' in elem: 532 if 'value' in elem: 533 rsp_val = elem['value'] 534 req_val_next = req_val 535 rsp_val_next = rsp_val + 1 536 req_val = None 537 elif 'do' in elem or 'dump' in elem: 538 mode = elem['do'] if 'do' in elem else elem['dump'] 539 540 v = mode.get('request', {}).get('value', None) 541 if v: 542 req_val = v 543 v = mode.get('reply', {}).get('value', None) 544 if v: 545 rsp_val = v 546 547 rsp_inc = 1 if 'reply' in mode else 0 548 req_val_next = req_val + 1 549 rsp_val_next = rsp_val + rsp_inc 550 else: 551 raise Exception("Can't parse directional ops") 552 553 if req_val == req_val_next: 554 req_val = None 555 if rsp_val == rsp_val_next: 556 rsp_val = None 557 558 skip = False 559 for exclude in self._exclude_ops: 560 skip |= bool(exclude.match(elem['name'])) 561 if not skip: 562 op = self.new_operation(elem, req_val, rsp_val) 563 564 req_val = req_val_next 565 rsp_val = rsp_val_next 566 567 self.msgs[op.name] = op 568 569 def find_operation(self, name): 570 """ 571 For a given operation name, find and return operation spec. 572 """ 573 for op in self.yaml['operations']['list']: 574 if name == op['name']: 575 return op 576 return None 577 578 def resolve(self): 579 self.resolve_up(super()) 580 581 definitions = self.yaml.get('definitions', []) 582 for elem in definitions: 583 if elem['type'] == 'enum' or elem['type'] == 'flags': 584 self.consts[elem['name']] = self.new_enum(elem) 585 elif elem['type'] == 'struct': 586 self.consts[elem['name']] = self.new_struct(elem) 587 else: 588 self.consts[elem['name']] = elem 589 590 for elem in self.yaml['attribute-sets']: 591 attr_set = self.new_attr_set(elem) 592 self.attr_sets[elem['name']] = attr_set 593 594 for elem in self.yaml.get('sub-messages', []): 595 sub_message = self.new_sub_message(elem) 596 self.sub_msgs[sub_message.name] = sub_message 597 598 if self.msg_id_model == 'unified': 599 self._dictify_ops_unified() 600 elif self.msg_id_model == 'directional': 601 self._dictify_ops_directional() 602 603 for op in self.msgs.values(): 604 if op.req_value is not None: 605 self.req_by_value[op.req_value] = op 606 if op.rsp_value is not None: 607 self.rsp_by_value[op.rsp_value] = op 608 if not op.is_async and 'attribute-set' in op: 609 self.ops[op.name] = op 610 elif op.is_async: 611 self.ntfs[op.name] = op 612 613 mcgs = self.yaml.get('mcast-groups') 614 if mcgs: 615 for elem in mcgs['list']: 616 mcg = self.new_mcast_group(elem) 617 self.mcast_groups[elem['name']] = mcg 618