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