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