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