brintos

brintos / linux-shallow public Read only

0
0
Text · 20.0 KiB · b6d6f8a Raw
612 lines · python
1# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause2 3import collections4import importlib5import os6import yaml7 8 9# To be loaded dynamically as needed10jsonschema = None11 12 13class SpecElement:14    """Netlink spec element.15 16    Abstract element of the Netlink spec. Implements the dictionary interface17    for access to the raw spec. Supports iterative resolution of dependencies18    across elements and class inheritance levels. The elements of the spec19    may refer to each other, and although loops should be very rare, having20    to maintain correct ordering of instantiation is painful, so the resolve()21    method should be used to perform parts of init which require access to22    other parts of the spec.23 24    Attributes:25        yaml        raw spec as loaded from the spec file26        family      back reference to the full family27 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 = yaml33        self.family = family34 35        if 'name' in self.yaml:36            self.name = self.yaml['name']37            self.ident_name = self.name.replace('-', '_')38 39        self._super_resolved = False40        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.yaml47 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 = True55 56    def resolve(self):57        pass58 59 60class SpecEnumEntry(SpecElement):61    """ Entry within an enum declared in the Netlink spec.62 63    Attributes:64        doc         documentation string65        enum_set    back reference to the enum66        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 flags70        user_value   user value, same as raw value for enums, for flags it's the mask71    """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_set79 80        if 'value' in yaml:81            self.value = yaml['value']82        elif prev:83            self.value = prev.value + 184        else:85            self.value = value_start86 87    def has_doc(self):88        return bool(self.doc)89 90    def raw_value(self):91        return self.value92 93    def user_value(self, as_flags=None):94        if self.enum_set['type'] == 'flags' or as_flags:95            return 1 << self.value96        else:97            return self.value98 99 100class SpecEnumSet(SpecElement):101    """ Enum type102 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 flags108        entries         entries by name109        entries_by_val  entries by value110    Methods:111        get_mask      for flags compute the mask of all defined values112    """113    def __init__(self, family, yaml):114        super().__init__(family, yaml)115 116        self.type = yaml['type']117 118        prev_entry = None119        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] = e125            self.entries_by_val[e.raw_value()] = e126            prev_entry = e127 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 True134        for entry in self.entries.values():135            if entry.has_doc():136                return True137        return False138 139    def get_mask(self, as_flags=None):140        mask = 0141        for e in self.entries.values():142            mask += e.user_value(as_flags)143        return mask144 145 146class SpecAttr(SpecElement):147    """ Single Netlink attribute type148 149    Represents a single attribute type within an attr space.150 151    Attributes:152        type          string, attribute type153        value         numerical ID when serialized154        attr_set      Attribute Set containing this attr155        is_multi      bool, attr may repeat multiple times156        struct_name   string, name of struct definition157        sub_type      string, name of sub type158        len           integer, optional byte length of binary types159        display_hint  string, hint to help choose format specifier160                      when displaying the value161        sub_message   string, name of sub message type162        selector      string, name of attribute used to select163                      sub-message type164 165        is_auto_scalar bool, attr is a variable-size scalar166    """167    def __init__(self, family, attr_set, yaml, value):168        super().__init__(family, yaml)169 170        self.type = yaml['type']171        self.value = value172        self.attr_set = attr_set173        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 spec191    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 None197    """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 = 1208            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] = attr214                self.attrs_by_val[attr.value] = attr215                val += 1216        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] = attr221                self.attrs_by_val[attr.value] = attr222 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.attrs231 232    def __iter__(self):233        yield from self.attrs234 235    def items(self):236        return self.attrs.items()237 238 239class SpecStructMember(SpecElement):240    """Struct member attribute241 242    Represents a single struct member attribute.243 244    Attributes:245        type        string, type of the member attribute246        byte_order  string or None for native byte order247        enum        string, name of the enum definition248        len         integer, optional byte length of binary types249        display_hint  string, hint to help choose format specifier250                      when displaying the value251        struct      string, name of nested struct type252    """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 type265 266    Represents a C struct definition.267 268    Attributes:269        members   ordered list of struct members270    """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.members283 284    def items(self):285        return self.members.items()286 287 288class SpecSubMessage(SpecElement):289    """ Netlink sub-message definition290 291    Represents a set of sub-message formats for polymorphic nlattrs292    that contain type-specific sub messages.293 294    Attributes:295        name     string, name of sub-message definition296        formats  dict of sub-message formats indexed by match value297    """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] = format305 306    def new_format(self, family, format):307        return SpecSubMessageFormat(family, format)308 309 310class SpecSubMessageFormat(SpecElement):311    """ Netlink sub-message format definition312 313    Represents a single format for a sub-message.314 315    Attributes:316        value         attribute value to match against type selector317        fixed_header  string, name of fixed header, or None318        attr_set      string, name of attribute set, or None319    """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 Operation330 331    Information about a single Netlink operation.332 333    Attributes:334        value           numerical ID when serialized, None if req/rsp values differ335 336        req_value       numerical ID when serialized, user -> kernel337        rsp_value       numerical ID when serialized, user <- kernel338        modes           supported operation modes (do, dump, event etc.)339        is_call         bool, whether the operation is a call340        is_async        bool, whether the operation is a notification341        is_resv         bool, whether the operation does not exist (it's just a reserved ID)342        attr_set        attribute set name343        fixed_header    string, optional name of fixed header struct344 345        yaml            raw spec as loaded from the spec file346    """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 None351        self.req_value = req_value352        self.rsp_value = rsp_value353 354        self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'}355        self.is_call = 'do' in yaml or 'dump' in yaml356        self.is_async = 'notify' in yaml or 'event' in yaml357        self.is_resv = not self.is_async and not self.is_call358        self.fixed_header = self.yaml.get('fixed-header', family.fixed_header)359 360        # Added by resolve:361        self.attr_set = None362        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 380class SpecMcastGroup(SpecElement):381    """Netlink Multicast Group382 383    Information about a multicast group.384 385    Value is only used for classic netlink families that use the386    netlink-raw schema. Genetlink families use dynamic ID allocation387    where the ids of multicast groups get resolved at runtime. Value388    will be None for genetlink families.389 390    Attributes:391        name      name of the mulitcast group392        value     integer id of this multicast group for netlink-raw or None393        yaml      raw spec as loaded from the spec file394    """395    def __init__(self, family, yaml):396        super().__init__(family, yaml)397        self.value = self.yaml.get('value')398 399 400class 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 skipped405    in the spec itself for brevity.406 407    The class can be used like a dictionary to access the raw spec408    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 sets416        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 / responses419        ntfs       dict of all async events420        consts     dict of all constants/enums421        fixed_header  string, optional name of family default fixed header struct422        mcast_groups  dict of all multicast groups (index by name)423        kernel_family   dict of kernel family attributes424    """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 jsonschema449 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 = None470        while len(self._resolution_list) > 0:471            resolved = []472            unresolved = self._resolution_list473            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 = e481                    continue482 483                resolved.append(elem)484 485            if len(resolved) == 0:486                raise last_exception487 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 = 1512        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 += 1518 519            self.msgs[op.name] = op520 521    def _dictify_ops_directional(self):522        self.fixed_header = self.yaml['operations'].get('fixed-header')523        req_val = rsp_val = 1524        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_val529                rsp_val_next = rsp_val + 1530                req_val = None531            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 = v537                v = mode.get('reply', {}).get('value', None)538                if v:539                    rsp_val = v540 541                rsp_inc = 1 if 'reply' in mode else 0542                req_val_next = req_val + 1543                rsp_val_next = rsp_val + rsp_inc544            else:545                raise Exception("Can't parse directional ops")546 547            if req_val == req_val_next:548                req_val = None549            if rsp_val == rsp_val_next:550                rsp_val = None551 552            skip = False553            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_next559            rsp_val = rsp_val_next560 561            self.msgs[op.name] = op562 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 op570      return None571 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']] = elem583 584        for elem in self.yaml['attribute-sets']:585            attr_set = self.new_attr_set(elem)586            self.attr_sets[elem['name']] = attr_set587 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_message591 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] = op600            if op.rsp_value is not None:601                self.rsp_by_value[op.rsp_value] = op602            if not op.is_async and 'attribute-set' in op:603                self.ops[op.name] = op604            elif op.is_async:605                self.ntfs[op.name] = op606 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']] = mcg612