~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

TOMOYO Linux Cross Reference
Linux/tools/net/ynl/lib/nlspec.py

Version: ~ [ linux-6.12-rc7 ] ~ [ linux-6.11.7 ] ~ [ linux-6.10.14 ] ~ [ linux-6.9.12 ] ~ [ linux-6.8.12 ] ~ [ linux-6.7.12 ] ~ [ linux-6.6.60 ] ~ [ linux-6.5.13 ] ~ [ linux-6.4.16 ] ~ [ linux-6.3.13 ] ~ [ linux-6.2.16 ] ~ [ linux-6.1.116 ] ~ [ linux-6.0.19 ] ~ [ linux-5.19.17 ] ~ [ linux-5.18.19 ] ~ [ linux-5.17.15 ] ~ [ linux-5.16.20 ] ~ [ linux-5.15.171 ] ~ [ linux-5.14.21 ] ~ [ linux-5.13.19 ] ~ [ linux-5.12.19 ] ~ [ linux-5.11.22 ] ~ [ linux-5.10.229 ] ~ [ linux-5.9.16 ] ~ [ linux-5.8.18 ] ~ [ linux-5.7.19 ] ~ [ linux-5.6.19 ] ~ [ linux-5.5.19 ] ~ [ linux-5.4.285 ] ~ [ linux-5.3.18 ] ~ [ linux-5.2.21 ] ~ [ linux-5.1.21 ] ~ [ linux-5.0.21 ] ~ [ linux-4.20.17 ] ~ [ linux-4.19.323 ] ~ [ linux-4.18.20 ] ~ [ linux-4.17.19 ] ~ [ linux-4.16.18 ] ~ [ linux-4.15.18 ] ~ [ linux-4.14.336 ] ~ [ linux-4.13.16 ] ~ [ linux-4.12.14 ] ~ [ linux-4.11.12 ] ~ [ linux-4.10.17 ] ~ [ linux-4.9.337 ] ~ [ linux-4.4.302 ] ~ [ linux-3.10.108 ] ~ [ linux-2.6.32.71 ] ~ [ linux-2.6.0 ] ~ [ linux-2.4.37.11 ] ~ [ unix-v6-master ] ~ [ ccs-tools-1.8.12 ] ~ [ policy-sample ] ~
Architecture: ~ [ i386 ] ~ [ alpha ] ~ [ m68k ] ~ [ mips ] ~ [ ppc ] ~ [ sparc ] ~ [ sparc64 ] ~

  1 # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
  2 
  3 import collections
  4 import importlib
  5 import os
  6 import yaml
  7 
  8 
  9 # To be loaded dynamically as needed
 10 jsonschema = None
 11 
 12 
 13 class 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 
 60 class 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 
100 class 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 
146 class 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 
185 class 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 
239 class 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 
263 class 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 
288 class 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 
310 class 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 
328 class 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         modes           supported operation modes (do, dump, event etc.)
339         is_call         bool, whether the operation is a call
340         is_async        bool, whether the operation is a notification
341         is_resv         bool, whether the operation does not exist (it's just a reserved ID)
342         attr_set        attribute set name
343         fixed_header    string, optional name of fixed header struct
344 
345         yaml            raw spec as loaded from the spec file
346     """
347     def __init__(self, family, yaml, req_value, rsp_value):
348         super().__init__(family, yaml)
349 
350         self.value = req_value if req_value == rsp_value else None
351         self.req_value = req_value
352         self.rsp_value = rsp_value
353 
354         self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'}
355         self.is_call = 'do' in yaml or 'dump' in yaml
356         self.is_async = 'notify' in yaml or 'event' in yaml
357         self.is_resv = not self.is_async and not self.is_call
358         self.fixed_header = self.yaml.get('fixed-header', family.fixed_header)
359 
360         # Added by resolve:
361         self.attr_set = None
362         delattr(self, "attr_set")
363 
364     def resolve(self):
365         self.resolve_up(super())
366 
367         if 'attribute-set' in self.yaml:
368             attr_set_name = self.yaml['attribute-set']
369         elif 'notify' in self.yaml:
370             msg = self.family.msgs[self.yaml['notify']]
371             attr_set_name = msg['attribute-set']
372         elif self.is_resv:
373             attr_set_name = ''
374         else:
375             raise Exception(f"Can't resolve attribute set for op '{self.name}'")
376         if attr_set_name:
377             self.attr_set = self.family.attr_sets[attr_set_name]
378 
379 
380 class SpecMcastGroup(SpecElement):
381     """Netlink Multicast Group
382 
383     Information about a multicast group.
384 
385     Value is only used for classic netlink families that use the
386     netlink-raw schema. Genetlink families use dynamic ID allocation
387     where the ids of multicast groups get resolved at runtime. Value
388     will be None for genetlink families.
389 
390     Attributes:
391         name      name of the mulitcast group
392         value     integer id of this multicast group for netlink-raw or None
393         yaml      raw spec as loaded from the spec file
394     """
395     def __init__(self, family, yaml):
396         super().__init__(family, yaml)
397         self.value = self.yaml.get('value')
398 
399 
400 class SpecFamily(SpecElement):
401     """ Netlink Family Spec class.
402 
403     Netlink family information loaded from a spec (e.g. in YAML).
404     Takes care of unfolding implicit information which can be skipped
405     in the spec itself for brevity.
406 
407     The class can be used like a dictionary to access the raw spec
408     elements but that's usually a bad idea.
409 
410     Attributes:
411         proto     protocol type (e.g. genetlink)
412         msg_id_model   enum-model for operations (unified, directional etc.)
413         license   spec license (loaded from an SPDX tag on the spec)
414 
415         attr_sets  dict of attribute sets
416         msgs       dict of all messages (index by name)
417         sub_msgs   dict of all sub messages (index by name)
418         ops        dict of all valid requests / responses
419         ntfs       dict of all async events
420         consts     dict of all constants/enums
421         fixed_header  string, optional name of family default fixed header struct
422         mcast_groups  dict of all multicast groups (index by name)
423         kernel_family   dict of kernel family attributes
424     """
425     def __init__(self, spec_path, schema_path=None, exclude_ops=None):
426         with open(spec_path, "r") as stream:
427             prefix = '# SPDX-License-Identifier: '
428             first = stream.readline().strip()
429             if not first.startswith(prefix):
430                 raise Exception('SPDX license tag required in the spec')
431             self.license = first[len(prefix):]
432 
433             stream.seek(0)
434             spec = yaml.safe_load(stream)
435 
436         self._resolution_list = []
437 
438         super().__init__(self, spec)
439 
440         self._exclude_ops = exclude_ops if exclude_ops else []
441 
442         self.proto = self.yaml.get('protocol', 'genetlink')
443         self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified')
444 
445         if schema_path is None:
446             schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml'
447         if schema_path:
448             global jsonschema
449 
450             with open(schema_path, "r") as stream:
451                 schema = yaml.safe_load(stream)
452 
453             if jsonschema is None:
454                 jsonschema = importlib.import_module("jsonschema")
455 
456             jsonschema.validate(self.yaml, schema)
457 
458         self.attr_sets = collections.OrderedDict()
459         self.sub_msgs = collections.OrderedDict()
460         self.msgs = collections.OrderedDict()
461         self.req_by_value = collections.OrderedDict()
462         self.rsp_by_value = collections.OrderedDict()
463         self.ops = collections.OrderedDict()
464         self.ntfs = collections.OrderedDict()
465         self.consts = collections.OrderedDict()
466         self.mcast_groups = collections.OrderedDict()
467         self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {}))
468 
469         last_exception = None
470         while len(self._resolution_list) > 0:
471             resolved = []
472             unresolved = self._resolution_list
473             self._resolution_list = []
474 
475             for elem in unresolved:
476                 try:
477                     elem.resolve()
478                 except (KeyError, AttributeError) as e:
479                     self._resolution_list.append(elem)
480                     last_exception = e
481                     continue
482 
483                 resolved.append(elem)
484 
485             if len(resolved) == 0:
486                 raise last_exception
487 
488     def new_enum(self, elem):
489         return SpecEnumSet(self, elem)
490 
491     def new_attr_set(self, elem):
492         return SpecAttrSet(self, elem)
493 
494     def new_struct(self, elem):
495         return SpecStruct(self, elem)
496 
497     def new_sub_message(self, elem):
498         return SpecSubMessage(self, elem);
499 
500     def new_operation(self, elem, req_val, rsp_val):
501         return SpecOperation(self, elem, req_val, rsp_val)
502 
503     def new_mcast_group(self, elem):
504         return SpecMcastGroup(self, elem)
505 
506     def add_unresolved(self, elem):
507         self._resolution_list.append(elem)
508 
509     def _dictify_ops_unified(self):
510         self.fixed_header = self.yaml['operations'].get('fixed-header')
511         val = 1
512         for elem in self.yaml['operations']['list']:
513             if 'value' in elem:
514                 val = elem['value']
515 
516             op = self.new_operation(elem, val, val)
517             val += 1
518 
519             self.msgs[op.name] = op
520 
521     def _dictify_ops_directional(self):
522         self.fixed_header = self.yaml['operations'].get('fixed-header')
523         req_val = rsp_val = 1
524         for elem in self.yaml['operations']['list']:
525             if 'notify' in elem or 'event' in elem:
526                 if 'value' in elem:
527                     rsp_val = elem['value']
528                 req_val_next = req_val
529                 rsp_val_next = rsp_val + 1
530                 req_val = None
531             elif 'do' in elem or 'dump' in elem:
532                 mode = elem['do'] if 'do' in elem else elem['dump']
533 
534                 v = mode.get('request', {}).get('value', None)
535                 if v:
536                     req_val = v
537                 v = mode.get('reply', {}).get('value', None)
538                 if v:
539                     rsp_val = v
540 
541                 rsp_inc = 1 if 'reply' in mode else 0
542                 req_val_next = req_val + 1
543                 rsp_val_next = rsp_val + rsp_inc
544             else:
545                 raise Exception("Can't parse directional ops")
546 
547             if req_val == req_val_next:
548                 req_val = None
549             if rsp_val == rsp_val_next:
550                 rsp_val = None
551 
552             skip = False
553             for exclude in self._exclude_ops:
554                 skip |= bool(exclude.match(elem['name']))
555             if not skip:
556                 op = self.new_operation(elem, req_val, rsp_val)
557 
558             req_val = req_val_next
559             rsp_val = rsp_val_next
560 
561             self.msgs[op.name] = op
562 
563     def find_operation(self, name):
564       """
565       For a given operation name, find and return operation spec.
566       """
567       for op in self.yaml['operations']['list']:
568         if name == op['name']:
569           return op
570       return None
571 
572     def resolve(self):
573         self.resolve_up(super())
574 
575         definitions = self.yaml.get('definitions', [])
576         for elem in definitions:
577             if elem['type'] == 'enum' or elem['type'] == 'flags':
578                 self.consts[elem['name']] = self.new_enum(elem)
579             elif elem['type'] == 'struct':
580                 self.consts[elem['name']] = self.new_struct(elem)
581             else:
582                 self.consts[elem['name']] = elem
583 
584         for elem in self.yaml['attribute-sets']:
585             attr_set = self.new_attr_set(elem)
586             self.attr_sets[elem['name']] = attr_set
587 
588         for elem in self.yaml.get('sub-messages', []):
589             sub_message = self.new_sub_message(elem)
590             self.sub_msgs[sub_message.name] = sub_message
591 
592         if self.msg_id_model == 'unified':
593             self._dictify_ops_unified()
594         elif self.msg_id_model == 'directional':
595             self._dictify_ops_directional()
596 
597         for op in self.msgs.values():
598             if op.req_value is not None:
599                 self.req_by_value[op.req_value] = op
600             if op.rsp_value is not None:
601                 self.rsp_by_value[op.rsp_value] = op
602             if not op.is_async and 'attribute-set' in op:
603                 self.ops[op.name] = op
604             elif op.is_async:
605                 self.ntfs[op.name] = op
606 
607         mcgs = self.yaml.get('mcast-groups')
608         if mcgs:
609             for elem in mcgs['list']:
610                 mcg = self.new_mcast_group(elem)
611                 self.mcast_groups[elem['name']] = mcg

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~

kernel.org | git.kernel.org | LWN.net | Project Home | SVN repository | Mail admin

Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.

sflogo.php