From 0518863407b8dcc7070fdbc1c015046d66777e78 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Fri, 13 Dec 2024 07:22:42 -0800 Subject: selftests: net: support setting recv_size in YNL recv_size parameter allows constraining the buffer size for dumps. It's useful in testing kernel handling of dump continuation, IOW testing dumps which span multiple skbs. Let the tests set this parameter when initializing the YNL family. Keep the normal default, we don't want tests to unintentionally behave very differently than normal code. Reviewed-by: Joe Damato Reviewed-by: Petr Machata Link: https://patch.msgid.link/20241213152244.3080955-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- tools/testing/selftests/net/lib/py/ynl.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'tools/testing/selftests/net/lib/py/ynl.py') diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py index a0d689d58c57..076a7e8dc3eb 100644 --- a/tools/testing/selftests/net/lib/py/ynl.py +++ b/tools/testing/selftests/net/lib/py/ynl.py @@ -32,23 +32,23 @@ except ModuleNotFoundError as e: # Set schema='' to avoid jsonschema validation, it's slow # class EthtoolFamily(YnlFamily): - def __init__(self): + def __init__(self, recv_size=0): super().__init__((SPEC_PATH / Path('ethtool.yaml')).as_posix(), - schema='') + schema='', recv_size=recv_size) class RtnlFamily(YnlFamily): - def __init__(self): + def __init__(self, recv_size=0): super().__init__((SPEC_PATH / Path('rt_link.yaml')).as_posix(), - schema='') + schema='', recv_size=recv_size) class NetdevFamily(YnlFamily): - def __init__(self): + def __init__(self, recv_size=0): super().__init__((SPEC_PATH / Path('netdev.yaml')).as_posix(), - schema='') + schema='', recv_size=recv_size) class NetshaperFamily(YnlFamily): - def __init__(self): + def __init__(self, recv_size=0): super().__init__((SPEC_PATH / Path('net_shaper.yaml')).as_posix(), - schema='') + schema='', recv_size=recv_size) -- cgit v1.2.3 From ab88c2b3739a3d839b04f57d9ee0d6b1dc311cc8 Mon Sep 17 00:00:00 2001 From: Jan Stancek Date: Wed, 8 Jan 2025 14:56:14 +0100 Subject: tools: ynl: move python code to separate sub-directory Move python code to a separate directory so it can be packaged as a python module. Updates existing references in selftests and docs. Also rename ynl-gen-[c|rst] to ynl_gen_[c|rst], avoid dashes as these prevent easy imports for entrypoints. Signed-off-by: Jan Stancek Reviewed-by: Donald Hunter Link: https://patch.msgid.link/a4151bad0e6984e7164d395125ce87fd2e048bf1.1736343575.git.jstancek@redhat.com Signed-off-by: Jakub Kicinski --- Documentation/Makefile | 2 +- Documentation/networking/multi-pf-netdev.rst | 4 +- Documentation/networking/napi.rst | 4 +- Documentation/networking/netlink_spec/readme.txt | 2 +- .../userspace-api/netlink/intro-specs.rst | 8 +- tools/net/ynl/Makefile | 2 + tools/net/ynl/cli.py | 119 - tools/net/ynl/ethtool.py | 439 --- tools/net/ynl/generated/Makefile | 2 +- tools/net/ynl/lib/.gitignore | 1 - tools/net/ynl/lib/Makefile | 1 - tools/net/ynl/lib/__init__.py | 8 - tools/net/ynl/lib/nlspec.py | 617 ---- tools/net/ynl/lib/ynl.py | 1067 ------- tools/net/ynl/pyynl/.gitignore | 2 + tools/net/ynl/pyynl/__init__.py | 0 tools/net/ynl/pyynl/cli.py | 119 + tools/net/ynl/pyynl/ethtool.py | 439 +++ tools/net/ynl/pyynl/lib/__init__.py | 8 + tools/net/ynl/pyynl/lib/nlspec.py | 617 ++++ tools/net/ynl/pyynl/lib/ynl.py | 1067 +++++++ tools/net/ynl/pyynl/ynl_gen_c.py | 3044 ++++++++++++++++++++ tools/net/ynl/pyynl/ynl_gen_rst.py | 453 +++ tools/net/ynl/ynl-gen-c.py | 3044 -------------------- tools/net/ynl/ynl-gen-rst.py | 453 --- tools/net/ynl/ynl-regen.sh | 2 +- tools/testing/selftests/net/lib/py/ynl.py | 4 +- tools/testing/selftests/net/ynl.mk | 3 +- 28 files changed, 5767 insertions(+), 5764 deletions(-) delete mode 100755 tools/net/ynl/cli.py delete mode 100755 tools/net/ynl/ethtool.py delete mode 100644 tools/net/ynl/lib/__init__.py delete mode 100644 tools/net/ynl/lib/nlspec.py delete mode 100644 tools/net/ynl/lib/ynl.py create mode 100644 tools/net/ynl/pyynl/.gitignore create mode 100644 tools/net/ynl/pyynl/__init__.py create mode 100755 tools/net/ynl/pyynl/cli.py create mode 100755 tools/net/ynl/pyynl/ethtool.py create mode 100644 tools/net/ynl/pyynl/lib/__init__.py create mode 100644 tools/net/ynl/pyynl/lib/nlspec.py create mode 100644 tools/net/ynl/pyynl/lib/ynl.py create mode 100755 tools/net/ynl/pyynl/ynl_gen_c.py create mode 100755 tools/net/ynl/pyynl/ynl_gen_rst.py delete mode 100755 tools/net/ynl/ynl-gen-c.py delete mode 100755 tools/net/ynl/ynl-gen-rst.py (limited to 'tools/testing/selftests/net/lib/py/ynl.py') diff --git a/Documentation/Makefile b/Documentation/Makefile index fa71602ec961..52c6c5a3efa9 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -104,7 +104,7 @@ quiet_cmd_sphinx = SPHINX $@ --> file://$(abspath $(BUILDDIR)/$3/$4) YNL_INDEX:=$(srctree)/Documentation/networking/netlink_spec/index.rst YNL_RST_DIR:=$(srctree)/Documentation/networking/netlink_spec YNL_YAML_DIR:=$(srctree)/Documentation/netlink/specs -YNL_TOOL:=$(srctree)/tools/net/ynl/ynl-gen-rst.py +YNL_TOOL:=$(srctree)/tools/net/ynl/pyynl/ynl_gen_rst.py YNL_RST_FILES_TMP := $(patsubst %.yaml,%.rst,$(wildcard $(YNL_YAML_DIR)/*.yaml)) YNL_RST_FILES := $(patsubst $(YNL_YAML_DIR)%,$(YNL_RST_DIR)%, $(YNL_RST_FILES_TMP)) diff --git a/Documentation/networking/multi-pf-netdev.rst b/Documentation/networking/multi-pf-netdev.rst index 2cd25d81aaa7..2f5a5bb3ca9a 100644 --- a/Documentation/networking/multi-pf-netdev.rst +++ b/Documentation/networking/multi-pf-netdev.rst @@ -89,7 +89,7 @@ Observability ============= The relation between PF, irq, napi, and queue can be observed via netlink spec:: - $ ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/netdev.yaml --dump queue-get --json='{"ifindex": 13}' + $ ./tools/net/ynl/pyynl/cli.py --spec Documentation/netlink/specs/netdev.yaml --dump queue-get --json='{"ifindex": 13}' [{'id': 0, 'ifindex': 13, 'napi-id': 539, 'type': 'rx'}, {'id': 1, 'ifindex': 13, 'napi-id': 540, 'type': 'rx'}, {'id': 2, 'ifindex': 13, 'napi-id': 541, 'type': 'rx'}, @@ -101,7 +101,7 @@ The relation between PF, irq, napi, and queue can be observed via netlink spec:: {'id': 3, 'ifindex': 13, 'napi-id': 542, 'type': 'tx'}, {'id': 4, 'ifindex': 13, 'napi-id': 543, 'type': 'tx'}] - $ ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/netdev.yaml --dump napi-get --json='{"ifindex": 13}' + $ ./tools/net/ynl/pyynl/cli.py --spec Documentation/netlink/specs/netdev.yaml --dump napi-get --json='{"ifindex": 13}' [{'id': 543, 'ifindex': 13, 'irq': 42}, {'id': 542, 'ifindex': 13, 'irq': 41}, {'id': 541, 'ifindex': 13, 'irq': 40}, diff --git a/Documentation/networking/napi.rst b/Documentation/networking/napi.rst index 02720dd71a76..6083210ab2a4 100644 --- a/Documentation/networking/napi.rst +++ b/Documentation/networking/napi.rst @@ -199,13 +199,13 @@ parameters mentioned above use hyphens instead of underscores: Per-NAPI configuration can be done programmatically in a user application or by using a script included in the kernel source tree: -``tools/net/ynl/cli.py``. +``tools/net/ynl/pyynl/cli.py``. For example, using the script: .. code-block:: bash - $ kernel-source/tools/net/ynl/cli.py \ + $ kernel-source/tools/net/ynl/pyynl/cli.py \ --spec Documentation/netlink/specs/netdev.yaml \ --do napi-set \ --json='{"id": 345, diff --git a/Documentation/networking/netlink_spec/readme.txt b/Documentation/networking/netlink_spec/readme.txt index 6763f99d216c..030b44aca4e6 100644 --- a/Documentation/networking/netlink_spec/readme.txt +++ b/Documentation/networking/netlink_spec/readme.txt @@ -1,4 +1,4 @@ SPDX-License-Identifier: GPL-2.0 This file is populated during the build of the documentation (htmldocs) by the -tools/net/ynl/ynl-gen-rst.py script. +tools/net/ynl/pyynl/ynl_gen_rst.py script. diff --git a/Documentation/userspace-api/netlink/intro-specs.rst b/Documentation/userspace-api/netlink/intro-specs.rst index bada89699455..a4435ae4628d 100644 --- a/Documentation/userspace-api/netlink/intro-specs.rst +++ b/Documentation/userspace-api/netlink/intro-specs.rst @@ -15,7 +15,7 @@ developing Netlink related code. The tool is implemented in Python and can use a YAML specification to issue Netlink requests to the kernel. Only Generic Netlink is supported. -The tool is located at ``tools/net/ynl/cli.py``. It accepts +The tool is located at ``tools/net/ynl/pyynl/cli.py``. It accepts a handul of arguments, the most important ones are: - ``--spec`` - point to the spec file @@ -27,7 +27,7 @@ YAML specs can be found under ``Documentation/netlink/specs/``. Example use:: - $ ./tools/net/ynl/cli.py --spec Documentation/netlink/specs/ethtool.yaml \ + $ ./tools/net/ynl/pyynl/cli.py --spec Documentation/netlink/specs/ethtool.yaml \ --do rings-get \ --json '{"header":{"dev-index": 18}}' {'header': {'dev-index': 18, 'dev-name': 'eni1np1'}, @@ -75,7 +75,7 @@ the two marker lines like above to a file, add that file to git, and run the regeneration tool. Grep the tree for ``YNL-GEN`` to see other examples. -The code generation itself is performed by ``tools/net/ynl/ynl-gen-c.py`` +The code generation itself is performed by ``tools/net/ynl/pyynl/ynl_gen_c.py`` but it takes a few arguments so calling it directly for each file quickly becomes tedious. @@ -84,7 +84,7 @@ YNL lib ``tools/net/ynl/lib/`` contains an implementation of a C library (based on libmnl) which integrates with code generated by -``tools/net/ynl/ynl-gen-c.py`` to create easy to use netlink wrappers. +``tools/net/ynl/pyynl/ynl_gen_c.py`` to create easy to use netlink wrappers. YNL basics ---------- diff --git a/tools/net/ynl/Makefile b/tools/net/ynl/Makefile index d1cdf2a8f826..5268b91bf7ed 100644 --- a/tools/net/ynl/Makefile +++ b/tools/net/ynl/Makefile @@ -21,5 +21,7 @@ clean distclean: fi \ done rm -f libynl.a + rm -rf pyynl/__pycache__ + rm -rf pyynl/lib/__pycache__ .PHONY: all clean distclean $(SUBDIRS) diff --git a/tools/net/ynl/cli.py b/tools/net/ynl/cli.py deleted file mode 100755 index 41d9fa5c818d..000000000000 --- a/tools/net/ynl/cli.py +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause - -import argparse -import json -import pathlib -import pprint -import sys - -sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) -from lib import YnlFamily, Netlink, NlError - - -class YnlEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, bytes): - return bytes.hex(obj) - if isinstance(obj, set): - return list(obj) - return json.JSONEncoder.default(self, obj) - - -def main(): - description = """ - YNL CLI utility - a general purpose netlink utility that uses YAML - specs to drive protocol encoding and decoding. - """ - epilog = """ - The --multi option can be repeated to include several do operations - in the same netlink payload. - """ - - parser = argparse.ArgumentParser(description=description, - epilog=epilog) - parser.add_argument('--spec', dest='spec', type=str, required=True) - parser.add_argument('--schema', dest='schema', type=str) - parser.add_argument('--no-schema', action='store_true') - parser.add_argument('--json', dest='json_text', type=str) - - group = parser.add_mutually_exclusive_group() - group.add_argument('--do', dest='do', metavar='DO-OPERATION', type=str) - group.add_argument('--multi', dest='multi', nargs=2, action='append', - metavar=('DO-OPERATION', 'JSON_TEXT'), type=str) - group.add_argument('--dump', dest='dump', metavar='DUMP-OPERATION', type=str) - group.add_argument('--list-ops', action='store_true') - group.add_argument('--list-msgs', action='store_true') - - parser.add_argument('--duration', dest='duration', type=int, - help='when subscribed, watch for DURATION seconds') - parser.add_argument('--sleep', dest='duration', type=int, - help='alias for duration') - parser.add_argument('--subscribe', dest='ntf', type=str) - parser.add_argument('--replace', dest='flags', action='append_const', - const=Netlink.NLM_F_REPLACE) - parser.add_argument('--excl', dest='flags', action='append_const', - const=Netlink.NLM_F_EXCL) - parser.add_argument('--create', dest='flags', action='append_const', - const=Netlink.NLM_F_CREATE) - parser.add_argument('--append', dest='flags', action='append_const', - const=Netlink.NLM_F_APPEND) - parser.add_argument('--process-unknown', action=argparse.BooleanOptionalAction) - parser.add_argument('--output-json', action='store_true') - parser.add_argument('--dbg-small-recv', default=0, const=4000, - action='store', nargs='?', type=int) - args = parser.parse_args() - - def output(msg): - if args.output_json: - print(json.dumps(msg, cls=YnlEncoder)) - else: - pprint.PrettyPrinter().pprint(msg) - - if args.no_schema: - args.schema = '' - - attrs = {} - if args.json_text: - attrs = json.loads(args.json_text) - - ynl = YnlFamily(args.spec, args.schema, args.process_unknown, - recv_size=args.dbg_small_recv) - if args.dbg_small_recv: - ynl.set_recv_dbg(True) - - if args.ntf: - ynl.ntf_subscribe(args.ntf) - - if args.list_ops: - for op_name, op in ynl.ops.items(): - print(op_name, " [", ", ".join(op.modes), "]") - if args.list_msgs: - for op_name, op in ynl.msgs.items(): - print(op_name, " [", ", ".join(op.modes), "]") - - try: - if args.do: - reply = ynl.do(args.do, attrs, args.flags) - output(reply) - if args.dump: - reply = ynl.dump(args.dump, attrs) - output(reply) - if args.multi: - ops = [ (item[0], json.loads(item[1]), args.flags or []) for item in args.multi ] - reply = ynl.do_multi(ops) - output(reply) - except NlError as e: - print(e) - exit(1) - - if args.ntf: - try: - for msg in ynl.poll_ntf(duration=args.duration): - output(msg) - except KeyboardInterrupt: - pass - - -if __name__ == "__main__": - main() diff --git a/tools/net/ynl/ethtool.py b/tools/net/ynl/ethtool.py deleted file mode 100755 index ebb0a11f67bf..000000000000 --- a/tools/net/ynl/ethtool.py +++ /dev/null @@ -1,439 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause - -import argparse -import json -import pathlib -import pprint -import sys -import re -import os - -sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) -from lib import YnlFamily - -def args_to_req(ynl, op_name, args, req): - """ - Verify and convert command-line arguments to the ynl-compatible request. - """ - valid_attrs = ynl.operation_do_attributes(op_name) - valid_attrs.remove('header') # not user-provided - - if len(args) == 0: - print(f'no attributes, expected: {valid_attrs}') - sys.exit(1) - - i = 0 - while i < len(args): - attr = args[i] - if i + 1 >= len(args): - print(f'expected value for \'{attr}\'') - sys.exit(1) - - if attr not in valid_attrs: - print(f'invalid attribute \'{attr}\', expected: {valid_attrs}') - sys.exit(1) - - val = args[i+1] - i += 2 - - req[attr] = val - -def print_field(reply, *desc): - """ - Pretty-print a set of fields from the reply. desc specifies the - fields and the optional type (bool/yn). - """ - if len(desc) == 0: - return print_field(reply, *zip(reply.keys(), reply.keys())) - - for spec in desc: - try: - field, name, tp = spec - except: - field, name = spec - tp = 'int' - - value = reply.get(field, None) - if tp == 'yn': - value = 'yes' if value else 'no' - elif tp == 'bool' or isinstance(value, bool): - value = 'on' if value else 'off' - else: - value = 'n/a' if value is None else value - - print(f'{name}: {value}') - -def print_speed(name, value): - """ - Print out the speed-like strings from the value dict. - """ - speed_re = re.compile(r'[0-9]+base[^/]+/.+') - speed = [ k for k, v in value.items() if v and speed_re.match(k) ] - print(f'{name}: {" ".join(speed)}') - -def doit(ynl, args, op_name): - """ - Prepare request header, parse arguments and doit. - """ - req = { - 'header': { - 'dev-name': args.device, - }, - } - - args_to_req(ynl, op_name, args.args, req) - ynl.do(op_name, req) - -def dumpit(ynl, args, op_name, extra = {}): - """ - Prepare request header, parse arguments and dumpit (filtering out the - devices we're not interested in). - """ - reply = ynl.dump(op_name, { 'header': {} } | extra) - if not reply: - return {} - - for msg in reply: - if msg['header']['dev-name'] == args.device: - if args.json: - pprint.PrettyPrinter().pprint(msg) - sys.exit(0) - msg.pop('header', None) - return msg - - print(f"Not supported for device {args.device}") - sys.exit(1) - -def bits_to_dict(attr): - """ - Convert ynl-formatted bitmask to a dict of bit=value. - """ - ret = {} - if 'bits' not in attr: - return dict() - if 'bit' not in attr['bits']: - return dict() - for bit in attr['bits']['bit']: - if bit['name'] == '': - continue - name = bit['name'] - value = bit.get('value', False) - ret[name] = value - return ret - -def main(): - parser = argparse.ArgumentParser(description='ethtool wannabe') - parser.add_argument('--json', action=argparse.BooleanOptionalAction) - parser.add_argument('--show-priv-flags', action=argparse.BooleanOptionalAction) - parser.add_argument('--set-priv-flags', action=argparse.BooleanOptionalAction) - parser.add_argument('--show-eee', action=argparse.BooleanOptionalAction) - parser.add_argument('--set-eee', action=argparse.BooleanOptionalAction) - parser.add_argument('-a', '--show-pause', action=argparse.BooleanOptionalAction) - parser.add_argument('-A', '--set-pause', action=argparse.BooleanOptionalAction) - parser.add_argument('-c', '--show-coalesce', action=argparse.BooleanOptionalAction) - parser.add_argument('-C', '--set-coalesce', action=argparse.BooleanOptionalAction) - parser.add_argument('-g', '--show-ring', action=argparse.BooleanOptionalAction) - parser.add_argument('-G', '--set-ring', action=argparse.BooleanOptionalAction) - parser.add_argument('-k', '--show-features', action=argparse.BooleanOptionalAction) - parser.add_argument('-K', '--set-features', action=argparse.BooleanOptionalAction) - parser.add_argument('-l', '--show-channels', action=argparse.BooleanOptionalAction) - parser.add_argument('-L', '--set-channels', action=argparse.BooleanOptionalAction) - parser.add_argument('-T', '--show-time-stamping', action=argparse.BooleanOptionalAction) - parser.add_argument('-S', '--statistics', action=argparse.BooleanOptionalAction) - # TODO: --show-tunnels tunnel-info-get - # TODO: --show-module module-get - # TODO: --get-plca-cfg plca-get - # TODO: --get-plca-status plca-get-status - # TODO: --show-mm mm-get - # TODO: --show-fec fec-get - # TODO: --dump-module-eerpom module-eeprom-get - # TODO: pse-get - # TODO: rss-get - parser.add_argument('device', metavar='device', type=str) - parser.add_argument('args', metavar='args', type=str, nargs='*') - global args - args = parser.parse_args() - - script_abs_dir = os.path.dirname(os.path.abspath(sys.argv[0])) - spec = os.path.join(script_abs_dir, - '../../../Documentation/netlink/specs/ethtool.yaml') - schema = os.path.join(script_abs_dir, - '../../../Documentation/netlink/genetlink-legacy.yaml') - - ynl = YnlFamily(spec, schema) - - if args.set_priv_flags: - # TODO: parse the bitmask - print("not implemented") - return - - if args.set_eee: - return doit(ynl, args, 'eee-set') - - if args.set_pause: - return doit(ynl, args, 'pause-set') - - if args.set_coalesce: - return doit(ynl, args, 'coalesce-set') - - if args.set_features: - # TODO: parse the bitmask - print("not implemented") - return - - if args.set_channels: - return doit(ynl, args, 'channels-set') - - if args.set_ring: - return doit(ynl, args, 'rings-set') - - if args.show_priv_flags: - flags = bits_to_dict(dumpit(ynl, args, 'privflags-get')['flags']) - print_field(flags) - return - - if args.show_eee: - eee = dumpit(ynl, args, 'eee-get') - ours = bits_to_dict(eee['modes-ours']) - peer = bits_to_dict(eee['modes-peer']) - - if 'enabled' in eee: - status = 'enabled' if eee['enabled'] else 'disabled' - if 'active' in eee and eee['active']: - status = status + ' - active' - else: - status = status + ' - inactive' - else: - status = 'not supported' - - print(f'EEE status: {status}') - print_field(eee, ('tx-lpi-timer', 'Tx LPI')) - print_speed('Advertised EEE link modes', ours) - print_speed('Link partner advertised EEE link modes', peer) - - return - - if args.show_pause: - print_field(dumpit(ynl, args, 'pause-get'), - ('autoneg', 'Autonegotiate', 'bool'), - ('rx', 'RX', 'bool'), - ('tx', 'TX', 'bool')) - return - - if args.show_coalesce: - print_field(dumpit(ynl, args, 'coalesce-get')) - return - - if args.show_features: - reply = dumpit(ynl, args, 'features-get') - available = bits_to_dict(reply['hw']) - requested = bits_to_dict(reply['wanted']).keys() - active = bits_to_dict(reply['active']).keys() - never_changed = bits_to_dict(reply['nochange']).keys() - - for f in sorted(available): - value = "off" - if f in active: - value = "on" - - fixed = "" - if f not in available or f in never_changed: - fixed = " [fixed]" - - req = "" - if f in requested: - if f in active: - req = " [requested on]" - else: - req = " [requested off]" - - print(f'{f}: {value}{fixed}{req}') - - return - - if args.show_channels: - reply = dumpit(ynl, args, 'channels-get') - print(f'Channel parameters for {args.device}:') - - print(f'Pre-set maximums:') - print_field(reply, - ('rx-max', 'RX'), - ('tx-max', 'TX'), - ('other-max', 'Other'), - ('combined-max', 'Combined')) - - print(f'Current hardware settings:') - print_field(reply, - ('rx-count', 'RX'), - ('tx-count', 'TX'), - ('other-count', 'Other'), - ('combined-count', 'Combined')) - - return - - if args.show_ring: - reply = dumpit(ynl, args, 'channels-get') - - print(f'Ring parameters for {args.device}:') - - print(f'Pre-set maximums:') - print_field(reply, - ('rx-max', 'RX'), - ('rx-mini-max', 'RX Mini'), - ('rx-jumbo-max', 'RX Jumbo'), - ('tx-max', 'TX')) - - print(f'Current hardware settings:') - print_field(reply, - ('rx', 'RX'), - ('rx-mini', 'RX Mini'), - ('rx-jumbo', 'RX Jumbo'), - ('tx', 'TX')) - - print_field(reply, - ('rx-buf-len', 'RX Buf Len'), - ('cqe-size', 'CQE Size'), - ('tx-push', 'TX Push', 'bool')) - - return - - if args.statistics: - print(f'NIC statistics:') - - # TODO: pass id? - strset = dumpit(ynl, args, 'strset-get') - pprint.PrettyPrinter().pprint(strset) - - req = { - 'groups': { - 'size': 1, - 'bits': { - 'bit': - # TODO: support passing the bitmask - #[ - #{ 'name': 'eth-phy', 'value': True }, - { 'name': 'eth-mac', 'value': True }, - #{ 'name': 'eth-ctrl', 'value': True }, - #{ 'name': 'rmon', 'value': True }, - #], - }, - }, - } - - rsp = dumpit(ynl, args, 'stats-get', req) - pprint.PrettyPrinter().pprint(rsp) - return - - if args.show_time_stamping: - req = { - 'header': { - 'flags': 'stats', - }, - } - - tsinfo = dumpit(ynl, args, 'tsinfo-get', req) - - print(f'Time stamping parameters for {args.device}:') - - print('Capabilities:') - [print(f'\t{v}') for v in bits_to_dict(tsinfo['timestamping'])] - - print(f'PTP Hardware Clock: {tsinfo["phc-index"]}') - - print('Hardware Transmit Timestamp Modes:') - [print(f'\t{v}') for v in bits_to_dict(tsinfo['tx-types'])] - - print('Hardware Receive Filter Modes:') - [print(f'\t{v}') for v in bits_to_dict(tsinfo['rx-filters'])] - - print('Statistics:') - [print(f'\t{k}: {v}') for k, v in tsinfo['stats'].items()] - return - - print(f'Settings for {args.device}:') - linkmodes = dumpit(ynl, args, 'linkmodes-get') - ours = bits_to_dict(linkmodes['ours']) - - supported_ports = ('TP', 'AUI', 'BNC', 'MII', 'FIBRE', 'Backplane') - ports = [ p for p in supported_ports if ours.get(p, False)] - print(f'Supported ports: [ {" ".join(ports)} ]') - - print_speed('Supported link modes', ours) - - print_field(ours, ('Pause', 'Supported pause frame use', 'yn')) - print_field(ours, ('Autoneg', 'Supports auto-negotiation', 'yn')) - - supported_fec = ('None', 'PS', 'BASER', 'LLRS') - fec = [ p for p in supported_fec if ours.get(p, False)] - fec_str = " ".join(fec) - if len(fec) == 0: - fec_str = "Not reported" - - print(f'Supported FEC modes: {fec_str}') - - speed = 'Unknown!' - if linkmodes['speed'] > 0 and linkmodes['speed'] < 0xffffffff: - speed = f'{linkmodes["speed"]}Mb/s' - print(f'Speed: {speed}') - - duplex_modes = { - 0: 'Half', - 1: 'Full', - } - duplex = duplex_modes.get(linkmodes["duplex"], None) - if not duplex: - duplex = f'Unknown! ({linkmodes["duplex"]})' - print(f'Duplex: {duplex}') - - autoneg = "off" - if linkmodes.get("autoneg", 0) != 0: - autoneg = "on" - print(f'Auto-negotiation: {autoneg}') - - ports = { - 0: 'Twisted Pair', - 1: 'AUI', - 2: 'MII', - 3: 'FIBRE', - 4: 'BNC', - 5: 'Directly Attached Copper', - 0xef: 'None', - } - linkinfo = dumpit(ynl, args, 'linkinfo-get') - print(f'Port: {ports.get(linkinfo["port"], "Other")}') - - print_field(linkinfo, ('phyaddr', 'PHYAD')) - - transceiver = { - 0: 'Internal', - 1: 'External', - } - print(f'Transceiver: {transceiver.get(linkinfo["transceiver"], "Unknown")}') - - mdix_ctrl = { - 1: 'off', - 2: 'on', - } - mdix = mdix_ctrl.get(linkinfo['tp-mdix-ctrl'], None) - if mdix: - mdix = mdix + ' (forced)' - else: - mdix = mdix_ctrl.get(linkinfo['tp-mdix'], 'Unknown (auto)') - print(f'MDI-X: {mdix}') - - debug = dumpit(ynl, args, 'debug-get') - msgmask = bits_to_dict(debug.get("msgmask", [])).keys() - print(f'Current message level: {" ".join(msgmask)}') - - linkstate = dumpit(ynl, args, 'linkstate-get') - detected_states = { - 0: 'no', - 1: 'yes', - } - # TODO: wol-get - detected = detected_states.get(linkstate['link'], 'unknown') - print(f'Link detected: {detected}') - -if __name__ == '__main__': - main() diff --git a/tools/net/ynl/generated/Makefile b/tools/net/ynl/generated/Makefile index 7db5240de58a..00af721b1571 100644 --- a/tools/net/ynl/generated/Makefile +++ b/tools/net/ynl/generated/Makefile @@ -12,7 +12,7 @@ include ../Makefile.deps YNL_GEN_ARG_ethtool:=--user-header linux/ethtool_netlink.h \ --exclude-op stats-get -TOOL:=../ynl-gen-c.py +TOOL:=../pyynl/ynl_gen_c.py GENS_PATHS=$(shell grep -nrI --files-without-match \ 'protocol: netlink' \ diff --git a/tools/net/ynl/lib/.gitignore b/tools/net/ynl/lib/.gitignore index 296c4035dbf2..a4383358ec72 100644 --- a/tools/net/ynl/lib/.gitignore +++ b/tools/net/ynl/lib/.gitignore @@ -1,2 +1 @@ -__pycache__/ *.d diff --git a/tools/net/ynl/lib/Makefile b/tools/net/ynl/lib/Makefile index 94c49cca3dca..4b2b98704ff9 100644 --- a/tools/net/ynl/lib/Makefile +++ b/tools/net/ynl/lib/Makefile @@ -19,7 +19,6 @@ ynl.a: $(OBJS) clean: rm -f *.o *.d *~ - rm -rf __pycache__ distclean: clean rm -f *.a diff --git a/tools/net/ynl/lib/__init__.py b/tools/net/ynl/lib/__init__.py deleted file mode 100644 index 9137b83e580a..000000000000 --- a/tools/net/ynl/lib/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause - -from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ - SpecFamily, SpecOperation -from .ynl import YnlFamily, Netlink, NlError - -__all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", - "SpecFamily", "SpecOperation", "YnlFamily", "Netlink", "NlError"] diff --git a/tools/net/ynl/lib/nlspec.py b/tools/net/ynl/lib/nlspec.py deleted file mode 100644 index 314ec8007496..000000000000 --- a/tools/net/ynl/lib/nlspec.py +++ /dev/null @@ -1,617 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause - -import collections -import importlib -import os -import yaml - - -# To be loaded dynamically as needed -jsonschema = None - - -class SpecElement: - """Netlink spec element. - - Abstract element of the Netlink spec. Implements the dictionary interface - for access to the raw spec. Supports iterative resolution of dependencies - across elements and class inheritance levels. The elements of the spec - may refer to each other, and although loops should be very rare, having - to maintain correct ordering of instantiation is painful, so the resolve() - method should be used to perform parts of init which require access to - other parts of the spec. - - Attributes: - yaml raw spec as loaded from the spec file - family back reference to the full family - - name name of the entity as listed in the spec (optional) - ident_name name which can be safely used as identifier in code (optional) - """ - def __init__(self, family, yaml): - self.yaml = yaml - self.family = family - - if 'name' in self.yaml: - self.name = self.yaml['name'] - self.ident_name = self.name.replace('-', '_') - - self._super_resolved = False - family.add_unresolved(self) - - def __getitem__(self, key): - return self.yaml[key] - - def __contains__(self, key): - return key in self.yaml - - def get(self, key, default=None): - return self.yaml.get(key, default) - - def resolve_up(self, up): - if not self._super_resolved: - up.resolve() - self._super_resolved = True - - def resolve(self): - pass - - -class SpecEnumEntry(SpecElement): - """ Entry within an enum declared in the Netlink spec. - - Attributes: - doc documentation string - enum_set back reference to the enum - value numerical value of this enum (use accessors in most situations!) - - Methods: - raw_value raw value, i.e. the id in the enum, unlike user value which is a mask for flags - user_value user value, same as raw value for enums, for flags it's the mask - """ - def __init__(self, enum_set, yaml, prev, value_start): - if isinstance(yaml, str): - yaml = {'name': yaml} - super().__init__(enum_set.family, yaml) - - self.doc = yaml.get('doc', '') - self.enum_set = enum_set - - if 'value' in yaml: - self.value = yaml['value'] - elif prev: - self.value = prev.value + 1 - else: - self.value = value_start - - def has_doc(self): - return bool(self.doc) - - def raw_value(self): - return self.value - - def user_value(self, as_flags=None): - if self.enum_set['type'] == 'flags' or as_flags: - return 1 << self.value - else: - return self.value - - -class SpecEnumSet(SpecElement): - """ Enum type - - Represents an enumeration (list of numerical constants) - as declared in the "definitions" section of the spec. - - Attributes: - type enum or flags - entries entries by name - entries_by_val entries by value - Methods: - get_mask for flags compute the mask of all defined values - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.type = yaml['type'] - - prev_entry = None - value_start = self.yaml.get('value-start', 0) - self.entries = dict() - self.entries_by_val = dict() - for entry in self.yaml['entries']: - e = self.new_entry(entry, prev_entry, value_start) - self.entries[e.name] = e - self.entries_by_val[e.raw_value()] = e - prev_entry = e - - def new_entry(self, entry, prev_entry, value_start): - return SpecEnumEntry(self, entry, prev_entry, value_start) - - def has_doc(self): - if 'doc' in self.yaml: - return True - return self.has_entry_doc() - - def has_entry_doc(self): - for entry in self.entries.values(): - if entry.has_doc(): - return True - return False - - def get_mask(self, as_flags=None): - mask = 0 - for e in self.entries.values(): - mask += e.user_value(as_flags) - return mask - - -class SpecAttr(SpecElement): - """ Single Netlink attribute type - - Represents a single attribute type within an attr space. - - Attributes: - type string, attribute type - value numerical ID when serialized - attr_set Attribute Set containing this attr - is_multi bool, attr may repeat multiple times - struct_name string, name of struct definition - sub_type string, name of sub type - len integer, optional byte length of binary types - display_hint string, hint to help choose format specifier - when displaying the value - sub_message string, name of sub message type - selector string, name of attribute used to select - sub-message type - - is_auto_scalar bool, attr is a variable-size scalar - """ - def __init__(self, family, attr_set, yaml, value): - super().__init__(family, yaml) - - self.type = yaml['type'] - self.value = value - self.attr_set = attr_set - self.is_multi = yaml.get('multi-attr', False) - self.struct_name = yaml.get('struct') - self.sub_type = yaml.get('sub-type') - self.byte_order = yaml.get('byte-order') - self.len = yaml.get('len') - self.display_hint = yaml.get('display-hint') - self.sub_message = yaml.get('sub-message') - self.selector = yaml.get('selector') - - self.is_auto_scalar = self.type == "sint" or self.type == "uint" - - -class SpecAttrSet(SpecElement): - """ Netlink Attribute Set class. - - Represents a ID space of attributes within Netlink. - - Note that unlike other elements, which expose contents of the raw spec - via the dictionary interface Attribute Set exposes attributes by name. - - Attributes: - attrs ordered dict of all attributes (indexed by name) - attrs_by_val ordered dict of all attributes (indexed by value) - subset_of parent set if this is a subset, otherwise None - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.subset_of = self.yaml.get('subset-of', None) - - self.attrs = collections.OrderedDict() - self.attrs_by_val = collections.OrderedDict() - - if self.subset_of is None: - val = 1 - for elem in self.yaml['attributes']: - if 'value' in elem: - val = elem['value'] - - attr = self.new_attr(elem, val) - self.attrs[attr.name] = attr - self.attrs_by_val[attr.value] = attr - val += 1 - else: - real_set = family.attr_sets[self.subset_of] - for elem in self.yaml['attributes']: - real_attr = real_set[elem['name']] - combined_elem = real_attr.yaml | elem - attr = self.new_attr(combined_elem, real_attr.value) - - self.attrs[attr.name] = attr - self.attrs_by_val[attr.value] = attr - - def new_attr(self, elem, value): - return SpecAttr(self.family, self, elem, value) - - def __getitem__(self, key): - return self.attrs[key] - - def __contains__(self, key): - return key in self.attrs - - def __iter__(self): - yield from self.attrs - - def items(self): - return self.attrs.items() - - -class SpecStructMember(SpecElement): - """Struct member attribute - - Represents a single struct member attribute. - - Attributes: - type string, type of the member attribute - byte_order string or None for native byte order - enum string, name of the enum definition - len integer, optional byte length of binary types - display_hint string, hint to help choose format specifier - when displaying the value - struct string, name of nested struct type - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - self.type = yaml['type'] - self.byte_order = yaml.get('byte-order') - self.enum = yaml.get('enum') - self.len = yaml.get('len') - self.display_hint = yaml.get('display-hint') - self.struct = yaml.get('struct') - - -class SpecStruct(SpecElement): - """Netlink struct type - - Represents a C struct definition. - - Attributes: - members ordered list of struct members - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.members = [] - for member in yaml.get('members', []): - self.members.append(self.new_member(family, member)) - - def new_member(self, family, elem): - return SpecStructMember(family, elem) - - def __iter__(self): - yield from self.members - - def items(self): - return self.members.items() - - -class SpecSubMessage(SpecElement): - """ Netlink sub-message definition - - Represents a set of sub-message formats for polymorphic nlattrs - that contain type-specific sub messages. - - Attributes: - name string, name of sub-message definition - formats dict of sub-message formats indexed by match value - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.formats = collections.OrderedDict() - for elem in self.yaml['formats']: - format = self.new_format(family, elem) - self.formats[format.value] = format - - def new_format(self, family, format): - return SpecSubMessageFormat(family, format) - - -class SpecSubMessageFormat(SpecElement): - """ Netlink sub-message format definition - - Represents a single format for a sub-message. - - Attributes: - value attribute value to match against type selector - fixed_header string, name of fixed header, or None - attr_set string, name of attribute set, or None - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - - self.value = yaml.get('value') - self.fixed_header = yaml.get('fixed-header') - self.attr_set = yaml.get('attribute-set') - - -class SpecOperation(SpecElement): - """Netlink Operation - - Information about a single Netlink operation. - - Attributes: - value numerical ID when serialized, None if req/rsp values differ - - req_value numerical ID when serialized, user -> kernel - rsp_value numerical ID when serialized, user <- kernel - modes supported operation modes (do, dump, event etc.) - is_call bool, whether the operation is a call - is_async bool, whether the operation is a notification - is_resv bool, whether the operation does not exist (it's just a reserved ID) - attr_set attribute set name - fixed_header string, optional name of fixed header struct - - yaml raw spec as loaded from the spec file - """ - def __init__(self, family, yaml, req_value, rsp_value): - super().__init__(family, yaml) - - self.value = req_value if req_value == rsp_value else None - self.req_value = req_value - self.rsp_value = rsp_value - - self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'} - self.is_call = 'do' in yaml or 'dump' in yaml - self.is_async = 'notify' in yaml or 'event' in yaml - self.is_resv = not self.is_async and not self.is_call - self.fixed_header = self.yaml.get('fixed-header', family.fixed_header) - - # Added by resolve: - self.attr_set = None - delattr(self, "attr_set") - - def resolve(self): - self.resolve_up(super()) - - if 'attribute-set' in self.yaml: - attr_set_name = self.yaml['attribute-set'] - elif 'notify' in self.yaml: - msg = self.family.msgs[self.yaml['notify']] - attr_set_name = msg['attribute-set'] - elif self.is_resv: - attr_set_name = '' - else: - raise Exception(f"Can't resolve attribute set for op '{self.name}'") - if attr_set_name: - self.attr_set = self.family.attr_sets[attr_set_name] - - -class SpecMcastGroup(SpecElement): - """Netlink Multicast Group - - Information about a multicast group. - - Value is only used for classic netlink families that use the - netlink-raw schema. Genetlink families use dynamic ID allocation - where the ids of multicast groups get resolved at runtime. Value - will be None for genetlink families. - - Attributes: - name name of the mulitcast group - value integer id of this multicast group for netlink-raw or None - yaml raw spec as loaded from the spec file - """ - def __init__(self, family, yaml): - super().__init__(family, yaml) - self.value = self.yaml.get('value') - - -class SpecFamily(SpecElement): - """ Netlink Family Spec class. - - Netlink family information loaded from a spec (e.g. in YAML). - Takes care of unfolding implicit information which can be skipped - in the spec itself for brevity. - - The class can be used like a dictionary to access the raw spec - elements but that's usually a bad idea. - - Attributes: - proto protocol type (e.g. genetlink) - msg_id_model enum-model for operations (unified, directional etc.) - license spec license (loaded from an SPDX tag on the spec) - - attr_sets dict of attribute sets - msgs dict of all messages (index by name) - sub_msgs dict of all sub messages (index by name) - ops dict of all valid requests / responses - ntfs dict of all async events - consts dict of all constants/enums - fixed_header string, optional name of family default fixed header struct - mcast_groups dict of all multicast groups (index by name) - kernel_family dict of kernel family attributes - """ - def __init__(self, spec_path, schema_path=None, exclude_ops=None): - with open(spec_path, "r") as stream: - prefix = '# SPDX-License-Identifier: ' - first = stream.readline().strip() - if not first.startswith(prefix): - raise Exception('SPDX license tag required in the spec') - self.license = first[len(prefix):] - - stream.seek(0) - spec = yaml.safe_load(stream) - - self._resolution_list = [] - - super().__init__(self, spec) - - self._exclude_ops = exclude_ops if exclude_ops else [] - - self.proto = self.yaml.get('protocol', 'genetlink') - self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') - - if schema_path is None: - schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' - if schema_path: - global jsonschema - - with open(schema_path, "r") as stream: - schema = yaml.safe_load(stream) - - if jsonschema is None: - jsonschema = importlib.import_module("jsonschema") - - jsonschema.validate(self.yaml, schema) - - self.attr_sets = collections.OrderedDict() - self.sub_msgs = collections.OrderedDict() - self.msgs = collections.OrderedDict() - self.req_by_value = collections.OrderedDict() - self.rsp_by_value = collections.OrderedDict() - self.ops = collections.OrderedDict() - self.ntfs = collections.OrderedDict() - self.consts = collections.OrderedDict() - self.mcast_groups = collections.OrderedDict() - self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {})) - - last_exception = None - while len(self._resolution_list) > 0: - resolved = [] - unresolved = self._resolution_list - self._resolution_list = [] - - for elem in unresolved: - try: - elem.resolve() - except (KeyError, AttributeError) as e: - self._resolution_list.append(elem) - last_exception = e - continue - - resolved.append(elem) - - if len(resolved) == 0: - raise last_exception - - def new_enum(self, elem): - return SpecEnumSet(self, elem) - - def new_attr_set(self, elem): - return SpecAttrSet(self, elem) - - def new_struct(self, elem): - return SpecStruct(self, elem) - - def new_sub_message(self, elem): - return SpecSubMessage(self, elem); - - def new_operation(self, elem, req_val, rsp_val): - return SpecOperation(self, elem, req_val, rsp_val) - - def new_mcast_group(self, elem): - return SpecMcastGroup(self, elem) - - def add_unresolved(self, elem): - self._resolution_list.append(elem) - - def _dictify_ops_unified(self): - self.fixed_header = self.yaml['operations'].get('fixed-header') - val = 1 - for elem in self.yaml['operations']['list']: - if 'value' in elem: - val = elem['value'] - - op = self.new_operation(elem, val, val) - val += 1 - - self.msgs[op.name] = op - - def _dictify_ops_directional(self): - self.fixed_header = self.yaml['operations'].get('fixed-header') - req_val = rsp_val = 1 - for elem in self.yaml['operations']['list']: - if 'notify' in elem or 'event' in elem: - if 'value' in elem: - rsp_val = elem['value'] - req_val_next = req_val - rsp_val_next = rsp_val + 1 - req_val = None - elif 'do' in elem or 'dump' in elem: - mode = elem['do'] if 'do' in elem else elem['dump'] - - v = mode.get('request', {}).get('value', None) - if v: - req_val = v - v = mode.get('reply', {}).get('value', None) - if v: - rsp_val = v - - rsp_inc = 1 if 'reply' in mode else 0 - req_val_next = req_val + 1 - rsp_val_next = rsp_val + rsp_inc - else: - raise Exception("Can't parse directional ops") - - if req_val == req_val_next: - req_val = None - if rsp_val == rsp_val_next: - rsp_val = None - - skip = False - for exclude in self._exclude_ops: - skip |= bool(exclude.match(elem['name'])) - if not skip: - op = self.new_operation(elem, req_val, rsp_val) - - req_val = req_val_next - rsp_val = rsp_val_next - - self.msgs[op.name] = op - - def find_operation(self, name): - """ - For a given operation name, find and return operation spec. - """ - for op in self.yaml['operations']['list']: - if name == op['name']: - return op - return None - - def resolve(self): - self.resolve_up(super()) - - definitions = self.yaml.get('definitions', []) - for elem in definitions: - if elem['type'] == 'enum' or elem['type'] == 'flags': - self.consts[elem['name']] = self.new_enum(elem) - elif elem['type'] == 'struct': - self.consts[elem['name']] = self.new_struct(elem) - else: - self.consts[elem['name']] = elem - - for elem in self.yaml['attribute-sets']: - attr_set = self.new_attr_set(elem) - self.attr_sets[elem['name']] = attr_set - - for elem in self.yaml.get('sub-messages', []): - sub_message = self.new_sub_message(elem) - self.sub_msgs[sub_message.name] = sub_message - - if self.msg_id_model == 'unified': - self._dictify_ops_unified() - elif self.msg_id_model == 'directional': - self._dictify_ops_directional() - - for op in self.msgs.values(): - if op.req_value is not None: - self.req_by_value[op.req_value] = op - if op.rsp_value is not None: - self.rsp_by_value[op.rsp_value] = op - if not op.is_async and 'attribute-set' in op: - self.ops[op.name] = op - elif op.is_async: - self.ntfs[op.name] = op - - mcgs = self.yaml.get('mcast-groups') - if mcgs: - for elem in mcgs['list']: - mcg = self.new_mcast_group(elem) - self.mcast_groups[elem['name']] = mcg diff --git a/tools/net/ynl/lib/ynl.py b/tools/net/ynl/lib/ynl.py deleted file mode 100644 index 08f8bf89cfc2..000000000000 --- a/tools/net/ynl/lib/ynl.py +++ /dev/null @@ -1,1067 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause - -from collections import namedtuple -from enum import Enum -import functools -import os -import random -import socket -import struct -from struct import Struct -import sys -import yaml -import ipaddress -import uuid -import queue -import selectors -import time - -from .nlspec import SpecFamily - -# -# Generic Netlink code which should really be in some library, but I can't quickly find one. -# - - -class Netlink: - # Netlink socket - SOL_NETLINK = 270 - - NETLINK_ADD_MEMBERSHIP = 1 - NETLINK_CAP_ACK = 10 - NETLINK_EXT_ACK = 11 - NETLINK_GET_STRICT_CHK = 12 - - # Netlink message - NLMSG_ERROR = 2 - NLMSG_DONE = 3 - - NLM_F_REQUEST = 1 - NLM_F_ACK = 4 - NLM_F_ROOT = 0x100 - NLM_F_MATCH = 0x200 - - NLM_F_REPLACE = 0x100 - NLM_F_EXCL = 0x200 - NLM_F_CREATE = 0x400 - NLM_F_APPEND = 0x800 - - NLM_F_CAPPED = 0x100 - NLM_F_ACK_TLVS = 0x200 - - NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH - - NLA_F_NESTED = 0x8000 - NLA_F_NET_BYTEORDER = 0x4000 - - NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER - - # Genetlink defines - NETLINK_GENERIC = 16 - - GENL_ID_CTRL = 0x10 - - # nlctrl - CTRL_CMD_GETFAMILY = 3 - - CTRL_ATTR_FAMILY_ID = 1 - CTRL_ATTR_FAMILY_NAME = 2 - CTRL_ATTR_MAXATTR = 5 - CTRL_ATTR_MCAST_GROUPS = 7 - - CTRL_ATTR_MCAST_GRP_NAME = 1 - CTRL_ATTR_MCAST_GRP_ID = 2 - - # Extack types - NLMSGERR_ATTR_MSG = 1 - NLMSGERR_ATTR_OFFS = 2 - NLMSGERR_ATTR_COOKIE = 3 - NLMSGERR_ATTR_POLICY = 4 - NLMSGERR_ATTR_MISS_TYPE = 5 - NLMSGERR_ATTR_MISS_NEST = 6 - - # Policy types - NL_POLICY_TYPE_ATTR_TYPE = 1 - NL_POLICY_TYPE_ATTR_MIN_VALUE_S = 2 - NL_POLICY_TYPE_ATTR_MAX_VALUE_S = 3 - NL_POLICY_TYPE_ATTR_MIN_VALUE_U = 4 - NL_POLICY_TYPE_ATTR_MAX_VALUE_U = 5 - NL_POLICY_TYPE_ATTR_MIN_LENGTH = 6 - NL_POLICY_TYPE_ATTR_MAX_LENGTH = 7 - NL_POLICY_TYPE_ATTR_POLICY_IDX = 8 - NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE = 9 - NL_POLICY_TYPE_ATTR_BITFIELD32_MASK = 10 - NL_POLICY_TYPE_ATTR_PAD = 11 - NL_POLICY_TYPE_ATTR_MASK = 12 - - AttrType = Enum('AttrType', ['flag', 'u8', 'u16', 'u32', 'u64', - 's8', 's16', 's32', 's64', - 'binary', 'string', 'nul-string', - 'nested', 'nested-array', - 'bitfield32', 'sint', 'uint']) - -class NlError(Exception): - def __init__(self, nl_msg): - self.nl_msg = nl_msg - self.error = -nl_msg.error - - def __str__(self): - return f"Netlink error: {os.strerror(self.error)}\n{self.nl_msg}" - - -class ConfigError(Exception): - pass - - -class NlAttr: - ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little']) - type_formats = { - 'u8' : ScalarFormat(Struct('B'), Struct("B"), Struct("B")), - 's8' : ScalarFormat(Struct('b'), Struct("b"), Struct("b")), - 'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("h"), Struct("I"), Struct("i"), Struct("Q"), Struct("q"), Struct(">= 1 - i += 1 - else: - value = enum.entries_by_val[raw].name - return value - - def _decode_binary(self, attr, attr_spec): - if attr_spec.struct_name: - decoded = self._decode_struct(attr.raw, attr_spec.struct_name) - elif attr_spec.sub_type: - decoded = attr.as_c_array(attr_spec.sub_type) - else: - decoded = attr.as_bin() - if attr_spec.display_hint: - decoded = self._formatted_string(decoded, attr_spec.display_hint) - return decoded - - def _decode_array_attr(self, attr, attr_spec): - decoded = [] - offset = 0 - while offset < len(attr.raw): - item = NlAttr(attr.raw, offset) - offset += item.full_len - - if attr_spec["sub-type"] == 'nest': - subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes']) - decoded.append({ item.type: subattrs }) - elif attr_spec["sub-type"] == 'binary': - subattrs = item.as_bin() - if attr_spec.display_hint: - subattrs = self._formatted_string(subattrs, attr_spec.display_hint) - decoded.append(subattrs) - elif attr_spec["sub-type"] in NlAttr.type_formats: - subattrs = item.as_scalar(attr_spec['sub-type'], attr_spec.byte_order) - if attr_spec.display_hint: - subattrs = self._formatted_string(subattrs, attr_spec.display_hint) - decoded.append(subattrs) - else: - raise Exception(f'Unknown {attr_spec["sub-type"]} with name {attr_spec["name"]}') - return decoded - - def _decode_nest_type_value(self, attr, attr_spec): - decoded = {} - value = attr - for name in attr_spec['type-value']: - value = NlAttr(value.raw, 0) - decoded[name] = value.type - subattrs = self._decode(NlAttrs(value.raw), attr_spec['nested-attributes']) - decoded.update(subattrs) - return decoded - - def _decode_unknown(self, attr): - if attr.is_nest: - return self._decode(NlAttrs(attr.raw), None) - else: - return attr.as_bin() - - def _rsp_add(self, rsp, name, is_multi, decoded): - if is_multi == None: - if name in rsp and type(rsp[name]) is not list: - rsp[name] = [rsp[name]] - is_multi = True - else: - is_multi = False - - if not is_multi: - rsp[name] = decoded - elif name in rsp: - rsp[name].append(decoded) - else: - rsp[name] = [decoded] - - def _resolve_selector(self, attr_spec, search_attrs): - sub_msg = attr_spec.sub_message - if sub_msg not in self.sub_msgs: - raise Exception(f"No sub-message spec named {sub_msg} for {attr_spec.name}") - sub_msg_spec = self.sub_msgs[sub_msg] - - selector = attr_spec.selector - value = search_attrs.lookup(selector) - if value not in sub_msg_spec.formats: - raise Exception(f"No message format for '{value}' in sub-message spec '{sub_msg}'") - - spec = sub_msg_spec.formats[value] - return spec - - def _decode_sub_msg(self, attr, attr_spec, search_attrs): - msg_format = self._resolve_selector(attr_spec, search_attrs) - decoded = {} - offset = 0 - if msg_format.fixed_header: - decoded.update(self._decode_struct(attr.raw, msg_format.fixed_header)); - offset = self._struct_size(msg_format.fixed_header) - if msg_format.attr_set: - if msg_format.attr_set in self.attr_sets: - subdict = self._decode(NlAttrs(attr.raw, offset), msg_format.attr_set) - decoded.update(subdict) - else: - raise Exception(f"Unknown attribute-set '{attr_space}' when decoding '{attr_spec.name}'") - return decoded - - def _decode(self, attrs, space, outer_attrs = None): - rsp = dict() - if space: - attr_space = self.attr_sets[space] - search_attrs = SpaceAttrs(attr_space, rsp, outer_attrs) - - for attr in attrs: - try: - attr_spec = attr_space.attrs_by_val[attr.type] - except (KeyError, UnboundLocalError): - if not self.process_unknown: - raise Exception(f"Space '{space}' has no attribute with value '{attr.type}'") - attr_name = f"UnknownAttr({attr.type})" - self._rsp_add(rsp, attr_name, None, self._decode_unknown(attr)) - continue - - try: - if attr_spec["type"] == 'nest': - subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes'], search_attrs) - decoded = subdict - elif attr_spec["type"] == 'string': - decoded = attr.as_strz() - elif attr_spec["type"] == 'binary': - decoded = self._decode_binary(attr, attr_spec) - elif attr_spec["type"] == 'flag': - decoded = True - elif attr_spec.is_auto_scalar: - decoded = attr.as_auto_scalar(attr_spec['type'], attr_spec.byte_order) - elif attr_spec["type"] in NlAttr.type_formats: - decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order) - if 'enum' in attr_spec: - decoded = self._decode_enum(decoded, attr_spec) - elif attr_spec.display_hint: - decoded = self._formatted_string(decoded, attr_spec.display_hint) - elif attr_spec["type"] == 'indexed-array': - decoded = self._decode_array_attr(attr, attr_spec) - elif attr_spec["type"] == 'bitfield32': - value, selector = struct.unpack("II", attr.raw) - if 'enum' in attr_spec: - value = self._decode_enum(value, attr_spec) - selector = self._decode_enum(selector, attr_spec) - decoded = {"value": value, "selector": selector} - elif attr_spec["type"] == 'sub-message': - decoded = self._decode_sub_msg(attr, attr_spec, search_attrs) - elif attr_spec["type"] == 'nest-type-value': - decoded = self._decode_nest_type_value(attr, attr_spec) - else: - if not self.process_unknown: - raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}') - decoded = self._decode_unknown(attr) - - self._rsp_add(rsp, attr_spec["name"], attr_spec.is_multi, decoded) - except: - print(f"Error decoding '{attr_spec.name}' from '{space}'") - raise - - return rsp - - def _decode_extack_path(self, attrs, attr_set, offset, target): - for attr in attrs: - try: - attr_spec = attr_set.attrs_by_val[attr.type] - except KeyError: - raise Exception(f"Space '{attr_set.name}' has no attribute with value '{attr.type}'") - if offset > target: - break - if offset == target: - return '.' + attr_spec.name - - if offset + attr.full_len <= target: - offset += attr.full_len - continue - if attr_spec['type'] != 'nest': - raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") - offset += 4 - subpath = self._decode_extack_path(NlAttrs(attr.raw), - self.attr_sets[attr_spec['nested-attributes']], - offset, target) - if subpath is None: - return None - return '.' + attr_spec.name + subpath - - return None - - def _decode_extack(self, request, op, extack): - if 'bad-attr-offs' not in extack: - return - - msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set), op) - offset = self.nlproto.msghdr_size() + self._struct_size(op.fixed_header) - path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset, - extack['bad-attr-offs']) - if path: - del extack['bad-attr-offs'] - extack['bad-attr'] = path - - def _struct_size(self, name): - if name: - members = self.consts[name].members - size = 0 - for m in members: - if m.type in ['pad', 'binary']: - if m.struct: - size += self._struct_size(m.struct) - else: - size += m.len - else: - format = NlAttr.get_format(m.type, m.byte_order) - size += format.size - return size - else: - return 0 - - def _decode_struct(self, data, name): - members = self.consts[name].members - attrs = dict() - offset = 0 - for m in members: - value = None - if m.type == 'pad': - offset += m.len - elif m.type == 'binary': - if m.struct: - len = self._struct_size(m.struct) - value = self._decode_struct(data[offset : offset + len], - m.struct) - offset += len - else: - value = data[offset : offset + m.len] - offset += m.len - else: - format = NlAttr.get_format(m.type, m.byte_order) - [ value ] = format.unpack_from(data, offset) - offset += format.size - if value is not None: - if m.enum: - value = self._decode_enum(value, m) - elif m.display_hint: - value = self._formatted_string(value, m.display_hint) - attrs[m.name] = value - return attrs - - def _encode_struct(self, name, vals): - members = self.consts[name].members - attr_payload = b'' - for m in members: - value = vals.pop(m.name) if m.name in vals else None - if m.type == 'pad': - attr_payload += bytearray(m.len) - elif m.type == 'binary': - if m.struct: - if value is None: - value = dict() - attr_payload += self._encode_struct(m.struct, value) - else: - if value is None: - attr_payload += bytearray(m.len) - else: - attr_payload += bytes.fromhex(value) - else: - if value is None: - value = 0 - format = NlAttr.get_format(m.type, m.byte_order) - attr_payload += format.pack(value) - return attr_payload - - def _formatted_string(self, raw, display_hint): - if display_hint == 'mac': - formatted = ':'.join('%02x' % b for b in raw) - elif display_hint == 'hex': - if isinstance(raw, int): - formatted = hex(raw) - else: - formatted = bytes.hex(raw, ' ') - elif display_hint in [ 'ipv4', 'ipv6' ]: - formatted = format(ipaddress.ip_address(raw)) - elif display_hint == 'uuid': - formatted = str(uuid.UUID(bytes=raw)) - else: - formatted = raw - return formatted - - def handle_ntf(self, decoded): - msg = dict() - if self.include_raw: - msg['raw'] = decoded - op = self.rsp_by_value[decoded.cmd()] - attrs = self._decode(decoded.raw_attrs, op.attr_set.name) - if op.fixed_header: - attrs.update(self._decode_struct(decoded.raw, op.fixed_header)) - - msg['name'] = op['name'] - msg['msg'] = attrs - self.async_msg_queue.put(msg) - - def check_ntf(self): - while True: - try: - reply = self.sock.recv(self._recv_size, socket.MSG_DONTWAIT) - except BlockingIOError: - return - - nms = NlMsgs(reply) - self._recv_dbg_print(reply, nms) - for nl_msg in nms: - if nl_msg.error: - print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) - print(nl_msg) - continue - if nl_msg.done: - print("Netlink done while checking for ntf!?") - continue - - decoded = self.nlproto.decode(self, nl_msg, None) - if decoded.cmd() not in self.async_msg_ids: - print("Unexpected msg id while checking for ntf", decoded) - continue - - self.handle_ntf(decoded) - - def poll_ntf(self, duration=None): - start_time = time.time() - selector = selectors.DefaultSelector() - selector.register(self.sock, selectors.EVENT_READ) - - while True: - try: - yield self.async_msg_queue.get_nowait() - except queue.Empty: - if duration is not None: - timeout = start_time + duration - time.time() - if timeout <= 0: - return - else: - timeout = None - events = selector.select(timeout) - if events: - self.check_ntf() - - def operation_do_attributes(self, name): - """ - For a given operation name, find and return a supported - set of attributes (as a dict). - """ - op = self.find_operation(name) - if not op: - return None - - return op['do']['request']['attributes'].copy() - - def _encode_message(self, op, vals, flags, req_seq): - nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK - for flag in flags or []: - nl_flags |= flag - - msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq) - if op.fixed_header: - msg += self._encode_struct(op.fixed_header, vals) - search_attrs = SpaceAttrs(op.attr_set, vals) - for name, value in vals.items(): - msg += self._add_attr(op.attr_set.name, name, value, search_attrs) - msg = _genl_msg_finalize(msg) - return msg - - def _ops(self, ops): - reqs_by_seq = {} - req_seq = random.randint(1024, 65535) - payload = b'' - for (method, vals, flags) in ops: - op = self.ops[method] - msg = self._encode_message(op, vals, flags, req_seq) - reqs_by_seq[req_seq] = (op, msg, flags) - payload += msg - req_seq += 1 - - self.sock.send(payload, 0) - - done = False - rsp = [] - op_rsp = [] - while not done: - reply = self.sock.recv(self._recv_size) - nms = NlMsgs(reply, attr_space=op.attr_set) - self._recv_dbg_print(reply, nms) - for nl_msg in nms: - if nl_msg.nl_seq in reqs_by_seq: - (op, req_msg, req_flags) = reqs_by_seq[nl_msg.nl_seq] - if nl_msg.extack: - self._decode_extack(req_msg, op, nl_msg.extack) - else: - op = None - req_flags = [] - - if nl_msg.error: - raise NlError(nl_msg) - if nl_msg.done: - if nl_msg.extack: - print("Netlink warning:") - print(nl_msg) - - if Netlink.NLM_F_DUMP in req_flags: - rsp.append(op_rsp) - elif not op_rsp: - rsp.append(None) - elif len(op_rsp) == 1: - rsp.append(op_rsp[0]) - else: - rsp.append(op_rsp) - op_rsp = [] - - del reqs_by_seq[nl_msg.nl_seq] - done = len(reqs_by_seq) == 0 - break - - decoded = self.nlproto.decode(self, nl_msg, op) - - # Check if this is a reply to our request - if nl_msg.nl_seq not in reqs_by_seq or decoded.cmd() != op.rsp_value: - if decoded.cmd() in self.async_msg_ids: - self.handle_ntf(decoded) - continue - else: - print('Unexpected message: ' + repr(decoded)) - continue - - rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name) - if op.fixed_header: - rsp_msg.update(self._decode_struct(decoded.raw, op.fixed_header)) - op_rsp.append(rsp_msg) - - return rsp - - def _op(self, method, vals, flags=None, dump=False): - req_flags = flags or [] - if dump: - req_flags.append(Netlink.NLM_F_DUMP) - - ops = [(method, vals, req_flags)] - return self._ops(ops)[0] - - def do(self, method, vals, flags=None): - return self._op(method, vals, flags) - - def dump(self, method, vals): - return self._op(method, vals, dump=True) - - def do_multi(self, ops): - return self._ops(ops) diff --git a/tools/net/ynl/pyynl/.gitignore b/tools/net/ynl/pyynl/.gitignore new file mode 100644 index 000000000000..b801cd2d016e --- /dev/null +++ b/tools/net/ynl/pyynl/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +lib/__pycache__/ diff --git a/tools/net/ynl/pyynl/__init__.py b/tools/net/ynl/pyynl/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/net/ynl/pyynl/cli.py b/tools/net/ynl/pyynl/cli.py new file mode 100755 index 000000000000..41d9fa5c818d --- /dev/null +++ b/tools/net/ynl/pyynl/cli.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +import argparse +import json +import pathlib +import pprint +import sys + +sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) +from lib import YnlFamily, Netlink, NlError + + +class YnlEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, bytes): + return bytes.hex(obj) + if isinstance(obj, set): + return list(obj) + return json.JSONEncoder.default(self, obj) + + +def main(): + description = """ + YNL CLI utility - a general purpose netlink utility that uses YAML + specs to drive protocol encoding and decoding. + """ + epilog = """ + The --multi option can be repeated to include several do operations + in the same netlink payload. + """ + + parser = argparse.ArgumentParser(description=description, + epilog=epilog) + parser.add_argument('--spec', dest='spec', type=str, required=True) + parser.add_argument('--schema', dest='schema', type=str) + parser.add_argument('--no-schema', action='store_true') + parser.add_argument('--json', dest='json_text', type=str) + + group = parser.add_mutually_exclusive_group() + group.add_argument('--do', dest='do', metavar='DO-OPERATION', type=str) + group.add_argument('--multi', dest='multi', nargs=2, action='append', + metavar=('DO-OPERATION', 'JSON_TEXT'), type=str) + group.add_argument('--dump', dest='dump', metavar='DUMP-OPERATION', type=str) + group.add_argument('--list-ops', action='store_true') + group.add_argument('--list-msgs', action='store_true') + + parser.add_argument('--duration', dest='duration', type=int, + help='when subscribed, watch for DURATION seconds') + parser.add_argument('--sleep', dest='duration', type=int, + help='alias for duration') + parser.add_argument('--subscribe', dest='ntf', type=str) + parser.add_argument('--replace', dest='flags', action='append_const', + const=Netlink.NLM_F_REPLACE) + parser.add_argument('--excl', dest='flags', action='append_const', + const=Netlink.NLM_F_EXCL) + parser.add_argument('--create', dest='flags', action='append_const', + const=Netlink.NLM_F_CREATE) + parser.add_argument('--append', dest='flags', action='append_const', + const=Netlink.NLM_F_APPEND) + parser.add_argument('--process-unknown', action=argparse.BooleanOptionalAction) + parser.add_argument('--output-json', action='store_true') + parser.add_argument('--dbg-small-recv', default=0, const=4000, + action='store', nargs='?', type=int) + args = parser.parse_args() + + def output(msg): + if args.output_json: + print(json.dumps(msg, cls=YnlEncoder)) + else: + pprint.PrettyPrinter().pprint(msg) + + if args.no_schema: + args.schema = '' + + attrs = {} + if args.json_text: + attrs = json.loads(args.json_text) + + ynl = YnlFamily(args.spec, args.schema, args.process_unknown, + recv_size=args.dbg_small_recv) + if args.dbg_small_recv: + ynl.set_recv_dbg(True) + + if args.ntf: + ynl.ntf_subscribe(args.ntf) + + if args.list_ops: + for op_name, op in ynl.ops.items(): + print(op_name, " [", ", ".join(op.modes), "]") + if args.list_msgs: + for op_name, op in ynl.msgs.items(): + print(op_name, " [", ", ".join(op.modes), "]") + + try: + if args.do: + reply = ynl.do(args.do, attrs, args.flags) + output(reply) + if args.dump: + reply = ynl.dump(args.dump, attrs) + output(reply) + if args.multi: + ops = [ (item[0], json.loads(item[1]), args.flags or []) for item in args.multi ] + reply = ynl.do_multi(ops) + output(reply) + except NlError as e: + print(e) + exit(1) + + if args.ntf: + try: + for msg in ynl.poll_ntf(duration=args.duration): + output(msg) + except KeyboardInterrupt: + pass + + +if __name__ == "__main__": + main() diff --git a/tools/net/ynl/pyynl/ethtool.py b/tools/net/ynl/pyynl/ethtool.py new file mode 100755 index 000000000000..ebb0a11f67bf --- /dev/null +++ b/tools/net/ynl/pyynl/ethtool.py @@ -0,0 +1,439 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +import argparse +import json +import pathlib +import pprint +import sys +import re +import os + +sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) +from lib import YnlFamily + +def args_to_req(ynl, op_name, args, req): + """ + Verify and convert command-line arguments to the ynl-compatible request. + """ + valid_attrs = ynl.operation_do_attributes(op_name) + valid_attrs.remove('header') # not user-provided + + if len(args) == 0: + print(f'no attributes, expected: {valid_attrs}') + sys.exit(1) + + i = 0 + while i < len(args): + attr = args[i] + if i + 1 >= len(args): + print(f'expected value for \'{attr}\'') + sys.exit(1) + + if attr not in valid_attrs: + print(f'invalid attribute \'{attr}\', expected: {valid_attrs}') + sys.exit(1) + + val = args[i+1] + i += 2 + + req[attr] = val + +def print_field(reply, *desc): + """ + Pretty-print a set of fields from the reply. desc specifies the + fields and the optional type (bool/yn). + """ + if len(desc) == 0: + return print_field(reply, *zip(reply.keys(), reply.keys())) + + for spec in desc: + try: + field, name, tp = spec + except: + field, name = spec + tp = 'int' + + value = reply.get(field, None) + if tp == 'yn': + value = 'yes' if value else 'no' + elif tp == 'bool' or isinstance(value, bool): + value = 'on' if value else 'off' + else: + value = 'n/a' if value is None else value + + print(f'{name}: {value}') + +def print_speed(name, value): + """ + Print out the speed-like strings from the value dict. + """ + speed_re = re.compile(r'[0-9]+base[^/]+/.+') + speed = [ k for k, v in value.items() if v and speed_re.match(k) ] + print(f'{name}: {" ".join(speed)}') + +def doit(ynl, args, op_name): + """ + Prepare request header, parse arguments and doit. + """ + req = { + 'header': { + 'dev-name': args.device, + }, + } + + args_to_req(ynl, op_name, args.args, req) + ynl.do(op_name, req) + +def dumpit(ynl, args, op_name, extra = {}): + """ + Prepare request header, parse arguments and dumpit (filtering out the + devices we're not interested in). + """ + reply = ynl.dump(op_name, { 'header': {} } | extra) + if not reply: + return {} + + for msg in reply: + if msg['header']['dev-name'] == args.device: + if args.json: + pprint.PrettyPrinter().pprint(msg) + sys.exit(0) + msg.pop('header', None) + return msg + + print(f"Not supported for device {args.device}") + sys.exit(1) + +def bits_to_dict(attr): + """ + Convert ynl-formatted bitmask to a dict of bit=value. + """ + ret = {} + if 'bits' not in attr: + return dict() + if 'bit' not in attr['bits']: + return dict() + for bit in attr['bits']['bit']: + if bit['name'] == '': + continue + name = bit['name'] + value = bit.get('value', False) + ret[name] = value + return ret + +def main(): + parser = argparse.ArgumentParser(description='ethtool wannabe') + parser.add_argument('--json', action=argparse.BooleanOptionalAction) + parser.add_argument('--show-priv-flags', action=argparse.BooleanOptionalAction) + parser.add_argument('--set-priv-flags', action=argparse.BooleanOptionalAction) + parser.add_argument('--show-eee', action=argparse.BooleanOptionalAction) + parser.add_argument('--set-eee', action=argparse.BooleanOptionalAction) + parser.add_argument('-a', '--show-pause', action=argparse.BooleanOptionalAction) + parser.add_argument('-A', '--set-pause', action=argparse.BooleanOptionalAction) + parser.add_argument('-c', '--show-coalesce', action=argparse.BooleanOptionalAction) + parser.add_argument('-C', '--set-coalesce', action=argparse.BooleanOptionalAction) + parser.add_argument('-g', '--show-ring', action=argparse.BooleanOptionalAction) + parser.add_argument('-G', '--set-ring', action=argparse.BooleanOptionalAction) + parser.add_argument('-k', '--show-features', action=argparse.BooleanOptionalAction) + parser.add_argument('-K', '--set-features', action=argparse.BooleanOptionalAction) + parser.add_argument('-l', '--show-channels', action=argparse.BooleanOptionalAction) + parser.add_argument('-L', '--set-channels', action=argparse.BooleanOptionalAction) + parser.add_argument('-T', '--show-time-stamping', action=argparse.BooleanOptionalAction) + parser.add_argument('-S', '--statistics', action=argparse.BooleanOptionalAction) + # TODO: --show-tunnels tunnel-info-get + # TODO: --show-module module-get + # TODO: --get-plca-cfg plca-get + # TODO: --get-plca-status plca-get-status + # TODO: --show-mm mm-get + # TODO: --show-fec fec-get + # TODO: --dump-module-eerpom module-eeprom-get + # TODO: pse-get + # TODO: rss-get + parser.add_argument('device', metavar='device', type=str) + parser.add_argument('args', metavar='args', type=str, nargs='*') + global args + args = parser.parse_args() + + script_abs_dir = os.path.dirname(os.path.abspath(sys.argv[0])) + spec = os.path.join(script_abs_dir, + '../../../Documentation/netlink/specs/ethtool.yaml') + schema = os.path.join(script_abs_dir, + '../../../Documentation/netlink/genetlink-legacy.yaml') + + ynl = YnlFamily(spec, schema) + + if args.set_priv_flags: + # TODO: parse the bitmask + print("not implemented") + return + + if args.set_eee: + return doit(ynl, args, 'eee-set') + + if args.set_pause: + return doit(ynl, args, 'pause-set') + + if args.set_coalesce: + return doit(ynl, args, 'coalesce-set') + + if args.set_features: + # TODO: parse the bitmask + print("not implemented") + return + + if args.set_channels: + return doit(ynl, args, 'channels-set') + + if args.set_ring: + return doit(ynl, args, 'rings-set') + + if args.show_priv_flags: + flags = bits_to_dict(dumpit(ynl, args, 'privflags-get')['flags']) + print_field(flags) + return + + if args.show_eee: + eee = dumpit(ynl, args, 'eee-get') + ours = bits_to_dict(eee['modes-ours']) + peer = bits_to_dict(eee['modes-peer']) + + if 'enabled' in eee: + status = 'enabled' if eee['enabled'] else 'disabled' + if 'active' in eee and eee['active']: + status = status + ' - active' + else: + status = status + ' - inactive' + else: + status = 'not supported' + + print(f'EEE status: {status}') + print_field(eee, ('tx-lpi-timer', 'Tx LPI')) + print_speed('Advertised EEE link modes', ours) + print_speed('Link partner advertised EEE link modes', peer) + + return + + if args.show_pause: + print_field(dumpit(ynl, args, 'pause-get'), + ('autoneg', 'Autonegotiate', 'bool'), + ('rx', 'RX', 'bool'), + ('tx', 'TX', 'bool')) + return + + if args.show_coalesce: + print_field(dumpit(ynl, args, 'coalesce-get')) + return + + if args.show_features: + reply = dumpit(ynl, args, 'features-get') + available = bits_to_dict(reply['hw']) + requested = bits_to_dict(reply['wanted']).keys() + active = bits_to_dict(reply['active']).keys() + never_changed = bits_to_dict(reply['nochange']).keys() + + for f in sorted(available): + value = "off" + if f in active: + value = "on" + + fixed = "" + if f not in available or f in never_changed: + fixed = " [fixed]" + + req = "" + if f in requested: + if f in active: + req = " [requested on]" + else: + req = " [requested off]" + + print(f'{f}: {value}{fixed}{req}') + + return + + if args.show_channels: + reply = dumpit(ynl, args, 'channels-get') + print(f'Channel parameters for {args.device}:') + + print(f'Pre-set maximums:') + print_field(reply, + ('rx-max', 'RX'), + ('tx-max', 'TX'), + ('other-max', 'Other'), + ('combined-max', 'Combined')) + + print(f'Current hardware settings:') + print_field(reply, + ('rx-count', 'RX'), + ('tx-count', 'TX'), + ('other-count', 'Other'), + ('combined-count', 'Combined')) + + return + + if args.show_ring: + reply = dumpit(ynl, args, 'channels-get') + + print(f'Ring parameters for {args.device}:') + + print(f'Pre-set maximums:') + print_field(reply, + ('rx-max', 'RX'), + ('rx-mini-max', 'RX Mini'), + ('rx-jumbo-max', 'RX Jumbo'), + ('tx-max', 'TX')) + + print(f'Current hardware settings:') + print_field(reply, + ('rx', 'RX'), + ('rx-mini', 'RX Mini'), + ('rx-jumbo', 'RX Jumbo'), + ('tx', 'TX')) + + print_field(reply, + ('rx-buf-len', 'RX Buf Len'), + ('cqe-size', 'CQE Size'), + ('tx-push', 'TX Push', 'bool')) + + return + + if args.statistics: + print(f'NIC statistics:') + + # TODO: pass id? + strset = dumpit(ynl, args, 'strset-get') + pprint.PrettyPrinter().pprint(strset) + + req = { + 'groups': { + 'size': 1, + 'bits': { + 'bit': + # TODO: support passing the bitmask + #[ + #{ 'name': 'eth-phy', 'value': True }, + { 'name': 'eth-mac', 'value': True }, + #{ 'name': 'eth-ctrl', 'value': True }, + #{ 'name': 'rmon', 'value': True }, + #], + }, + }, + } + + rsp = dumpit(ynl, args, 'stats-get', req) + pprint.PrettyPrinter().pprint(rsp) + return + + if args.show_time_stamping: + req = { + 'header': { + 'flags': 'stats', + }, + } + + tsinfo = dumpit(ynl, args, 'tsinfo-get', req) + + print(f'Time stamping parameters for {args.device}:') + + print('Capabilities:') + [print(f'\t{v}') for v in bits_to_dict(tsinfo['timestamping'])] + + print(f'PTP Hardware Clock: {tsinfo["phc-index"]}') + + print('Hardware Transmit Timestamp Modes:') + [print(f'\t{v}') for v in bits_to_dict(tsinfo['tx-types'])] + + print('Hardware Receive Filter Modes:') + [print(f'\t{v}') for v in bits_to_dict(tsinfo['rx-filters'])] + + print('Statistics:') + [print(f'\t{k}: {v}') for k, v in tsinfo['stats'].items()] + return + + print(f'Settings for {args.device}:') + linkmodes = dumpit(ynl, args, 'linkmodes-get') + ours = bits_to_dict(linkmodes['ours']) + + supported_ports = ('TP', 'AUI', 'BNC', 'MII', 'FIBRE', 'Backplane') + ports = [ p for p in supported_ports if ours.get(p, False)] + print(f'Supported ports: [ {" ".join(ports)} ]') + + print_speed('Supported link modes', ours) + + print_field(ours, ('Pause', 'Supported pause frame use', 'yn')) + print_field(ours, ('Autoneg', 'Supports auto-negotiation', 'yn')) + + supported_fec = ('None', 'PS', 'BASER', 'LLRS') + fec = [ p for p in supported_fec if ours.get(p, False)] + fec_str = " ".join(fec) + if len(fec) == 0: + fec_str = "Not reported" + + print(f'Supported FEC modes: {fec_str}') + + speed = 'Unknown!' + if linkmodes['speed'] > 0 and linkmodes['speed'] < 0xffffffff: + speed = f'{linkmodes["speed"]}Mb/s' + print(f'Speed: {speed}') + + duplex_modes = { + 0: 'Half', + 1: 'Full', + } + duplex = duplex_modes.get(linkmodes["duplex"], None) + if not duplex: + duplex = f'Unknown! ({linkmodes["duplex"]})' + print(f'Duplex: {duplex}') + + autoneg = "off" + if linkmodes.get("autoneg", 0) != 0: + autoneg = "on" + print(f'Auto-negotiation: {autoneg}') + + ports = { + 0: 'Twisted Pair', + 1: 'AUI', + 2: 'MII', + 3: 'FIBRE', + 4: 'BNC', + 5: 'Directly Attached Copper', + 0xef: 'None', + } + linkinfo = dumpit(ynl, args, 'linkinfo-get') + print(f'Port: {ports.get(linkinfo["port"], "Other")}') + + print_field(linkinfo, ('phyaddr', 'PHYAD')) + + transceiver = { + 0: 'Internal', + 1: 'External', + } + print(f'Transceiver: {transceiver.get(linkinfo["transceiver"], "Unknown")}') + + mdix_ctrl = { + 1: 'off', + 2: 'on', + } + mdix = mdix_ctrl.get(linkinfo['tp-mdix-ctrl'], None) + if mdix: + mdix = mdix + ' (forced)' + else: + mdix = mdix_ctrl.get(linkinfo['tp-mdix'], 'Unknown (auto)') + print(f'MDI-X: {mdix}') + + debug = dumpit(ynl, args, 'debug-get') + msgmask = bits_to_dict(debug.get("msgmask", [])).keys() + print(f'Current message level: {" ".join(msgmask)}') + + linkstate = dumpit(ynl, args, 'linkstate-get') + detected_states = { + 0: 'no', + 1: 'yes', + } + # TODO: wol-get + detected = detected_states.get(linkstate['link'], 'unknown') + print(f'Link detected: {detected}') + +if __name__ == '__main__': + main() diff --git a/tools/net/ynl/pyynl/lib/__init__.py b/tools/net/ynl/pyynl/lib/__init__.py new file mode 100644 index 000000000000..9137b83e580a --- /dev/null +++ b/tools/net/ynl/pyynl/lib/__init__.py @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +from .nlspec import SpecAttr, SpecAttrSet, SpecEnumEntry, SpecEnumSet, \ + SpecFamily, SpecOperation +from .ynl import YnlFamily, Netlink, NlError + +__all__ = ["SpecAttr", "SpecAttrSet", "SpecEnumEntry", "SpecEnumSet", + "SpecFamily", "SpecOperation", "YnlFamily", "Netlink", "NlError"] diff --git a/tools/net/ynl/pyynl/lib/nlspec.py b/tools/net/ynl/pyynl/lib/nlspec.py new file mode 100644 index 000000000000..314ec8007496 --- /dev/null +++ b/tools/net/ynl/pyynl/lib/nlspec.py @@ -0,0 +1,617 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +import collections +import importlib +import os +import yaml + + +# To be loaded dynamically as needed +jsonschema = None + + +class SpecElement: + """Netlink spec element. + + Abstract element of the Netlink spec. Implements the dictionary interface + for access to the raw spec. Supports iterative resolution of dependencies + across elements and class inheritance levels. The elements of the spec + may refer to each other, and although loops should be very rare, having + to maintain correct ordering of instantiation is painful, so the resolve() + method should be used to perform parts of init which require access to + other parts of the spec. + + Attributes: + yaml raw spec as loaded from the spec file + family back reference to the full family + + name name of the entity as listed in the spec (optional) + ident_name name which can be safely used as identifier in code (optional) + """ + def __init__(self, family, yaml): + self.yaml = yaml + self.family = family + + if 'name' in self.yaml: + self.name = self.yaml['name'] + self.ident_name = self.name.replace('-', '_') + + self._super_resolved = False + family.add_unresolved(self) + + def __getitem__(self, key): + return self.yaml[key] + + def __contains__(self, key): + return key in self.yaml + + def get(self, key, default=None): + return self.yaml.get(key, default) + + def resolve_up(self, up): + if not self._super_resolved: + up.resolve() + self._super_resolved = True + + def resolve(self): + pass + + +class SpecEnumEntry(SpecElement): + """ Entry within an enum declared in the Netlink spec. + + Attributes: + doc documentation string + enum_set back reference to the enum + value numerical value of this enum (use accessors in most situations!) + + Methods: + raw_value raw value, i.e. the id in the enum, unlike user value which is a mask for flags + user_value user value, same as raw value for enums, for flags it's the mask + """ + def __init__(self, enum_set, yaml, prev, value_start): + if isinstance(yaml, str): + yaml = {'name': yaml} + super().__init__(enum_set.family, yaml) + + self.doc = yaml.get('doc', '') + self.enum_set = enum_set + + if 'value' in yaml: + self.value = yaml['value'] + elif prev: + self.value = prev.value + 1 + else: + self.value = value_start + + def has_doc(self): + return bool(self.doc) + + def raw_value(self): + return self.value + + def user_value(self, as_flags=None): + if self.enum_set['type'] == 'flags' or as_flags: + return 1 << self.value + else: + return self.value + + +class SpecEnumSet(SpecElement): + """ Enum type + + Represents an enumeration (list of numerical constants) + as declared in the "definitions" section of the spec. + + Attributes: + type enum or flags + entries entries by name + entries_by_val entries by value + Methods: + get_mask for flags compute the mask of all defined values + """ + def __init__(self, family, yaml): + super().__init__(family, yaml) + + self.type = yaml['type'] + + prev_entry = None + value_start = self.yaml.get('value-start', 0) + self.entries = dict() + self.entries_by_val = dict() + for entry in self.yaml['entries']: + e = self.new_entry(entry, prev_entry, value_start) + self.entries[e.name] = e + self.entries_by_val[e.raw_value()] = e + prev_entry = e + + def new_entry(self, entry, prev_entry, value_start): + return SpecEnumEntry(self, entry, prev_entry, value_start) + + def has_doc(self): + if 'doc' in self.yaml: + return True + return self.has_entry_doc() + + def has_entry_doc(self): + for entry in self.entries.values(): + if entry.has_doc(): + return True + return False + + def get_mask(self, as_flags=None): + mask = 0 + for e in self.entries.values(): + mask += e.user_value(as_flags) + return mask + + +class SpecAttr(SpecElement): + """ Single Netlink attribute type + + Represents a single attribute type within an attr space. + + Attributes: + type string, attribute type + value numerical ID when serialized + attr_set Attribute Set containing this attr + is_multi bool, attr may repeat multiple times + struct_name string, name of struct definition + sub_type string, name of sub type + len integer, optional byte length of binary types + display_hint string, hint to help choose format specifier + when displaying the value + sub_message string, name of sub message type + selector string, name of attribute used to select + sub-message type + + is_auto_scalar bool, attr is a variable-size scalar + """ + def __init__(self, family, attr_set, yaml, value): + super().__init__(family, yaml) + + self.type = yaml['type'] + self.value = value + self.attr_set = attr_set + self.is_multi = yaml.get('multi-attr', False) + self.struct_name = yaml.get('struct') + self.sub_type = yaml.get('sub-type') + self.byte_order = yaml.get('byte-order') + self.len = yaml.get('len') + self.display_hint = yaml.get('display-hint') + self.sub_message = yaml.get('sub-message') + self.selector = yaml.get('selector') + + self.is_auto_scalar = self.type == "sint" or self.type == "uint" + + +class SpecAttrSet(SpecElement): + """ Netlink Attribute Set class. + + Represents a ID space of attributes within Netlink. + + Note that unlike other elements, which expose contents of the raw spec + via the dictionary interface Attribute Set exposes attributes by name. + + Attributes: + attrs ordered dict of all attributes (indexed by name) + attrs_by_val ordered dict of all attributes (indexed by value) + subset_of parent set if this is a subset, otherwise None + """ + def __init__(self, family, yaml): + super().__init__(family, yaml) + + self.subset_of = self.yaml.get('subset-of', None) + + self.attrs = collections.OrderedDict() + self.attrs_by_val = collections.OrderedDict() + + if self.subset_of is None: + val = 1 + for elem in self.yaml['attributes']: + if 'value' in elem: + val = elem['value'] + + attr = self.new_attr(elem, val) + self.attrs[attr.name] = attr + self.attrs_by_val[attr.value] = attr + val += 1 + else: + real_set = family.attr_sets[self.subset_of] + for elem in self.yaml['attributes']: + real_attr = real_set[elem['name']] + combined_elem = real_attr.yaml | elem + attr = self.new_attr(combined_elem, real_attr.value) + + self.attrs[attr.name] = attr + self.attrs_by_val[attr.value] = attr + + def new_attr(self, elem, value): + return SpecAttr(self.family, self, elem, value) + + def __getitem__(self, key): + return self.attrs[key] + + def __contains__(self, key): + return key in self.attrs + + def __iter__(self): + yield from self.attrs + + def items(self): + return self.attrs.items() + + +class SpecStructMember(SpecElement): + """Struct member attribute + + Represents a single struct member attribute. + + Attributes: + type string, type of the member attribute + byte_order string or None for native byte order + enum string, name of the enum definition + len integer, optional byte length of binary types + display_hint string, hint to help choose format specifier + when displaying the value + struct string, name of nested struct type + """ + def __init__(self, family, yaml): + super().__init__(family, yaml) + self.type = yaml['type'] + self.byte_order = yaml.get('byte-order') + self.enum = yaml.get('enum') + self.len = yaml.get('len') + self.display_hint = yaml.get('display-hint') + self.struct = yaml.get('struct') + + +class SpecStruct(SpecElement): + """Netlink struct type + + Represents a C struct definition. + + Attributes: + members ordered list of struct members + """ + def __init__(self, family, yaml): + super().__init__(family, yaml) + + self.members = [] + for member in yaml.get('members', []): + self.members.append(self.new_member(family, member)) + + def new_member(self, family, elem): + return SpecStructMember(family, elem) + + def __iter__(self): + yield from self.members + + def items(self): + return self.members.items() + + +class SpecSubMessage(SpecElement): + """ Netlink sub-message definition + + Represents a set of sub-message formats for polymorphic nlattrs + that contain type-specific sub messages. + + Attributes: + name string, name of sub-message definition + formats dict of sub-message formats indexed by match value + """ + def __init__(self, family, yaml): + super().__init__(family, yaml) + + self.formats = collections.OrderedDict() + for elem in self.yaml['formats']: + format = self.new_format(family, elem) + self.formats[format.value] = format + + def new_format(self, family, format): + return SpecSubMessageFormat(family, format) + + +class SpecSubMessageFormat(SpecElement): + """ Netlink sub-message format definition + + Represents a single format for a sub-message. + + Attributes: + value attribute value to match against type selector + fixed_header string, name of fixed header, or None + attr_set string, name of attribute set, or None + """ + def __init__(self, family, yaml): + super().__init__(family, yaml) + + self.value = yaml.get('value') + self.fixed_header = yaml.get('fixed-header') + self.attr_set = yaml.get('attribute-set') + + +class SpecOperation(SpecElement): + """Netlink Operation + + Information about a single Netlink operation. + + Attributes: + value numerical ID when serialized, None if req/rsp values differ + + req_value numerical ID when serialized, user -> kernel + rsp_value numerical ID when serialized, user <- kernel + modes supported operation modes (do, dump, event etc.) + is_call bool, whether the operation is a call + is_async bool, whether the operation is a notification + is_resv bool, whether the operation does not exist (it's just a reserved ID) + attr_set attribute set name + fixed_header string, optional name of fixed header struct + + yaml raw spec as loaded from the spec file + """ + def __init__(self, family, yaml, req_value, rsp_value): + super().__init__(family, yaml) + + self.value = req_value if req_value == rsp_value else None + self.req_value = req_value + self.rsp_value = rsp_value + + self.modes = yaml.keys() & {'do', 'dump', 'event', 'notify'} + self.is_call = 'do' in yaml or 'dump' in yaml + self.is_async = 'notify' in yaml or 'event' in yaml + self.is_resv = not self.is_async and not self.is_call + self.fixed_header = self.yaml.get('fixed-header', family.fixed_header) + + # Added by resolve: + self.attr_set = None + delattr(self, "attr_set") + + def resolve(self): + self.resolve_up(super()) + + if 'attribute-set' in self.yaml: + attr_set_name = self.yaml['attribute-set'] + elif 'notify' in self.yaml: + msg = self.family.msgs[self.yaml['notify']] + attr_set_name = msg['attribute-set'] + elif self.is_resv: + attr_set_name = '' + else: + raise Exception(f"Can't resolve attribute set for op '{self.name}'") + if attr_set_name: + self.attr_set = self.family.attr_sets[attr_set_name] + + +class SpecMcastGroup(SpecElement): + """Netlink Multicast Group + + Information about a multicast group. + + Value is only used for classic netlink families that use the + netlink-raw schema. Genetlink families use dynamic ID allocation + where the ids of multicast groups get resolved at runtime. Value + will be None for genetlink families. + + Attributes: + name name of the mulitcast group + value integer id of this multicast group for netlink-raw or None + yaml raw spec as loaded from the spec file + """ + def __init__(self, family, yaml): + super().__init__(family, yaml) + self.value = self.yaml.get('value') + + +class SpecFamily(SpecElement): + """ Netlink Family Spec class. + + Netlink family information loaded from a spec (e.g. in YAML). + Takes care of unfolding implicit information which can be skipped + in the spec itself for brevity. + + The class can be used like a dictionary to access the raw spec + elements but that's usually a bad idea. + + Attributes: + proto protocol type (e.g. genetlink) + msg_id_model enum-model for operations (unified, directional etc.) + license spec license (loaded from an SPDX tag on the spec) + + attr_sets dict of attribute sets + msgs dict of all messages (index by name) + sub_msgs dict of all sub messages (index by name) + ops dict of all valid requests / responses + ntfs dict of all async events + consts dict of all constants/enums + fixed_header string, optional name of family default fixed header struct + mcast_groups dict of all multicast groups (index by name) + kernel_family dict of kernel family attributes + """ + def __init__(self, spec_path, schema_path=None, exclude_ops=None): + with open(spec_path, "r") as stream: + prefix = '# SPDX-License-Identifier: ' + first = stream.readline().strip() + if not first.startswith(prefix): + raise Exception('SPDX license tag required in the spec') + self.license = first[len(prefix):] + + stream.seek(0) + spec = yaml.safe_load(stream) + + self._resolution_list = [] + + super().__init__(self, spec) + + self._exclude_ops = exclude_ops if exclude_ops else [] + + self.proto = self.yaml.get('protocol', 'genetlink') + self.msg_id_model = self.yaml['operations'].get('enum-model', 'unified') + + if schema_path is None: + schema_path = os.path.dirname(os.path.dirname(spec_path)) + f'/{self.proto}.yaml' + if schema_path: + global jsonschema + + with open(schema_path, "r") as stream: + schema = yaml.safe_load(stream) + + if jsonschema is None: + jsonschema = importlib.import_module("jsonschema") + + jsonschema.validate(self.yaml, schema) + + self.attr_sets = collections.OrderedDict() + self.sub_msgs = collections.OrderedDict() + self.msgs = collections.OrderedDict() + self.req_by_value = collections.OrderedDict() + self.rsp_by_value = collections.OrderedDict() + self.ops = collections.OrderedDict() + self.ntfs = collections.OrderedDict() + self.consts = collections.OrderedDict() + self.mcast_groups = collections.OrderedDict() + self.kernel_family = collections.OrderedDict(self.yaml.get('kernel-family', {})) + + last_exception = None + while len(self._resolution_list) > 0: + resolved = [] + unresolved = self._resolution_list + self._resolution_list = [] + + for elem in unresolved: + try: + elem.resolve() + except (KeyError, AttributeError) as e: + self._resolution_list.append(elem) + last_exception = e + continue + + resolved.append(elem) + + if len(resolved) == 0: + raise last_exception + + def new_enum(self, elem): + return SpecEnumSet(self, elem) + + def new_attr_set(self, elem): + return SpecAttrSet(self, elem) + + def new_struct(self, elem): + return SpecStruct(self, elem) + + def new_sub_message(self, elem): + return SpecSubMessage(self, elem); + + def new_operation(self, elem, req_val, rsp_val): + return SpecOperation(self, elem, req_val, rsp_val) + + def new_mcast_group(self, elem): + return SpecMcastGroup(self, elem) + + def add_unresolved(self, elem): + self._resolution_list.append(elem) + + def _dictify_ops_unified(self): + self.fixed_header = self.yaml['operations'].get('fixed-header') + val = 1 + for elem in self.yaml['operations']['list']: + if 'value' in elem: + val = elem['value'] + + op = self.new_operation(elem, val, val) + val += 1 + + self.msgs[op.name] = op + + def _dictify_ops_directional(self): + self.fixed_header = self.yaml['operations'].get('fixed-header') + req_val = rsp_val = 1 + for elem in self.yaml['operations']['list']: + if 'notify' in elem or 'event' in elem: + if 'value' in elem: + rsp_val = elem['value'] + req_val_next = req_val + rsp_val_next = rsp_val + 1 + req_val = None + elif 'do' in elem or 'dump' in elem: + mode = elem['do'] if 'do' in elem else elem['dump'] + + v = mode.get('request', {}).get('value', None) + if v: + req_val = v + v = mode.get('reply', {}).get('value', None) + if v: + rsp_val = v + + rsp_inc = 1 if 'reply' in mode else 0 + req_val_next = req_val + 1 + rsp_val_next = rsp_val + rsp_inc + else: + raise Exception("Can't parse directional ops") + + if req_val == req_val_next: + req_val = None + if rsp_val == rsp_val_next: + rsp_val = None + + skip = False + for exclude in self._exclude_ops: + skip |= bool(exclude.match(elem['name'])) + if not skip: + op = self.new_operation(elem, req_val, rsp_val) + + req_val = req_val_next + rsp_val = rsp_val_next + + self.msgs[op.name] = op + + def find_operation(self, name): + """ + For a given operation name, find and return operation spec. + """ + for op in self.yaml['operations']['list']: + if name == op['name']: + return op + return None + + def resolve(self): + self.resolve_up(super()) + + definitions = self.yaml.get('definitions', []) + for elem in definitions: + if elem['type'] == 'enum' or elem['type'] == 'flags': + self.consts[elem['name']] = self.new_enum(elem) + elif elem['type'] == 'struct': + self.consts[elem['name']] = self.new_struct(elem) + else: + self.consts[elem['name']] = elem + + for elem in self.yaml['attribute-sets']: + attr_set = self.new_attr_set(elem) + self.attr_sets[elem['name']] = attr_set + + for elem in self.yaml.get('sub-messages', []): + sub_message = self.new_sub_message(elem) + self.sub_msgs[sub_message.name] = sub_message + + if self.msg_id_model == 'unified': + self._dictify_ops_unified() + elif self.msg_id_model == 'directional': + self._dictify_ops_directional() + + for op in self.msgs.values(): + if op.req_value is not None: + self.req_by_value[op.req_value] = op + if op.rsp_value is not None: + self.rsp_by_value[op.rsp_value] = op + if not op.is_async and 'attribute-set' in op: + self.ops[op.name] = op + elif op.is_async: + self.ntfs[op.name] = op + + mcgs = self.yaml.get('mcast-groups') + if mcgs: + for elem in mcgs['list']: + mcg = self.new_mcast_group(elem) + self.mcast_groups[elem['name']] = mcg diff --git a/tools/net/ynl/pyynl/lib/ynl.py b/tools/net/ynl/pyynl/lib/ynl.py new file mode 100644 index 000000000000..08f8bf89cfc2 --- /dev/null +++ b/tools/net/ynl/pyynl/lib/ynl.py @@ -0,0 +1,1067 @@ +# SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause + +from collections import namedtuple +from enum import Enum +import functools +import os +import random +import socket +import struct +from struct import Struct +import sys +import yaml +import ipaddress +import uuid +import queue +import selectors +import time + +from .nlspec import SpecFamily + +# +# Generic Netlink code which should really be in some library, but I can't quickly find one. +# + + +class Netlink: + # Netlink socket + SOL_NETLINK = 270 + + NETLINK_ADD_MEMBERSHIP = 1 + NETLINK_CAP_ACK = 10 + NETLINK_EXT_ACK = 11 + NETLINK_GET_STRICT_CHK = 12 + + # Netlink message + NLMSG_ERROR = 2 + NLMSG_DONE = 3 + + NLM_F_REQUEST = 1 + NLM_F_ACK = 4 + NLM_F_ROOT = 0x100 + NLM_F_MATCH = 0x200 + + NLM_F_REPLACE = 0x100 + NLM_F_EXCL = 0x200 + NLM_F_CREATE = 0x400 + NLM_F_APPEND = 0x800 + + NLM_F_CAPPED = 0x100 + NLM_F_ACK_TLVS = 0x200 + + NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH + + NLA_F_NESTED = 0x8000 + NLA_F_NET_BYTEORDER = 0x4000 + + NLA_TYPE_MASK = NLA_F_NESTED | NLA_F_NET_BYTEORDER + + # Genetlink defines + NETLINK_GENERIC = 16 + + GENL_ID_CTRL = 0x10 + + # nlctrl + CTRL_CMD_GETFAMILY = 3 + + CTRL_ATTR_FAMILY_ID = 1 + CTRL_ATTR_FAMILY_NAME = 2 + CTRL_ATTR_MAXATTR = 5 + CTRL_ATTR_MCAST_GROUPS = 7 + + CTRL_ATTR_MCAST_GRP_NAME = 1 + CTRL_ATTR_MCAST_GRP_ID = 2 + + # Extack types + NLMSGERR_ATTR_MSG = 1 + NLMSGERR_ATTR_OFFS = 2 + NLMSGERR_ATTR_COOKIE = 3 + NLMSGERR_ATTR_POLICY = 4 + NLMSGERR_ATTR_MISS_TYPE = 5 + NLMSGERR_ATTR_MISS_NEST = 6 + + # Policy types + NL_POLICY_TYPE_ATTR_TYPE = 1 + NL_POLICY_TYPE_ATTR_MIN_VALUE_S = 2 + NL_POLICY_TYPE_ATTR_MAX_VALUE_S = 3 + NL_POLICY_TYPE_ATTR_MIN_VALUE_U = 4 + NL_POLICY_TYPE_ATTR_MAX_VALUE_U = 5 + NL_POLICY_TYPE_ATTR_MIN_LENGTH = 6 + NL_POLICY_TYPE_ATTR_MAX_LENGTH = 7 + NL_POLICY_TYPE_ATTR_POLICY_IDX = 8 + NL_POLICY_TYPE_ATTR_POLICY_MAXTYPE = 9 + NL_POLICY_TYPE_ATTR_BITFIELD32_MASK = 10 + NL_POLICY_TYPE_ATTR_PAD = 11 + NL_POLICY_TYPE_ATTR_MASK = 12 + + AttrType = Enum('AttrType', ['flag', 'u8', 'u16', 'u32', 'u64', + 's8', 's16', 's32', 's64', + 'binary', 'string', 'nul-string', + 'nested', 'nested-array', + 'bitfield32', 'sint', 'uint']) + +class NlError(Exception): + def __init__(self, nl_msg): + self.nl_msg = nl_msg + self.error = -nl_msg.error + + def __str__(self): + return f"Netlink error: {os.strerror(self.error)}\n{self.nl_msg}" + + +class ConfigError(Exception): + pass + + +class NlAttr: + ScalarFormat = namedtuple('ScalarFormat', ['native', 'big', 'little']) + type_formats = { + 'u8' : ScalarFormat(Struct('B'), Struct("B"), Struct("B")), + 's8' : ScalarFormat(Struct('b'), Struct("b"), Struct("b")), + 'u16': ScalarFormat(Struct('H'), Struct(">H"), Struct("h"), Struct("I"), Struct("i"), Struct("Q"), Struct("q"), Struct(">= 1 + i += 1 + else: + value = enum.entries_by_val[raw].name + return value + + def _decode_binary(self, attr, attr_spec): + if attr_spec.struct_name: + decoded = self._decode_struct(attr.raw, attr_spec.struct_name) + elif attr_spec.sub_type: + decoded = attr.as_c_array(attr_spec.sub_type) + else: + decoded = attr.as_bin() + if attr_spec.display_hint: + decoded = self._formatted_string(decoded, attr_spec.display_hint) + return decoded + + def _decode_array_attr(self, attr, attr_spec): + decoded = [] + offset = 0 + while offset < len(attr.raw): + item = NlAttr(attr.raw, offset) + offset += item.full_len + + if attr_spec["sub-type"] == 'nest': + subattrs = self._decode(NlAttrs(item.raw), attr_spec['nested-attributes']) + decoded.append({ item.type: subattrs }) + elif attr_spec["sub-type"] == 'binary': + subattrs = item.as_bin() + if attr_spec.display_hint: + subattrs = self._formatted_string(subattrs, attr_spec.display_hint) + decoded.append(subattrs) + elif attr_spec["sub-type"] in NlAttr.type_formats: + subattrs = item.as_scalar(attr_spec['sub-type'], attr_spec.byte_order) + if attr_spec.display_hint: + subattrs = self._formatted_string(subattrs, attr_spec.display_hint) + decoded.append(subattrs) + else: + raise Exception(f'Unknown {attr_spec["sub-type"]} with name {attr_spec["name"]}') + return decoded + + def _decode_nest_type_value(self, attr, attr_spec): + decoded = {} + value = attr + for name in attr_spec['type-value']: + value = NlAttr(value.raw, 0) + decoded[name] = value.type + subattrs = self._decode(NlAttrs(value.raw), attr_spec['nested-attributes']) + decoded.update(subattrs) + return decoded + + def _decode_unknown(self, attr): + if attr.is_nest: + return self._decode(NlAttrs(attr.raw), None) + else: + return attr.as_bin() + + def _rsp_add(self, rsp, name, is_multi, decoded): + if is_multi == None: + if name in rsp and type(rsp[name]) is not list: + rsp[name] = [rsp[name]] + is_multi = True + else: + is_multi = False + + if not is_multi: + rsp[name] = decoded + elif name in rsp: + rsp[name].append(decoded) + else: + rsp[name] = [decoded] + + def _resolve_selector(self, attr_spec, search_attrs): + sub_msg = attr_spec.sub_message + if sub_msg not in self.sub_msgs: + raise Exception(f"No sub-message spec named {sub_msg} for {attr_spec.name}") + sub_msg_spec = self.sub_msgs[sub_msg] + + selector = attr_spec.selector + value = search_attrs.lookup(selector) + if value not in sub_msg_spec.formats: + raise Exception(f"No message format for '{value}' in sub-message spec '{sub_msg}'") + + spec = sub_msg_spec.formats[value] + return spec + + def _decode_sub_msg(self, attr, attr_spec, search_attrs): + msg_format = self._resolve_selector(attr_spec, search_attrs) + decoded = {} + offset = 0 + if msg_format.fixed_header: + decoded.update(self._decode_struct(attr.raw, msg_format.fixed_header)); + offset = self._struct_size(msg_format.fixed_header) + if msg_format.attr_set: + if msg_format.attr_set in self.attr_sets: + subdict = self._decode(NlAttrs(attr.raw, offset), msg_format.attr_set) + decoded.update(subdict) + else: + raise Exception(f"Unknown attribute-set '{attr_space}' when decoding '{attr_spec.name}'") + return decoded + + def _decode(self, attrs, space, outer_attrs = None): + rsp = dict() + if space: + attr_space = self.attr_sets[space] + search_attrs = SpaceAttrs(attr_space, rsp, outer_attrs) + + for attr in attrs: + try: + attr_spec = attr_space.attrs_by_val[attr.type] + except (KeyError, UnboundLocalError): + if not self.process_unknown: + raise Exception(f"Space '{space}' has no attribute with value '{attr.type}'") + attr_name = f"UnknownAttr({attr.type})" + self._rsp_add(rsp, attr_name, None, self._decode_unknown(attr)) + continue + + try: + if attr_spec["type"] == 'nest': + subdict = self._decode(NlAttrs(attr.raw), attr_spec['nested-attributes'], search_attrs) + decoded = subdict + elif attr_spec["type"] == 'string': + decoded = attr.as_strz() + elif attr_spec["type"] == 'binary': + decoded = self._decode_binary(attr, attr_spec) + elif attr_spec["type"] == 'flag': + decoded = True + elif attr_spec.is_auto_scalar: + decoded = attr.as_auto_scalar(attr_spec['type'], attr_spec.byte_order) + elif attr_spec["type"] in NlAttr.type_formats: + decoded = attr.as_scalar(attr_spec['type'], attr_spec.byte_order) + if 'enum' in attr_spec: + decoded = self._decode_enum(decoded, attr_spec) + elif attr_spec.display_hint: + decoded = self._formatted_string(decoded, attr_spec.display_hint) + elif attr_spec["type"] == 'indexed-array': + decoded = self._decode_array_attr(attr, attr_spec) + elif attr_spec["type"] == 'bitfield32': + value, selector = struct.unpack("II", attr.raw) + if 'enum' in attr_spec: + value = self._decode_enum(value, attr_spec) + selector = self._decode_enum(selector, attr_spec) + decoded = {"value": value, "selector": selector} + elif attr_spec["type"] == 'sub-message': + decoded = self._decode_sub_msg(attr, attr_spec, search_attrs) + elif attr_spec["type"] == 'nest-type-value': + decoded = self._decode_nest_type_value(attr, attr_spec) + else: + if not self.process_unknown: + raise Exception(f'Unknown {attr_spec["type"]} with name {attr_spec["name"]}') + decoded = self._decode_unknown(attr) + + self._rsp_add(rsp, attr_spec["name"], attr_spec.is_multi, decoded) + except: + print(f"Error decoding '{attr_spec.name}' from '{space}'") + raise + + return rsp + + def _decode_extack_path(self, attrs, attr_set, offset, target): + for attr in attrs: + try: + attr_spec = attr_set.attrs_by_val[attr.type] + except KeyError: + raise Exception(f"Space '{attr_set.name}' has no attribute with value '{attr.type}'") + if offset > target: + break + if offset == target: + return '.' + attr_spec.name + + if offset + attr.full_len <= target: + offset += attr.full_len + continue + if attr_spec['type'] != 'nest': + raise Exception(f"Can't dive into {attr.type} ({attr_spec['name']}) for extack") + offset += 4 + subpath = self._decode_extack_path(NlAttrs(attr.raw), + self.attr_sets[attr_spec['nested-attributes']], + offset, target) + if subpath is None: + return None + return '.' + attr_spec.name + subpath + + return None + + def _decode_extack(self, request, op, extack): + if 'bad-attr-offs' not in extack: + return + + msg = self.nlproto.decode(self, NlMsg(request, 0, op.attr_set), op) + offset = self.nlproto.msghdr_size() + self._struct_size(op.fixed_header) + path = self._decode_extack_path(msg.raw_attrs, op.attr_set, offset, + extack['bad-attr-offs']) + if path: + del extack['bad-attr-offs'] + extack['bad-attr'] = path + + def _struct_size(self, name): + if name: + members = self.consts[name].members + size = 0 + for m in members: + if m.type in ['pad', 'binary']: + if m.struct: + size += self._struct_size(m.struct) + else: + size += m.len + else: + format = NlAttr.get_format(m.type, m.byte_order) + size += format.size + return size + else: + return 0 + + def _decode_struct(self, data, name): + members = self.consts[name].members + attrs = dict() + offset = 0 + for m in members: + value = None + if m.type == 'pad': + offset += m.len + elif m.type == 'binary': + if m.struct: + len = self._struct_size(m.struct) + value = self._decode_struct(data[offset : offset + len], + m.struct) + offset += len + else: + value = data[offset : offset + m.len] + offset += m.len + else: + format = NlAttr.get_format(m.type, m.byte_order) + [ value ] = format.unpack_from(data, offset) + offset += format.size + if value is not None: + if m.enum: + value = self._decode_enum(value, m) + elif m.display_hint: + value = self._formatted_string(value, m.display_hint) + attrs[m.name] = value + return attrs + + def _encode_struct(self, name, vals): + members = self.consts[name].members + attr_payload = b'' + for m in members: + value = vals.pop(m.name) if m.name in vals else None + if m.type == 'pad': + attr_payload += bytearray(m.len) + elif m.type == 'binary': + if m.struct: + if value is None: + value = dict() + attr_payload += self._encode_struct(m.struct, value) + else: + if value is None: + attr_payload += bytearray(m.len) + else: + attr_payload += bytes.fromhex(value) + else: + if value is None: + value = 0 + format = NlAttr.get_format(m.type, m.byte_order) + attr_payload += format.pack(value) + return attr_payload + + def _formatted_string(self, raw, display_hint): + if display_hint == 'mac': + formatted = ':'.join('%02x' % b for b in raw) + elif display_hint == 'hex': + if isinstance(raw, int): + formatted = hex(raw) + else: + formatted = bytes.hex(raw, ' ') + elif display_hint in [ 'ipv4', 'ipv6' ]: + formatted = format(ipaddress.ip_address(raw)) + elif display_hint == 'uuid': + formatted = str(uuid.UUID(bytes=raw)) + else: + formatted = raw + return formatted + + def handle_ntf(self, decoded): + msg = dict() + if self.include_raw: + msg['raw'] = decoded + op = self.rsp_by_value[decoded.cmd()] + attrs = self._decode(decoded.raw_attrs, op.attr_set.name) + if op.fixed_header: + attrs.update(self._decode_struct(decoded.raw, op.fixed_header)) + + msg['name'] = op['name'] + msg['msg'] = attrs + self.async_msg_queue.put(msg) + + def check_ntf(self): + while True: + try: + reply = self.sock.recv(self._recv_size, socket.MSG_DONTWAIT) + except BlockingIOError: + return + + nms = NlMsgs(reply) + self._recv_dbg_print(reply, nms) + for nl_msg in nms: + if nl_msg.error: + print("Netlink error in ntf!?", os.strerror(-nl_msg.error)) + print(nl_msg) + continue + if nl_msg.done: + print("Netlink done while checking for ntf!?") + continue + + decoded = self.nlproto.decode(self, nl_msg, None) + if decoded.cmd() not in self.async_msg_ids: + print("Unexpected msg id while checking for ntf", decoded) + continue + + self.handle_ntf(decoded) + + def poll_ntf(self, duration=None): + start_time = time.time() + selector = selectors.DefaultSelector() + selector.register(self.sock, selectors.EVENT_READ) + + while True: + try: + yield self.async_msg_queue.get_nowait() + except queue.Empty: + if duration is not None: + timeout = start_time + duration - time.time() + if timeout <= 0: + return + else: + timeout = None + events = selector.select(timeout) + if events: + self.check_ntf() + + def operation_do_attributes(self, name): + """ + For a given operation name, find and return a supported + set of attributes (as a dict). + """ + op = self.find_operation(name) + if not op: + return None + + return op['do']['request']['attributes'].copy() + + def _encode_message(self, op, vals, flags, req_seq): + nl_flags = Netlink.NLM_F_REQUEST | Netlink.NLM_F_ACK + for flag in flags or []: + nl_flags |= flag + + msg = self.nlproto.message(nl_flags, op.req_value, 1, req_seq) + if op.fixed_header: + msg += self._encode_struct(op.fixed_header, vals) + search_attrs = SpaceAttrs(op.attr_set, vals) + for name, value in vals.items(): + msg += self._add_attr(op.attr_set.name, name, value, search_attrs) + msg = _genl_msg_finalize(msg) + return msg + + def _ops(self, ops): + reqs_by_seq = {} + req_seq = random.randint(1024, 65535) + payload = b'' + for (method, vals, flags) in ops: + op = self.ops[method] + msg = self._encode_message(op, vals, flags, req_seq) + reqs_by_seq[req_seq] = (op, msg, flags) + payload += msg + req_seq += 1 + + self.sock.send(payload, 0) + + done = False + rsp = [] + op_rsp = [] + while not done: + reply = self.sock.recv(self._recv_size) + nms = NlMsgs(reply, attr_space=op.attr_set) + self._recv_dbg_print(reply, nms) + for nl_msg in nms: + if nl_msg.nl_seq in reqs_by_seq: + (op, req_msg, req_flags) = reqs_by_seq[nl_msg.nl_seq] + if nl_msg.extack: + self._decode_extack(req_msg, op, nl_msg.extack) + else: + op = None + req_flags = [] + + if nl_msg.error: + raise NlError(nl_msg) + if nl_msg.done: + if nl_msg.extack: + print("Netlink warning:") + print(nl_msg) + + if Netlink.NLM_F_DUMP in req_flags: + rsp.append(op_rsp) + elif not op_rsp: + rsp.append(None) + elif len(op_rsp) == 1: + rsp.append(op_rsp[0]) + else: + rsp.append(op_rsp) + op_rsp = [] + + del reqs_by_seq[nl_msg.nl_seq] + done = len(reqs_by_seq) == 0 + break + + decoded = self.nlproto.decode(self, nl_msg, op) + + # Check if this is a reply to our request + if nl_msg.nl_seq not in reqs_by_seq or decoded.cmd() != op.rsp_value: + if decoded.cmd() in self.async_msg_ids: + self.handle_ntf(decoded) + continue + else: + print('Unexpected message: ' + repr(decoded)) + continue + + rsp_msg = self._decode(decoded.raw_attrs, op.attr_set.name) + if op.fixed_header: + rsp_msg.update(self._decode_struct(decoded.raw, op.fixed_header)) + op_rsp.append(rsp_msg) + + return rsp + + def _op(self, method, vals, flags=None, dump=False): + req_flags = flags or [] + if dump: + req_flags.append(Netlink.NLM_F_DUMP) + + ops = [(method, vals, req_flags)] + return self._ops(ops)[0] + + def do(self, method, vals, flags=None): + return self._op(method, vals, flags) + + def dump(self, method, vals): + return self._op(method, vals, dump=True) + + def do_multi(self, ops): + return self._ops(ops) diff --git a/tools/net/ynl/pyynl/ynl_gen_c.py b/tools/net/ynl/pyynl/ynl_gen_c.py new file mode 100755 index 000000000000..d3a7dfbcf929 --- /dev/null +++ b/tools/net/ynl/pyynl/ynl_gen_c.py @@ -0,0 +1,3044 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) + +import argparse +import collections +import filecmp +import pathlib +import os +import re +import shutil +import sys +import tempfile +import yaml + +sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) +from lib import SpecFamily, SpecAttrSet, SpecAttr, SpecOperation, SpecEnumSet, SpecEnumEntry + + +def c_upper(name): + return name.upper().replace('-', '_') + + +def c_lower(name): + return name.lower().replace('-', '_') + + +def limit_to_number(name): + """ + Turn a string limit like u32-max or s64-min into its numerical value + """ + if name[0] == 'u' and name.endswith('-min'): + return 0 + width = int(name[1:-4]) + if name[0] == 's': + width -= 1 + value = (1 << width) - 1 + if name[0] == 's' and name.endswith('-min'): + value = -value - 1 + return value + + +class BaseNlLib: + def get_family_id(self): + return 'ys->family_id' + + +class Type(SpecAttr): + def __init__(self, family, attr_set, attr, value): + super().__init__(family, attr_set, attr, value) + + self.attr = attr + self.attr_set = attr_set + self.type = attr['type'] + self.checks = attr.get('checks', {}) + + self.request = False + self.reply = False + + if 'len' in attr: + self.len = attr['len'] + + if 'nested-attributes' in attr: + self.nested_attrs = attr['nested-attributes'] + if self.nested_attrs == family.name: + self.nested_render_name = c_lower(f"{family.ident_name}") + else: + self.nested_render_name = c_lower(f"{family.ident_name}_{self.nested_attrs}") + + if self.nested_attrs in self.family.consts: + self.nested_struct_type = 'struct ' + self.nested_render_name + '_' + else: + self.nested_struct_type = 'struct ' + self.nested_render_name + + self.c_name = c_lower(self.name) + if self.c_name in _C_KW: + self.c_name += '_' + + # Added by resolve(): + self.enum_name = None + delattr(self, "enum_name") + + def _get_real_attr(self): + # if the attr is for a subset return the "real" attr (just one down, does not recurse) + return self.family.attr_sets[self.attr_set.subset_of][self.name] + + def set_request(self): + self.request = True + if self.attr_set.subset_of: + self._get_real_attr().set_request() + + def set_reply(self): + self.reply = True + if self.attr_set.subset_of: + self._get_real_attr().set_reply() + + def get_limit(self, limit, default=None): + value = self.checks.get(limit, default) + if value is None: + return value + if isinstance(value, int): + return value + if value in self.family.consts: + raise Exception("Resolving family constants not implemented, yet") + return limit_to_number(value) + + def get_limit_str(self, limit, default=None, suffix=''): + value = self.checks.get(limit, default) + if value is None: + return '' + if isinstance(value, int): + return str(value) + suffix + if value in self.family.consts: + return c_upper(f"{self.family['name']}-{value}") + return c_upper(value) + + def resolve(self): + if 'name-prefix' in self.attr: + enum_name = f"{self.attr['name-prefix']}{self.name}" + else: + enum_name = f"{self.attr_set.name_prefix}{self.name}" + self.enum_name = c_upper(enum_name) + + if self.attr_set.subset_of: + if self.checks != self._get_real_attr().checks: + raise Exception("Overriding checks not supported by codegen, yet") + + def is_multi_val(self): + return None + + def is_scalar(self): + return self.type in {'u8', 'u16', 'u32', 'u64', 's32', 's64'} + + def is_recursive(self): + return False + + def is_recursive_for_op(self, ri): + return self.is_recursive() and not ri.op + + def presence_type(self): + return 'bit' + + def presence_member(self, space, type_filter): + if self.presence_type() != type_filter: + return + + if self.presence_type() == 'bit': + pfx = '__' if space == 'user' else '' + return f"{pfx}u32 {self.c_name}:1;" + + if self.presence_type() == 'len': + pfx = '__' if space == 'user' else '' + return f"{pfx}u32 {self.c_name}_len;" + + def _complex_member_type(self, ri): + return None + + def free_needs_iter(self): + return False + + def free(self, ri, var, ref): + if self.is_multi_val() or self.presence_type() == 'len': + ri.cw.p(f'free({var}->{ref}{self.c_name});') + + def arg_member(self, ri): + member = self._complex_member_type(ri) + if member: + arg = [member + ' *' + self.c_name] + if self.presence_type() == 'count': + arg += ['unsigned int n_' + self.c_name] + return arg + raise Exception(f"Struct member not implemented for class type {self.type}") + + def struct_member(self, ri): + if self.is_multi_val(): + ri.cw.p(f"unsigned int n_{self.c_name};") + member = self._complex_member_type(ri) + if member: + ptr = '*' if self.is_multi_val() else '' + if self.is_recursive_for_op(ri): + ptr = '*' + ri.cw.p(f"{member} {ptr}{self.c_name};") + return + members = self.arg_member(ri) + for one in members: + ri.cw.p(one + ';') + + def _attr_policy(self, policy): + return '{ .type = ' + policy + ', }' + + def attr_policy(self, cw): + policy = f'NLA_{c_upper(self.type)}' + if self.attr.get('byte-order') == 'big-endian': + if self.type in {'u16', 'u32'}: + policy = f'NLA_BE{self.type[1:]}' + + spec = self._attr_policy(policy) + cw.p(f"\t[{self.enum_name}] = {spec},") + + def _attr_typol(self): + raise Exception(f"Type policy not implemented for class type {self.type}") + + def attr_typol(self, cw): + typol = self._attr_typol() + cw.p(f'[{self.enum_name}] = {"{"} .name = "{self.name}", {typol}{"}"},') + + def _attr_put_line(self, ri, var, line): + if self.presence_type() == 'bit': + ri.cw.p(f"if ({var}->_present.{self.c_name})") + elif self.presence_type() == 'len': + ri.cw.p(f"if ({var}->_present.{self.c_name}_len)") + ri.cw.p(f"{line};") + + def _attr_put_simple(self, ri, var, put_type): + line = f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name})" + self._attr_put_line(ri, var, line) + + def attr_put(self, ri, var): + raise Exception(f"Put not implemented for class type {self.type}") + + def _attr_get(self, ri, var): + raise Exception(f"Attr get not implemented for class type {self.type}") + + def attr_get(self, ri, var, first): + lines, init_lines, local_vars = self._attr_get(ri, var) + if type(lines) is str: + lines = [lines] + if type(init_lines) is str: + init_lines = [init_lines] + + kw = 'if' if first else 'else if' + ri.cw.block_start(line=f"{kw} (type == {self.enum_name})") + if local_vars: + for local in local_vars: + ri.cw.p(local) + ri.cw.nl() + + if not self.is_multi_val(): + ri.cw.p("if (ynl_attr_validate(yarg, attr))") + ri.cw.p("return YNL_PARSE_CB_ERROR;") + if self.presence_type() == 'bit': + ri.cw.p(f"{var}->_present.{self.c_name} = 1;") + + if init_lines: + ri.cw.nl() + for line in init_lines: + ri.cw.p(line) + + for line in lines: + ri.cw.p(line) + ri.cw.block_end() + return True + + def _setter_lines(self, ri, member, presence): + raise Exception(f"Setter not implemented for class type {self.type}") + + def setter(self, ri, space, direction, deref=False, ref=None): + ref = (ref if ref else []) + [self.c_name] + var = "req" + member = f"{var}->{'.'.join(ref)}" + + code = [] + presence = '' + for i in range(0, len(ref)): + presence = f"{var}->{'.'.join(ref[:i] + [''])}_present.{ref[i]}" + # Every layer below last is a nest, so we know it uses bit presence + # last layer is "self" and may be a complex type + if i == len(ref) - 1 and self.presence_type() != 'bit': + continue + code.append(presence + ' = 1;') + code += self._setter_lines(ri, member, presence) + + func_name = f"{op_prefix(ri, direction, deref=deref)}_set_{'_'.join(ref)}" + free = bool([x for x in code if 'free(' in x]) + alloc = bool([x for x in code if 'alloc(' in x]) + if free and not alloc: + func_name = '__' + func_name + ri.cw.write_func('static inline void', func_name, body=code, + args=[f'{type_name(ri, direction, deref=deref)} *{var}'] + self.arg_member(ri)) + + +class TypeUnused(Type): + def presence_type(self): + return '' + + def arg_member(self, ri): + return [] + + def _attr_get(self, ri, var): + return ['return YNL_PARSE_CB_ERROR;'], None, None + + def _attr_typol(self): + return '.type = YNL_PT_REJECT, ' + + def attr_policy(self, cw): + pass + + def attr_put(self, ri, var): + pass + + def attr_get(self, ri, var, first): + pass + + def setter(self, ri, space, direction, deref=False, ref=None): + pass + + +class TypePad(Type): + def presence_type(self): + return '' + + def arg_member(self, ri): + return [] + + def _attr_typol(self): + return '.type = YNL_PT_IGNORE, ' + + def attr_put(self, ri, var): + pass + + def attr_get(self, ri, var, first): + pass + + def attr_policy(self, cw): + pass + + def setter(self, ri, space, direction, deref=False, ref=None): + pass + + +class TypeScalar(Type): + def __init__(self, family, attr_set, attr, value): + super().__init__(family, attr_set, attr, value) + + self.byte_order_comment = '' + if 'byte-order' in attr: + self.byte_order_comment = f" /* {attr['byte-order']} */" + + if 'enum' in self.attr: + enum = self.family.consts[self.attr['enum']] + low, high = enum.value_range() + if 'min' not in self.checks: + if low != 0 or self.type[0] == 's': + self.checks['min'] = low + if 'max' not in self.checks: + self.checks['max'] = high + + if 'min' in self.checks and 'max' in self.checks: + if self.get_limit('min') > self.get_limit('max'): + raise Exception(f'Invalid limit for "{self.name}" min: {self.get_limit("min")} max: {self.get_limit("max")}') + self.checks['range'] = True + + low = min(self.get_limit('min', 0), self.get_limit('max', 0)) + high = max(self.get_limit('min', 0), self.get_limit('max', 0)) + if low < 0 and self.type[0] == 'u': + raise Exception(f'Invalid limit for "{self.name}" negative limit for unsigned type') + if low < -32768 or high > 32767: + self.checks['full-range'] = True + + # Added by resolve(): + self.is_bitfield = None + delattr(self, "is_bitfield") + self.type_name = None + delattr(self, "type_name") + + def resolve(self): + self.resolve_up(super()) + + if 'enum-as-flags' in self.attr and self.attr['enum-as-flags']: + self.is_bitfield = True + elif 'enum' in self.attr: + self.is_bitfield = self.family.consts[self.attr['enum']]['type'] == 'flags' + else: + self.is_bitfield = False + + if not self.is_bitfield and 'enum' in self.attr: + self.type_name = self.family.consts[self.attr['enum']].user_type + elif self.is_auto_scalar: + self.type_name = '__' + self.type[0] + '64' + else: + self.type_name = '__' + self.type + + def _attr_policy(self, policy): + if 'flags-mask' in self.checks or self.is_bitfield: + if self.is_bitfield: + enum = self.family.consts[self.attr['enum']] + mask = enum.get_mask(as_flags=True) + else: + flags = self.family.consts[self.checks['flags-mask']] + flag_cnt = len(flags['entries']) + mask = (1 << flag_cnt) - 1 + return f"NLA_POLICY_MASK({policy}, 0x{mask:x})" + elif 'full-range' in self.checks: + return f"NLA_POLICY_FULL_RANGE({policy}, &{c_lower(self.enum_name)}_range)" + elif 'range' in self.checks: + return f"NLA_POLICY_RANGE({policy}, {self.get_limit_str('min')}, {self.get_limit_str('max')})" + elif 'min' in self.checks: + return f"NLA_POLICY_MIN({policy}, {self.get_limit_str('min')})" + elif 'max' in self.checks: + return f"NLA_POLICY_MAX({policy}, {self.get_limit_str('max')})" + return super()._attr_policy(policy) + + def _attr_typol(self): + return f'.type = YNL_PT_U{c_upper(self.type[1:])}, ' + + def arg_member(self, ri): + return [f'{self.type_name} {self.c_name}{self.byte_order_comment}'] + + def attr_put(self, ri, var): + self._attr_put_simple(ri, var, self.type) + + def _attr_get(self, ri, var): + return f"{var}->{self.c_name} = ynl_attr_get_{self.type}(attr);", None, None + + def _setter_lines(self, ri, member, presence): + return [f"{member} = {self.c_name};"] + + +class TypeFlag(Type): + def arg_member(self, ri): + return [] + + def _attr_typol(self): + return '.type = YNL_PT_FLAG, ' + + def attr_put(self, ri, var): + self._attr_put_line(ri, var, f"ynl_attr_put(nlh, {self.enum_name}, NULL, 0)") + + def _attr_get(self, ri, var): + return [], None, None + + def _setter_lines(self, ri, member, presence): + return [] + + +class TypeString(Type): + def arg_member(self, ri): + return [f"const char *{self.c_name}"] + + def presence_type(self): + return 'len' + + def struct_member(self, ri): + ri.cw.p(f"char *{self.c_name};") + + def _attr_typol(self): + return f'.type = YNL_PT_NUL_STR, ' + + def _attr_policy(self, policy): + if 'exact-len' in self.checks: + mem = 'NLA_POLICY_EXACT_LEN(' + self.get_limit_str('exact-len') + ')' + else: + mem = '{ .type = ' + policy + if 'max-len' in self.checks: + mem += ', .len = ' + self.get_limit_str('max-len') + mem += ', }' + return mem + + def attr_policy(self, cw): + if self.checks.get('unterminated-ok', False): + policy = 'NLA_STRING' + else: + policy = 'NLA_NUL_STRING' + + spec = self._attr_policy(policy) + cw.p(f"\t[{self.enum_name}] = {spec},") + + def attr_put(self, ri, var): + self._attr_put_simple(ri, var, 'str') + + def _attr_get(self, ri, var): + len_mem = var + '->_present.' + self.c_name + '_len' + return [f"{len_mem} = len;", + f"{var}->{self.c_name} = malloc(len + 1);", + f"memcpy({var}->{self.c_name}, ynl_attr_get_str(attr), len);", + f"{var}->{self.c_name}[len] = 0;"], \ + ['len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));'], \ + ['unsigned int len;'] + + def _setter_lines(self, ri, member, presence): + return [f"free({member});", + f"{presence}_len = strlen({self.c_name});", + f"{member} = malloc({presence}_len + 1);", + f'memcpy({member}, {self.c_name}, {presence}_len);', + f'{member}[{presence}_len] = 0;'] + + +class TypeBinary(Type): + def arg_member(self, ri): + return [f"const void *{self.c_name}", 'size_t len'] + + def presence_type(self): + return 'len' + + def struct_member(self, ri): + ri.cw.p(f"void *{self.c_name};") + + def _attr_typol(self): + return f'.type = YNL_PT_BINARY,' + + def _attr_policy(self, policy): + if len(self.checks) == 0: + pass + elif len(self.checks) == 1: + check_name = list(self.checks)[0] + if check_name not in {'exact-len', 'min-len', 'max-len'}: + raise Exception('Unsupported check for binary type: ' + check_name) + else: + raise Exception('More than one check for binary type not implemented, yet') + + if len(self.checks) == 0: + mem = '{ .type = NLA_BINARY, }' + elif 'exact-len' in self.checks: + mem = 'NLA_POLICY_EXACT_LEN(' + self.get_limit_str('exact-len') + ')' + elif 'min-len' in self.checks: + mem = '{ .len = ' + self.get_limit_str('min-len') + ', }' + elif 'max-len' in self.checks: + mem = 'NLA_POLICY_MAX_LEN(' + self.get_limit_str('max-len') + ')' + + return mem + + def attr_put(self, ri, var): + self._attr_put_line(ri, var, f"ynl_attr_put(nlh, {self.enum_name}, " + + f"{var}->{self.c_name}, {var}->_present.{self.c_name}_len)") + + def _attr_get(self, ri, var): + len_mem = var + '->_present.' + self.c_name + '_len' + return [f"{len_mem} = len;", + f"{var}->{self.c_name} = malloc(len);", + f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ + ['len = ynl_attr_data_len(attr);'], \ + ['unsigned int len;'] + + def _setter_lines(self, ri, member, presence): + return [f"free({member});", + f"{presence}_len = len;", + f"{member} = malloc({presence}_len);", + f'memcpy({member}, {self.c_name}, {presence}_len);'] + + +class TypeBitfield32(Type): + def _complex_member_type(self, ri): + return "struct nla_bitfield32" + + def _attr_typol(self): + return f'.type = YNL_PT_BITFIELD32, ' + + def _attr_policy(self, policy): + if not 'enum' in self.attr: + raise Exception('Enum required for bitfield32 attr') + enum = self.family.consts[self.attr['enum']] + mask = enum.get_mask(as_flags=True) + return f"NLA_POLICY_BITFIELD32({mask})" + + def attr_put(self, ri, var): + line = f"ynl_attr_put(nlh, {self.enum_name}, &{var}->{self.c_name}, sizeof(struct nla_bitfield32))" + self._attr_put_line(ri, var, line) + + def _attr_get(self, ri, var): + return f"memcpy(&{var}->{self.c_name}, ynl_attr_data(attr), sizeof(struct nla_bitfield32));", None, None + + def _setter_lines(self, ri, member, presence): + return [f"memcpy(&{member}, {self.c_name}, sizeof(struct nla_bitfield32));"] + + +class TypeNest(Type): + def is_recursive(self): + return self.family.pure_nested_structs[self.nested_attrs].recursive + + def _complex_member_type(self, ri): + return self.nested_struct_type + + def free(self, ri, var, ref): + at = '&' + if self.is_recursive_for_op(ri): + at = '' + ri.cw.p(f'if ({var}->{ref}{self.c_name})') + ri.cw.p(f'{self.nested_render_name}_free({at}{var}->{ref}{self.c_name});') + + def _attr_typol(self): + return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' + + def _attr_policy(self, policy): + return 'NLA_POLICY_NESTED(' + self.nested_render_name + '_nl_policy)' + + def attr_put(self, ri, var): + at = '' if self.is_recursive_for_op(ri) else '&' + self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " + + f"{self.enum_name}, {at}{var}->{self.c_name})") + + def _attr_get(self, ri, var): + get_lines = [f"if ({self.nested_render_name}_parse(&parg, attr))", + "return YNL_PARSE_CB_ERROR;"] + init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;", + f"parg.data = &{var}->{self.c_name};"] + return get_lines, init_lines, None + + def setter(self, ri, space, direction, deref=False, ref=None): + ref = (ref if ref else []) + [self.c_name] + + for _, attr in ri.family.pure_nested_structs[self.nested_attrs].member_list(): + if attr.is_recursive(): + continue + attr.setter(ri, self.nested_attrs, direction, deref=deref, ref=ref) + + +class TypeMultiAttr(Type): + def __init__(self, family, attr_set, attr, value, base_type): + super().__init__(family, attr_set, attr, value) + + self.base_type = base_type + + def is_multi_val(self): + return True + + def presence_type(self): + return 'count' + + def _complex_member_type(self, ri): + if 'type' not in self.attr or self.attr['type'] == 'nest': + return self.nested_struct_type + elif self.attr['type'] in scalars: + scalar_pfx = '__' if ri.ku_space == 'user' else '' + return scalar_pfx + self.attr['type'] + else: + raise Exception(f"Sub-type {self.attr['type']} not supported yet") + + def free_needs_iter(self): + return 'type' not in self.attr or self.attr['type'] == 'nest' + + def free(self, ri, var, ref): + if self.attr['type'] in scalars: + ri.cw.p(f"free({var}->{ref}{self.c_name});") + elif 'type' not in self.attr or self.attr['type'] == 'nest': + ri.cw.p(f"for (i = 0; i < {var}->{ref}n_{self.c_name}; i++)") + ri.cw.p(f'{self.nested_render_name}_free(&{var}->{ref}{self.c_name}[i]);') + ri.cw.p(f"free({var}->{ref}{self.c_name});") + else: + raise Exception(f"Free of MultiAttr sub-type {self.attr['type']} not supported yet") + + def _attr_policy(self, policy): + return self.base_type._attr_policy(policy) + + def _attr_typol(self): + return self.base_type._attr_typol() + + def _attr_get(self, ri, var): + return f'n_{self.c_name}++;', None, None + + def attr_put(self, ri, var): + if self.attr['type'] in scalars: + put_type = self.type + ri.cw.p(f"for (unsigned int i = 0; i < {var}->n_{self.c_name}; i++)") + ri.cw.p(f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name}[i]);") + elif 'type' not in self.attr or self.attr['type'] == 'nest': + ri.cw.p(f"for (unsigned int i = 0; i < {var}->n_{self.c_name}; i++)") + self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " + + f"{self.enum_name}, &{var}->{self.c_name}[i])") + else: + raise Exception(f"Put of MultiAttr sub-type {self.attr['type']} not supported yet") + + def _setter_lines(self, ri, member, presence): + # For multi-attr we have a count, not presence, hack up the presence + presence = presence[:-(len('_present.') + len(self.c_name))] + "n_" + self.c_name + return [f"free({member});", + f"{member} = {self.c_name};", + f"{presence} = n_{self.c_name};"] + + +class TypeArrayNest(Type): + def is_multi_val(self): + return True + + def presence_type(self): + return 'count' + + def _complex_member_type(self, ri): + if 'sub-type' not in self.attr or self.attr['sub-type'] == 'nest': + return self.nested_struct_type + elif self.attr['sub-type'] in scalars: + scalar_pfx = '__' if ri.ku_space == 'user' else '' + return scalar_pfx + self.attr['sub-type'] + else: + raise Exception(f"Sub-type {self.attr['sub-type']} not supported yet") + + def _attr_typol(self): + return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' + + def _attr_get(self, ri, var): + local_vars = ['const struct nlattr *attr2;'] + get_lines = [f'attr_{self.c_name} = attr;', + 'ynl_attr_for_each_nested(attr2, attr)', + f'\t{var}->n_{self.c_name}++;'] + return get_lines, None, local_vars + + +class TypeNestTypeValue(Type): + def _complex_member_type(self, ri): + return self.nested_struct_type + + def _attr_typol(self): + return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' + + def _attr_get(self, ri, var): + prev = 'attr' + tv_args = '' + get_lines = [] + local_vars = [] + init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;", + f"parg.data = &{var}->{self.c_name};"] + if 'type-value' in self.attr: + tv_names = [c_lower(x) for x in self.attr["type-value"]] + local_vars += [f'const struct nlattr *attr_{", *attr_".join(tv_names)};'] + local_vars += [f'__u32 {", ".join(tv_names)};'] + for level in self.attr["type-value"]: + level = c_lower(level) + get_lines += [f'attr_{level} = ynl_attr_data({prev});'] + get_lines += [f'{level} = ynl_attr_type(attr_{level});'] + prev = 'attr_' + level + + tv_args = f", {', '.join(tv_names)}" + + get_lines += [f"{self.nested_render_name}_parse(&parg, {prev}{tv_args});"] + return get_lines, init_lines, local_vars + + +class Struct: + def __init__(self, family, space_name, type_list=None, inherited=None): + self.family = family + self.space_name = space_name + self.attr_set = family.attr_sets[space_name] + # Use list to catch comparisons with empty sets + self._inherited = inherited if inherited is not None else [] + self.inherited = [] + + self.nested = type_list is None + if family.name == c_lower(space_name): + self.render_name = c_lower(family.ident_name) + else: + self.render_name = c_lower(family.ident_name + '-' + space_name) + self.struct_name = 'struct ' + self.render_name + if self.nested and space_name in family.consts: + self.struct_name += '_' + self.ptr_name = self.struct_name + ' *' + # All attr sets this one contains, directly or multiple levels down + self.child_nests = set() + + self.request = False + self.reply = False + self.recursive = False + + self.attr_list = [] + self.attrs = dict() + if type_list is not None: + for t in type_list: + self.attr_list.append((t, self.attr_set[t]),) + else: + for t in self.attr_set: + self.attr_list.append((t, self.attr_set[t]),) + + max_val = 0 + self.attr_max_val = None + for name, attr in self.attr_list: + if attr.value >= max_val: + max_val = attr.value + self.attr_max_val = attr + self.attrs[name] = attr + + def __iter__(self): + yield from self.attrs + + def __getitem__(self, key): + return self.attrs[key] + + def member_list(self): + return self.attr_list + + def set_inherited(self, new_inherited): + if self._inherited != new_inherited: + raise Exception("Inheriting different members not supported") + self.inherited = [c_lower(x) for x in sorted(self._inherited)] + + +class EnumEntry(SpecEnumEntry): + def __init__(self, enum_set, yaml, prev, value_start): + super().__init__(enum_set, yaml, prev, value_start) + + if prev: + self.value_change = (self.value != prev.value + 1) + else: + self.value_change = (self.value != 0) + self.value_change = self.value_change or self.enum_set['type'] == 'flags' + + # Added by resolve: + self.c_name = None + delattr(self, "c_name") + + def resolve(self): + self.resolve_up(super()) + + self.c_name = c_upper(self.enum_set.value_pfx + self.name) + + +class EnumSet(SpecEnumSet): + def __init__(self, family, yaml): + self.render_name = c_lower(family.ident_name + '-' + yaml['name']) + + if 'enum-name' in yaml: + if yaml['enum-name']: + self.enum_name = 'enum ' + c_lower(yaml['enum-name']) + self.user_type = self.enum_name + else: + self.enum_name = None + else: + self.enum_name = 'enum ' + self.render_name + + if self.enum_name: + self.user_type = self.enum_name + else: + self.user_type = 'int' + + self.value_pfx = yaml.get('name-prefix', f"{family.ident_name}-{yaml['name']}-") + self.header = yaml.get('header', None) + self.enum_cnt_name = yaml.get('enum-cnt-name', None) + + super().__init__(family, yaml) + + def new_entry(self, entry, prev_entry, value_start): + return EnumEntry(self, entry, prev_entry, value_start) + + def value_range(self): + low = min([x.value for x in self.entries.values()]) + high = max([x.value for x in self.entries.values()]) + + if high - low + 1 != len(self.entries): + raise Exception("Can't get value range for a noncontiguous enum") + + return low, high + + +class AttrSet(SpecAttrSet): + def __init__(self, family, yaml): + super().__init__(family, yaml) + + if self.subset_of is None: + if 'name-prefix' in yaml: + pfx = yaml['name-prefix'] + elif self.name == family.name: + pfx = family.ident_name + '-a-' + else: + pfx = f"{family.ident_name}-a-{self.name}-" + self.name_prefix = c_upper(pfx) + self.max_name = c_upper(self.yaml.get('attr-max-name', f"{self.name_prefix}max")) + self.cnt_name = c_upper(self.yaml.get('attr-cnt-name', f"__{self.name_prefix}max")) + else: + self.name_prefix = family.attr_sets[self.subset_of].name_prefix + self.max_name = family.attr_sets[self.subset_of].max_name + self.cnt_name = family.attr_sets[self.subset_of].cnt_name + + # Added by resolve: + self.c_name = None + delattr(self, "c_name") + + def resolve(self): + self.c_name = c_lower(self.name) + if self.c_name in _C_KW: + self.c_name += '_' + if self.c_name == self.family.c_name: + self.c_name = '' + + def new_attr(self, elem, value): + if elem['type'] in scalars: + t = TypeScalar(self.family, self, elem, value) + elif elem['type'] == 'unused': + t = TypeUnused(self.family, self, elem, value) + elif elem['type'] == 'pad': + t = TypePad(self.family, self, elem, value) + elif elem['type'] == 'flag': + t = TypeFlag(self.family, self, elem, value) + elif elem['type'] == 'string': + t = TypeString(self.family, self, elem, value) + elif elem['type'] == 'binary': + t = TypeBinary(self.family, self, elem, value) + elif elem['type'] == 'bitfield32': + t = TypeBitfield32(self.family, self, elem, value) + elif elem['type'] == 'nest': + t = TypeNest(self.family, self, elem, value) + elif elem['type'] == 'indexed-array' and 'sub-type' in elem: + if elem["sub-type"] == 'nest': + t = TypeArrayNest(self.family, self, elem, value) + else: + raise Exception(f'new_attr: unsupported sub-type {elem["sub-type"]}') + elif elem['type'] == 'nest-type-value': + t = TypeNestTypeValue(self.family, self, elem, value) + else: + raise Exception(f"No typed class for type {elem['type']}") + + if 'multi-attr' in elem and elem['multi-attr']: + t = TypeMultiAttr(self.family, self, elem, value, t) + + return t + + +class Operation(SpecOperation): + def __init__(self, family, yaml, req_value, rsp_value): + super().__init__(family, yaml, req_value, rsp_value) + + self.render_name = c_lower(family.ident_name + '_' + self.name) + + self.dual_policy = ('do' in yaml and 'request' in yaml['do']) and \ + ('dump' in yaml and 'request' in yaml['dump']) + + self.has_ntf = False + + # Added by resolve: + self.enum_name = None + delattr(self, "enum_name") + + def resolve(self): + self.resolve_up(super()) + + if not self.is_async: + self.enum_name = self.family.op_prefix + c_upper(self.name) + else: + self.enum_name = self.family.async_op_prefix + c_upper(self.name) + + def mark_has_ntf(self): + self.has_ntf = True + + +class Family(SpecFamily): + def __init__(self, file_name, exclude_ops): + # Added by resolve: + self.c_name = None + delattr(self, "c_name") + self.op_prefix = None + delattr(self, "op_prefix") + self.async_op_prefix = None + delattr(self, "async_op_prefix") + self.mcgrps = None + delattr(self, "mcgrps") + self.consts = None + delattr(self, "consts") + self.hooks = None + delattr(self, "hooks") + + super().__init__(file_name, exclude_ops=exclude_ops) + + self.fam_key = c_upper(self.yaml.get('c-family-name', self.yaml["name"] + '_FAMILY_NAME')) + self.ver_key = c_upper(self.yaml.get('c-version-name', self.yaml["name"] + '_FAMILY_VERSION')) + + if 'definitions' not in self.yaml: + self.yaml['definitions'] = [] + + if 'uapi-header' in self.yaml: + self.uapi_header = self.yaml['uapi-header'] + else: + self.uapi_header = f"linux/{self.ident_name}.h" + if self.uapi_header.startswith("linux/") and self.uapi_header.endswith('.h'): + self.uapi_header_name = self.uapi_header[6:-2] + else: + self.uapi_header_name = self.ident_name + + def resolve(self): + self.resolve_up(super()) + + if self.yaml.get('protocol', 'genetlink') not in {'genetlink', 'genetlink-c', 'genetlink-legacy'}: + raise Exception("Codegen only supported for genetlink") + + self.c_name = c_lower(self.ident_name) + if 'name-prefix' in self.yaml['operations']: + self.op_prefix = c_upper(self.yaml['operations']['name-prefix']) + else: + self.op_prefix = c_upper(self.yaml['name'] + '-cmd-') + if 'async-prefix' in self.yaml['operations']: + self.async_op_prefix = c_upper(self.yaml['operations']['async-prefix']) + else: + self.async_op_prefix = self.op_prefix + + self.mcgrps = self.yaml.get('mcast-groups', {'list': []}) + + self.hooks = dict() + for when in ['pre', 'post']: + self.hooks[when] = dict() + for op_mode in ['do', 'dump']: + self.hooks[when][op_mode] = dict() + self.hooks[when][op_mode]['set'] = set() + self.hooks[when][op_mode]['list'] = [] + + # dict space-name -> 'request': set(attrs), 'reply': set(attrs) + self.root_sets = dict() + # dict space-name -> set('request', 'reply') + self.pure_nested_structs = dict() + + self._mark_notify() + self._mock_up_events() + + self._load_root_sets() + self._load_nested_sets() + self._load_attr_use() + self._load_hooks() + + self.kernel_policy = self.yaml.get('kernel-policy', 'split') + if self.kernel_policy == 'global': + self._load_global_policy() + + def new_enum(self, elem): + return EnumSet(self, elem) + + def new_attr_set(self, elem): + return AttrSet(self, elem) + + def new_operation(self, elem, req_value, rsp_value): + return Operation(self, elem, req_value, rsp_value) + + def _mark_notify(self): + for op in self.msgs.values(): + if 'notify' in op: + self.ops[op['notify']].mark_has_ntf() + + # Fake a 'do' equivalent of all events, so that we can render their response parsing + def _mock_up_events(self): + for op in self.yaml['operations']['list']: + if 'event' in op: + op['do'] = { + 'reply': { + 'attributes': op['event']['attributes'] + } + } + + def _load_root_sets(self): + for op_name, op in self.msgs.items(): + if 'attribute-set' not in op: + continue + + req_attrs = set() + rsp_attrs = set() + for op_mode in ['do', 'dump']: + if op_mode in op and 'request' in op[op_mode]: + req_attrs.update(set(op[op_mode]['request']['attributes'])) + if op_mode in op and 'reply' in op[op_mode]: + rsp_attrs.update(set(op[op_mode]['reply']['attributes'])) + if 'event' in op: + rsp_attrs.update(set(op['event']['attributes'])) + + if op['attribute-set'] not in self.root_sets: + self.root_sets[op['attribute-set']] = {'request': req_attrs, 'reply': rsp_attrs} + else: + self.root_sets[op['attribute-set']]['request'].update(req_attrs) + self.root_sets[op['attribute-set']]['reply'].update(rsp_attrs) + + def _sort_pure_types(self): + # Try to reorder according to dependencies + pns_key_list = list(self.pure_nested_structs.keys()) + pns_key_seen = set() + rounds = len(pns_key_list) ** 2 # it's basically bubble sort + for _ in range(rounds): + if len(pns_key_list) == 0: + break + name = pns_key_list.pop(0) + finished = True + for _, spec in self.attr_sets[name].items(): + if 'nested-attributes' in spec: + nested = spec['nested-attributes'] + # If the unknown nest we hit is recursive it's fine, it'll be a pointer + if self.pure_nested_structs[nested].recursive: + continue + if nested not in pns_key_seen: + # Dicts are sorted, this will make struct last + struct = self.pure_nested_structs.pop(name) + self.pure_nested_structs[name] = struct + finished = False + break + if finished: + pns_key_seen.add(name) + else: + pns_key_list.append(name) + + def _load_nested_sets(self): + attr_set_queue = list(self.root_sets.keys()) + attr_set_seen = set(self.root_sets.keys()) + + while len(attr_set_queue): + a_set = attr_set_queue.pop(0) + for attr, spec in self.attr_sets[a_set].items(): + if 'nested-attributes' not in spec: + continue + + nested = spec['nested-attributes'] + if nested not in attr_set_seen: + attr_set_queue.append(nested) + attr_set_seen.add(nested) + + inherit = set() + if nested not in self.root_sets: + if nested not in self.pure_nested_structs: + self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit) + else: + raise Exception(f'Using attr set as root and nested not supported - {nested}') + + if 'type-value' in spec: + if nested in self.root_sets: + raise Exception("Inheriting members to a space used as root not supported") + inherit.update(set(spec['type-value'])) + elif spec['type'] == 'indexed-array': + inherit.add('idx') + self.pure_nested_structs[nested].set_inherited(inherit) + + for root_set, rs_members in self.root_sets.items(): + for attr, spec in self.attr_sets[root_set].items(): + if 'nested-attributes' in spec: + nested = spec['nested-attributes'] + if attr in rs_members['request']: + self.pure_nested_structs[nested].request = True + if attr in rs_members['reply']: + self.pure_nested_structs[nested].reply = True + + self._sort_pure_types() + + # Propagate the request / reply / recursive + for attr_set, struct in reversed(self.pure_nested_structs.items()): + for _, spec in self.attr_sets[attr_set].items(): + if 'nested-attributes' in spec: + child_name = spec['nested-attributes'] + struct.child_nests.add(child_name) + child = self.pure_nested_structs.get(child_name) + if child: + if not child.recursive: + struct.child_nests.update(child.child_nests) + child.request |= struct.request + child.reply |= struct.reply + if attr_set in struct.child_nests: + struct.recursive = True + + self._sort_pure_types() + + def _load_attr_use(self): + for _, struct in self.pure_nested_structs.items(): + if struct.request: + for _, arg in struct.member_list(): + arg.set_request() + if struct.reply: + for _, arg in struct.member_list(): + arg.set_reply() + + for root_set, rs_members in self.root_sets.items(): + for attr, spec in self.attr_sets[root_set].items(): + if attr in rs_members['request']: + spec.set_request() + if attr in rs_members['reply']: + spec.set_reply() + + def _load_global_policy(self): + global_set = set() + attr_set_name = None + for op_name, op in self.ops.items(): + if not op: + continue + if 'attribute-set' not in op: + continue + + if attr_set_name is None: + attr_set_name = op['attribute-set'] + if attr_set_name != op['attribute-set']: + raise Exception('For a global policy all ops must use the same set') + + for op_mode in ['do', 'dump']: + if op_mode in op: + req = op[op_mode].get('request') + if req: + global_set.update(req.get('attributes', [])) + + self.global_policy = [] + self.global_policy_set = attr_set_name + for attr in self.attr_sets[attr_set_name]: + if attr in global_set: + self.global_policy.append(attr) + + def _load_hooks(self): + for op in self.ops.values(): + for op_mode in ['do', 'dump']: + if op_mode not in op: + continue + for when in ['pre', 'post']: + if when not in op[op_mode]: + continue + name = op[op_mode][when] + if name in self.hooks[when][op_mode]['set']: + continue + self.hooks[when][op_mode]['set'].add(name) + self.hooks[when][op_mode]['list'].append(name) + + +class RenderInfo: + def __init__(self, cw, family, ku_space, op, op_mode, attr_set=None): + self.family = family + self.nl = cw.nlib + self.ku_space = ku_space + self.op_mode = op_mode + self.op = op + + self.fixed_hdr = None + if op and op.fixed_header: + self.fixed_hdr = 'struct ' + c_lower(op.fixed_header) + + # 'do' and 'dump' response parsing is identical + self.type_consistent = True + if op_mode != 'do' and 'dump' in op: + if 'do' in op: + if ('reply' in op['do']) != ('reply' in op["dump"]): + self.type_consistent = False + elif 'reply' in op['do'] and op["do"]["reply"] != op["dump"]["reply"]: + self.type_consistent = False + else: + self.type_consistent = False + + self.attr_set = attr_set + if not self.attr_set: + self.attr_set = op['attribute-set'] + + self.type_name_conflict = False + if op: + self.type_name = c_lower(op.name) + else: + self.type_name = c_lower(attr_set) + if attr_set in family.consts: + self.type_name_conflict = True + + self.cw = cw + + self.struct = dict() + if op_mode == 'notify': + op_mode = 'do' + for op_dir in ['request', 'reply']: + if op: + type_list = [] + if op_dir in op[op_mode]: + type_list = op[op_mode][op_dir]['attributes'] + self.struct[op_dir] = Struct(family, self.attr_set, type_list=type_list) + if op_mode == 'event': + self.struct['reply'] = Struct(family, self.attr_set, type_list=op['event']['attributes']) + + +class CodeWriter: + def __init__(self, nlib, out_file=None, overwrite=True): + self.nlib = nlib + self._overwrite = overwrite + + self._nl = False + self._block_end = False + self._silent_block = False + self._ind = 0 + self._ifdef_block = None + if out_file is None: + self._out = os.sys.stdout + else: + self._out = tempfile.NamedTemporaryFile('w+') + self._out_file = out_file + + def __del__(self): + self.close_out_file() + + def close_out_file(self): + if self._out == os.sys.stdout: + return + # Avoid modifying the file if contents didn't change + self._out.flush() + if not self._overwrite and os.path.isfile(self._out_file): + if filecmp.cmp(self._out.name, self._out_file, shallow=False): + return + with open(self._out_file, 'w+') as out_file: + self._out.seek(0) + shutil.copyfileobj(self._out, out_file) + self._out.close() + self._out = os.sys.stdout + + @classmethod + def _is_cond(cls, line): + return line.startswith('if') or line.startswith('while') or line.startswith('for') + + def p(self, line, add_ind=0): + if self._block_end: + self._block_end = False + if line.startswith('else'): + line = '} ' + line + else: + self._out.write('\t' * self._ind + '}\n') + + if self._nl: + self._out.write('\n') + self._nl = False + + ind = self._ind + if line[-1] == ':': + ind -= 1 + if self._silent_block: + ind += 1 + self._silent_block = line.endswith(')') and CodeWriter._is_cond(line) + if line[0] == '#': + ind = 0 + if add_ind: + ind += add_ind + self._out.write('\t' * ind + line + '\n') + + def nl(self): + self._nl = True + + def block_start(self, line=''): + if line: + line = line + ' ' + self.p(line + '{') + self._ind += 1 + + def block_end(self, line=''): + if line and line[0] not in {';', ','}: + line = ' ' + line + self._ind -= 1 + self._nl = False + if not line: + # Delay printing closing bracket in case "else" comes next + if self._block_end: + self._out.write('\t' * (self._ind + 1) + '}\n') + self._block_end = True + else: + self.p('}' + line) + + def write_doc_line(self, doc, indent=True): + words = doc.split() + line = ' *' + for word in words: + if len(line) + len(word) >= 79: + self.p(line) + line = ' *' + if indent: + line += ' ' + line += ' ' + word + self.p(line) + + def write_func_prot(self, qual_ret, name, args=None, doc=None, suffix=''): + if not args: + args = ['void'] + + if doc: + self.p('/*') + self.p(' * ' + doc) + self.p(' */') + + oneline = qual_ret + if qual_ret[-1] != '*': + oneline += ' ' + oneline += f"{name}({', '.join(args)}){suffix}" + + if len(oneline) < 80: + self.p(oneline) + return + + v = qual_ret + if len(v) > 3: + self.p(v) + v = '' + elif qual_ret[-1] != '*': + v += ' ' + v += name + '(' + ind = '\t' * (len(v) // 8) + ' ' * (len(v) % 8) + delta_ind = len(v) - len(ind) + v += args[0] + i = 1 + while i < len(args): + next_len = len(v) + len(args[i]) + if v[0] == '\t': + next_len += delta_ind + if next_len > 76: + self.p(v + ',') + v = ind + else: + v += ', ' + v += args[i] + i += 1 + self.p(v + ')' + suffix) + + def write_func_lvar(self, local_vars): + if not local_vars: + return + + if type(local_vars) is str: + local_vars = [local_vars] + + local_vars.sort(key=len, reverse=True) + for var in local_vars: + self.p(var) + self.nl() + + def write_func(self, qual_ret, name, body, args=None, local_vars=None): + self.write_func_prot(qual_ret=qual_ret, name=name, args=args) + self.write_func_lvar(local_vars=local_vars) + + self.block_start() + for line in body: + self.p(line) + self.block_end() + + def writes_defines(self, defines): + longest = 0 + for define in defines: + if len(define[0]) > longest: + longest = len(define[0]) + longest = ((longest + 8) // 8) * 8 + for define in defines: + line = '#define ' + define[0] + line += '\t' * ((longest - len(define[0]) + 7) // 8) + if type(define[1]) is int: + line += str(define[1]) + elif type(define[1]) is str: + line += '"' + define[1] + '"' + self.p(line) + + def write_struct_init(self, members): + longest = max([len(x[0]) for x in members]) + longest += 1 # because we prepend a . + longest = ((longest + 8) // 8) * 8 + for one in members: + line = '.' + one[0] + line += '\t' * ((longest - len(one[0]) - 1 + 7) // 8) + line += '= ' + str(one[1]) + ',' + self.p(line) + + def ifdef_block(self, config): + config_option = None + if config: + config_option = 'CONFIG_' + c_upper(config) + if self._ifdef_block == config_option: + return + + if self._ifdef_block: + self.p('#endif /* ' + self._ifdef_block + ' */') + if config_option: + self.p('#ifdef ' + config_option) + self._ifdef_block = config_option + + +scalars = {'u8', 'u16', 'u32', 'u64', 's32', 's64', 'uint', 'sint'} + +direction_to_suffix = { + 'reply': '_rsp', + 'request': '_req', + '': '' +} + +op_mode_to_wrapper = { + 'do': '', + 'dump': '_list', + 'notify': '_ntf', + 'event': '', +} + +_C_KW = { + 'auto', + 'bool', + 'break', + 'case', + 'char', + 'const', + 'continue', + 'default', + 'do', + 'double', + 'else', + 'enum', + 'extern', + 'float', + 'for', + 'goto', + 'if', + 'inline', + 'int', + 'long', + 'register', + 'return', + 'short', + 'signed', + 'sizeof', + 'static', + 'struct', + 'switch', + 'typedef', + 'union', + 'unsigned', + 'void', + 'volatile', + 'while' +} + + +def rdir(direction): + if direction == 'reply': + return 'request' + if direction == 'request': + return 'reply' + return direction + + +def op_prefix(ri, direction, deref=False): + suffix = f"_{ri.type_name}" + + if not ri.op_mode or ri.op_mode == 'do': + suffix += f"{direction_to_suffix[direction]}" + else: + if direction == 'request': + suffix += '_req_dump' + else: + if ri.type_consistent: + if deref: + suffix += f"{direction_to_suffix[direction]}" + else: + suffix += op_mode_to_wrapper[ri.op_mode] + else: + suffix += '_rsp' + suffix += '_dump' if deref else '_list' + + return f"{ri.family.c_name}{suffix}" + + +def type_name(ri, direction, deref=False): + return f"struct {op_prefix(ri, direction, deref=deref)}" + + +def print_prototype(ri, direction, terminate=True, doc=None): + suffix = ';' if terminate else '' + + fname = ri.op.render_name + if ri.op_mode == 'dump': + fname += '_dump' + + args = ['struct ynl_sock *ys'] + if 'request' in ri.op[ri.op_mode]: + args.append(f"{type_name(ri, direction)} *" + f"{direction_to_suffix[direction][1:]}") + + ret = 'int' + if 'reply' in ri.op[ri.op_mode]: + ret = f"{type_name(ri, rdir(direction))} *" + + ri.cw.write_func_prot(ret, fname, args, doc=doc, suffix=suffix) + + +def print_req_prototype(ri): + print_prototype(ri, "request", doc=ri.op['doc']) + + +def print_dump_prototype(ri): + print_prototype(ri, "request") + + +def put_typol_fwd(cw, struct): + cw.p(f'extern const struct ynl_policy_nest {struct.render_name}_nest;') + + +def put_typol(cw, struct): + type_max = struct.attr_set.max_name + cw.block_start(line=f'const struct ynl_policy_attr {struct.render_name}_policy[{type_max} + 1] =') + + for _, arg in struct.member_list(): + arg.attr_typol(cw) + + cw.block_end(line=';') + cw.nl() + + cw.block_start(line=f'const struct ynl_policy_nest {struct.render_name}_nest =') + cw.p(f'.max_attr = {type_max},') + cw.p(f'.table = {struct.render_name}_policy,') + cw.block_end(line=';') + cw.nl() + + +def _put_enum_to_str_helper(cw, render_name, map_name, arg_name, enum=None): + args = [f'int {arg_name}'] + if enum: + args = [enum.user_type + ' ' + arg_name] + cw.write_func_prot('const char *', f'{render_name}_str', args) + cw.block_start() + if enum and enum.type == 'flags': + cw.p(f'{arg_name} = ffs({arg_name}) - 1;') + cw.p(f'if ({arg_name} < 0 || {arg_name} >= (int)YNL_ARRAY_SIZE({map_name}))') + cw.p('return NULL;') + cw.p(f'return {map_name}[{arg_name}];') + cw.block_end() + cw.nl() + + +def put_op_name_fwd(family, cw): + cw.write_func_prot('const char *', f'{family.c_name}_op_str', ['int op'], suffix=';') + + +def put_op_name(family, cw): + map_name = f'{family.c_name}_op_strmap' + cw.block_start(line=f"static const char * const {map_name}[] =") + for op_name, op in family.msgs.items(): + if op.rsp_value: + # Make sure we don't add duplicated entries, if multiple commands + # produce the same response in legacy families. + if family.rsp_by_value[op.rsp_value] != op: + cw.p(f'// skip "{op_name}", duplicate reply value') + continue + + if op.req_value == op.rsp_value: + cw.p(f'[{op.enum_name}] = "{op_name}",') + else: + cw.p(f'[{op.rsp_value}] = "{op_name}",') + cw.block_end(line=';') + cw.nl() + + _put_enum_to_str_helper(cw, family.c_name + '_op', map_name, 'op') + + +def put_enum_to_str_fwd(family, cw, enum): + args = [enum.user_type + ' value'] + cw.write_func_prot('const char *', f'{enum.render_name}_str', args, suffix=';') + + +def put_enum_to_str(family, cw, enum): + map_name = f'{enum.render_name}_strmap' + cw.block_start(line=f"static const char * const {map_name}[] =") + for entry in enum.entries.values(): + cw.p(f'[{entry.value}] = "{entry.name}",') + cw.block_end(line=';') + cw.nl() + + _put_enum_to_str_helper(cw, enum.render_name, map_name, 'value', enum=enum) + + +def put_req_nested_prototype(ri, struct, suffix=';'): + func_args = ['struct nlmsghdr *nlh', + 'unsigned int attr_type', + f'{struct.ptr_name}obj'] + + ri.cw.write_func_prot('int', f'{struct.render_name}_put', func_args, + suffix=suffix) + + +def put_req_nested(ri, struct): + put_req_nested_prototype(ri, struct, suffix='') + ri.cw.block_start() + ri.cw.write_func_lvar('struct nlattr *nest;') + + ri.cw.p("nest = ynl_attr_nest_start(nlh, attr_type);") + + for _, arg in struct.member_list(): + arg.attr_put(ri, "obj") + + ri.cw.p("ynl_attr_nest_end(nlh, nest);") + + ri.cw.nl() + ri.cw.p('return 0;') + ri.cw.block_end() + ri.cw.nl() + + +def _multi_parse(ri, struct, init_lines, local_vars): + if struct.nested: + iter_line = "ynl_attr_for_each_nested(attr, nested)" + else: + if ri.fixed_hdr: + local_vars += ['void *hdr;'] + iter_line = "ynl_attr_for_each(attr, nlh, yarg->ys->family->hdr_len)" + + array_nests = set() + multi_attrs = set() + needs_parg = False + for arg, aspec in struct.member_list(): + if aspec['type'] == 'indexed-array' and 'sub-type' in aspec: + if aspec["sub-type"] == 'nest': + local_vars.append(f'const struct nlattr *attr_{aspec.c_name};') + array_nests.add(arg) + else: + raise Exception(f'Not supported sub-type {aspec["sub-type"]}') + if 'multi-attr' in aspec: + multi_attrs.add(arg) + needs_parg |= 'nested-attributes' in aspec + if array_nests or multi_attrs: + local_vars.append('int i;') + if needs_parg: + local_vars.append('struct ynl_parse_arg parg;') + init_lines.append('parg.ys = yarg->ys;') + + all_multi = array_nests | multi_attrs + + for anest in sorted(all_multi): + local_vars.append(f"unsigned int n_{struct[anest].c_name} = 0;") + + ri.cw.block_start() + ri.cw.write_func_lvar(local_vars) + + for line in init_lines: + ri.cw.p(line) + ri.cw.nl() + + for arg in struct.inherited: + ri.cw.p(f'dst->{arg} = {arg};') + + if ri.fixed_hdr: + ri.cw.p('hdr = ynl_nlmsg_data_offset(nlh, sizeof(struct genlmsghdr));') + ri.cw.p(f"memcpy(&dst->_hdr, hdr, sizeof({ri.fixed_hdr}));") + for anest in sorted(all_multi): + aspec = struct[anest] + ri.cw.p(f"if (dst->{aspec.c_name})") + ri.cw.p(f'return ynl_error_parse(yarg, "attribute already present ({struct.attr_set.name}.{aspec.name})");') + + ri.cw.nl() + ri.cw.block_start(line=iter_line) + ri.cw.p('unsigned int type = ynl_attr_type(attr);') + ri.cw.nl() + + first = True + for _, arg in struct.member_list(): + good = arg.attr_get(ri, 'dst', first=first) + # First may be 'unused' or 'pad', ignore those + first &= not good + + ri.cw.block_end() + ri.cw.nl() + + for anest in sorted(array_nests): + aspec = struct[anest] + + ri.cw.block_start(line=f"if (n_{aspec.c_name})") + ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};") + ri.cw.p('i = 0;') + ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;") + ri.cw.block_start(line=f"ynl_attr_for_each_nested(attr, attr_{aspec.c_name})") + ri.cw.p(f"parg.data = &dst->{aspec.c_name}[i];") + ri.cw.p(f"if ({aspec.nested_render_name}_parse(&parg, attr, ynl_attr_type(attr)))") + ri.cw.p('return YNL_PARSE_CB_ERROR;') + ri.cw.p('i++;') + ri.cw.block_end() + ri.cw.block_end() + ri.cw.nl() + + for anest in sorted(multi_attrs): + aspec = struct[anest] + ri.cw.block_start(line=f"if (n_{aspec.c_name})") + ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") + ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};") + ri.cw.p('i = 0;') + if 'nested-attributes' in aspec: + ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;") + ri.cw.block_start(line=iter_line) + ri.cw.block_start(line=f"if (ynl_attr_type(attr) == {aspec.enum_name})") + if 'nested-attributes' in aspec: + ri.cw.p(f"parg.data = &dst->{aspec.c_name}[i];") + ri.cw.p(f"if ({aspec.nested_render_name}_parse(&parg, attr))") + ri.cw.p('return YNL_PARSE_CB_ERROR;') + elif aspec.type in scalars: + ri.cw.p(f"dst->{aspec.c_name}[i] = ynl_attr_get_{aspec.type}(attr);") + else: + raise Exception('Nest parsing type not supported yet') + ri.cw.p('i++;') + ri.cw.block_end() + ri.cw.block_end() + ri.cw.block_end() + ri.cw.nl() + + if struct.nested: + ri.cw.p('return 0;') + else: + ri.cw.p('return YNL_PARSE_CB_OK;') + ri.cw.block_end() + ri.cw.nl() + + +def parse_rsp_nested_prototype(ri, struct, suffix=';'): + func_args = ['struct ynl_parse_arg *yarg', + 'const struct nlattr *nested'] + for arg in struct.inherited: + func_args.append('__u32 ' + arg) + + ri.cw.write_func_prot('int', f'{struct.render_name}_parse', func_args, + suffix=suffix) + + +def parse_rsp_nested(ri, struct): + parse_rsp_nested_prototype(ri, struct, suffix='') + + local_vars = ['const struct nlattr *attr;', + f'{struct.ptr_name}dst = yarg->data;'] + init_lines = [] + + if struct.member_list(): + _multi_parse(ri, struct, init_lines, local_vars) + else: + # Empty nest + ri.cw.block_start() + ri.cw.p('return 0;') + ri.cw.block_end() + ri.cw.nl() + + +def parse_rsp_msg(ri, deref=False): + if 'reply' not in ri.op[ri.op_mode] and ri.op_mode != 'event': + return + + func_args = ['const struct nlmsghdr *nlh', + 'struct ynl_parse_arg *yarg'] + + local_vars = [f'{type_name(ri, "reply", deref=deref)} *dst;', + 'const struct nlattr *attr;'] + init_lines = ['dst = yarg->data;'] + + ri.cw.write_func_prot('int', f'{op_prefix(ri, "reply", deref=deref)}_parse', func_args) + + if ri.struct["reply"].member_list(): + _multi_parse(ri, ri.struct["reply"], init_lines, local_vars) + else: + # Empty reply + ri.cw.block_start() + ri.cw.p('return YNL_PARSE_CB_OK;') + ri.cw.block_end() + ri.cw.nl() + + +def print_req(ri): + ret_ok = '0' + ret_err = '-1' + direction = "request" + local_vars = ['struct ynl_req_state yrs = { .yarg = { .ys = ys, }, };', + 'struct nlmsghdr *nlh;', + 'int err;'] + + if 'reply' in ri.op[ri.op_mode]: + ret_ok = 'rsp' + ret_err = 'NULL' + local_vars += [f'{type_name(ri, rdir(direction))} *rsp;'] + + if ri.fixed_hdr: + local_vars += ['size_t hdr_len;', + 'void *hdr;'] + + print_prototype(ri, direction, terminate=False) + ri.cw.block_start() + ri.cw.write_func_lvar(local_vars) + + ri.cw.p(f"nlh = ynl_gemsg_start_req(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);") + + ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;") + if 'reply' in ri.op[ri.op_mode]: + ri.cw.p(f"yrs.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;") + ri.cw.nl() + + if ri.fixed_hdr: + ri.cw.p("hdr_len = sizeof(req->_hdr);") + ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);") + ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);") + ri.cw.nl() + + for _, attr in ri.struct["request"].member_list(): + attr.attr_put(ri, "req") + ri.cw.nl() + + if 'reply' in ri.op[ri.op_mode]: + ri.cw.p('rsp = calloc(1, sizeof(*rsp));') + ri.cw.p('yrs.yarg.data = rsp;') + ri.cw.p(f"yrs.cb = {op_prefix(ri, 'reply')}_parse;") + if ri.op.value is not None: + ri.cw.p(f'yrs.rsp_cmd = {ri.op.enum_name};') + else: + ri.cw.p(f'yrs.rsp_cmd = {ri.op.rsp_value};') + ri.cw.nl() + ri.cw.p("err = ynl_exec(ys, nlh, &yrs);") + ri.cw.p('if (err < 0)') + if 'reply' in ri.op[ri.op_mode]: + ri.cw.p('goto err_free;') + else: + ri.cw.p('return -1;') + ri.cw.nl() + + ri.cw.p(f"return {ret_ok};") + ri.cw.nl() + + if 'reply' in ri.op[ri.op_mode]: + ri.cw.p('err_free:') + ri.cw.p(f"{call_free(ri, rdir(direction), 'rsp')}") + ri.cw.p(f"return {ret_err};") + + ri.cw.block_end() + + +def print_dump(ri): + direction = "request" + print_prototype(ri, direction, terminate=False) + ri.cw.block_start() + local_vars = ['struct ynl_dump_state yds = {};', + 'struct nlmsghdr *nlh;', + 'int err;'] + + if ri.fixed_hdr: + local_vars += ['size_t hdr_len;', + 'void *hdr;'] + + ri.cw.write_func_lvar(local_vars) + + ri.cw.p('yds.yarg.ys = ys;') + ri.cw.p(f"yds.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;") + ri.cw.p("yds.yarg.data = NULL;") + ri.cw.p(f"yds.alloc_sz = sizeof({type_name(ri, rdir(direction))});") + ri.cw.p(f"yds.cb = {op_prefix(ri, 'reply', deref=True)}_parse;") + if ri.op.value is not None: + ri.cw.p(f'yds.rsp_cmd = {ri.op.enum_name};') + else: + ri.cw.p(f'yds.rsp_cmd = {ri.op.rsp_value};') + ri.cw.nl() + ri.cw.p(f"nlh = ynl_gemsg_start_dump(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);") + + if ri.fixed_hdr: + ri.cw.p("hdr_len = sizeof(req->_hdr);") + ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);") + ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);") + ri.cw.nl() + + if "request" in ri.op[ri.op_mode]: + ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;") + ri.cw.nl() + for _, attr in ri.struct["request"].member_list(): + attr.attr_put(ri, "req") + ri.cw.nl() + + ri.cw.p('err = ynl_exec_dump(ys, nlh, &yds);') + ri.cw.p('if (err < 0)') + ri.cw.p('goto free_list;') + ri.cw.nl() + + ri.cw.p('return yds.first;') + ri.cw.nl() + ri.cw.p('free_list:') + ri.cw.p(call_free(ri, rdir(direction), 'yds.first')) + ri.cw.p('return NULL;') + ri.cw.block_end() + + +def call_free(ri, direction, var): + return f"{op_prefix(ri, direction)}_free({var});" + + +def free_arg_name(direction): + if direction: + return direction_to_suffix[direction][1:] + return 'obj' + + +def print_alloc_wrapper(ri, direction): + name = op_prefix(ri, direction) + ri.cw.write_func_prot(f'static inline struct {name} *', f"{name}_alloc", [f"void"]) + ri.cw.block_start() + ri.cw.p(f'return calloc(1, sizeof(struct {name}));') + ri.cw.block_end() + + +def print_free_prototype(ri, direction, suffix=';'): + name = op_prefix(ri, direction) + struct_name = name + if ri.type_name_conflict: + struct_name += '_' + arg = free_arg_name(direction) + ri.cw.write_func_prot('void', f"{name}_free", [f"struct {struct_name} *{arg}"], suffix=suffix) + + +def _print_type(ri, direction, struct): + suffix = f'_{ri.type_name}{direction_to_suffix[direction]}' + if not direction and ri.type_name_conflict: + suffix += '_' + + if ri.op_mode == 'dump': + suffix += '_dump' + + ri.cw.block_start(line=f"struct {ri.family.c_name}{suffix}") + + if ri.fixed_hdr: + ri.cw.p(ri.fixed_hdr + ' _hdr;') + ri.cw.nl() + + meta_started = False + for _, attr in struct.member_list(): + for type_filter in ['len', 'bit']: + line = attr.presence_member(ri.ku_space, type_filter) + if line: + if not meta_started: + ri.cw.block_start(line=f"struct") + meta_started = True + ri.cw.p(line) + if meta_started: + ri.cw.block_end(line='_present;') + ri.cw.nl() + + for arg in struct.inherited: + ri.cw.p(f"__u32 {arg};") + + for _, attr in struct.member_list(): + attr.struct_member(ri) + + ri.cw.block_end(line=';') + ri.cw.nl() + + +def print_type(ri, direction): + _print_type(ri, direction, ri.struct[direction]) + + +def print_type_full(ri, struct): + _print_type(ri, "", struct) + + +def print_type_helpers(ri, direction, deref=False): + print_free_prototype(ri, direction) + ri.cw.nl() + + if ri.ku_space == 'user' and direction == 'request': + for _, attr in ri.struct[direction].member_list(): + attr.setter(ri, ri.attr_set, direction, deref=deref) + ri.cw.nl() + + +def print_req_type_helpers(ri): + if len(ri.struct["request"].attr_list) == 0: + return + print_alloc_wrapper(ri, "request") + print_type_helpers(ri, "request") + + +def print_rsp_type_helpers(ri): + if 'reply' not in ri.op[ri.op_mode]: + return + print_type_helpers(ri, "reply") + + +def print_parse_prototype(ri, direction, terminate=True): + suffix = "_rsp" if direction == "reply" else "_req" + term = ';' if terminate else '' + + ri.cw.write_func_prot('void', f"{ri.op.render_name}{suffix}_parse", + ['const struct nlattr **tb', + f"struct {ri.op.render_name}{suffix} *req"], + suffix=term) + + +def print_req_type(ri): + if len(ri.struct["request"].attr_list) == 0: + return + print_type(ri, "request") + + +def print_req_free(ri): + if 'request' not in ri.op[ri.op_mode]: + return + _free_type(ri, 'request', ri.struct['request']) + + +def print_rsp_type(ri): + if (ri.op_mode == 'do' or ri.op_mode == 'dump') and 'reply' in ri.op[ri.op_mode]: + direction = 'reply' + elif ri.op_mode == 'event': + direction = 'reply' + else: + return + print_type(ri, direction) + + +def print_wrapped_type(ri): + ri.cw.block_start(line=f"{type_name(ri, 'reply')}") + if ri.op_mode == 'dump': + ri.cw.p(f"{type_name(ri, 'reply')} *next;") + elif ri.op_mode == 'notify' or ri.op_mode == 'event': + ri.cw.p('__u16 family;') + ri.cw.p('__u8 cmd;') + ri.cw.p('struct ynl_ntf_base_type *next;') + ri.cw.p(f"void (*free)({type_name(ri, 'reply')} *ntf);") + ri.cw.p(f"{type_name(ri, 'reply', deref=True)} obj __attribute__((aligned(8)));") + ri.cw.block_end(line=';') + ri.cw.nl() + print_free_prototype(ri, 'reply') + ri.cw.nl() + + +def _free_type_members_iter(ri, struct): + for _, attr in struct.member_list(): + if attr.free_needs_iter(): + ri.cw.p('unsigned int i;') + ri.cw.nl() + break + + +def _free_type_members(ri, var, struct, ref=''): + for _, attr in struct.member_list(): + attr.free(ri, var, ref) + + +def _free_type(ri, direction, struct): + var = free_arg_name(direction) + + print_free_prototype(ri, direction, suffix='') + ri.cw.block_start() + _free_type_members_iter(ri, struct) + _free_type_members(ri, var, struct) + if direction: + ri.cw.p(f'free({var});') + ri.cw.block_end() + ri.cw.nl() + + +def free_rsp_nested_prototype(ri): + print_free_prototype(ri, "") + + +def free_rsp_nested(ri, struct): + _free_type(ri, "", struct) + + +def print_rsp_free(ri): + if 'reply' not in ri.op[ri.op_mode]: + return + _free_type(ri, 'reply', ri.struct['reply']) + + +def print_dump_type_free(ri): + sub_type = type_name(ri, 'reply') + + print_free_prototype(ri, 'reply', suffix='') + ri.cw.block_start() + ri.cw.p(f"{sub_type} *next = rsp;") + ri.cw.nl() + ri.cw.block_start(line='while ((void *)next != YNL_LIST_END)') + _free_type_members_iter(ri, ri.struct['reply']) + ri.cw.p('rsp = next;') + ri.cw.p('next = rsp->next;') + ri.cw.nl() + + _free_type_members(ri, 'rsp', ri.struct['reply'], ref='obj.') + ri.cw.p(f'free(rsp);') + ri.cw.block_end() + ri.cw.block_end() + ri.cw.nl() + + +def print_ntf_type_free(ri): + print_free_prototype(ri, 'reply', suffix='') + ri.cw.block_start() + _free_type_members_iter(ri, ri.struct['reply']) + _free_type_members(ri, 'rsp', ri.struct['reply'], ref='obj.') + ri.cw.p(f'free(rsp);') + ri.cw.block_end() + ri.cw.nl() + + +def print_req_policy_fwd(cw, struct, ri=None, terminate=True): + if terminate and ri and policy_should_be_static(struct.family): + return + + if terminate: + prefix = 'extern ' + else: + if ri and policy_should_be_static(struct.family): + prefix = 'static ' + else: + prefix = '' + + suffix = ';' if terminate else ' = {' + + max_attr = struct.attr_max_val + if ri: + name = ri.op.render_name + if ri.op.dual_policy: + name += '_' + ri.op_mode + else: + name = struct.render_name + cw.p(f"{prefix}const struct nla_policy {name}_nl_policy[{max_attr.enum_name} + 1]{suffix}") + + +def print_req_policy(cw, struct, ri=None): + if ri and ri.op: + cw.ifdef_block(ri.op.get('config-cond', None)) + print_req_policy_fwd(cw, struct, ri=ri, terminate=False) + for _, arg in struct.member_list(): + arg.attr_policy(cw) + cw.p("};") + cw.ifdef_block(None) + cw.nl() + + +def kernel_can_gen_family_struct(family): + return family.proto == 'genetlink' + + +def policy_should_be_static(family): + return family.kernel_policy == 'split' or kernel_can_gen_family_struct(family) + + +def print_kernel_policy_ranges(family, cw): + first = True + for _, attr_set in family.attr_sets.items(): + if attr_set.subset_of: + continue + + for _, attr in attr_set.items(): + if not attr.request: + continue + if 'full-range' not in attr.checks: + continue + + if first: + cw.p('/* Integer value ranges */') + first = False + + sign = '' if attr.type[0] == 'u' else '_signed' + suffix = 'ULL' if attr.type[0] == 'u' else 'LL' + cw.block_start(line=f'static const struct netlink_range_validation{sign} {c_lower(attr.enum_name)}_range =') + members = [] + if 'min' in attr.checks: + members.append(('min', attr.get_limit_str('min', suffix=suffix))) + if 'max' in attr.checks: + members.append(('max', attr.get_limit_str('max', suffix=suffix))) + cw.write_struct_init(members) + cw.block_end(line=';') + cw.nl() + + +def print_kernel_op_table_fwd(family, cw, terminate): + exported = not kernel_can_gen_family_struct(family) + + if not terminate or exported: + cw.p(f"/* Ops table for {family.ident_name} */") + + pol_to_struct = {'global': 'genl_small_ops', + 'per-op': 'genl_ops', + 'split': 'genl_split_ops'} + struct_type = pol_to_struct[family.kernel_policy] + + if not exported: + cnt = "" + elif family.kernel_policy == 'split': + cnt = 0 + for op in family.ops.values(): + if 'do' in op: + cnt += 1 + if 'dump' in op: + cnt += 1 + else: + cnt = len(family.ops) + + qual = 'static const' if not exported else 'const' + line = f"{qual} struct {struct_type} {family.c_name}_nl_ops[{cnt}]" + if terminate: + cw.p(f"extern {line};") + else: + cw.block_start(line=line + ' =') + + if not terminate: + return + + cw.nl() + for name in family.hooks['pre']['do']['list']: + cw.write_func_prot('int', c_lower(name), + ['const struct genl_split_ops *ops', + 'struct sk_buff *skb', 'struct genl_info *info'], suffix=';') + for name in family.hooks['post']['do']['list']: + cw.write_func_prot('void', c_lower(name), + ['const struct genl_split_ops *ops', + 'struct sk_buff *skb', 'struct genl_info *info'], suffix=';') + for name in family.hooks['pre']['dump']['list']: + cw.write_func_prot('int', c_lower(name), + ['struct netlink_callback *cb'], suffix=';') + for name in family.hooks['post']['dump']['list']: + cw.write_func_prot('int', c_lower(name), + ['struct netlink_callback *cb'], suffix=';') + + cw.nl() + + for op_name, op in family.ops.items(): + if op.is_async: + continue + + if 'do' in op: + name = c_lower(f"{family.ident_name}-nl-{op_name}-doit") + cw.write_func_prot('int', name, + ['struct sk_buff *skb', 'struct genl_info *info'], suffix=';') + + if 'dump' in op: + name = c_lower(f"{family.ident_name}-nl-{op_name}-dumpit") + cw.write_func_prot('int', name, + ['struct sk_buff *skb', 'struct netlink_callback *cb'], suffix=';') + cw.nl() + + +def print_kernel_op_table_hdr(family, cw): + print_kernel_op_table_fwd(family, cw, terminate=True) + + +def print_kernel_op_table(family, cw): + print_kernel_op_table_fwd(family, cw, terminate=False) + if family.kernel_policy == 'global' or family.kernel_policy == 'per-op': + for op_name, op in family.ops.items(): + if op.is_async: + continue + + cw.ifdef_block(op.get('config-cond', None)) + cw.block_start() + members = [('cmd', op.enum_name)] + if 'dont-validate' in op: + members.append(('validate', + ' | '.join([c_upper('genl-dont-validate-' + x) + for x in op['dont-validate']])), ) + for op_mode in ['do', 'dump']: + if op_mode in op: + name = c_lower(f"{family.ident_name}-nl-{op_name}-{op_mode}it") + members.append((op_mode + 'it', name)) + if family.kernel_policy == 'per-op': + struct = Struct(family, op['attribute-set'], + type_list=op['do']['request']['attributes']) + + name = c_lower(f"{family.ident_name}-{op_name}-nl-policy") + members.append(('policy', name)) + members.append(('maxattr', struct.attr_max_val.enum_name)) + if 'flags' in op: + members.append(('flags', ' | '.join([c_upper('genl-' + x) for x in op['flags']]))) + cw.write_struct_init(members) + cw.block_end(line=',') + elif family.kernel_policy == 'split': + cb_names = {'do': {'pre': 'pre_doit', 'post': 'post_doit'}, + 'dump': {'pre': 'start', 'post': 'done'}} + + for op_name, op in family.ops.items(): + for op_mode in ['do', 'dump']: + if op.is_async or op_mode not in op: + continue + + cw.ifdef_block(op.get('config-cond', None)) + cw.block_start() + members = [('cmd', op.enum_name)] + if 'dont-validate' in op: + dont_validate = [] + for x in op['dont-validate']: + if op_mode == 'do' and x in ['dump', 'dump-strict']: + continue + if op_mode == "dump" and x == 'strict': + continue + dont_validate.append(x) + + if dont_validate: + members.append(('validate', + ' | '.join([c_upper('genl-dont-validate-' + x) + for x in dont_validate])), ) + name = c_lower(f"{family.ident_name}-nl-{op_name}-{op_mode}it") + if 'pre' in op[op_mode]: + members.append((cb_names[op_mode]['pre'], c_lower(op[op_mode]['pre']))) + members.append((op_mode + 'it', name)) + if 'post' in op[op_mode]: + members.append((cb_names[op_mode]['post'], c_lower(op[op_mode]['post']))) + if 'request' in op[op_mode]: + struct = Struct(family, op['attribute-set'], + type_list=op[op_mode]['request']['attributes']) + + if op.dual_policy: + name = c_lower(f"{family.ident_name}-{op_name}-{op_mode}-nl-policy") + else: + name = c_lower(f"{family.ident_name}-{op_name}-nl-policy") + members.append(('policy', name)) + members.append(('maxattr', struct.attr_max_val.enum_name)) + flags = (op['flags'] if 'flags' in op else []) + ['cmd-cap-' + op_mode] + members.append(('flags', ' | '.join([c_upper('genl-' + x) for x in flags]))) + cw.write_struct_init(members) + cw.block_end(line=',') + cw.ifdef_block(None) + + cw.block_end(line=';') + cw.nl() + + +def print_kernel_mcgrp_hdr(family, cw): + if not family.mcgrps['list']: + return + + cw.block_start('enum') + for grp in family.mcgrps['list']: + grp_id = c_upper(f"{family.ident_name}-nlgrp-{grp['name']},") + cw.p(grp_id) + cw.block_end(';') + cw.nl() + + +def print_kernel_mcgrp_src(family, cw): + if not family.mcgrps['list']: + return + + cw.block_start('static const struct genl_multicast_group ' + family.c_name + '_nl_mcgrps[] =') + for grp in family.mcgrps['list']: + name = grp['name'] + grp_id = c_upper(f"{family.ident_name}-nlgrp-{name}") + cw.p('[' + grp_id + '] = { "' + name + '", },') + cw.block_end(';') + cw.nl() + + +def print_kernel_family_struct_hdr(family, cw): + if not kernel_can_gen_family_struct(family): + return + + cw.p(f"extern struct genl_family {family.c_name}_nl_family;") + cw.nl() + if 'sock-priv' in family.kernel_family: + cw.p(f'void {family.c_name}_nl_sock_priv_init({family.kernel_family["sock-priv"]} *priv);') + cw.p(f'void {family.c_name}_nl_sock_priv_destroy({family.kernel_family["sock-priv"]} *priv);') + cw.nl() + + +def print_kernel_family_struct_src(family, cw): + if not kernel_can_gen_family_struct(family): + return + + cw.block_start(f"struct genl_family {family.ident_name}_nl_family __ro_after_init =") + cw.p('.name\t\t= ' + family.fam_key + ',') + cw.p('.version\t= ' + family.ver_key + ',') + cw.p('.netnsok\t= true,') + cw.p('.parallel_ops\t= true,') + cw.p('.module\t\t= THIS_MODULE,') + if family.kernel_policy == 'per-op': + cw.p(f'.ops\t\t= {family.c_name}_nl_ops,') + cw.p(f'.n_ops\t\t= ARRAY_SIZE({family.c_name}_nl_ops),') + elif family.kernel_policy == 'split': + cw.p(f'.split_ops\t= {family.c_name}_nl_ops,') + cw.p(f'.n_split_ops\t= ARRAY_SIZE({family.c_name}_nl_ops),') + if family.mcgrps['list']: + cw.p(f'.mcgrps\t\t= {family.c_name}_nl_mcgrps,') + cw.p(f'.n_mcgrps\t= ARRAY_SIZE({family.c_name}_nl_mcgrps),') + if 'sock-priv' in family.kernel_family: + cw.p(f'.sock_priv_size\t= sizeof({family.kernel_family["sock-priv"]}),') + # Force cast here, actual helpers take pointer to the real type. + cw.p(f'.sock_priv_init\t= (void *){family.c_name}_nl_sock_priv_init,') + cw.p(f'.sock_priv_destroy = (void *){family.c_name}_nl_sock_priv_destroy,') + cw.block_end(';') + + +def uapi_enum_start(family, cw, obj, ckey='', enum_name='enum-name'): + start_line = 'enum' + if enum_name in obj: + if obj[enum_name]: + start_line = 'enum ' + c_lower(obj[enum_name]) + elif ckey and ckey in obj: + start_line = 'enum ' + family.c_name + '_' + c_lower(obj[ckey]) + cw.block_start(line=start_line) + + +def render_uapi_unified(family, cw, max_by_define, separate_ntf): + max_name = c_upper(family.get('cmd-max-name', f"{family.op_prefix}MAX")) + cnt_name = c_upper(family.get('cmd-cnt-name', f"__{family.op_prefix}MAX")) + max_value = f"({cnt_name} - 1)" + + uapi_enum_start(family, cw, family['operations'], 'enum-name') + val = 0 + for op in family.msgs.values(): + if separate_ntf and ('notify' in op or 'event' in op): + continue + + suffix = ',' + if op.value != val: + suffix = f" = {op.value}," + val = op.value + cw.p(op.enum_name + suffix) + val += 1 + cw.nl() + cw.p(cnt_name + ('' if max_by_define else ',')) + if not max_by_define: + cw.p(f"{max_name} = {max_value}") + cw.block_end(line=';') + if max_by_define: + cw.p(f"#define {max_name} {max_value}") + cw.nl() + + +def render_uapi_directional(family, cw, max_by_define): + max_name = f"{family.op_prefix}USER_MAX" + cnt_name = f"__{family.op_prefix}USER_CNT" + max_value = f"({cnt_name} - 1)" + + cw.block_start(line='enum') + cw.p(c_upper(f'{family.name}_MSG_USER_NONE = 0,')) + val = 0 + for op in family.msgs.values(): + if 'do' in op and 'event' not in op: + suffix = ',' + if op.value and op.value != val: + suffix = f" = {op.value}," + val = op.value + cw.p(op.enum_name + suffix) + val += 1 + cw.nl() + cw.p(cnt_name + ('' if max_by_define else ',')) + if not max_by_define: + cw.p(f"{max_name} = {max_value}") + cw.block_end(line=';') + if max_by_define: + cw.p(f"#define {max_name} {max_value}") + cw.nl() + + max_name = f"{family.op_prefix}KERNEL_MAX" + cnt_name = f"__{family.op_prefix}KERNEL_CNT" + max_value = f"({cnt_name} - 1)" + + cw.block_start(line='enum') + cw.p(c_upper(f'{family.name}_MSG_KERNEL_NONE = 0,')) + val = 0 + for op in family.msgs.values(): + if ('do' in op and 'reply' in op['do']) or 'notify' in op or 'event' in op: + enum_name = op.enum_name + if 'event' not in op and 'notify' not in op: + enum_name = f'{enum_name}_REPLY' + + suffix = ',' + if op.value and op.value != val: + suffix = f" = {op.value}," + val = op.value + cw.p(enum_name + suffix) + val += 1 + cw.nl() + cw.p(cnt_name + ('' if max_by_define else ',')) + if not max_by_define: + cw.p(f"{max_name} = {max_value}") + cw.block_end(line=';') + if max_by_define: + cw.p(f"#define {max_name} {max_value}") + cw.nl() + + +def render_uapi(family, cw): + hdr_prot = f"_UAPI_LINUX_{c_upper(family.uapi_header_name)}_H" + hdr_prot = hdr_prot.replace('/', '_') + cw.p('#ifndef ' + hdr_prot) + cw.p('#define ' + hdr_prot) + cw.nl() + + defines = [(family.fam_key, family["name"]), + (family.ver_key, family.get('version', 1))] + cw.writes_defines(defines) + cw.nl() + + defines = [] + for const in family['definitions']: + if const['type'] != 'const': + cw.writes_defines(defines) + defines = [] + cw.nl() + + # Write kdoc for enum and flags (one day maybe also structs) + if const['type'] == 'enum' or const['type'] == 'flags': + enum = family.consts[const['name']] + + if enum.header: + continue + + if enum.has_doc(): + if enum.has_entry_doc(): + cw.p('/**') + doc = '' + if 'doc' in enum: + doc = ' - ' + enum['doc'] + cw.write_doc_line(enum.enum_name + doc) + else: + cw.p('/*') + cw.write_doc_line(enum['doc'], indent=False) + for entry in enum.entries.values(): + if entry.has_doc(): + doc = '@' + entry.c_name + ': ' + entry['doc'] + cw.write_doc_line(doc) + cw.p(' */') + + uapi_enum_start(family, cw, const, 'name') + name_pfx = const.get('name-prefix', f"{family.ident_name}-{const['name']}-") + for entry in enum.entries.values(): + suffix = ',' + if entry.value_change: + suffix = f" = {entry.user_value()}" + suffix + cw.p(entry.c_name + suffix) + + if const.get('render-max', False): + cw.nl() + cw.p('/* private: */') + if const['type'] == 'flags': + max_name = c_upper(name_pfx + 'mask') + max_val = f' = {enum.get_mask()},' + cw.p(max_name + max_val) + else: + cnt_name = enum.enum_cnt_name + max_name = c_upper(name_pfx + 'max') + if not cnt_name: + cnt_name = '__' + name_pfx + 'max' + cw.p(c_upper(cnt_name) + ',') + cw.p(max_name + ' = (' + c_upper(cnt_name) + ' - 1)') + cw.block_end(line=';') + cw.nl() + elif const['type'] == 'const': + defines.append([c_upper(family.get('c-define-name', + f"{family.ident_name}-{const['name']}")), + const['value']]) + + if defines: + cw.writes_defines(defines) + cw.nl() + + max_by_define = family.get('max-by-define', False) + + for _, attr_set in family.attr_sets.items(): + if attr_set.subset_of: + continue + + max_value = f"({attr_set.cnt_name} - 1)" + + val = 0 + uapi_enum_start(family, cw, attr_set.yaml, 'enum-name') + for _, attr in attr_set.items(): + suffix = ',' + if attr.value != val: + suffix = f" = {attr.value}," + val = attr.value + val += 1 + cw.p(attr.enum_name + suffix) + if attr_set.items(): + cw.nl() + cw.p(attr_set.cnt_name + ('' if max_by_define else ',')) + if not max_by_define: + cw.p(f"{attr_set.max_name} = {max_value}") + cw.block_end(line=';') + if max_by_define: + cw.p(f"#define {attr_set.max_name} {max_value}") + cw.nl() + + # Commands + separate_ntf = 'async-prefix' in family['operations'] + + if family.msg_id_model == 'unified': + render_uapi_unified(family, cw, max_by_define, separate_ntf) + elif family.msg_id_model == 'directional': + render_uapi_directional(family, cw, max_by_define) + else: + raise Exception(f'Unsupported message enum-model {family.msg_id_model}') + + if separate_ntf: + uapi_enum_start(family, cw, family['operations'], enum_name='async-enum') + for op in family.msgs.values(): + if separate_ntf and not ('notify' in op or 'event' in op): + continue + + suffix = ',' + if 'value' in op: + suffix = f" = {op['value']}," + cw.p(op.enum_name + suffix) + cw.block_end(line=';') + cw.nl() + + # Multicast + defines = [] + for grp in family.mcgrps['list']: + name = grp['name'] + defines.append([c_upper(grp.get('c-define-name', f"{family.ident_name}-mcgrp-{name}")), + f'{name}']) + cw.nl() + if defines: + cw.writes_defines(defines) + cw.nl() + + cw.p(f'#endif /* {hdr_prot} */') + + +def _render_user_ntf_entry(ri, op): + ri.cw.block_start(line=f"[{op.enum_name}] = ") + ri.cw.p(f".alloc_sz\t= sizeof({type_name(ri, 'event')}),") + ri.cw.p(f".cb\t\t= {op_prefix(ri, 'reply', deref=True)}_parse,") + ri.cw.p(f".policy\t\t= &{ri.struct['reply'].render_name}_nest,") + ri.cw.p(f".free\t\t= (void *){op_prefix(ri, 'notify')}_free,") + ri.cw.block_end(line=',') + + +def render_user_family(family, cw, prototype): + symbol = f'const struct ynl_family ynl_{family.c_name}_family' + if prototype: + cw.p(f'extern {symbol};') + return + + if family.ntfs: + cw.block_start(line=f"static const struct ynl_ntf_info {family['name']}_ntf_info[] = ") + for ntf_op_name, ntf_op in family.ntfs.items(): + if 'notify' in ntf_op: + op = family.ops[ntf_op['notify']] + ri = RenderInfo(cw, family, "user", op, "notify") + elif 'event' in ntf_op: + ri = RenderInfo(cw, family, "user", ntf_op, "event") + else: + raise Exception('Invalid notification ' + ntf_op_name) + _render_user_ntf_entry(ri, ntf_op) + for op_name, op in family.ops.items(): + if 'event' not in op: + continue + ri = RenderInfo(cw, family, "user", op, "event") + _render_user_ntf_entry(ri, op) + cw.block_end(line=";") + cw.nl() + + cw.block_start(f'{symbol} = ') + cw.p(f'.name\t\t= "{family.c_name}",') + if family.fixed_header: + cw.p(f'.hdr_len\t= sizeof(struct genlmsghdr) + sizeof(struct {c_lower(family.fixed_header)}),') + else: + cw.p('.hdr_len\t= sizeof(struct genlmsghdr),') + if family.ntfs: + cw.p(f".ntf_info\t= {family['name']}_ntf_info,") + cw.p(f".ntf_info_size\t= YNL_ARRAY_SIZE({family['name']}_ntf_info),") + cw.block_end(line=';') + + +def family_contains_bitfield32(family): + for _, attr_set in family.attr_sets.items(): + if attr_set.subset_of: + continue + for _, attr in attr_set.items(): + if attr.type == "bitfield32": + return True + return False + + +def find_kernel_root(full_path): + sub_path = '' + while True: + sub_path = os.path.join(os.path.basename(full_path), sub_path) + full_path = os.path.dirname(full_path) + maintainers = os.path.join(full_path, "MAINTAINERS") + if os.path.exists(maintainers): + return full_path, sub_path[:-1] + + +def main(): + parser = argparse.ArgumentParser(description='Netlink simple parsing generator') + parser.add_argument('--mode', dest='mode', type=str, required=True, + choices=('user', 'kernel', 'uapi')) + parser.add_argument('--spec', dest='spec', type=str, required=True) + parser.add_argument('--header', dest='header', action='store_true', default=None) + parser.add_argument('--source', dest='header', action='store_false') + parser.add_argument('--user-header', nargs='+', default=[]) + parser.add_argument('--cmp-out', action='store_true', default=None, + help='Do not overwrite the output file if the new output is identical to the old') + parser.add_argument('--exclude-op', action='append', default=[]) + parser.add_argument('-o', dest='out_file', type=str, default=None) + args = parser.parse_args() + + if args.header is None: + parser.error("--header or --source is required") + + exclude_ops = [re.compile(expr) for expr in args.exclude_op] + + try: + parsed = Family(args.spec, exclude_ops) + if parsed.license != '((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)': + print('Spec license:', parsed.license) + print('License must be: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)') + os.sys.exit(1) + except yaml.YAMLError as exc: + print(exc) + os.sys.exit(1) + return + + cw = CodeWriter(BaseNlLib(), args.out_file, overwrite=(not args.cmp_out)) + + _, spec_kernel = find_kernel_root(args.spec) + if args.mode == 'uapi' or args.header: + cw.p(f'/* SPDX-License-Identifier: {parsed.license} */') + else: + cw.p(f'// SPDX-License-Identifier: {parsed.license}') + cw.p("/* Do not edit directly, auto-generated from: */") + cw.p(f"/*\t{spec_kernel} */") + cw.p(f"/* YNL-GEN {args.mode} {'header' if args.header else 'source'} */") + if args.exclude_op or args.user_header: + line = '' + line += ' --user-header '.join([''] + args.user_header) + line += ' --exclude-op '.join([''] + args.exclude_op) + cw.p(f'/* YNL-ARG{line} */') + cw.nl() + + if args.mode == 'uapi': + render_uapi(parsed, cw) + return + + hdr_prot = f"_LINUX_{parsed.c_name.upper()}_GEN_H" + if args.header: + cw.p('#ifndef ' + hdr_prot) + cw.p('#define ' + hdr_prot) + cw.nl() + + if args.out_file: + hdr_file = os.path.basename(args.out_file[:-2]) + ".h" + else: + hdr_file = "generated_header_file.h" + + if args.mode == 'kernel': + cw.p('#include ') + cw.p('#include ') + cw.nl() + if not args.header: + if args.out_file: + cw.p(f'#include "{hdr_file}"') + cw.nl() + headers = ['uapi/' + parsed.uapi_header] + headers += parsed.kernel_family.get('headers', []) + else: + cw.p('#include ') + cw.p('#include ') + if args.header: + cw.p('#include ') + if family_contains_bitfield32(parsed): + cw.p('#include ') + else: + cw.p(f'#include "{hdr_file}"') + cw.p('#include "ynl.h"') + headers = [] + for definition in parsed['definitions']: + if 'header' in definition: + headers.append(definition['header']) + if args.mode == 'user': + headers.append(parsed.uapi_header) + seen_header = [] + for one in headers: + if one not in seen_header: + cw.p(f"#include <{one}>") + seen_header.append(one) + cw.nl() + + if args.mode == "user": + if not args.header: + cw.p("#include ") + cw.nl() + for one in args.user_header: + cw.p(f'#include "{one}"') + else: + cw.p('struct ynl_sock;') + cw.nl() + render_user_family(parsed, cw, True) + cw.nl() + + if args.mode == "kernel": + if args.header: + for _, struct in sorted(parsed.pure_nested_structs.items()): + if struct.request: + cw.p('/* Common nested types */') + break + for attr_set, struct in sorted(parsed.pure_nested_structs.items()): + if struct.request: + print_req_policy_fwd(cw, struct) + cw.nl() + + if parsed.kernel_policy == 'global': + cw.p(f"/* Global operation policy for {parsed.name} */") + + struct = Struct(parsed, parsed.global_policy_set, type_list=parsed.global_policy) + print_req_policy_fwd(cw, struct) + cw.nl() + + if parsed.kernel_policy in {'per-op', 'split'}: + for op_name, op in parsed.ops.items(): + if 'do' in op and 'event' not in op: + ri = RenderInfo(cw, parsed, args.mode, op, "do") + print_req_policy_fwd(cw, ri.struct['request'], ri=ri) + cw.nl() + + print_kernel_op_table_hdr(parsed, cw) + print_kernel_mcgrp_hdr(parsed, cw) + print_kernel_family_struct_hdr(parsed, cw) + else: + print_kernel_policy_ranges(parsed, cw) + + for _, struct in sorted(parsed.pure_nested_structs.items()): + if struct.request: + cw.p('/* Common nested types */') + break + for attr_set, struct in sorted(parsed.pure_nested_structs.items()): + if struct.request: + print_req_policy(cw, struct) + cw.nl() + + if parsed.kernel_policy == 'global': + cw.p(f"/* Global operation policy for {parsed.name} */") + + struct = Struct(parsed, parsed.global_policy_set, type_list=parsed.global_policy) + print_req_policy(cw, struct) + cw.nl() + + for op_name, op in parsed.ops.items(): + if parsed.kernel_policy in {'per-op', 'split'}: + for op_mode in ['do', 'dump']: + if op_mode in op and 'request' in op[op_mode]: + cw.p(f"/* {op.enum_name} - {op_mode} */") + ri = RenderInfo(cw, parsed, args.mode, op, op_mode) + print_req_policy(cw, ri.struct['request'], ri=ri) + cw.nl() + + print_kernel_op_table(parsed, cw) + print_kernel_mcgrp_src(parsed, cw) + print_kernel_family_struct_src(parsed, cw) + + if args.mode == "user": + if args.header: + cw.p('/* Enums */') + put_op_name_fwd(parsed, cw) + + for name, const in parsed.consts.items(): + if isinstance(const, EnumSet): + put_enum_to_str_fwd(parsed, cw, const) + cw.nl() + + cw.p('/* Common nested types */') + for attr_set, struct in parsed.pure_nested_structs.items(): + ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set) + print_type_full(ri, struct) + + for op_name, op in parsed.ops.items(): + cw.p(f"/* ============== {op.enum_name} ============== */") + + if 'do' in op and 'event' not in op: + cw.p(f"/* {op.enum_name} - do */") + ri = RenderInfo(cw, parsed, args.mode, op, "do") + print_req_type(ri) + print_req_type_helpers(ri) + cw.nl() + print_rsp_type(ri) + print_rsp_type_helpers(ri) + cw.nl() + print_req_prototype(ri) + cw.nl() + + if 'dump' in op: + cw.p(f"/* {op.enum_name} - dump */") + ri = RenderInfo(cw, parsed, args.mode, op, 'dump') + print_req_type(ri) + print_req_type_helpers(ri) + if not ri.type_consistent: + print_rsp_type(ri) + print_wrapped_type(ri) + print_dump_prototype(ri) + cw.nl() + + if op.has_ntf: + cw.p(f"/* {op.enum_name} - notify */") + ri = RenderInfo(cw, parsed, args.mode, op, 'notify') + if not ri.type_consistent: + raise Exception(f'Only notifications with consistent types supported ({op.name})') + print_wrapped_type(ri) + + for op_name, op in parsed.ntfs.items(): + if 'event' in op: + ri = RenderInfo(cw, parsed, args.mode, op, 'event') + cw.p(f"/* {op.enum_name} - event */") + print_rsp_type(ri) + cw.nl() + print_wrapped_type(ri) + cw.nl() + else: + cw.p('/* Enums */') + put_op_name(parsed, cw) + + for name, const in parsed.consts.items(): + if isinstance(const, EnumSet): + put_enum_to_str(parsed, cw, const) + cw.nl() + + has_recursive_nests = False + cw.p('/* Policies */') + for struct in parsed.pure_nested_structs.values(): + if struct.recursive: + put_typol_fwd(cw, struct) + has_recursive_nests = True + if has_recursive_nests: + cw.nl() + for name in parsed.pure_nested_structs: + struct = Struct(parsed, name) + put_typol(cw, struct) + for name in parsed.root_sets: + struct = Struct(parsed, name) + put_typol(cw, struct) + + cw.p('/* Common nested types */') + if has_recursive_nests: + for attr_set, struct in parsed.pure_nested_structs.items(): + ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set) + free_rsp_nested_prototype(ri) + if struct.request: + put_req_nested_prototype(ri, struct) + if struct.reply: + parse_rsp_nested_prototype(ri, struct) + cw.nl() + for attr_set, struct in parsed.pure_nested_structs.items(): + ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set) + + free_rsp_nested(ri, struct) + if struct.request: + put_req_nested(ri, struct) + if struct.reply: + parse_rsp_nested(ri, struct) + + for op_name, op in parsed.ops.items(): + cw.p(f"/* ============== {op.enum_name} ============== */") + if 'do' in op and 'event' not in op: + cw.p(f"/* {op.enum_name} - do */") + ri = RenderInfo(cw, parsed, args.mode, op, "do") + print_req_free(ri) + print_rsp_free(ri) + parse_rsp_msg(ri) + print_req(ri) + cw.nl() + + if 'dump' in op: + cw.p(f"/* {op.enum_name} - dump */") + ri = RenderInfo(cw, parsed, args.mode, op, "dump") + if not ri.type_consistent: + parse_rsp_msg(ri, deref=True) + print_req_free(ri) + print_dump_type_free(ri) + print_dump(ri) + cw.nl() + + if op.has_ntf: + cw.p(f"/* {op.enum_name} - notify */") + ri = RenderInfo(cw, parsed, args.mode, op, 'notify') + if not ri.type_consistent: + raise Exception(f'Only notifications with consistent types supported ({op.name})') + print_ntf_type_free(ri) + + for op_name, op in parsed.ntfs.items(): + if 'event' in op: + cw.p(f"/* {op.enum_name} - event */") + + ri = RenderInfo(cw, parsed, args.mode, op, "do") + parse_rsp_msg(ri) + + ri = RenderInfo(cw, parsed, args.mode, op, "event") + print_ntf_type_free(ri) + cw.nl() + render_user_family(parsed, cw, False) + + if args.header: + cw.p(f'#endif /* {hdr_prot} */') + + +if __name__ == "__main__": + main() diff --git a/tools/net/ynl/pyynl/ynl_gen_rst.py b/tools/net/ynl/pyynl/ynl_gen_rst.py new file mode 100755 index 000000000000..6c56d0d726b4 --- /dev/null +++ b/tools/net/ynl/pyynl/ynl_gen_rst.py @@ -0,0 +1,453 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +# -*- coding: utf-8; mode: python -*- + +""" + Script to auto generate the documentation for Netlink specifications. + + :copyright: Copyright (C) 2023 Breno Leitao + :license: GPL Version 2, June 1991 see linux/COPYING for details. + + This script performs extensive parsing to the Linux kernel's netlink YAML + spec files, in an effort to avoid needing to heavily mark up the original + YAML file. + + This code is split in three big parts: + 1) RST formatters: Use to convert a string to a RST output + 2) Parser helpers: Functions to parse the YAML data structure + 3) Main function and small helpers +""" + +from typing import Any, Dict, List +import os.path +import sys +import argparse +import logging +import yaml + + +SPACE_PER_LEVEL = 4 + + +# RST Formatters +# ============== +def headroom(level: int) -> str: + """Return space to format""" + return " " * (level * SPACE_PER_LEVEL) + + +def bold(text: str) -> str: + """Format bold text""" + return f"**{text}**" + + +def inline(text: str) -> str: + """Format inline text""" + return f"``{text}``" + + +def sanitize(text: str) -> str: + """Remove newlines and multiple spaces""" + # This is useful for some fields that are spread across multiple lines + return str(text).replace("\n", " ").strip() + + +def rst_fields(key: str, value: str, level: int = 0) -> str: + """Return a RST formatted field""" + return headroom(level) + f":{key}: {value}" + + +def rst_definition(key: str, value: Any, level: int = 0) -> str: + """Format a single rst definition""" + return headroom(level) + key + "\n" + headroom(level + 1) + str(value) + + +def rst_paragraph(paragraph: str, level: int = 0) -> str: + """Return a formatted paragraph""" + return headroom(level) + paragraph + + +def rst_bullet(item: str, level: int = 0) -> str: + """Return a formatted a bullet""" + return headroom(level) + f"- {item}" + + +def rst_subsection(title: str) -> str: + """Add a sub-section to the document""" + return f"{title}\n" + "-" * len(title) + + +def rst_subsubsection(title: str) -> str: + """Add a sub-sub-section to the document""" + return f"{title}\n" + "~" * len(title) + + +def rst_section(namespace: str, prefix: str, title: str) -> str: + """Add a section to the document""" + return f".. _{namespace}-{prefix}-{title}:\n\n{title}\n" + "=" * len(title) + + +def rst_subtitle(title: str) -> str: + """Add a subtitle to the document""" + return "\n" + "-" * len(title) + f"\n{title}\n" + "-" * len(title) + "\n\n" + + +def rst_title(title: str) -> str: + """Add a title to the document""" + return "=" * len(title) + f"\n{title}\n" + "=" * len(title) + "\n\n" + + +def rst_list_inline(list_: List[str], level: int = 0) -> str: + """Format a list using inlines""" + return headroom(level) + "[" + ", ".join(inline(i) for i in list_) + "]" + + +def rst_ref(namespace: str, prefix: str, name: str) -> str: + """Add a hyperlink to the document""" + mappings = {'enum': 'definition', + 'fixed-header': 'definition', + 'nested-attributes': 'attribute-set', + 'struct': 'definition'} + if prefix in mappings: + prefix = mappings[prefix] + return f":ref:`{namespace}-{prefix}-{name}`" + + +def rst_header() -> str: + """The headers for all the auto generated RST files""" + lines = [] + + lines.append(rst_paragraph(".. SPDX-License-Identifier: GPL-2.0")) + lines.append(rst_paragraph(".. NOTE: This document was auto-generated.\n\n")) + + return "\n".join(lines) + + +def rst_toctree(maxdepth: int = 2) -> str: + """Generate a toctree RST primitive""" + lines = [] + + lines.append(".. toctree::") + lines.append(f" :maxdepth: {maxdepth}\n\n") + + return "\n".join(lines) + + +def rst_label(title: str) -> str: + """Return a formatted label""" + return f".. _{title}:\n\n" + + +# Parsers +# ======= + + +def parse_mcast_group(mcast_group: List[Dict[str, Any]]) -> str: + """Parse 'multicast' group list and return a formatted string""" + lines = [] + for group in mcast_group: + lines.append(rst_bullet(group["name"])) + + return "\n".join(lines) + + +def parse_do(do_dict: Dict[str, Any], level: int = 0) -> str: + """Parse 'do' section and return a formatted string""" + lines = [] + for key in do_dict.keys(): + lines.append(rst_paragraph(bold(key), level + 1)) + if key in ['request', 'reply']: + lines.append(parse_do_attributes(do_dict[key], level + 1) + "\n") + else: + lines.append(headroom(level + 2) + do_dict[key] + "\n") + + return "\n".join(lines) + + +def parse_do_attributes(attrs: Dict[str, Any], level: int = 0) -> str: + """Parse 'attributes' section""" + if "attributes" not in attrs: + return "" + lines = [rst_fields("attributes", rst_list_inline(attrs["attributes"]), level + 1)] + + return "\n".join(lines) + + +def parse_operations(operations: List[Dict[str, Any]], namespace: str) -> str: + """Parse operations block""" + preprocessed = ["name", "doc", "title", "do", "dump", "flags"] + linkable = ["fixed-header", "attribute-set"] + lines = [] + + for operation in operations: + lines.append(rst_section(namespace, 'operation', operation["name"])) + lines.append(rst_paragraph(operation["doc"]) + "\n") + + for key in operation.keys(): + if key in preprocessed: + # Skip the special fields + continue + value = operation[key] + if key in linkable: + value = rst_ref(namespace, key, value) + lines.append(rst_fields(key, value, 0)) + if 'flags' in operation: + lines.append(rst_fields('flags', rst_list_inline(operation['flags']))) + + if "do" in operation: + lines.append(rst_paragraph(":do:", 0)) + lines.append(parse_do(operation["do"], 0)) + if "dump" in operation: + lines.append(rst_paragraph(":dump:", 0)) + lines.append(parse_do(operation["dump"], 0)) + + # New line after fields + lines.append("\n") + + return "\n".join(lines) + + +def parse_entries(entries: List[Dict[str, Any]], level: int) -> str: + """Parse a list of entries""" + ignored = ["pad"] + lines = [] + for entry in entries: + if isinstance(entry, dict): + # entries could be a list or a dictionary + field_name = entry.get("name", "") + if field_name in ignored: + continue + type_ = entry.get("type") + if type_: + field_name += f" ({inline(type_)})" + lines.append( + rst_fields(field_name, sanitize(entry.get("doc", "")), level) + ) + elif isinstance(entry, list): + lines.append(rst_list_inline(entry, level)) + else: + lines.append(rst_bullet(inline(sanitize(entry)), level)) + + lines.append("\n") + return "\n".join(lines) + + +def parse_definitions(defs: Dict[str, Any], namespace: str) -> str: + """Parse definitions section""" + preprocessed = ["name", "entries", "members"] + ignored = ["render-max"] # This is not printed + lines = [] + + for definition in defs: + lines.append(rst_section(namespace, 'definition', definition["name"])) + for k in definition.keys(): + if k in preprocessed + ignored: + continue + lines.append(rst_fields(k, sanitize(definition[k]), 0)) + + # Field list needs to finish with a new line + lines.append("\n") + if "entries" in definition: + lines.append(rst_paragraph(":entries:", 0)) + lines.append(parse_entries(definition["entries"], 1)) + if "members" in definition: + lines.append(rst_paragraph(":members:", 0)) + lines.append(parse_entries(definition["members"], 1)) + + return "\n".join(lines) + + +def parse_attr_sets(entries: List[Dict[str, Any]], namespace: str) -> str: + """Parse attribute from attribute-set""" + preprocessed = ["name", "type"] + linkable = ["enum", "nested-attributes", "struct", "sub-message"] + ignored = ["checks"] + lines = [] + + for entry in entries: + lines.append(rst_section(namespace, 'attribute-set', entry["name"])) + for attr in entry["attributes"]: + type_ = attr.get("type") + attr_line = attr["name"] + if type_: + # Add the attribute type in the same line + attr_line += f" ({inline(type_)})" + + lines.append(rst_subsubsection(attr_line)) + + for k in attr.keys(): + if k in preprocessed + ignored: + continue + if k in linkable: + value = rst_ref(namespace, k, attr[k]) + else: + value = sanitize(attr[k]) + lines.append(rst_fields(k, value, 0)) + lines.append("\n") + + return "\n".join(lines) + + +def parse_sub_messages(entries: List[Dict[str, Any]], namespace: str) -> str: + """Parse sub-message definitions""" + lines = [] + + for entry in entries: + lines.append(rst_section(namespace, 'sub-message', entry["name"])) + for fmt in entry["formats"]: + value = fmt["value"] + + lines.append(rst_bullet(bold(value))) + for attr in ['fixed-header', 'attribute-set']: + if attr in fmt: + lines.append(rst_fields(attr, + rst_ref(namespace, attr, fmt[attr]), + 1)) + lines.append("\n") + + return "\n".join(lines) + + +def parse_yaml(obj: Dict[str, Any]) -> str: + """Format the whole YAML into a RST string""" + lines = [] + + # Main header + + lines.append(rst_header()) + + family = obj['name'] + + title = f"Family ``{family}`` netlink specification" + lines.append(rst_title(title)) + lines.append(rst_paragraph(".. contents:: :depth: 3\n")) + + if "doc" in obj: + lines.append(rst_subtitle("Summary")) + lines.append(rst_paragraph(obj["doc"], 0)) + + # Operations + if "operations" in obj: + lines.append(rst_subtitle("Operations")) + lines.append(parse_operations(obj["operations"]["list"], family)) + + # Multicast groups + if "mcast-groups" in obj: + lines.append(rst_subtitle("Multicast groups")) + lines.append(parse_mcast_group(obj["mcast-groups"]["list"])) + + # Definitions + if "definitions" in obj: + lines.append(rst_subtitle("Definitions")) + lines.append(parse_definitions(obj["definitions"], family)) + + # Attributes set + if "attribute-sets" in obj: + lines.append(rst_subtitle("Attribute sets")) + lines.append(parse_attr_sets(obj["attribute-sets"], family)) + + # Sub-messages + if "sub-messages" in obj: + lines.append(rst_subtitle("Sub-messages")) + lines.append(parse_sub_messages(obj["sub-messages"], family)) + + return "\n".join(lines) + + +# Main functions +# ============== + + +def parse_arguments() -> argparse.Namespace: + """Parse arguments from user""" + parser = argparse.ArgumentParser(description="Netlink RST generator") + + parser.add_argument("-v", "--verbose", action="store_true") + parser.add_argument("-o", "--output", help="Output file name") + + # Index and input are mutually exclusive + group = parser.add_mutually_exclusive_group() + group.add_argument( + "-x", "--index", action="store_true", help="Generate the index page" + ) + group.add_argument("-i", "--input", help="YAML file name") + + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + + if args.input and not os.path.isfile(args.input): + logging.warning("%s is not a valid file.", args.input) + sys.exit(-1) + + if not args.output: + logging.error("No output file specified.") + sys.exit(-1) + + if os.path.isfile(args.output): + logging.debug("%s already exists. Overwriting it.", args.output) + + return args + + +def parse_yaml_file(filename: str) -> str: + """Transform the YAML specified by filename into a rst-formmated string""" + with open(filename, "r", encoding="utf-8") as spec_file: + yaml_data = yaml.safe_load(spec_file) + content = parse_yaml(yaml_data) + + return content + + +def write_to_rstfile(content: str, filename: str) -> None: + """Write the generated content into an RST file""" + logging.debug("Saving RST file to %s", filename) + + with open(filename, "w", encoding="utf-8") as rst_file: + rst_file.write(content) + + +def generate_main_index_rst(output: str) -> None: + """Generate the `networking_spec/index` content and write to the file""" + lines = [] + + lines.append(rst_header()) + lines.append(rst_label("specs")) + lines.append(rst_title("Netlink Family Specifications")) + lines.append(rst_toctree(1)) + + index_dir = os.path.dirname(output) + logging.debug("Looking for .rst files in %s", index_dir) + for filename in sorted(os.listdir(index_dir)): + if not filename.endswith(".rst") or filename == "index.rst": + continue + lines.append(f" {filename.replace('.rst', '')}\n") + + logging.debug("Writing an index file at %s", output) + write_to_rstfile("".join(lines), output) + + +def main() -> None: + """Main function that reads the YAML files and generates the RST files""" + + args = parse_arguments() + + if args.input: + logging.debug("Parsing %s", args.input) + try: + content = parse_yaml_file(os.path.join(args.input)) + except Exception as exception: + logging.warning("Failed to parse %s.", args.input) + logging.warning(exception) + sys.exit(-1) + + write_to_rstfile(content, args.output) + + if args.index: + # Generate the index RST file + generate_main_index_rst(args.output) + + +if __name__ == "__main__": + main() diff --git a/tools/net/ynl/ynl-gen-c.py b/tools/net/ynl/ynl-gen-c.py deleted file mode 100755 index d3a7dfbcf929..000000000000 --- a/tools/net/ynl/ynl-gen-c.py +++ /dev/null @@ -1,3044 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) - -import argparse -import collections -import filecmp -import pathlib -import os -import re -import shutil -import sys -import tempfile -import yaml - -sys.path.append(pathlib.Path(__file__).resolve().parent.as_posix()) -from lib import SpecFamily, SpecAttrSet, SpecAttr, SpecOperation, SpecEnumSet, SpecEnumEntry - - -def c_upper(name): - return name.upper().replace('-', '_') - - -def c_lower(name): - return name.lower().replace('-', '_') - - -def limit_to_number(name): - """ - Turn a string limit like u32-max or s64-min into its numerical value - """ - if name[0] == 'u' and name.endswith('-min'): - return 0 - width = int(name[1:-4]) - if name[0] == 's': - width -= 1 - value = (1 << width) - 1 - if name[0] == 's' and name.endswith('-min'): - value = -value - 1 - return value - - -class BaseNlLib: - def get_family_id(self): - return 'ys->family_id' - - -class Type(SpecAttr): - def __init__(self, family, attr_set, attr, value): - super().__init__(family, attr_set, attr, value) - - self.attr = attr - self.attr_set = attr_set - self.type = attr['type'] - self.checks = attr.get('checks', {}) - - self.request = False - self.reply = False - - if 'len' in attr: - self.len = attr['len'] - - if 'nested-attributes' in attr: - self.nested_attrs = attr['nested-attributes'] - if self.nested_attrs == family.name: - self.nested_render_name = c_lower(f"{family.ident_name}") - else: - self.nested_render_name = c_lower(f"{family.ident_name}_{self.nested_attrs}") - - if self.nested_attrs in self.family.consts: - self.nested_struct_type = 'struct ' + self.nested_render_name + '_' - else: - self.nested_struct_type = 'struct ' + self.nested_render_name - - self.c_name = c_lower(self.name) - if self.c_name in _C_KW: - self.c_name += '_' - - # Added by resolve(): - self.enum_name = None - delattr(self, "enum_name") - - def _get_real_attr(self): - # if the attr is for a subset return the "real" attr (just one down, does not recurse) - return self.family.attr_sets[self.attr_set.subset_of][self.name] - - def set_request(self): - self.request = True - if self.attr_set.subset_of: - self._get_real_attr().set_request() - - def set_reply(self): - self.reply = True - if self.attr_set.subset_of: - self._get_real_attr().set_reply() - - def get_limit(self, limit, default=None): - value = self.checks.get(limit, default) - if value is None: - return value - if isinstance(value, int): - return value - if value in self.family.consts: - raise Exception("Resolving family constants not implemented, yet") - return limit_to_number(value) - - def get_limit_str(self, limit, default=None, suffix=''): - value = self.checks.get(limit, default) - if value is None: - return '' - if isinstance(value, int): - return str(value) + suffix - if value in self.family.consts: - return c_upper(f"{self.family['name']}-{value}") - return c_upper(value) - - def resolve(self): - if 'name-prefix' in self.attr: - enum_name = f"{self.attr['name-prefix']}{self.name}" - else: - enum_name = f"{self.attr_set.name_prefix}{self.name}" - self.enum_name = c_upper(enum_name) - - if self.attr_set.subset_of: - if self.checks != self._get_real_attr().checks: - raise Exception("Overriding checks not supported by codegen, yet") - - def is_multi_val(self): - return None - - def is_scalar(self): - return self.type in {'u8', 'u16', 'u32', 'u64', 's32', 's64'} - - def is_recursive(self): - return False - - def is_recursive_for_op(self, ri): - return self.is_recursive() and not ri.op - - def presence_type(self): - return 'bit' - - def presence_member(self, space, type_filter): - if self.presence_type() != type_filter: - return - - if self.presence_type() == 'bit': - pfx = '__' if space == 'user' else '' - return f"{pfx}u32 {self.c_name}:1;" - - if self.presence_type() == 'len': - pfx = '__' if space == 'user' else '' - return f"{pfx}u32 {self.c_name}_len;" - - def _complex_member_type(self, ri): - return None - - def free_needs_iter(self): - return False - - def free(self, ri, var, ref): - if self.is_multi_val() or self.presence_type() == 'len': - ri.cw.p(f'free({var}->{ref}{self.c_name});') - - def arg_member(self, ri): - member = self._complex_member_type(ri) - if member: - arg = [member + ' *' + self.c_name] - if self.presence_type() == 'count': - arg += ['unsigned int n_' + self.c_name] - return arg - raise Exception(f"Struct member not implemented for class type {self.type}") - - def struct_member(self, ri): - if self.is_multi_val(): - ri.cw.p(f"unsigned int n_{self.c_name};") - member = self._complex_member_type(ri) - if member: - ptr = '*' if self.is_multi_val() else '' - if self.is_recursive_for_op(ri): - ptr = '*' - ri.cw.p(f"{member} {ptr}{self.c_name};") - return - members = self.arg_member(ri) - for one in members: - ri.cw.p(one + ';') - - def _attr_policy(self, policy): - return '{ .type = ' + policy + ', }' - - def attr_policy(self, cw): - policy = f'NLA_{c_upper(self.type)}' - if self.attr.get('byte-order') == 'big-endian': - if self.type in {'u16', 'u32'}: - policy = f'NLA_BE{self.type[1:]}' - - spec = self._attr_policy(policy) - cw.p(f"\t[{self.enum_name}] = {spec},") - - def _attr_typol(self): - raise Exception(f"Type policy not implemented for class type {self.type}") - - def attr_typol(self, cw): - typol = self._attr_typol() - cw.p(f'[{self.enum_name}] = {"{"} .name = "{self.name}", {typol}{"}"},') - - def _attr_put_line(self, ri, var, line): - if self.presence_type() == 'bit': - ri.cw.p(f"if ({var}->_present.{self.c_name})") - elif self.presence_type() == 'len': - ri.cw.p(f"if ({var}->_present.{self.c_name}_len)") - ri.cw.p(f"{line};") - - def _attr_put_simple(self, ri, var, put_type): - line = f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name})" - self._attr_put_line(ri, var, line) - - def attr_put(self, ri, var): - raise Exception(f"Put not implemented for class type {self.type}") - - def _attr_get(self, ri, var): - raise Exception(f"Attr get not implemented for class type {self.type}") - - def attr_get(self, ri, var, first): - lines, init_lines, local_vars = self._attr_get(ri, var) - if type(lines) is str: - lines = [lines] - if type(init_lines) is str: - init_lines = [init_lines] - - kw = 'if' if first else 'else if' - ri.cw.block_start(line=f"{kw} (type == {self.enum_name})") - if local_vars: - for local in local_vars: - ri.cw.p(local) - ri.cw.nl() - - if not self.is_multi_val(): - ri.cw.p("if (ynl_attr_validate(yarg, attr))") - ri.cw.p("return YNL_PARSE_CB_ERROR;") - if self.presence_type() == 'bit': - ri.cw.p(f"{var}->_present.{self.c_name} = 1;") - - if init_lines: - ri.cw.nl() - for line in init_lines: - ri.cw.p(line) - - for line in lines: - ri.cw.p(line) - ri.cw.block_end() - return True - - def _setter_lines(self, ri, member, presence): - raise Exception(f"Setter not implemented for class type {self.type}") - - def setter(self, ri, space, direction, deref=False, ref=None): - ref = (ref if ref else []) + [self.c_name] - var = "req" - member = f"{var}->{'.'.join(ref)}" - - code = [] - presence = '' - for i in range(0, len(ref)): - presence = f"{var}->{'.'.join(ref[:i] + [''])}_present.{ref[i]}" - # Every layer below last is a nest, so we know it uses bit presence - # last layer is "self" and may be a complex type - if i == len(ref) - 1 and self.presence_type() != 'bit': - continue - code.append(presence + ' = 1;') - code += self._setter_lines(ri, member, presence) - - func_name = f"{op_prefix(ri, direction, deref=deref)}_set_{'_'.join(ref)}" - free = bool([x for x in code if 'free(' in x]) - alloc = bool([x for x in code if 'alloc(' in x]) - if free and not alloc: - func_name = '__' + func_name - ri.cw.write_func('static inline void', func_name, body=code, - args=[f'{type_name(ri, direction, deref=deref)} *{var}'] + self.arg_member(ri)) - - -class TypeUnused(Type): - def presence_type(self): - return '' - - def arg_member(self, ri): - return [] - - def _attr_get(self, ri, var): - return ['return YNL_PARSE_CB_ERROR;'], None, None - - def _attr_typol(self): - return '.type = YNL_PT_REJECT, ' - - def attr_policy(self, cw): - pass - - def attr_put(self, ri, var): - pass - - def attr_get(self, ri, var, first): - pass - - def setter(self, ri, space, direction, deref=False, ref=None): - pass - - -class TypePad(Type): - def presence_type(self): - return '' - - def arg_member(self, ri): - return [] - - def _attr_typol(self): - return '.type = YNL_PT_IGNORE, ' - - def attr_put(self, ri, var): - pass - - def attr_get(self, ri, var, first): - pass - - def attr_policy(self, cw): - pass - - def setter(self, ri, space, direction, deref=False, ref=None): - pass - - -class TypeScalar(Type): - def __init__(self, family, attr_set, attr, value): - super().__init__(family, attr_set, attr, value) - - self.byte_order_comment = '' - if 'byte-order' in attr: - self.byte_order_comment = f" /* {attr['byte-order']} */" - - if 'enum' in self.attr: - enum = self.family.consts[self.attr['enum']] - low, high = enum.value_range() - if 'min' not in self.checks: - if low != 0 or self.type[0] == 's': - self.checks['min'] = low - if 'max' not in self.checks: - self.checks['max'] = high - - if 'min' in self.checks and 'max' in self.checks: - if self.get_limit('min') > self.get_limit('max'): - raise Exception(f'Invalid limit for "{self.name}" min: {self.get_limit("min")} max: {self.get_limit("max")}') - self.checks['range'] = True - - low = min(self.get_limit('min', 0), self.get_limit('max', 0)) - high = max(self.get_limit('min', 0), self.get_limit('max', 0)) - if low < 0 and self.type[0] == 'u': - raise Exception(f'Invalid limit for "{self.name}" negative limit for unsigned type') - if low < -32768 or high > 32767: - self.checks['full-range'] = True - - # Added by resolve(): - self.is_bitfield = None - delattr(self, "is_bitfield") - self.type_name = None - delattr(self, "type_name") - - def resolve(self): - self.resolve_up(super()) - - if 'enum-as-flags' in self.attr and self.attr['enum-as-flags']: - self.is_bitfield = True - elif 'enum' in self.attr: - self.is_bitfield = self.family.consts[self.attr['enum']]['type'] == 'flags' - else: - self.is_bitfield = False - - if not self.is_bitfield and 'enum' in self.attr: - self.type_name = self.family.consts[self.attr['enum']].user_type - elif self.is_auto_scalar: - self.type_name = '__' + self.type[0] + '64' - else: - self.type_name = '__' + self.type - - def _attr_policy(self, policy): - if 'flags-mask' in self.checks or self.is_bitfield: - if self.is_bitfield: - enum = self.family.consts[self.attr['enum']] - mask = enum.get_mask(as_flags=True) - else: - flags = self.family.consts[self.checks['flags-mask']] - flag_cnt = len(flags['entries']) - mask = (1 << flag_cnt) - 1 - return f"NLA_POLICY_MASK({policy}, 0x{mask:x})" - elif 'full-range' in self.checks: - return f"NLA_POLICY_FULL_RANGE({policy}, &{c_lower(self.enum_name)}_range)" - elif 'range' in self.checks: - return f"NLA_POLICY_RANGE({policy}, {self.get_limit_str('min')}, {self.get_limit_str('max')})" - elif 'min' in self.checks: - return f"NLA_POLICY_MIN({policy}, {self.get_limit_str('min')})" - elif 'max' in self.checks: - return f"NLA_POLICY_MAX({policy}, {self.get_limit_str('max')})" - return super()._attr_policy(policy) - - def _attr_typol(self): - return f'.type = YNL_PT_U{c_upper(self.type[1:])}, ' - - def arg_member(self, ri): - return [f'{self.type_name} {self.c_name}{self.byte_order_comment}'] - - def attr_put(self, ri, var): - self._attr_put_simple(ri, var, self.type) - - def _attr_get(self, ri, var): - return f"{var}->{self.c_name} = ynl_attr_get_{self.type}(attr);", None, None - - def _setter_lines(self, ri, member, presence): - return [f"{member} = {self.c_name};"] - - -class TypeFlag(Type): - def arg_member(self, ri): - return [] - - def _attr_typol(self): - return '.type = YNL_PT_FLAG, ' - - def attr_put(self, ri, var): - self._attr_put_line(ri, var, f"ynl_attr_put(nlh, {self.enum_name}, NULL, 0)") - - def _attr_get(self, ri, var): - return [], None, None - - def _setter_lines(self, ri, member, presence): - return [] - - -class TypeString(Type): - def arg_member(self, ri): - return [f"const char *{self.c_name}"] - - def presence_type(self): - return 'len' - - def struct_member(self, ri): - ri.cw.p(f"char *{self.c_name};") - - def _attr_typol(self): - return f'.type = YNL_PT_NUL_STR, ' - - def _attr_policy(self, policy): - if 'exact-len' in self.checks: - mem = 'NLA_POLICY_EXACT_LEN(' + self.get_limit_str('exact-len') + ')' - else: - mem = '{ .type = ' + policy - if 'max-len' in self.checks: - mem += ', .len = ' + self.get_limit_str('max-len') - mem += ', }' - return mem - - def attr_policy(self, cw): - if self.checks.get('unterminated-ok', False): - policy = 'NLA_STRING' - else: - policy = 'NLA_NUL_STRING' - - spec = self._attr_policy(policy) - cw.p(f"\t[{self.enum_name}] = {spec},") - - def attr_put(self, ri, var): - self._attr_put_simple(ri, var, 'str') - - def _attr_get(self, ri, var): - len_mem = var + '->_present.' + self.c_name + '_len' - return [f"{len_mem} = len;", - f"{var}->{self.c_name} = malloc(len + 1);", - f"memcpy({var}->{self.c_name}, ynl_attr_get_str(attr), len);", - f"{var}->{self.c_name}[len] = 0;"], \ - ['len = strnlen(ynl_attr_get_str(attr), ynl_attr_data_len(attr));'], \ - ['unsigned int len;'] - - def _setter_lines(self, ri, member, presence): - return [f"free({member});", - f"{presence}_len = strlen({self.c_name});", - f"{member} = malloc({presence}_len + 1);", - f'memcpy({member}, {self.c_name}, {presence}_len);', - f'{member}[{presence}_len] = 0;'] - - -class TypeBinary(Type): - def arg_member(self, ri): - return [f"const void *{self.c_name}", 'size_t len'] - - def presence_type(self): - return 'len' - - def struct_member(self, ri): - ri.cw.p(f"void *{self.c_name};") - - def _attr_typol(self): - return f'.type = YNL_PT_BINARY,' - - def _attr_policy(self, policy): - if len(self.checks) == 0: - pass - elif len(self.checks) == 1: - check_name = list(self.checks)[0] - if check_name not in {'exact-len', 'min-len', 'max-len'}: - raise Exception('Unsupported check for binary type: ' + check_name) - else: - raise Exception('More than one check for binary type not implemented, yet') - - if len(self.checks) == 0: - mem = '{ .type = NLA_BINARY, }' - elif 'exact-len' in self.checks: - mem = 'NLA_POLICY_EXACT_LEN(' + self.get_limit_str('exact-len') + ')' - elif 'min-len' in self.checks: - mem = '{ .len = ' + self.get_limit_str('min-len') + ', }' - elif 'max-len' in self.checks: - mem = 'NLA_POLICY_MAX_LEN(' + self.get_limit_str('max-len') + ')' - - return mem - - def attr_put(self, ri, var): - self._attr_put_line(ri, var, f"ynl_attr_put(nlh, {self.enum_name}, " + - f"{var}->{self.c_name}, {var}->_present.{self.c_name}_len)") - - def _attr_get(self, ri, var): - len_mem = var + '->_present.' + self.c_name + '_len' - return [f"{len_mem} = len;", - f"{var}->{self.c_name} = malloc(len);", - f"memcpy({var}->{self.c_name}, ynl_attr_data(attr), len);"], \ - ['len = ynl_attr_data_len(attr);'], \ - ['unsigned int len;'] - - def _setter_lines(self, ri, member, presence): - return [f"free({member});", - f"{presence}_len = len;", - f"{member} = malloc({presence}_len);", - f'memcpy({member}, {self.c_name}, {presence}_len);'] - - -class TypeBitfield32(Type): - def _complex_member_type(self, ri): - return "struct nla_bitfield32" - - def _attr_typol(self): - return f'.type = YNL_PT_BITFIELD32, ' - - def _attr_policy(self, policy): - if not 'enum' in self.attr: - raise Exception('Enum required for bitfield32 attr') - enum = self.family.consts[self.attr['enum']] - mask = enum.get_mask(as_flags=True) - return f"NLA_POLICY_BITFIELD32({mask})" - - def attr_put(self, ri, var): - line = f"ynl_attr_put(nlh, {self.enum_name}, &{var}->{self.c_name}, sizeof(struct nla_bitfield32))" - self._attr_put_line(ri, var, line) - - def _attr_get(self, ri, var): - return f"memcpy(&{var}->{self.c_name}, ynl_attr_data(attr), sizeof(struct nla_bitfield32));", None, None - - def _setter_lines(self, ri, member, presence): - return [f"memcpy(&{member}, {self.c_name}, sizeof(struct nla_bitfield32));"] - - -class TypeNest(Type): - def is_recursive(self): - return self.family.pure_nested_structs[self.nested_attrs].recursive - - def _complex_member_type(self, ri): - return self.nested_struct_type - - def free(self, ri, var, ref): - at = '&' - if self.is_recursive_for_op(ri): - at = '' - ri.cw.p(f'if ({var}->{ref}{self.c_name})') - ri.cw.p(f'{self.nested_render_name}_free({at}{var}->{ref}{self.c_name});') - - def _attr_typol(self): - return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' - - def _attr_policy(self, policy): - return 'NLA_POLICY_NESTED(' + self.nested_render_name + '_nl_policy)' - - def attr_put(self, ri, var): - at = '' if self.is_recursive_for_op(ri) else '&' - self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " + - f"{self.enum_name}, {at}{var}->{self.c_name})") - - def _attr_get(self, ri, var): - get_lines = [f"if ({self.nested_render_name}_parse(&parg, attr))", - "return YNL_PARSE_CB_ERROR;"] - init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;", - f"parg.data = &{var}->{self.c_name};"] - return get_lines, init_lines, None - - def setter(self, ri, space, direction, deref=False, ref=None): - ref = (ref if ref else []) + [self.c_name] - - for _, attr in ri.family.pure_nested_structs[self.nested_attrs].member_list(): - if attr.is_recursive(): - continue - attr.setter(ri, self.nested_attrs, direction, deref=deref, ref=ref) - - -class TypeMultiAttr(Type): - def __init__(self, family, attr_set, attr, value, base_type): - super().__init__(family, attr_set, attr, value) - - self.base_type = base_type - - def is_multi_val(self): - return True - - def presence_type(self): - return 'count' - - def _complex_member_type(self, ri): - if 'type' not in self.attr or self.attr['type'] == 'nest': - return self.nested_struct_type - elif self.attr['type'] in scalars: - scalar_pfx = '__' if ri.ku_space == 'user' else '' - return scalar_pfx + self.attr['type'] - else: - raise Exception(f"Sub-type {self.attr['type']} not supported yet") - - def free_needs_iter(self): - return 'type' not in self.attr or self.attr['type'] == 'nest' - - def free(self, ri, var, ref): - if self.attr['type'] in scalars: - ri.cw.p(f"free({var}->{ref}{self.c_name});") - elif 'type' not in self.attr or self.attr['type'] == 'nest': - ri.cw.p(f"for (i = 0; i < {var}->{ref}n_{self.c_name}; i++)") - ri.cw.p(f'{self.nested_render_name}_free(&{var}->{ref}{self.c_name}[i]);') - ri.cw.p(f"free({var}->{ref}{self.c_name});") - else: - raise Exception(f"Free of MultiAttr sub-type {self.attr['type']} not supported yet") - - def _attr_policy(self, policy): - return self.base_type._attr_policy(policy) - - def _attr_typol(self): - return self.base_type._attr_typol() - - def _attr_get(self, ri, var): - return f'n_{self.c_name}++;', None, None - - def attr_put(self, ri, var): - if self.attr['type'] in scalars: - put_type = self.type - ri.cw.p(f"for (unsigned int i = 0; i < {var}->n_{self.c_name}; i++)") - ri.cw.p(f"ynl_attr_put_{put_type}(nlh, {self.enum_name}, {var}->{self.c_name}[i]);") - elif 'type' not in self.attr or self.attr['type'] == 'nest': - ri.cw.p(f"for (unsigned int i = 0; i < {var}->n_{self.c_name}; i++)") - self._attr_put_line(ri, var, f"{self.nested_render_name}_put(nlh, " + - f"{self.enum_name}, &{var}->{self.c_name}[i])") - else: - raise Exception(f"Put of MultiAttr sub-type {self.attr['type']} not supported yet") - - def _setter_lines(self, ri, member, presence): - # For multi-attr we have a count, not presence, hack up the presence - presence = presence[:-(len('_present.') + len(self.c_name))] + "n_" + self.c_name - return [f"free({member});", - f"{member} = {self.c_name};", - f"{presence} = n_{self.c_name};"] - - -class TypeArrayNest(Type): - def is_multi_val(self): - return True - - def presence_type(self): - return 'count' - - def _complex_member_type(self, ri): - if 'sub-type' not in self.attr or self.attr['sub-type'] == 'nest': - return self.nested_struct_type - elif self.attr['sub-type'] in scalars: - scalar_pfx = '__' if ri.ku_space == 'user' else '' - return scalar_pfx + self.attr['sub-type'] - else: - raise Exception(f"Sub-type {self.attr['sub-type']} not supported yet") - - def _attr_typol(self): - return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' - - def _attr_get(self, ri, var): - local_vars = ['const struct nlattr *attr2;'] - get_lines = [f'attr_{self.c_name} = attr;', - 'ynl_attr_for_each_nested(attr2, attr)', - f'\t{var}->n_{self.c_name}++;'] - return get_lines, None, local_vars - - -class TypeNestTypeValue(Type): - def _complex_member_type(self, ri): - return self.nested_struct_type - - def _attr_typol(self): - return f'.type = YNL_PT_NEST, .nest = &{self.nested_render_name}_nest, ' - - def _attr_get(self, ri, var): - prev = 'attr' - tv_args = '' - get_lines = [] - local_vars = [] - init_lines = [f"parg.rsp_policy = &{self.nested_render_name}_nest;", - f"parg.data = &{var}->{self.c_name};"] - if 'type-value' in self.attr: - tv_names = [c_lower(x) for x in self.attr["type-value"]] - local_vars += [f'const struct nlattr *attr_{", *attr_".join(tv_names)};'] - local_vars += [f'__u32 {", ".join(tv_names)};'] - for level in self.attr["type-value"]: - level = c_lower(level) - get_lines += [f'attr_{level} = ynl_attr_data({prev});'] - get_lines += [f'{level} = ynl_attr_type(attr_{level});'] - prev = 'attr_' + level - - tv_args = f", {', '.join(tv_names)}" - - get_lines += [f"{self.nested_render_name}_parse(&parg, {prev}{tv_args});"] - return get_lines, init_lines, local_vars - - -class Struct: - def __init__(self, family, space_name, type_list=None, inherited=None): - self.family = family - self.space_name = space_name - self.attr_set = family.attr_sets[space_name] - # Use list to catch comparisons with empty sets - self._inherited = inherited if inherited is not None else [] - self.inherited = [] - - self.nested = type_list is None - if family.name == c_lower(space_name): - self.render_name = c_lower(family.ident_name) - else: - self.render_name = c_lower(family.ident_name + '-' + space_name) - self.struct_name = 'struct ' + self.render_name - if self.nested and space_name in family.consts: - self.struct_name += '_' - self.ptr_name = self.struct_name + ' *' - # All attr sets this one contains, directly or multiple levels down - self.child_nests = set() - - self.request = False - self.reply = False - self.recursive = False - - self.attr_list = [] - self.attrs = dict() - if type_list is not None: - for t in type_list: - self.attr_list.append((t, self.attr_set[t]),) - else: - for t in self.attr_set: - self.attr_list.append((t, self.attr_set[t]),) - - max_val = 0 - self.attr_max_val = None - for name, attr in self.attr_list: - if attr.value >= max_val: - max_val = attr.value - self.attr_max_val = attr - self.attrs[name] = attr - - def __iter__(self): - yield from self.attrs - - def __getitem__(self, key): - return self.attrs[key] - - def member_list(self): - return self.attr_list - - def set_inherited(self, new_inherited): - if self._inherited != new_inherited: - raise Exception("Inheriting different members not supported") - self.inherited = [c_lower(x) for x in sorted(self._inherited)] - - -class EnumEntry(SpecEnumEntry): - def __init__(self, enum_set, yaml, prev, value_start): - super().__init__(enum_set, yaml, prev, value_start) - - if prev: - self.value_change = (self.value != prev.value + 1) - else: - self.value_change = (self.value != 0) - self.value_change = self.value_change or self.enum_set['type'] == 'flags' - - # Added by resolve: - self.c_name = None - delattr(self, "c_name") - - def resolve(self): - self.resolve_up(super()) - - self.c_name = c_upper(self.enum_set.value_pfx + self.name) - - -class EnumSet(SpecEnumSet): - def __init__(self, family, yaml): - self.render_name = c_lower(family.ident_name + '-' + yaml['name']) - - if 'enum-name' in yaml: - if yaml['enum-name']: - self.enum_name = 'enum ' + c_lower(yaml['enum-name']) - self.user_type = self.enum_name - else: - self.enum_name = None - else: - self.enum_name = 'enum ' + self.render_name - - if self.enum_name: - self.user_type = self.enum_name - else: - self.user_type = 'int' - - self.value_pfx = yaml.get('name-prefix', f"{family.ident_name}-{yaml['name']}-") - self.header = yaml.get('header', None) - self.enum_cnt_name = yaml.get('enum-cnt-name', None) - - super().__init__(family, yaml) - - def new_entry(self, entry, prev_entry, value_start): - return EnumEntry(self, entry, prev_entry, value_start) - - def value_range(self): - low = min([x.value for x in self.entries.values()]) - high = max([x.value for x in self.entries.values()]) - - if high - low + 1 != len(self.entries): - raise Exception("Can't get value range for a noncontiguous enum") - - return low, high - - -class AttrSet(SpecAttrSet): - def __init__(self, family, yaml): - super().__init__(family, yaml) - - if self.subset_of is None: - if 'name-prefix' in yaml: - pfx = yaml['name-prefix'] - elif self.name == family.name: - pfx = family.ident_name + '-a-' - else: - pfx = f"{family.ident_name}-a-{self.name}-" - self.name_prefix = c_upper(pfx) - self.max_name = c_upper(self.yaml.get('attr-max-name', f"{self.name_prefix}max")) - self.cnt_name = c_upper(self.yaml.get('attr-cnt-name', f"__{self.name_prefix}max")) - else: - self.name_prefix = family.attr_sets[self.subset_of].name_prefix - self.max_name = family.attr_sets[self.subset_of].max_name - self.cnt_name = family.attr_sets[self.subset_of].cnt_name - - # Added by resolve: - self.c_name = None - delattr(self, "c_name") - - def resolve(self): - self.c_name = c_lower(self.name) - if self.c_name in _C_KW: - self.c_name += '_' - if self.c_name == self.family.c_name: - self.c_name = '' - - def new_attr(self, elem, value): - if elem['type'] in scalars: - t = TypeScalar(self.family, self, elem, value) - elif elem['type'] == 'unused': - t = TypeUnused(self.family, self, elem, value) - elif elem['type'] == 'pad': - t = TypePad(self.family, self, elem, value) - elif elem['type'] == 'flag': - t = TypeFlag(self.family, self, elem, value) - elif elem['type'] == 'string': - t = TypeString(self.family, self, elem, value) - elif elem['type'] == 'binary': - t = TypeBinary(self.family, self, elem, value) - elif elem['type'] == 'bitfield32': - t = TypeBitfield32(self.family, self, elem, value) - elif elem['type'] == 'nest': - t = TypeNest(self.family, self, elem, value) - elif elem['type'] == 'indexed-array' and 'sub-type' in elem: - if elem["sub-type"] == 'nest': - t = TypeArrayNest(self.family, self, elem, value) - else: - raise Exception(f'new_attr: unsupported sub-type {elem["sub-type"]}') - elif elem['type'] == 'nest-type-value': - t = TypeNestTypeValue(self.family, self, elem, value) - else: - raise Exception(f"No typed class for type {elem['type']}") - - if 'multi-attr' in elem and elem['multi-attr']: - t = TypeMultiAttr(self.family, self, elem, value, t) - - return t - - -class Operation(SpecOperation): - def __init__(self, family, yaml, req_value, rsp_value): - super().__init__(family, yaml, req_value, rsp_value) - - self.render_name = c_lower(family.ident_name + '_' + self.name) - - self.dual_policy = ('do' in yaml and 'request' in yaml['do']) and \ - ('dump' in yaml and 'request' in yaml['dump']) - - self.has_ntf = False - - # Added by resolve: - self.enum_name = None - delattr(self, "enum_name") - - def resolve(self): - self.resolve_up(super()) - - if not self.is_async: - self.enum_name = self.family.op_prefix + c_upper(self.name) - else: - self.enum_name = self.family.async_op_prefix + c_upper(self.name) - - def mark_has_ntf(self): - self.has_ntf = True - - -class Family(SpecFamily): - def __init__(self, file_name, exclude_ops): - # Added by resolve: - self.c_name = None - delattr(self, "c_name") - self.op_prefix = None - delattr(self, "op_prefix") - self.async_op_prefix = None - delattr(self, "async_op_prefix") - self.mcgrps = None - delattr(self, "mcgrps") - self.consts = None - delattr(self, "consts") - self.hooks = None - delattr(self, "hooks") - - super().__init__(file_name, exclude_ops=exclude_ops) - - self.fam_key = c_upper(self.yaml.get('c-family-name', self.yaml["name"] + '_FAMILY_NAME')) - self.ver_key = c_upper(self.yaml.get('c-version-name', self.yaml["name"] + '_FAMILY_VERSION')) - - if 'definitions' not in self.yaml: - self.yaml['definitions'] = [] - - if 'uapi-header' in self.yaml: - self.uapi_header = self.yaml['uapi-header'] - else: - self.uapi_header = f"linux/{self.ident_name}.h" - if self.uapi_header.startswith("linux/") and self.uapi_header.endswith('.h'): - self.uapi_header_name = self.uapi_header[6:-2] - else: - self.uapi_header_name = self.ident_name - - def resolve(self): - self.resolve_up(super()) - - if self.yaml.get('protocol', 'genetlink') not in {'genetlink', 'genetlink-c', 'genetlink-legacy'}: - raise Exception("Codegen only supported for genetlink") - - self.c_name = c_lower(self.ident_name) - if 'name-prefix' in self.yaml['operations']: - self.op_prefix = c_upper(self.yaml['operations']['name-prefix']) - else: - self.op_prefix = c_upper(self.yaml['name'] + '-cmd-') - if 'async-prefix' in self.yaml['operations']: - self.async_op_prefix = c_upper(self.yaml['operations']['async-prefix']) - else: - self.async_op_prefix = self.op_prefix - - self.mcgrps = self.yaml.get('mcast-groups', {'list': []}) - - self.hooks = dict() - for when in ['pre', 'post']: - self.hooks[when] = dict() - for op_mode in ['do', 'dump']: - self.hooks[when][op_mode] = dict() - self.hooks[when][op_mode]['set'] = set() - self.hooks[when][op_mode]['list'] = [] - - # dict space-name -> 'request': set(attrs), 'reply': set(attrs) - self.root_sets = dict() - # dict space-name -> set('request', 'reply') - self.pure_nested_structs = dict() - - self._mark_notify() - self._mock_up_events() - - self._load_root_sets() - self._load_nested_sets() - self._load_attr_use() - self._load_hooks() - - self.kernel_policy = self.yaml.get('kernel-policy', 'split') - if self.kernel_policy == 'global': - self._load_global_policy() - - def new_enum(self, elem): - return EnumSet(self, elem) - - def new_attr_set(self, elem): - return AttrSet(self, elem) - - def new_operation(self, elem, req_value, rsp_value): - return Operation(self, elem, req_value, rsp_value) - - def _mark_notify(self): - for op in self.msgs.values(): - if 'notify' in op: - self.ops[op['notify']].mark_has_ntf() - - # Fake a 'do' equivalent of all events, so that we can render their response parsing - def _mock_up_events(self): - for op in self.yaml['operations']['list']: - if 'event' in op: - op['do'] = { - 'reply': { - 'attributes': op['event']['attributes'] - } - } - - def _load_root_sets(self): - for op_name, op in self.msgs.items(): - if 'attribute-set' not in op: - continue - - req_attrs = set() - rsp_attrs = set() - for op_mode in ['do', 'dump']: - if op_mode in op and 'request' in op[op_mode]: - req_attrs.update(set(op[op_mode]['request']['attributes'])) - if op_mode in op and 'reply' in op[op_mode]: - rsp_attrs.update(set(op[op_mode]['reply']['attributes'])) - if 'event' in op: - rsp_attrs.update(set(op['event']['attributes'])) - - if op['attribute-set'] not in self.root_sets: - self.root_sets[op['attribute-set']] = {'request': req_attrs, 'reply': rsp_attrs} - else: - self.root_sets[op['attribute-set']]['request'].update(req_attrs) - self.root_sets[op['attribute-set']]['reply'].update(rsp_attrs) - - def _sort_pure_types(self): - # Try to reorder according to dependencies - pns_key_list = list(self.pure_nested_structs.keys()) - pns_key_seen = set() - rounds = len(pns_key_list) ** 2 # it's basically bubble sort - for _ in range(rounds): - if len(pns_key_list) == 0: - break - name = pns_key_list.pop(0) - finished = True - for _, spec in self.attr_sets[name].items(): - if 'nested-attributes' in spec: - nested = spec['nested-attributes'] - # If the unknown nest we hit is recursive it's fine, it'll be a pointer - if self.pure_nested_structs[nested].recursive: - continue - if nested not in pns_key_seen: - # Dicts are sorted, this will make struct last - struct = self.pure_nested_structs.pop(name) - self.pure_nested_structs[name] = struct - finished = False - break - if finished: - pns_key_seen.add(name) - else: - pns_key_list.append(name) - - def _load_nested_sets(self): - attr_set_queue = list(self.root_sets.keys()) - attr_set_seen = set(self.root_sets.keys()) - - while len(attr_set_queue): - a_set = attr_set_queue.pop(0) - for attr, spec in self.attr_sets[a_set].items(): - if 'nested-attributes' not in spec: - continue - - nested = spec['nested-attributes'] - if nested not in attr_set_seen: - attr_set_queue.append(nested) - attr_set_seen.add(nested) - - inherit = set() - if nested not in self.root_sets: - if nested not in self.pure_nested_structs: - self.pure_nested_structs[nested] = Struct(self, nested, inherited=inherit) - else: - raise Exception(f'Using attr set as root and nested not supported - {nested}') - - if 'type-value' in spec: - if nested in self.root_sets: - raise Exception("Inheriting members to a space used as root not supported") - inherit.update(set(spec['type-value'])) - elif spec['type'] == 'indexed-array': - inherit.add('idx') - self.pure_nested_structs[nested].set_inherited(inherit) - - for root_set, rs_members in self.root_sets.items(): - for attr, spec in self.attr_sets[root_set].items(): - if 'nested-attributes' in spec: - nested = spec['nested-attributes'] - if attr in rs_members['request']: - self.pure_nested_structs[nested].request = True - if attr in rs_members['reply']: - self.pure_nested_structs[nested].reply = True - - self._sort_pure_types() - - # Propagate the request / reply / recursive - for attr_set, struct in reversed(self.pure_nested_structs.items()): - for _, spec in self.attr_sets[attr_set].items(): - if 'nested-attributes' in spec: - child_name = spec['nested-attributes'] - struct.child_nests.add(child_name) - child = self.pure_nested_structs.get(child_name) - if child: - if not child.recursive: - struct.child_nests.update(child.child_nests) - child.request |= struct.request - child.reply |= struct.reply - if attr_set in struct.child_nests: - struct.recursive = True - - self._sort_pure_types() - - def _load_attr_use(self): - for _, struct in self.pure_nested_structs.items(): - if struct.request: - for _, arg in struct.member_list(): - arg.set_request() - if struct.reply: - for _, arg in struct.member_list(): - arg.set_reply() - - for root_set, rs_members in self.root_sets.items(): - for attr, spec in self.attr_sets[root_set].items(): - if attr in rs_members['request']: - spec.set_request() - if attr in rs_members['reply']: - spec.set_reply() - - def _load_global_policy(self): - global_set = set() - attr_set_name = None - for op_name, op in self.ops.items(): - if not op: - continue - if 'attribute-set' not in op: - continue - - if attr_set_name is None: - attr_set_name = op['attribute-set'] - if attr_set_name != op['attribute-set']: - raise Exception('For a global policy all ops must use the same set') - - for op_mode in ['do', 'dump']: - if op_mode in op: - req = op[op_mode].get('request') - if req: - global_set.update(req.get('attributes', [])) - - self.global_policy = [] - self.global_policy_set = attr_set_name - for attr in self.attr_sets[attr_set_name]: - if attr in global_set: - self.global_policy.append(attr) - - def _load_hooks(self): - for op in self.ops.values(): - for op_mode in ['do', 'dump']: - if op_mode not in op: - continue - for when in ['pre', 'post']: - if when not in op[op_mode]: - continue - name = op[op_mode][when] - if name in self.hooks[when][op_mode]['set']: - continue - self.hooks[when][op_mode]['set'].add(name) - self.hooks[when][op_mode]['list'].append(name) - - -class RenderInfo: - def __init__(self, cw, family, ku_space, op, op_mode, attr_set=None): - self.family = family - self.nl = cw.nlib - self.ku_space = ku_space - self.op_mode = op_mode - self.op = op - - self.fixed_hdr = None - if op and op.fixed_header: - self.fixed_hdr = 'struct ' + c_lower(op.fixed_header) - - # 'do' and 'dump' response parsing is identical - self.type_consistent = True - if op_mode != 'do' and 'dump' in op: - if 'do' in op: - if ('reply' in op['do']) != ('reply' in op["dump"]): - self.type_consistent = False - elif 'reply' in op['do'] and op["do"]["reply"] != op["dump"]["reply"]: - self.type_consistent = False - else: - self.type_consistent = False - - self.attr_set = attr_set - if not self.attr_set: - self.attr_set = op['attribute-set'] - - self.type_name_conflict = False - if op: - self.type_name = c_lower(op.name) - else: - self.type_name = c_lower(attr_set) - if attr_set in family.consts: - self.type_name_conflict = True - - self.cw = cw - - self.struct = dict() - if op_mode == 'notify': - op_mode = 'do' - for op_dir in ['request', 'reply']: - if op: - type_list = [] - if op_dir in op[op_mode]: - type_list = op[op_mode][op_dir]['attributes'] - self.struct[op_dir] = Struct(family, self.attr_set, type_list=type_list) - if op_mode == 'event': - self.struct['reply'] = Struct(family, self.attr_set, type_list=op['event']['attributes']) - - -class CodeWriter: - def __init__(self, nlib, out_file=None, overwrite=True): - self.nlib = nlib - self._overwrite = overwrite - - self._nl = False - self._block_end = False - self._silent_block = False - self._ind = 0 - self._ifdef_block = None - if out_file is None: - self._out = os.sys.stdout - else: - self._out = tempfile.NamedTemporaryFile('w+') - self._out_file = out_file - - def __del__(self): - self.close_out_file() - - def close_out_file(self): - if self._out == os.sys.stdout: - return - # Avoid modifying the file if contents didn't change - self._out.flush() - if not self._overwrite and os.path.isfile(self._out_file): - if filecmp.cmp(self._out.name, self._out_file, shallow=False): - return - with open(self._out_file, 'w+') as out_file: - self._out.seek(0) - shutil.copyfileobj(self._out, out_file) - self._out.close() - self._out = os.sys.stdout - - @classmethod - def _is_cond(cls, line): - return line.startswith('if') or line.startswith('while') or line.startswith('for') - - def p(self, line, add_ind=0): - if self._block_end: - self._block_end = False - if line.startswith('else'): - line = '} ' + line - else: - self._out.write('\t' * self._ind + '}\n') - - if self._nl: - self._out.write('\n') - self._nl = False - - ind = self._ind - if line[-1] == ':': - ind -= 1 - if self._silent_block: - ind += 1 - self._silent_block = line.endswith(')') and CodeWriter._is_cond(line) - if line[0] == '#': - ind = 0 - if add_ind: - ind += add_ind - self._out.write('\t' * ind + line + '\n') - - def nl(self): - self._nl = True - - def block_start(self, line=''): - if line: - line = line + ' ' - self.p(line + '{') - self._ind += 1 - - def block_end(self, line=''): - if line and line[0] not in {';', ','}: - line = ' ' + line - self._ind -= 1 - self._nl = False - if not line: - # Delay printing closing bracket in case "else" comes next - if self._block_end: - self._out.write('\t' * (self._ind + 1) + '}\n') - self._block_end = True - else: - self.p('}' + line) - - def write_doc_line(self, doc, indent=True): - words = doc.split() - line = ' *' - for word in words: - if len(line) + len(word) >= 79: - self.p(line) - line = ' *' - if indent: - line += ' ' - line += ' ' + word - self.p(line) - - def write_func_prot(self, qual_ret, name, args=None, doc=None, suffix=''): - if not args: - args = ['void'] - - if doc: - self.p('/*') - self.p(' * ' + doc) - self.p(' */') - - oneline = qual_ret - if qual_ret[-1] != '*': - oneline += ' ' - oneline += f"{name}({', '.join(args)}){suffix}" - - if len(oneline) < 80: - self.p(oneline) - return - - v = qual_ret - if len(v) > 3: - self.p(v) - v = '' - elif qual_ret[-1] != '*': - v += ' ' - v += name + '(' - ind = '\t' * (len(v) // 8) + ' ' * (len(v) % 8) - delta_ind = len(v) - len(ind) - v += args[0] - i = 1 - while i < len(args): - next_len = len(v) + len(args[i]) - if v[0] == '\t': - next_len += delta_ind - if next_len > 76: - self.p(v + ',') - v = ind - else: - v += ', ' - v += args[i] - i += 1 - self.p(v + ')' + suffix) - - def write_func_lvar(self, local_vars): - if not local_vars: - return - - if type(local_vars) is str: - local_vars = [local_vars] - - local_vars.sort(key=len, reverse=True) - for var in local_vars: - self.p(var) - self.nl() - - def write_func(self, qual_ret, name, body, args=None, local_vars=None): - self.write_func_prot(qual_ret=qual_ret, name=name, args=args) - self.write_func_lvar(local_vars=local_vars) - - self.block_start() - for line in body: - self.p(line) - self.block_end() - - def writes_defines(self, defines): - longest = 0 - for define in defines: - if len(define[0]) > longest: - longest = len(define[0]) - longest = ((longest + 8) // 8) * 8 - for define in defines: - line = '#define ' + define[0] - line += '\t' * ((longest - len(define[0]) + 7) // 8) - if type(define[1]) is int: - line += str(define[1]) - elif type(define[1]) is str: - line += '"' + define[1] + '"' - self.p(line) - - def write_struct_init(self, members): - longest = max([len(x[0]) for x in members]) - longest += 1 # because we prepend a . - longest = ((longest + 8) // 8) * 8 - for one in members: - line = '.' + one[0] - line += '\t' * ((longest - len(one[0]) - 1 + 7) // 8) - line += '= ' + str(one[1]) + ',' - self.p(line) - - def ifdef_block(self, config): - config_option = None - if config: - config_option = 'CONFIG_' + c_upper(config) - if self._ifdef_block == config_option: - return - - if self._ifdef_block: - self.p('#endif /* ' + self._ifdef_block + ' */') - if config_option: - self.p('#ifdef ' + config_option) - self._ifdef_block = config_option - - -scalars = {'u8', 'u16', 'u32', 'u64', 's32', 's64', 'uint', 'sint'} - -direction_to_suffix = { - 'reply': '_rsp', - 'request': '_req', - '': '' -} - -op_mode_to_wrapper = { - 'do': '', - 'dump': '_list', - 'notify': '_ntf', - 'event': '', -} - -_C_KW = { - 'auto', - 'bool', - 'break', - 'case', - 'char', - 'const', - 'continue', - 'default', - 'do', - 'double', - 'else', - 'enum', - 'extern', - 'float', - 'for', - 'goto', - 'if', - 'inline', - 'int', - 'long', - 'register', - 'return', - 'short', - 'signed', - 'sizeof', - 'static', - 'struct', - 'switch', - 'typedef', - 'union', - 'unsigned', - 'void', - 'volatile', - 'while' -} - - -def rdir(direction): - if direction == 'reply': - return 'request' - if direction == 'request': - return 'reply' - return direction - - -def op_prefix(ri, direction, deref=False): - suffix = f"_{ri.type_name}" - - if not ri.op_mode or ri.op_mode == 'do': - suffix += f"{direction_to_suffix[direction]}" - else: - if direction == 'request': - suffix += '_req_dump' - else: - if ri.type_consistent: - if deref: - suffix += f"{direction_to_suffix[direction]}" - else: - suffix += op_mode_to_wrapper[ri.op_mode] - else: - suffix += '_rsp' - suffix += '_dump' if deref else '_list' - - return f"{ri.family.c_name}{suffix}" - - -def type_name(ri, direction, deref=False): - return f"struct {op_prefix(ri, direction, deref=deref)}" - - -def print_prototype(ri, direction, terminate=True, doc=None): - suffix = ';' if terminate else '' - - fname = ri.op.render_name - if ri.op_mode == 'dump': - fname += '_dump' - - args = ['struct ynl_sock *ys'] - if 'request' in ri.op[ri.op_mode]: - args.append(f"{type_name(ri, direction)} *" + f"{direction_to_suffix[direction][1:]}") - - ret = 'int' - if 'reply' in ri.op[ri.op_mode]: - ret = f"{type_name(ri, rdir(direction))} *" - - ri.cw.write_func_prot(ret, fname, args, doc=doc, suffix=suffix) - - -def print_req_prototype(ri): - print_prototype(ri, "request", doc=ri.op['doc']) - - -def print_dump_prototype(ri): - print_prototype(ri, "request") - - -def put_typol_fwd(cw, struct): - cw.p(f'extern const struct ynl_policy_nest {struct.render_name}_nest;') - - -def put_typol(cw, struct): - type_max = struct.attr_set.max_name - cw.block_start(line=f'const struct ynl_policy_attr {struct.render_name}_policy[{type_max} + 1] =') - - for _, arg in struct.member_list(): - arg.attr_typol(cw) - - cw.block_end(line=';') - cw.nl() - - cw.block_start(line=f'const struct ynl_policy_nest {struct.render_name}_nest =') - cw.p(f'.max_attr = {type_max},') - cw.p(f'.table = {struct.render_name}_policy,') - cw.block_end(line=';') - cw.nl() - - -def _put_enum_to_str_helper(cw, render_name, map_name, arg_name, enum=None): - args = [f'int {arg_name}'] - if enum: - args = [enum.user_type + ' ' + arg_name] - cw.write_func_prot('const char *', f'{render_name}_str', args) - cw.block_start() - if enum and enum.type == 'flags': - cw.p(f'{arg_name} = ffs({arg_name}) - 1;') - cw.p(f'if ({arg_name} < 0 || {arg_name} >= (int)YNL_ARRAY_SIZE({map_name}))') - cw.p('return NULL;') - cw.p(f'return {map_name}[{arg_name}];') - cw.block_end() - cw.nl() - - -def put_op_name_fwd(family, cw): - cw.write_func_prot('const char *', f'{family.c_name}_op_str', ['int op'], suffix=';') - - -def put_op_name(family, cw): - map_name = f'{family.c_name}_op_strmap' - cw.block_start(line=f"static const char * const {map_name}[] =") - for op_name, op in family.msgs.items(): - if op.rsp_value: - # Make sure we don't add duplicated entries, if multiple commands - # produce the same response in legacy families. - if family.rsp_by_value[op.rsp_value] != op: - cw.p(f'// skip "{op_name}", duplicate reply value') - continue - - if op.req_value == op.rsp_value: - cw.p(f'[{op.enum_name}] = "{op_name}",') - else: - cw.p(f'[{op.rsp_value}] = "{op_name}",') - cw.block_end(line=';') - cw.nl() - - _put_enum_to_str_helper(cw, family.c_name + '_op', map_name, 'op') - - -def put_enum_to_str_fwd(family, cw, enum): - args = [enum.user_type + ' value'] - cw.write_func_prot('const char *', f'{enum.render_name}_str', args, suffix=';') - - -def put_enum_to_str(family, cw, enum): - map_name = f'{enum.render_name}_strmap' - cw.block_start(line=f"static const char * const {map_name}[] =") - for entry in enum.entries.values(): - cw.p(f'[{entry.value}] = "{entry.name}",') - cw.block_end(line=';') - cw.nl() - - _put_enum_to_str_helper(cw, enum.render_name, map_name, 'value', enum=enum) - - -def put_req_nested_prototype(ri, struct, suffix=';'): - func_args = ['struct nlmsghdr *nlh', - 'unsigned int attr_type', - f'{struct.ptr_name}obj'] - - ri.cw.write_func_prot('int', f'{struct.render_name}_put', func_args, - suffix=suffix) - - -def put_req_nested(ri, struct): - put_req_nested_prototype(ri, struct, suffix='') - ri.cw.block_start() - ri.cw.write_func_lvar('struct nlattr *nest;') - - ri.cw.p("nest = ynl_attr_nest_start(nlh, attr_type);") - - for _, arg in struct.member_list(): - arg.attr_put(ri, "obj") - - ri.cw.p("ynl_attr_nest_end(nlh, nest);") - - ri.cw.nl() - ri.cw.p('return 0;') - ri.cw.block_end() - ri.cw.nl() - - -def _multi_parse(ri, struct, init_lines, local_vars): - if struct.nested: - iter_line = "ynl_attr_for_each_nested(attr, nested)" - else: - if ri.fixed_hdr: - local_vars += ['void *hdr;'] - iter_line = "ynl_attr_for_each(attr, nlh, yarg->ys->family->hdr_len)" - - array_nests = set() - multi_attrs = set() - needs_parg = False - for arg, aspec in struct.member_list(): - if aspec['type'] == 'indexed-array' and 'sub-type' in aspec: - if aspec["sub-type"] == 'nest': - local_vars.append(f'const struct nlattr *attr_{aspec.c_name};') - array_nests.add(arg) - else: - raise Exception(f'Not supported sub-type {aspec["sub-type"]}') - if 'multi-attr' in aspec: - multi_attrs.add(arg) - needs_parg |= 'nested-attributes' in aspec - if array_nests or multi_attrs: - local_vars.append('int i;') - if needs_parg: - local_vars.append('struct ynl_parse_arg parg;') - init_lines.append('parg.ys = yarg->ys;') - - all_multi = array_nests | multi_attrs - - for anest in sorted(all_multi): - local_vars.append(f"unsigned int n_{struct[anest].c_name} = 0;") - - ri.cw.block_start() - ri.cw.write_func_lvar(local_vars) - - for line in init_lines: - ri.cw.p(line) - ri.cw.nl() - - for arg in struct.inherited: - ri.cw.p(f'dst->{arg} = {arg};') - - if ri.fixed_hdr: - ri.cw.p('hdr = ynl_nlmsg_data_offset(nlh, sizeof(struct genlmsghdr));') - ri.cw.p(f"memcpy(&dst->_hdr, hdr, sizeof({ri.fixed_hdr}));") - for anest in sorted(all_multi): - aspec = struct[anest] - ri.cw.p(f"if (dst->{aspec.c_name})") - ri.cw.p(f'return ynl_error_parse(yarg, "attribute already present ({struct.attr_set.name}.{aspec.name})");') - - ri.cw.nl() - ri.cw.block_start(line=iter_line) - ri.cw.p('unsigned int type = ynl_attr_type(attr);') - ri.cw.nl() - - first = True - for _, arg in struct.member_list(): - good = arg.attr_get(ri, 'dst', first=first) - # First may be 'unused' or 'pad', ignore those - first &= not good - - ri.cw.block_end() - ri.cw.nl() - - for anest in sorted(array_nests): - aspec = struct[anest] - - ri.cw.block_start(line=f"if (n_{aspec.c_name})") - ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") - ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};") - ri.cw.p('i = 0;') - ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;") - ri.cw.block_start(line=f"ynl_attr_for_each_nested(attr, attr_{aspec.c_name})") - ri.cw.p(f"parg.data = &dst->{aspec.c_name}[i];") - ri.cw.p(f"if ({aspec.nested_render_name}_parse(&parg, attr, ynl_attr_type(attr)))") - ri.cw.p('return YNL_PARSE_CB_ERROR;') - ri.cw.p('i++;') - ri.cw.block_end() - ri.cw.block_end() - ri.cw.nl() - - for anest in sorted(multi_attrs): - aspec = struct[anest] - ri.cw.block_start(line=f"if (n_{aspec.c_name})") - ri.cw.p(f"dst->{aspec.c_name} = calloc(n_{aspec.c_name}, sizeof(*dst->{aspec.c_name}));") - ri.cw.p(f"dst->n_{aspec.c_name} = n_{aspec.c_name};") - ri.cw.p('i = 0;') - if 'nested-attributes' in aspec: - ri.cw.p(f"parg.rsp_policy = &{aspec.nested_render_name}_nest;") - ri.cw.block_start(line=iter_line) - ri.cw.block_start(line=f"if (ynl_attr_type(attr) == {aspec.enum_name})") - if 'nested-attributes' in aspec: - ri.cw.p(f"parg.data = &dst->{aspec.c_name}[i];") - ri.cw.p(f"if ({aspec.nested_render_name}_parse(&parg, attr))") - ri.cw.p('return YNL_PARSE_CB_ERROR;') - elif aspec.type in scalars: - ri.cw.p(f"dst->{aspec.c_name}[i] = ynl_attr_get_{aspec.type}(attr);") - else: - raise Exception('Nest parsing type not supported yet') - ri.cw.p('i++;') - ri.cw.block_end() - ri.cw.block_end() - ri.cw.block_end() - ri.cw.nl() - - if struct.nested: - ri.cw.p('return 0;') - else: - ri.cw.p('return YNL_PARSE_CB_OK;') - ri.cw.block_end() - ri.cw.nl() - - -def parse_rsp_nested_prototype(ri, struct, suffix=';'): - func_args = ['struct ynl_parse_arg *yarg', - 'const struct nlattr *nested'] - for arg in struct.inherited: - func_args.append('__u32 ' + arg) - - ri.cw.write_func_prot('int', f'{struct.render_name}_parse', func_args, - suffix=suffix) - - -def parse_rsp_nested(ri, struct): - parse_rsp_nested_prototype(ri, struct, suffix='') - - local_vars = ['const struct nlattr *attr;', - f'{struct.ptr_name}dst = yarg->data;'] - init_lines = [] - - if struct.member_list(): - _multi_parse(ri, struct, init_lines, local_vars) - else: - # Empty nest - ri.cw.block_start() - ri.cw.p('return 0;') - ri.cw.block_end() - ri.cw.nl() - - -def parse_rsp_msg(ri, deref=False): - if 'reply' not in ri.op[ri.op_mode] and ri.op_mode != 'event': - return - - func_args = ['const struct nlmsghdr *nlh', - 'struct ynl_parse_arg *yarg'] - - local_vars = [f'{type_name(ri, "reply", deref=deref)} *dst;', - 'const struct nlattr *attr;'] - init_lines = ['dst = yarg->data;'] - - ri.cw.write_func_prot('int', f'{op_prefix(ri, "reply", deref=deref)}_parse', func_args) - - if ri.struct["reply"].member_list(): - _multi_parse(ri, ri.struct["reply"], init_lines, local_vars) - else: - # Empty reply - ri.cw.block_start() - ri.cw.p('return YNL_PARSE_CB_OK;') - ri.cw.block_end() - ri.cw.nl() - - -def print_req(ri): - ret_ok = '0' - ret_err = '-1' - direction = "request" - local_vars = ['struct ynl_req_state yrs = { .yarg = { .ys = ys, }, };', - 'struct nlmsghdr *nlh;', - 'int err;'] - - if 'reply' in ri.op[ri.op_mode]: - ret_ok = 'rsp' - ret_err = 'NULL' - local_vars += [f'{type_name(ri, rdir(direction))} *rsp;'] - - if ri.fixed_hdr: - local_vars += ['size_t hdr_len;', - 'void *hdr;'] - - print_prototype(ri, direction, terminate=False) - ri.cw.block_start() - ri.cw.write_func_lvar(local_vars) - - ri.cw.p(f"nlh = ynl_gemsg_start_req(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);") - - ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;") - if 'reply' in ri.op[ri.op_mode]: - ri.cw.p(f"yrs.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;") - ri.cw.nl() - - if ri.fixed_hdr: - ri.cw.p("hdr_len = sizeof(req->_hdr);") - ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);") - ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);") - ri.cw.nl() - - for _, attr in ri.struct["request"].member_list(): - attr.attr_put(ri, "req") - ri.cw.nl() - - if 'reply' in ri.op[ri.op_mode]: - ri.cw.p('rsp = calloc(1, sizeof(*rsp));') - ri.cw.p('yrs.yarg.data = rsp;') - ri.cw.p(f"yrs.cb = {op_prefix(ri, 'reply')}_parse;") - if ri.op.value is not None: - ri.cw.p(f'yrs.rsp_cmd = {ri.op.enum_name};') - else: - ri.cw.p(f'yrs.rsp_cmd = {ri.op.rsp_value};') - ri.cw.nl() - ri.cw.p("err = ynl_exec(ys, nlh, &yrs);") - ri.cw.p('if (err < 0)') - if 'reply' in ri.op[ri.op_mode]: - ri.cw.p('goto err_free;') - else: - ri.cw.p('return -1;') - ri.cw.nl() - - ri.cw.p(f"return {ret_ok};") - ri.cw.nl() - - if 'reply' in ri.op[ri.op_mode]: - ri.cw.p('err_free:') - ri.cw.p(f"{call_free(ri, rdir(direction), 'rsp')}") - ri.cw.p(f"return {ret_err};") - - ri.cw.block_end() - - -def print_dump(ri): - direction = "request" - print_prototype(ri, direction, terminate=False) - ri.cw.block_start() - local_vars = ['struct ynl_dump_state yds = {};', - 'struct nlmsghdr *nlh;', - 'int err;'] - - if ri.fixed_hdr: - local_vars += ['size_t hdr_len;', - 'void *hdr;'] - - ri.cw.write_func_lvar(local_vars) - - ri.cw.p('yds.yarg.ys = ys;') - ri.cw.p(f"yds.yarg.rsp_policy = &{ri.struct['reply'].render_name}_nest;") - ri.cw.p("yds.yarg.data = NULL;") - ri.cw.p(f"yds.alloc_sz = sizeof({type_name(ri, rdir(direction))});") - ri.cw.p(f"yds.cb = {op_prefix(ri, 'reply', deref=True)}_parse;") - if ri.op.value is not None: - ri.cw.p(f'yds.rsp_cmd = {ri.op.enum_name};') - else: - ri.cw.p(f'yds.rsp_cmd = {ri.op.rsp_value};') - ri.cw.nl() - ri.cw.p(f"nlh = ynl_gemsg_start_dump(ys, {ri.nl.get_family_id()}, {ri.op.enum_name}, 1);") - - if ri.fixed_hdr: - ri.cw.p("hdr_len = sizeof(req->_hdr);") - ri.cw.p("hdr = ynl_nlmsg_put_extra_header(nlh, hdr_len);") - ri.cw.p("memcpy(hdr, &req->_hdr, hdr_len);") - ri.cw.nl() - - if "request" in ri.op[ri.op_mode]: - ri.cw.p(f"ys->req_policy = &{ri.struct['request'].render_name}_nest;") - ri.cw.nl() - for _, attr in ri.struct["request"].member_list(): - attr.attr_put(ri, "req") - ri.cw.nl() - - ri.cw.p('err = ynl_exec_dump(ys, nlh, &yds);') - ri.cw.p('if (err < 0)') - ri.cw.p('goto free_list;') - ri.cw.nl() - - ri.cw.p('return yds.first;') - ri.cw.nl() - ri.cw.p('free_list:') - ri.cw.p(call_free(ri, rdir(direction), 'yds.first')) - ri.cw.p('return NULL;') - ri.cw.block_end() - - -def call_free(ri, direction, var): - return f"{op_prefix(ri, direction)}_free({var});" - - -def free_arg_name(direction): - if direction: - return direction_to_suffix[direction][1:] - return 'obj' - - -def print_alloc_wrapper(ri, direction): - name = op_prefix(ri, direction) - ri.cw.write_func_prot(f'static inline struct {name} *', f"{name}_alloc", [f"void"]) - ri.cw.block_start() - ri.cw.p(f'return calloc(1, sizeof(struct {name}));') - ri.cw.block_end() - - -def print_free_prototype(ri, direction, suffix=';'): - name = op_prefix(ri, direction) - struct_name = name - if ri.type_name_conflict: - struct_name += '_' - arg = free_arg_name(direction) - ri.cw.write_func_prot('void', f"{name}_free", [f"struct {struct_name} *{arg}"], suffix=suffix) - - -def _print_type(ri, direction, struct): - suffix = f'_{ri.type_name}{direction_to_suffix[direction]}' - if not direction and ri.type_name_conflict: - suffix += '_' - - if ri.op_mode == 'dump': - suffix += '_dump' - - ri.cw.block_start(line=f"struct {ri.family.c_name}{suffix}") - - if ri.fixed_hdr: - ri.cw.p(ri.fixed_hdr + ' _hdr;') - ri.cw.nl() - - meta_started = False - for _, attr in struct.member_list(): - for type_filter in ['len', 'bit']: - line = attr.presence_member(ri.ku_space, type_filter) - if line: - if not meta_started: - ri.cw.block_start(line=f"struct") - meta_started = True - ri.cw.p(line) - if meta_started: - ri.cw.block_end(line='_present;') - ri.cw.nl() - - for arg in struct.inherited: - ri.cw.p(f"__u32 {arg};") - - for _, attr in struct.member_list(): - attr.struct_member(ri) - - ri.cw.block_end(line=';') - ri.cw.nl() - - -def print_type(ri, direction): - _print_type(ri, direction, ri.struct[direction]) - - -def print_type_full(ri, struct): - _print_type(ri, "", struct) - - -def print_type_helpers(ri, direction, deref=False): - print_free_prototype(ri, direction) - ri.cw.nl() - - if ri.ku_space == 'user' and direction == 'request': - for _, attr in ri.struct[direction].member_list(): - attr.setter(ri, ri.attr_set, direction, deref=deref) - ri.cw.nl() - - -def print_req_type_helpers(ri): - if len(ri.struct["request"].attr_list) == 0: - return - print_alloc_wrapper(ri, "request") - print_type_helpers(ri, "request") - - -def print_rsp_type_helpers(ri): - if 'reply' not in ri.op[ri.op_mode]: - return - print_type_helpers(ri, "reply") - - -def print_parse_prototype(ri, direction, terminate=True): - suffix = "_rsp" if direction == "reply" else "_req" - term = ';' if terminate else '' - - ri.cw.write_func_prot('void', f"{ri.op.render_name}{suffix}_parse", - ['const struct nlattr **tb', - f"struct {ri.op.render_name}{suffix} *req"], - suffix=term) - - -def print_req_type(ri): - if len(ri.struct["request"].attr_list) == 0: - return - print_type(ri, "request") - - -def print_req_free(ri): - if 'request' not in ri.op[ri.op_mode]: - return - _free_type(ri, 'request', ri.struct['request']) - - -def print_rsp_type(ri): - if (ri.op_mode == 'do' or ri.op_mode == 'dump') and 'reply' in ri.op[ri.op_mode]: - direction = 'reply' - elif ri.op_mode == 'event': - direction = 'reply' - else: - return - print_type(ri, direction) - - -def print_wrapped_type(ri): - ri.cw.block_start(line=f"{type_name(ri, 'reply')}") - if ri.op_mode == 'dump': - ri.cw.p(f"{type_name(ri, 'reply')} *next;") - elif ri.op_mode == 'notify' or ri.op_mode == 'event': - ri.cw.p('__u16 family;') - ri.cw.p('__u8 cmd;') - ri.cw.p('struct ynl_ntf_base_type *next;') - ri.cw.p(f"void (*free)({type_name(ri, 'reply')} *ntf);") - ri.cw.p(f"{type_name(ri, 'reply', deref=True)} obj __attribute__((aligned(8)));") - ri.cw.block_end(line=';') - ri.cw.nl() - print_free_prototype(ri, 'reply') - ri.cw.nl() - - -def _free_type_members_iter(ri, struct): - for _, attr in struct.member_list(): - if attr.free_needs_iter(): - ri.cw.p('unsigned int i;') - ri.cw.nl() - break - - -def _free_type_members(ri, var, struct, ref=''): - for _, attr in struct.member_list(): - attr.free(ri, var, ref) - - -def _free_type(ri, direction, struct): - var = free_arg_name(direction) - - print_free_prototype(ri, direction, suffix='') - ri.cw.block_start() - _free_type_members_iter(ri, struct) - _free_type_members(ri, var, struct) - if direction: - ri.cw.p(f'free({var});') - ri.cw.block_end() - ri.cw.nl() - - -def free_rsp_nested_prototype(ri): - print_free_prototype(ri, "") - - -def free_rsp_nested(ri, struct): - _free_type(ri, "", struct) - - -def print_rsp_free(ri): - if 'reply' not in ri.op[ri.op_mode]: - return - _free_type(ri, 'reply', ri.struct['reply']) - - -def print_dump_type_free(ri): - sub_type = type_name(ri, 'reply') - - print_free_prototype(ri, 'reply', suffix='') - ri.cw.block_start() - ri.cw.p(f"{sub_type} *next = rsp;") - ri.cw.nl() - ri.cw.block_start(line='while ((void *)next != YNL_LIST_END)') - _free_type_members_iter(ri, ri.struct['reply']) - ri.cw.p('rsp = next;') - ri.cw.p('next = rsp->next;') - ri.cw.nl() - - _free_type_members(ri, 'rsp', ri.struct['reply'], ref='obj.') - ri.cw.p(f'free(rsp);') - ri.cw.block_end() - ri.cw.block_end() - ri.cw.nl() - - -def print_ntf_type_free(ri): - print_free_prototype(ri, 'reply', suffix='') - ri.cw.block_start() - _free_type_members_iter(ri, ri.struct['reply']) - _free_type_members(ri, 'rsp', ri.struct['reply'], ref='obj.') - ri.cw.p(f'free(rsp);') - ri.cw.block_end() - ri.cw.nl() - - -def print_req_policy_fwd(cw, struct, ri=None, terminate=True): - if terminate and ri and policy_should_be_static(struct.family): - return - - if terminate: - prefix = 'extern ' - else: - if ri and policy_should_be_static(struct.family): - prefix = 'static ' - else: - prefix = '' - - suffix = ';' if terminate else ' = {' - - max_attr = struct.attr_max_val - if ri: - name = ri.op.render_name - if ri.op.dual_policy: - name += '_' + ri.op_mode - else: - name = struct.render_name - cw.p(f"{prefix}const struct nla_policy {name}_nl_policy[{max_attr.enum_name} + 1]{suffix}") - - -def print_req_policy(cw, struct, ri=None): - if ri and ri.op: - cw.ifdef_block(ri.op.get('config-cond', None)) - print_req_policy_fwd(cw, struct, ri=ri, terminate=False) - for _, arg in struct.member_list(): - arg.attr_policy(cw) - cw.p("};") - cw.ifdef_block(None) - cw.nl() - - -def kernel_can_gen_family_struct(family): - return family.proto == 'genetlink' - - -def policy_should_be_static(family): - return family.kernel_policy == 'split' or kernel_can_gen_family_struct(family) - - -def print_kernel_policy_ranges(family, cw): - first = True - for _, attr_set in family.attr_sets.items(): - if attr_set.subset_of: - continue - - for _, attr in attr_set.items(): - if not attr.request: - continue - if 'full-range' not in attr.checks: - continue - - if first: - cw.p('/* Integer value ranges */') - first = False - - sign = '' if attr.type[0] == 'u' else '_signed' - suffix = 'ULL' if attr.type[0] == 'u' else 'LL' - cw.block_start(line=f'static const struct netlink_range_validation{sign} {c_lower(attr.enum_name)}_range =') - members = [] - if 'min' in attr.checks: - members.append(('min', attr.get_limit_str('min', suffix=suffix))) - if 'max' in attr.checks: - members.append(('max', attr.get_limit_str('max', suffix=suffix))) - cw.write_struct_init(members) - cw.block_end(line=';') - cw.nl() - - -def print_kernel_op_table_fwd(family, cw, terminate): - exported = not kernel_can_gen_family_struct(family) - - if not terminate or exported: - cw.p(f"/* Ops table for {family.ident_name} */") - - pol_to_struct = {'global': 'genl_small_ops', - 'per-op': 'genl_ops', - 'split': 'genl_split_ops'} - struct_type = pol_to_struct[family.kernel_policy] - - if not exported: - cnt = "" - elif family.kernel_policy == 'split': - cnt = 0 - for op in family.ops.values(): - if 'do' in op: - cnt += 1 - if 'dump' in op: - cnt += 1 - else: - cnt = len(family.ops) - - qual = 'static const' if not exported else 'const' - line = f"{qual} struct {struct_type} {family.c_name}_nl_ops[{cnt}]" - if terminate: - cw.p(f"extern {line};") - else: - cw.block_start(line=line + ' =') - - if not terminate: - return - - cw.nl() - for name in family.hooks['pre']['do']['list']: - cw.write_func_prot('int', c_lower(name), - ['const struct genl_split_ops *ops', - 'struct sk_buff *skb', 'struct genl_info *info'], suffix=';') - for name in family.hooks['post']['do']['list']: - cw.write_func_prot('void', c_lower(name), - ['const struct genl_split_ops *ops', - 'struct sk_buff *skb', 'struct genl_info *info'], suffix=';') - for name in family.hooks['pre']['dump']['list']: - cw.write_func_prot('int', c_lower(name), - ['struct netlink_callback *cb'], suffix=';') - for name in family.hooks['post']['dump']['list']: - cw.write_func_prot('int', c_lower(name), - ['struct netlink_callback *cb'], suffix=';') - - cw.nl() - - for op_name, op in family.ops.items(): - if op.is_async: - continue - - if 'do' in op: - name = c_lower(f"{family.ident_name}-nl-{op_name}-doit") - cw.write_func_prot('int', name, - ['struct sk_buff *skb', 'struct genl_info *info'], suffix=';') - - if 'dump' in op: - name = c_lower(f"{family.ident_name}-nl-{op_name}-dumpit") - cw.write_func_prot('int', name, - ['struct sk_buff *skb', 'struct netlink_callback *cb'], suffix=';') - cw.nl() - - -def print_kernel_op_table_hdr(family, cw): - print_kernel_op_table_fwd(family, cw, terminate=True) - - -def print_kernel_op_table(family, cw): - print_kernel_op_table_fwd(family, cw, terminate=False) - if family.kernel_policy == 'global' or family.kernel_policy == 'per-op': - for op_name, op in family.ops.items(): - if op.is_async: - continue - - cw.ifdef_block(op.get('config-cond', None)) - cw.block_start() - members = [('cmd', op.enum_name)] - if 'dont-validate' in op: - members.append(('validate', - ' | '.join([c_upper('genl-dont-validate-' + x) - for x in op['dont-validate']])), ) - for op_mode in ['do', 'dump']: - if op_mode in op: - name = c_lower(f"{family.ident_name}-nl-{op_name}-{op_mode}it") - members.append((op_mode + 'it', name)) - if family.kernel_policy == 'per-op': - struct = Struct(family, op['attribute-set'], - type_list=op['do']['request']['attributes']) - - name = c_lower(f"{family.ident_name}-{op_name}-nl-policy") - members.append(('policy', name)) - members.append(('maxattr', struct.attr_max_val.enum_name)) - if 'flags' in op: - members.append(('flags', ' | '.join([c_upper('genl-' + x) for x in op['flags']]))) - cw.write_struct_init(members) - cw.block_end(line=',') - elif family.kernel_policy == 'split': - cb_names = {'do': {'pre': 'pre_doit', 'post': 'post_doit'}, - 'dump': {'pre': 'start', 'post': 'done'}} - - for op_name, op in family.ops.items(): - for op_mode in ['do', 'dump']: - if op.is_async or op_mode not in op: - continue - - cw.ifdef_block(op.get('config-cond', None)) - cw.block_start() - members = [('cmd', op.enum_name)] - if 'dont-validate' in op: - dont_validate = [] - for x in op['dont-validate']: - if op_mode == 'do' and x in ['dump', 'dump-strict']: - continue - if op_mode == "dump" and x == 'strict': - continue - dont_validate.append(x) - - if dont_validate: - members.append(('validate', - ' | '.join([c_upper('genl-dont-validate-' + x) - for x in dont_validate])), ) - name = c_lower(f"{family.ident_name}-nl-{op_name}-{op_mode}it") - if 'pre' in op[op_mode]: - members.append((cb_names[op_mode]['pre'], c_lower(op[op_mode]['pre']))) - members.append((op_mode + 'it', name)) - if 'post' in op[op_mode]: - members.append((cb_names[op_mode]['post'], c_lower(op[op_mode]['post']))) - if 'request' in op[op_mode]: - struct = Struct(family, op['attribute-set'], - type_list=op[op_mode]['request']['attributes']) - - if op.dual_policy: - name = c_lower(f"{family.ident_name}-{op_name}-{op_mode}-nl-policy") - else: - name = c_lower(f"{family.ident_name}-{op_name}-nl-policy") - members.append(('policy', name)) - members.append(('maxattr', struct.attr_max_val.enum_name)) - flags = (op['flags'] if 'flags' in op else []) + ['cmd-cap-' + op_mode] - members.append(('flags', ' | '.join([c_upper('genl-' + x) for x in flags]))) - cw.write_struct_init(members) - cw.block_end(line=',') - cw.ifdef_block(None) - - cw.block_end(line=';') - cw.nl() - - -def print_kernel_mcgrp_hdr(family, cw): - if not family.mcgrps['list']: - return - - cw.block_start('enum') - for grp in family.mcgrps['list']: - grp_id = c_upper(f"{family.ident_name}-nlgrp-{grp['name']},") - cw.p(grp_id) - cw.block_end(';') - cw.nl() - - -def print_kernel_mcgrp_src(family, cw): - if not family.mcgrps['list']: - return - - cw.block_start('static const struct genl_multicast_group ' + family.c_name + '_nl_mcgrps[] =') - for grp in family.mcgrps['list']: - name = grp['name'] - grp_id = c_upper(f"{family.ident_name}-nlgrp-{name}") - cw.p('[' + grp_id + '] = { "' + name + '", },') - cw.block_end(';') - cw.nl() - - -def print_kernel_family_struct_hdr(family, cw): - if not kernel_can_gen_family_struct(family): - return - - cw.p(f"extern struct genl_family {family.c_name}_nl_family;") - cw.nl() - if 'sock-priv' in family.kernel_family: - cw.p(f'void {family.c_name}_nl_sock_priv_init({family.kernel_family["sock-priv"]} *priv);') - cw.p(f'void {family.c_name}_nl_sock_priv_destroy({family.kernel_family["sock-priv"]} *priv);') - cw.nl() - - -def print_kernel_family_struct_src(family, cw): - if not kernel_can_gen_family_struct(family): - return - - cw.block_start(f"struct genl_family {family.ident_name}_nl_family __ro_after_init =") - cw.p('.name\t\t= ' + family.fam_key + ',') - cw.p('.version\t= ' + family.ver_key + ',') - cw.p('.netnsok\t= true,') - cw.p('.parallel_ops\t= true,') - cw.p('.module\t\t= THIS_MODULE,') - if family.kernel_policy == 'per-op': - cw.p(f'.ops\t\t= {family.c_name}_nl_ops,') - cw.p(f'.n_ops\t\t= ARRAY_SIZE({family.c_name}_nl_ops),') - elif family.kernel_policy == 'split': - cw.p(f'.split_ops\t= {family.c_name}_nl_ops,') - cw.p(f'.n_split_ops\t= ARRAY_SIZE({family.c_name}_nl_ops),') - if family.mcgrps['list']: - cw.p(f'.mcgrps\t\t= {family.c_name}_nl_mcgrps,') - cw.p(f'.n_mcgrps\t= ARRAY_SIZE({family.c_name}_nl_mcgrps),') - if 'sock-priv' in family.kernel_family: - cw.p(f'.sock_priv_size\t= sizeof({family.kernel_family["sock-priv"]}),') - # Force cast here, actual helpers take pointer to the real type. - cw.p(f'.sock_priv_init\t= (void *){family.c_name}_nl_sock_priv_init,') - cw.p(f'.sock_priv_destroy = (void *){family.c_name}_nl_sock_priv_destroy,') - cw.block_end(';') - - -def uapi_enum_start(family, cw, obj, ckey='', enum_name='enum-name'): - start_line = 'enum' - if enum_name in obj: - if obj[enum_name]: - start_line = 'enum ' + c_lower(obj[enum_name]) - elif ckey and ckey in obj: - start_line = 'enum ' + family.c_name + '_' + c_lower(obj[ckey]) - cw.block_start(line=start_line) - - -def render_uapi_unified(family, cw, max_by_define, separate_ntf): - max_name = c_upper(family.get('cmd-max-name', f"{family.op_prefix}MAX")) - cnt_name = c_upper(family.get('cmd-cnt-name', f"__{family.op_prefix}MAX")) - max_value = f"({cnt_name} - 1)" - - uapi_enum_start(family, cw, family['operations'], 'enum-name') - val = 0 - for op in family.msgs.values(): - if separate_ntf and ('notify' in op or 'event' in op): - continue - - suffix = ',' - if op.value != val: - suffix = f" = {op.value}," - val = op.value - cw.p(op.enum_name + suffix) - val += 1 - cw.nl() - cw.p(cnt_name + ('' if max_by_define else ',')) - if not max_by_define: - cw.p(f"{max_name} = {max_value}") - cw.block_end(line=';') - if max_by_define: - cw.p(f"#define {max_name} {max_value}") - cw.nl() - - -def render_uapi_directional(family, cw, max_by_define): - max_name = f"{family.op_prefix}USER_MAX" - cnt_name = f"__{family.op_prefix}USER_CNT" - max_value = f"({cnt_name} - 1)" - - cw.block_start(line='enum') - cw.p(c_upper(f'{family.name}_MSG_USER_NONE = 0,')) - val = 0 - for op in family.msgs.values(): - if 'do' in op and 'event' not in op: - suffix = ',' - if op.value and op.value != val: - suffix = f" = {op.value}," - val = op.value - cw.p(op.enum_name + suffix) - val += 1 - cw.nl() - cw.p(cnt_name + ('' if max_by_define else ',')) - if not max_by_define: - cw.p(f"{max_name} = {max_value}") - cw.block_end(line=';') - if max_by_define: - cw.p(f"#define {max_name} {max_value}") - cw.nl() - - max_name = f"{family.op_prefix}KERNEL_MAX" - cnt_name = f"__{family.op_prefix}KERNEL_CNT" - max_value = f"({cnt_name} - 1)" - - cw.block_start(line='enum') - cw.p(c_upper(f'{family.name}_MSG_KERNEL_NONE = 0,')) - val = 0 - for op in family.msgs.values(): - if ('do' in op and 'reply' in op['do']) or 'notify' in op or 'event' in op: - enum_name = op.enum_name - if 'event' not in op and 'notify' not in op: - enum_name = f'{enum_name}_REPLY' - - suffix = ',' - if op.value and op.value != val: - suffix = f" = {op.value}," - val = op.value - cw.p(enum_name + suffix) - val += 1 - cw.nl() - cw.p(cnt_name + ('' if max_by_define else ',')) - if not max_by_define: - cw.p(f"{max_name} = {max_value}") - cw.block_end(line=';') - if max_by_define: - cw.p(f"#define {max_name} {max_value}") - cw.nl() - - -def render_uapi(family, cw): - hdr_prot = f"_UAPI_LINUX_{c_upper(family.uapi_header_name)}_H" - hdr_prot = hdr_prot.replace('/', '_') - cw.p('#ifndef ' + hdr_prot) - cw.p('#define ' + hdr_prot) - cw.nl() - - defines = [(family.fam_key, family["name"]), - (family.ver_key, family.get('version', 1))] - cw.writes_defines(defines) - cw.nl() - - defines = [] - for const in family['definitions']: - if const['type'] != 'const': - cw.writes_defines(defines) - defines = [] - cw.nl() - - # Write kdoc for enum and flags (one day maybe also structs) - if const['type'] == 'enum' or const['type'] == 'flags': - enum = family.consts[const['name']] - - if enum.header: - continue - - if enum.has_doc(): - if enum.has_entry_doc(): - cw.p('/**') - doc = '' - if 'doc' in enum: - doc = ' - ' + enum['doc'] - cw.write_doc_line(enum.enum_name + doc) - else: - cw.p('/*') - cw.write_doc_line(enum['doc'], indent=False) - for entry in enum.entries.values(): - if entry.has_doc(): - doc = '@' + entry.c_name + ': ' + entry['doc'] - cw.write_doc_line(doc) - cw.p(' */') - - uapi_enum_start(family, cw, const, 'name') - name_pfx = const.get('name-prefix', f"{family.ident_name}-{const['name']}-") - for entry in enum.entries.values(): - suffix = ',' - if entry.value_change: - suffix = f" = {entry.user_value()}" + suffix - cw.p(entry.c_name + suffix) - - if const.get('render-max', False): - cw.nl() - cw.p('/* private: */') - if const['type'] == 'flags': - max_name = c_upper(name_pfx + 'mask') - max_val = f' = {enum.get_mask()},' - cw.p(max_name + max_val) - else: - cnt_name = enum.enum_cnt_name - max_name = c_upper(name_pfx + 'max') - if not cnt_name: - cnt_name = '__' + name_pfx + 'max' - cw.p(c_upper(cnt_name) + ',') - cw.p(max_name + ' = (' + c_upper(cnt_name) + ' - 1)') - cw.block_end(line=';') - cw.nl() - elif const['type'] == 'const': - defines.append([c_upper(family.get('c-define-name', - f"{family.ident_name}-{const['name']}")), - const['value']]) - - if defines: - cw.writes_defines(defines) - cw.nl() - - max_by_define = family.get('max-by-define', False) - - for _, attr_set in family.attr_sets.items(): - if attr_set.subset_of: - continue - - max_value = f"({attr_set.cnt_name} - 1)" - - val = 0 - uapi_enum_start(family, cw, attr_set.yaml, 'enum-name') - for _, attr in attr_set.items(): - suffix = ',' - if attr.value != val: - suffix = f" = {attr.value}," - val = attr.value - val += 1 - cw.p(attr.enum_name + suffix) - if attr_set.items(): - cw.nl() - cw.p(attr_set.cnt_name + ('' if max_by_define else ',')) - if not max_by_define: - cw.p(f"{attr_set.max_name} = {max_value}") - cw.block_end(line=';') - if max_by_define: - cw.p(f"#define {attr_set.max_name} {max_value}") - cw.nl() - - # Commands - separate_ntf = 'async-prefix' in family['operations'] - - if family.msg_id_model == 'unified': - render_uapi_unified(family, cw, max_by_define, separate_ntf) - elif family.msg_id_model == 'directional': - render_uapi_directional(family, cw, max_by_define) - else: - raise Exception(f'Unsupported message enum-model {family.msg_id_model}') - - if separate_ntf: - uapi_enum_start(family, cw, family['operations'], enum_name='async-enum') - for op in family.msgs.values(): - if separate_ntf and not ('notify' in op or 'event' in op): - continue - - suffix = ',' - if 'value' in op: - suffix = f" = {op['value']}," - cw.p(op.enum_name + suffix) - cw.block_end(line=';') - cw.nl() - - # Multicast - defines = [] - for grp in family.mcgrps['list']: - name = grp['name'] - defines.append([c_upper(grp.get('c-define-name', f"{family.ident_name}-mcgrp-{name}")), - f'{name}']) - cw.nl() - if defines: - cw.writes_defines(defines) - cw.nl() - - cw.p(f'#endif /* {hdr_prot} */') - - -def _render_user_ntf_entry(ri, op): - ri.cw.block_start(line=f"[{op.enum_name}] = ") - ri.cw.p(f".alloc_sz\t= sizeof({type_name(ri, 'event')}),") - ri.cw.p(f".cb\t\t= {op_prefix(ri, 'reply', deref=True)}_parse,") - ri.cw.p(f".policy\t\t= &{ri.struct['reply'].render_name}_nest,") - ri.cw.p(f".free\t\t= (void *){op_prefix(ri, 'notify')}_free,") - ri.cw.block_end(line=',') - - -def render_user_family(family, cw, prototype): - symbol = f'const struct ynl_family ynl_{family.c_name}_family' - if prototype: - cw.p(f'extern {symbol};') - return - - if family.ntfs: - cw.block_start(line=f"static const struct ynl_ntf_info {family['name']}_ntf_info[] = ") - for ntf_op_name, ntf_op in family.ntfs.items(): - if 'notify' in ntf_op: - op = family.ops[ntf_op['notify']] - ri = RenderInfo(cw, family, "user", op, "notify") - elif 'event' in ntf_op: - ri = RenderInfo(cw, family, "user", ntf_op, "event") - else: - raise Exception('Invalid notification ' + ntf_op_name) - _render_user_ntf_entry(ri, ntf_op) - for op_name, op in family.ops.items(): - if 'event' not in op: - continue - ri = RenderInfo(cw, family, "user", op, "event") - _render_user_ntf_entry(ri, op) - cw.block_end(line=";") - cw.nl() - - cw.block_start(f'{symbol} = ') - cw.p(f'.name\t\t= "{family.c_name}",') - if family.fixed_header: - cw.p(f'.hdr_len\t= sizeof(struct genlmsghdr) + sizeof(struct {c_lower(family.fixed_header)}),') - else: - cw.p('.hdr_len\t= sizeof(struct genlmsghdr),') - if family.ntfs: - cw.p(f".ntf_info\t= {family['name']}_ntf_info,") - cw.p(f".ntf_info_size\t= YNL_ARRAY_SIZE({family['name']}_ntf_info),") - cw.block_end(line=';') - - -def family_contains_bitfield32(family): - for _, attr_set in family.attr_sets.items(): - if attr_set.subset_of: - continue - for _, attr in attr_set.items(): - if attr.type == "bitfield32": - return True - return False - - -def find_kernel_root(full_path): - sub_path = '' - while True: - sub_path = os.path.join(os.path.basename(full_path), sub_path) - full_path = os.path.dirname(full_path) - maintainers = os.path.join(full_path, "MAINTAINERS") - if os.path.exists(maintainers): - return full_path, sub_path[:-1] - - -def main(): - parser = argparse.ArgumentParser(description='Netlink simple parsing generator') - parser.add_argument('--mode', dest='mode', type=str, required=True, - choices=('user', 'kernel', 'uapi')) - parser.add_argument('--spec', dest='spec', type=str, required=True) - parser.add_argument('--header', dest='header', action='store_true', default=None) - parser.add_argument('--source', dest='header', action='store_false') - parser.add_argument('--user-header', nargs='+', default=[]) - parser.add_argument('--cmp-out', action='store_true', default=None, - help='Do not overwrite the output file if the new output is identical to the old') - parser.add_argument('--exclude-op', action='append', default=[]) - parser.add_argument('-o', dest='out_file', type=str, default=None) - args = parser.parse_args() - - if args.header is None: - parser.error("--header or --source is required") - - exclude_ops = [re.compile(expr) for expr in args.exclude_op] - - try: - parsed = Family(args.spec, exclude_ops) - if parsed.license != '((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)': - print('Spec license:', parsed.license) - print('License must be: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)') - os.sys.exit(1) - except yaml.YAMLError as exc: - print(exc) - os.sys.exit(1) - return - - cw = CodeWriter(BaseNlLib(), args.out_file, overwrite=(not args.cmp_out)) - - _, spec_kernel = find_kernel_root(args.spec) - if args.mode == 'uapi' or args.header: - cw.p(f'/* SPDX-License-Identifier: {parsed.license} */') - else: - cw.p(f'// SPDX-License-Identifier: {parsed.license}') - cw.p("/* Do not edit directly, auto-generated from: */") - cw.p(f"/*\t{spec_kernel} */") - cw.p(f"/* YNL-GEN {args.mode} {'header' if args.header else 'source'} */") - if args.exclude_op or args.user_header: - line = '' - line += ' --user-header '.join([''] + args.user_header) - line += ' --exclude-op '.join([''] + args.exclude_op) - cw.p(f'/* YNL-ARG{line} */') - cw.nl() - - if args.mode == 'uapi': - render_uapi(parsed, cw) - return - - hdr_prot = f"_LINUX_{parsed.c_name.upper()}_GEN_H" - if args.header: - cw.p('#ifndef ' + hdr_prot) - cw.p('#define ' + hdr_prot) - cw.nl() - - if args.out_file: - hdr_file = os.path.basename(args.out_file[:-2]) + ".h" - else: - hdr_file = "generated_header_file.h" - - if args.mode == 'kernel': - cw.p('#include ') - cw.p('#include ') - cw.nl() - if not args.header: - if args.out_file: - cw.p(f'#include "{hdr_file}"') - cw.nl() - headers = ['uapi/' + parsed.uapi_header] - headers += parsed.kernel_family.get('headers', []) - else: - cw.p('#include ') - cw.p('#include ') - if args.header: - cw.p('#include ') - if family_contains_bitfield32(parsed): - cw.p('#include ') - else: - cw.p(f'#include "{hdr_file}"') - cw.p('#include "ynl.h"') - headers = [] - for definition in parsed['definitions']: - if 'header' in definition: - headers.append(definition['header']) - if args.mode == 'user': - headers.append(parsed.uapi_header) - seen_header = [] - for one in headers: - if one not in seen_header: - cw.p(f"#include <{one}>") - seen_header.append(one) - cw.nl() - - if args.mode == "user": - if not args.header: - cw.p("#include ") - cw.nl() - for one in args.user_header: - cw.p(f'#include "{one}"') - else: - cw.p('struct ynl_sock;') - cw.nl() - render_user_family(parsed, cw, True) - cw.nl() - - if args.mode == "kernel": - if args.header: - for _, struct in sorted(parsed.pure_nested_structs.items()): - if struct.request: - cw.p('/* Common nested types */') - break - for attr_set, struct in sorted(parsed.pure_nested_structs.items()): - if struct.request: - print_req_policy_fwd(cw, struct) - cw.nl() - - if parsed.kernel_policy == 'global': - cw.p(f"/* Global operation policy for {parsed.name} */") - - struct = Struct(parsed, parsed.global_policy_set, type_list=parsed.global_policy) - print_req_policy_fwd(cw, struct) - cw.nl() - - if parsed.kernel_policy in {'per-op', 'split'}: - for op_name, op in parsed.ops.items(): - if 'do' in op and 'event' not in op: - ri = RenderInfo(cw, parsed, args.mode, op, "do") - print_req_policy_fwd(cw, ri.struct['request'], ri=ri) - cw.nl() - - print_kernel_op_table_hdr(parsed, cw) - print_kernel_mcgrp_hdr(parsed, cw) - print_kernel_family_struct_hdr(parsed, cw) - else: - print_kernel_policy_ranges(parsed, cw) - - for _, struct in sorted(parsed.pure_nested_structs.items()): - if struct.request: - cw.p('/* Common nested types */') - break - for attr_set, struct in sorted(parsed.pure_nested_structs.items()): - if struct.request: - print_req_policy(cw, struct) - cw.nl() - - if parsed.kernel_policy == 'global': - cw.p(f"/* Global operation policy for {parsed.name} */") - - struct = Struct(parsed, parsed.global_policy_set, type_list=parsed.global_policy) - print_req_policy(cw, struct) - cw.nl() - - for op_name, op in parsed.ops.items(): - if parsed.kernel_policy in {'per-op', 'split'}: - for op_mode in ['do', 'dump']: - if op_mode in op and 'request' in op[op_mode]: - cw.p(f"/* {op.enum_name} - {op_mode} */") - ri = RenderInfo(cw, parsed, args.mode, op, op_mode) - print_req_policy(cw, ri.struct['request'], ri=ri) - cw.nl() - - print_kernel_op_table(parsed, cw) - print_kernel_mcgrp_src(parsed, cw) - print_kernel_family_struct_src(parsed, cw) - - if args.mode == "user": - if args.header: - cw.p('/* Enums */') - put_op_name_fwd(parsed, cw) - - for name, const in parsed.consts.items(): - if isinstance(const, EnumSet): - put_enum_to_str_fwd(parsed, cw, const) - cw.nl() - - cw.p('/* Common nested types */') - for attr_set, struct in parsed.pure_nested_structs.items(): - ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set) - print_type_full(ri, struct) - - for op_name, op in parsed.ops.items(): - cw.p(f"/* ============== {op.enum_name} ============== */") - - if 'do' in op and 'event' not in op: - cw.p(f"/* {op.enum_name} - do */") - ri = RenderInfo(cw, parsed, args.mode, op, "do") - print_req_type(ri) - print_req_type_helpers(ri) - cw.nl() - print_rsp_type(ri) - print_rsp_type_helpers(ri) - cw.nl() - print_req_prototype(ri) - cw.nl() - - if 'dump' in op: - cw.p(f"/* {op.enum_name} - dump */") - ri = RenderInfo(cw, parsed, args.mode, op, 'dump') - print_req_type(ri) - print_req_type_helpers(ri) - if not ri.type_consistent: - print_rsp_type(ri) - print_wrapped_type(ri) - print_dump_prototype(ri) - cw.nl() - - if op.has_ntf: - cw.p(f"/* {op.enum_name} - notify */") - ri = RenderInfo(cw, parsed, args.mode, op, 'notify') - if not ri.type_consistent: - raise Exception(f'Only notifications with consistent types supported ({op.name})') - print_wrapped_type(ri) - - for op_name, op in parsed.ntfs.items(): - if 'event' in op: - ri = RenderInfo(cw, parsed, args.mode, op, 'event') - cw.p(f"/* {op.enum_name} - event */") - print_rsp_type(ri) - cw.nl() - print_wrapped_type(ri) - cw.nl() - else: - cw.p('/* Enums */') - put_op_name(parsed, cw) - - for name, const in parsed.consts.items(): - if isinstance(const, EnumSet): - put_enum_to_str(parsed, cw, const) - cw.nl() - - has_recursive_nests = False - cw.p('/* Policies */') - for struct in parsed.pure_nested_structs.values(): - if struct.recursive: - put_typol_fwd(cw, struct) - has_recursive_nests = True - if has_recursive_nests: - cw.nl() - for name in parsed.pure_nested_structs: - struct = Struct(parsed, name) - put_typol(cw, struct) - for name in parsed.root_sets: - struct = Struct(parsed, name) - put_typol(cw, struct) - - cw.p('/* Common nested types */') - if has_recursive_nests: - for attr_set, struct in parsed.pure_nested_structs.items(): - ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set) - free_rsp_nested_prototype(ri) - if struct.request: - put_req_nested_prototype(ri, struct) - if struct.reply: - parse_rsp_nested_prototype(ri, struct) - cw.nl() - for attr_set, struct in parsed.pure_nested_structs.items(): - ri = RenderInfo(cw, parsed, args.mode, "", "", attr_set) - - free_rsp_nested(ri, struct) - if struct.request: - put_req_nested(ri, struct) - if struct.reply: - parse_rsp_nested(ri, struct) - - for op_name, op in parsed.ops.items(): - cw.p(f"/* ============== {op.enum_name} ============== */") - if 'do' in op and 'event' not in op: - cw.p(f"/* {op.enum_name} - do */") - ri = RenderInfo(cw, parsed, args.mode, op, "do") - print_req_free(ri) - print_rsp_free(ri) - parse_rsp_msg(ri) - print_req(ri) - cw.nl() - - if 'dump' in op: - cw.p(f"/* {op.enum_name} - dump */") - ri = RenderInfo(cw, parsed, args.mode, op, "dump") - if not ri.type_consistent: - parse_rsp_msg(ri, deref=True) - print_req_free(ri) - print_dump_type_free(ri) - print_dump(ri) - cw.nl() - - if op.has_ntf: - cw.p(f"/* {op.enum_name} - notify */") - ri = RenderInfo(cw, parsed, args.mode, op, 'notify') - if not ri.type_consistent: - raise Exception(f'Only notifications with consistent types supported ({op.name})') - print_ntf_type_free(ri) - - for op_name, op in parsed.ntfs.items(): - if 'event' in op: - cw.p(f"/* {op.enum_name} - event */") - - ri = RenderInfo(cw, parsed, args.mode, op, "do") - parse_rsp_msg(ri) - - ri = RenderInfo(cw, parsed, args.mode, op, "event") - print_ntf_type_free(ri) - cw.nl() - render_user_family(parsed, cw, False) - - if args.header: - cw.p(f'#endif /* {hdr_prot} */') - - -if __name__ == "__main__": - main() diff --git a/tools/net/ynl/ynl-gen-rst.py b/tools/net/ynl/ynl-gen-rst.py deleted file mode 100755 index 6c56d0d726b4..000000000000 --- a/tools/net/ynl/ynl-gen-rst.py +++ /dev/null @@ -1,453 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-License-Identifier: GPL-2.0 -# -*- coding: utf-8; mode: python -*- - -""" - Script to auto generate the documentation for Netlink specifications. - - :copyright: Copyright (C) 2023 Breno Leitao - :license: GPL Version 2, June 1991 see linux/COPYING for details. - - This script performs extensive parsing to the Linux kernel's netlink YAML - spec files, in an effort to avoid needing to heavily mark up the original - YAML file. - - This code is split in three big parts: - 1) RST formatters: Use to convert a string to a RST output - 2) Parser helpers: Functions to parse the YAML data structure - 3) Main function and small helpers -""" - -from typing import Any, Dict, List -import os.path -import sys -import argparse -import logging -import yaml - - -SPACE_PER_LEVEL = 4 - - -# RST Formatters -# ============== -def headroom(level: int) -> str: - """Return space to format""" - return " " * (level * SPACE_PER_LEVEL) - - -def bold(text: str) -> str: - """Format bold text""" - return f"**{text}**" - - -def inline(text: str) -> str: - """Format inline text""" - return f"``{text}``" - - -def sanitize(text: str) -> str: - """Remove newlines and multiple spaces""" - # This is useful for some fields that are spread across multiple lines - return str(text).replace("\n", " ").strip() - - -def rst_fields(key: str, value: str, level: int = 0) -> str: - """Return a RST formatted field""" - return headroom(level) + f":{key}: {value}" - - -def rst_definition(key: str, value: Any, level: int = 0) -> str: - """Format a single rst definition""" - return headroom(level) + key + "\n" + headroom(level + 1) + str(value) - - -def rst_paragraph(paragraph: str, level: int = 0) -> str: - """Return a formatted paragraph""" - return headroom(level) + paragraph - - -def rst_bullet(item: str, level: int = 0) -> str: - """Return a formatted a bullet""" - return headroom(level) + f"- {item}" - - -def rst_subsection(title: str) -> str: - """Add a sub-section to the document""" - return f"{title}\n" + "-" * len(title) - - -def rst_subsubsection(title: str) -> str: - """Add a sub-sub-section to the document""" - return f"{title}\n" + "~" * len(title) - - -def rst_section(namespace: str, prefix: str, title: str) -> str: - """Add a section to the document""" - return f".. _{namespace}-{prefix}-{title}:\n\n{title}\n" + "=" * len(title) - - -def rst_subtitle(title: str) -> str: - """Add a subtitle to the document""" - return "\n" + "-" * len(title) + f"\n{title}\n" + "-" * len(title) + "\n\n" - - -def rst_title(title: str) -> str: - """Add a title to the document""" - return "=" * len(title) + f"\n{title}\n" + "=" * len(title) + "\n\n" - - -def rst_list_inline(list_: List[str], level: int = 0) -> str: - """Format a list using inlines""" - return headroom(level) + "[" + ", ".join(inline(i) for i in list_) + "]" - - -def rst_ref(namespace: str, prefix: str, name: str) -> str: - """Add a hyperlink to the document""" - mappings = {'enum': 'definition', - 'fixed-header': 'definition', - 'nested-attributes': 'attribute-set', - 'struct': 'definition'} - if prefix in mappings: - prefix = mappings[prefix] - return f":ref:`{namespace}-{prefix}-{name}`" - - -def rst_header() -> str: - """The headers for all the auto generated RST files""" - lines = [] - - lines.append(rst_paragraph(".. SPDX-License-Identifier: GPL-2.0")) - lines.append(rst_paragraph(".. NOTE: This document was auto-generated.\n\n")) - - return "\n".join(lines) - - -def rst_toctree(maxdepth: int = 2) -> str: - """Generate a toctree RST primitive""" - lines = [] - - lines.append(".. toctree::") - lines.append(f" :maxdepth: {maxdepth}\n\n") - - return "\n".join(lines) - - -def rst_label(title: str) -> str: - """Return a formatted label""" - return f".. _{title}:\n\n" - - -# Parsers -# ======= - - -def parse_mcast_group(mcast_group: List[Dict[str, Any]]) -> str: - """Parse 'multicast' group list and return a formatted string""" - lines = [] - for group in mcast_group: - lines.append(rst_bullet(group["name"])) - - return "\n".join(lines) - - -def parse_do(do_dict: Dict[str, Any], level: int = 0) -> str: - """Parse 'do' section and return a formatted string""" - lines = [] - for key in do_dict.keys(): - lines.append(rst_paragraph(bold(key), level + 1)) - if key in ['request', 'reply']: - lines.append(parse_do_attributes(do_dict[key], level + 1) + "\n") - else: - lines.append(headroom(level + 2) + do_dict[key] + "\n") - - return "\n".join(lines) - - -def parse_do_attributes(attrs: Dict[str, Any], level: int = 0) -> str: - """Parse 'attributes' section""" - if "attributes" not in attrs: - return "" - lines = [rst_fields("attributes", rst_list_inline(attrs["attributes"]), level + 1)] - - return "\n".join(lines) - - -def parse_operations(operations: List[Dict[str, Any]], namespace: str) -> str: - """Parse operations block""" - preprocessed = ["name", "doc", "title", "do", "dump", "flags"] - linkable = ["fixed-header", "attribute-set"] - lines = [] - - for operation in operations: - lines.append(rst_section(namespace, 'operation', operation["name"])) - lines.append(rst_paragraph(operation["doc"]) + "\n") - - for key in operation.keys(): - if key in preprocessed: - # Skip the special fields - continue - value = operation[key] - if key in linkable: - value = rst_ref(namespace, key, value) - lines.append(rst_fields(key, value, 0)) - if 'flags' in operation: - lines.append(rst_fields('flags', rst_list_inline(operation['flags']))) - - if "do" in operation: - lines.append(rst_paragraph(":do:", 0)) - lines.append(parse_do(operation["do"], 0)) - if "dump" in operation: - lines.append(rst_paragraph(":dump:", 0)) - lines.append(parse_do(operation["dump"], 0)) - - # New line after fields - lines.append("\n") - - return "\n".join(lines) - - -def parse_entries(entries: List[Dict[str, Any]], level: int) -> str: - """Parse a list of entries""" - ignored = ["pad"] - lines = [] - for entry in entries: - if isinstance(entry, dict): - # entries could be a list or a dictionary - field_name = entry.get("name", "") - if field_name in ignored: - continue - type_ = entry.get("type") - if type_: - field_name += f" ({inline(type_)})" - lines.append( - rst_fields(field_name, sanitize(entry.get("doc", "")), level) - ) - elif isinstance(entry, list): - lines.append(rst_list_inline(entry, level)) - else: - lines.append(rst_bullet(inline(sanitize(entry)), level)) - - lines.append("\n") - return "\n".join(lines) - - -def parse_definitions(defs: Dict[str, Any], namespace: str) -> str: - """Parse definitions section""" - preprocessed = ["name", "entries", "members"] - ignored = ["render-max"] # This is not printed - lines = [] - - for definition in defs: - lines.append(rst_section(namespace, 'definition', definition["name"])) - for k in definition.keys(): - if k in preprocessed + ignored: - continue - lines.append(rst_fields(k, sanitize(definition[k]), 0)) - - # Field list needs to finish with a new line - lines.append("\n") - if "entries" in definition: - lines.append(rst_paragraph(":entries:", 0)) - lines.append(parse_entries(definition["entries"], 1)) - if "members" in definition: - lines.append(rst_paragraph(":members:", 0)) - lines.append(parse_entries(definition["members"], 1)) - - return "\n".join(lines) - - -def parse_attr_sets(entries: List[Dict[str, Any]], namespace: str) -> str: - """Parse attribute from attribute-set""" - preprocessed = ["name", "type"] - linkable = ["enum", "nested-attributes", "struct", "sub-message"] - ignored = ["checks"] - lines = [] - - for entry in entries: - lines.append(rst_section(namespace, 'attribute-set', entry["name"])) - for attr in entry["attributes"]: - type_ = attr.get("type") - attr_line = attr["name"] - if type_: - # Add the attribute type in the same line - attr_line += f" ({inline(type_)})" - - lines.append(rst_subsubsection(attr_line)) - - for k in attr.keys(): - if k in preprocessed + ignored: - continue - if k in linkable: - value = rst_ref(namespace, k, attr[k]) - else: - value = sanitize(attr[k]) - lines.append(rst_fields(k, value, 0)) - lines.append("\n") - - return "\n".join(lines) - - -def parse_sub_messages(entries: List[Dict[str, Any]], namespace: str) -> str: - """Parse sub-message definitions""" - lines = [] - - for entry in entries: - lines.append(rst_section(namespace, 'sub-message', entry["name"])) - for fmt in entry["formats"]: - value = fmt["value"] - - lines.append(rst_bullet(bold(value))) - for attr in ['fixed-header', 'attribute-set']: - if attr in fmt: - lines.append(rst_fields(attr, - rst_ref(namespace, attr, fmt[attr]), - 1)) - lines.append("\n") - - return "\n".join(lines) - - -def parse_yaml(obj: Dict[str, Any]) -> str: - """Format the whole YAML into a RST string""" - lines = [] - - # Main header - - lines.append(rst_header()) - - family = obj['name'] - - title = f"Family ``{family}`` netlink specification" - lines.append(rst_title(title)) - lines.append(rst_paragraph(".. contents:: :depth: 3\n")) - - if "doc" in obj: - lines.append(rst_subtitle("Summary")) - lines.append(rst_paragraph(obj["doc"], 0)) - - # Operations - if "operations" in obj: - lines.append(rst_subtitle("Operations")) - lines.append(parse_operations(obj["operations"]["list"], family)) - - # Multicast groups - if "mcast-groups" in obj: - lines.append(rst_subtitle("Multicast groups")) - lines.append(parse_mcast_group(obj["mcast-groups"]["list"])) - - # Definitions - if "definitions" in obj: - lines.append(rst_subtitle("Definitions")) - lines.append(parse_definitions(obj["definitions"], family)) - - # Attributes set - if "attribute-sets" in obj: - lines.append(rst_subtitle("Attribute sets")) - lines.append(parse_attr_sets(obj["attribute-sets"], family)) - - # Sub-messages - if "sub-messages" in obj: - lines.append(rst_subtitle("Sub-messages")) - lines.append(parse_sub_messages(obj["sub-messages"], family)) - - return "\n".join(lines) - - -# Main functions -# ============== - - -def parse_arguments() -> argparse.Namespace: - """Parse arguments from user""" - parser = argparse.ArgumentParser(description="Netlink RST generator") - - parser.add_argument("-v", "--verbose", action="store_true") - parser.add_argument("-o", "--output", help="Output file name") - - # Index and input are mutually exclusive - group = parser.add_mutually_exclusive_group() - group.add_argument( - "-x", "--index", action="store_true", help="Generate the index page" - ) - group.add_argument("-i", "--input", help="YAML file name") - - args = parser.parse_args() - - if args.verbose: - logging.basicConfig(level=logging.DEBUG) - - if args.input and not os.path.isfile(args.input): - logging.warning("%s is not a valid file.", args.input) - sys.exit(-1) - - if not args.output: - logging.error("No output file specified.") - sys.exit(-1) - - if os.path.isfile(args.output): - logging.debug("%s already exists. Overwriting it.", args.output) - - return args - - -def parse_yaml_file(filename: str) -> str: - """Transform the YAML specified by filename into a rst-formmated string""" - with open(filename, "r", encoding="utf-8") as spec_file: - yaml_data = yaml.safe_load(spec_file) - content = parse_yaml(yaml_data) - - return content - - -def write_to_rstfile(content: str, filename: str) -> None: - """Write the generated content into an RST file""" - logging.debug("Saving RST file to %s", filename) - - with open(filename, "w", encoding="utf-8") as rst_file: - rst_file.write(content) - - -def generate_main_index_rst(output: str) -> None: - """Generate the `networking_spec/index` content and write to the file""" - lines = [] - - lines.append(rst_header()) - lines.append(rst_label("specs")) - lines.append(rst_title("Netlink Family Specifications")) - lines.append(rst_toctree(1)) - - index_dir = os.path.dirname(output) - logging.debug("Looking for .rst files in %s", index_dir) - for filename in sorted(os.listdir(index_dir)): - if not filename.endswith(".rst") or filename == "index.rst": - continue - lines.append(f" {filename.replace('.rst', '')}\n") - - logging.debug("Writing an index file at %s", output) - write_to_rstfile("".join(lines), output) - - -def main() -> None: - """Main function that reads the YAML files and generates the RST files""" - - args = parse_arguments() - - if args.input: - logging.debug("Parsing %s", args.input) - try: - content = parse_yaml_file(os.path.join(args.input)) - except Exception as exception: - logging.warning("Failed to parse %s.", args.input) - logging.warning(exception) - sys.exit(-1) - - write_to_rstfile(content, args.output) - - if args.index: - # Generate the index RST file - generate_main_index_rst(args.output) - - -if __name__ == "__main__": - main() diff --git a/tools/net/ynl/ynl-regen.sh b/tools/net/ynl/ynl-regen.sh index a37304dcc88e..81b4ecd89100 100755 --- a/tools/net/ynl/ynl-regen.sh +++ b/tools/net/ynl/ynl-regen.sh @@ -1,7 +1,7 @@ #!/bin/bash # SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause -TOOL=$(dirname $(realpath $0))/ynl-gen-c.py +TOOL=$(dirname $(realpath $0))/pyynl/ynl_gen_c.py force= search= diff --git a/tools/testing/selftests/net/lib/py/ynl.py b/tools/testing/selftests/net/lib/py/ynl.py index 076a7e8dc3eb..ad1e36baee2a 100644 --- a/tools/testing/selftests/net/lib/py/ynl.py +++ b/tools/testing/selftests/net/lib/py/ynl.py @@ -13,14 +13,14 @@ try: SPEC_PATH = KSFT_DIR / "net/lib/specs" sys.path.append(tools_full_path.as_posix()) - from net.lib.ynl.lib import YnlFamily, NlError + from net.lib.ynl.pyynl.lib import YnlFamily, NlError else: # Running in tree tools_full_path = KSRC / "tools" SPEC_PATH = KSRC / "Documentation/netlink/specs" sys.path.append(tools_full_path.as_posix()) - from net.ynl.lib import YnlFamily, NlError + from net.ynl.pyynl.lib import YnlFamily, NlError except ModuleNotFoundError as e: ksft_pr("Failed importing `ynl` library from kernel sources") ksft_pr(str(e)) diff --git a/tools/testing/selftests/net/ynl.mk b/tools/testing/selftests/net/ynl.mk index d43afe243779..12e7cae251be 100644 --- a/tools/testing/selftests/net/ynl.mk +++ b/tools/testing/selftests/net/ynl.mk @@ -31,7 +31,8 @@ $(OUTPUT)/libynl.a: $(YNL_SPECS) $(OUTPUT)/.libynl-$(YNL_GENS_HASH).sig $(Q)cp $(top_srcdir)/tools/net/ynl/libynl.a $(OUTPUT)/libynl.a EXTRA_CLEAN += \ - $(top_srcdir)/tools/net/ynl/lib/__pycache__ \ + $(top_srcdir)/tools/net/ynl/pyynl/__pycache__ \ + $(top_srcdir)/tools/net/ynl/pyynl/lib/__pycache__ \ $(top_srcdir)/tools/net/ynl/lib/*.[ado] \ $(OUTPUT)/.libynl-*.sig \ $(OUTPUT)/libynl.a -- cgit v1.2.3