diff options
author | Chuck Lever <chuck.lever@oracle.com> | 2024-09-13 14:08:13 -0400 |
---|---|---|
committer | Chuck Lever <chuck.lever@oracle.com> | 2024-09-20 19:31:39 -0400 |
commit | 4b132aacb0768ac1e652cf517097ea6f237214b9 (patch) | |
tree | 74a112db399fec7cfe72202bc092e0eb29695ced /tools/net/sunrpc/xdrgen/subcmds/source.py | |
parent | 45bb63ed20e02ae146336412889fe5450316a84f (diff) |
tools: Add xdrgen
Add a Python-based tool for translating XDR specifications into XDR
encoder and decoder functions written in the Linux kernel's C coding
style. The generator attempts to match the usual C coding style of
the Linux kernel's SunRPC consumers.
This approach is similar to the netlink code generator in
tools/net/ynl .
The maintainability benefits of machine-generated XDR code include:
- Stronger type checking
- Reduces the number of bugs introduced by human error
- Makes the XDR code easier to audit and analyze
- Enables rapid prototyping of new RPC-based protocols
- Hardens the layering between protocol logic and marshaling
- Makes it easier to add observability on demand
- Unit tests might be built for both the tool and (automatically)
for the generated code
In addition, converting the XDR layer to use memory-safe languages
such as Rust will be easier if much of the code can be converted
automatically.
Tested-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Diffstat (limited to 'tools/net/sunrpc/xdrgen/subcmds/source.py')
-rw-r--r-- | tools/net/sunrpc/xdrgen/subcmds/source.py | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/tools/net/sunrpc/xdrgen/subcmds/source.py b/tools/net/sunrpc/xdrgen/subcmds/source.py new file mode 100644 index 000000000000..00c04ad15b89 --- /dev/null +++ b/tools/net/sunrpc/xdrgen/subcmds/source.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# ex: set filetype=python: + +"""Translate an XDR specification into executable code that +can be compiled for the Linux kernel.""" + +import logging + +from argparse import Namespace +from lark import logger +from lark.exceptions import UnexpectedInput + +from generators.source_top import XdrSourceTopGenerator +from generators.enum import XdrEnumGenerator +from generators.pointer import XdrPointerGenerator +from generators.program import XdrProgramGenerator +from generators.typedef import XdrTypedefGenerator +from generators.struct import XdrStructGenerator +from generators.union import XdrUnionGenerator + +from xdr_ast import transform_parse_tree, _RpcProgram, Specification +from xdr_ast import _XdrAst, _XdrEnum, _XdrPointer +from xdr_ast import _XdrStruct, _XdrTypedef, _XdrUnion + +from xdr_parse import xdr_parser, set_xdr_annotate + +logger.setLevel(logging.INFO) + + +def emit_source_decoder(node: _XdrAst, language: str, peer: str) -> None: + """Emit one XDR decoder function for a source file""" + if isinstance(node, _XdrEnum): + gen = XdrEnumGenerator(language, peer) + elif isinstance(node, _XdrPointer): + gen = XdrPointerGenerator(language, peer) + elif isinstance(node, _XdrTypedef): + gen = XdrTypedefGenerator(language, peer) + elif isinstance(node, _XdrStruct): + gen = XdrStructGenerator(language, peer) + elif isinstance(node, _XdrUnion): + gen = XdrUnionGenerator(language, peer) + elif isinstance(node, _RpcProgram): + gen = XdrProgramGenerator(language, peer) + else: + return + gen.emit_decoder(node) + + +def emit_source_encoder(node: _XdrAst, language: str, peer: str) -> None: + """Emit one XDR encoder function for a source file""" + if isinstance(node, _XdrEnum): + gen = XdrEnumGenerator(language, peer) + elif isinstance(node, _XdrPointer): + gen = XdrPointerGenerator(language, peer) + elif isinstance(node, _XdrTypedef): + gen = XdrTypedefGenerator(language, peer) + elif isinstance(node, _XdrStruct): + gen = XdrStructGenerator(language, peer) + elif isinstance(node, _XdrUnion): + gen = XdrUnionGenerator(language, peer) + elif isinstance(node, _RpcProgram): + gen = XdrProgramGenerator(language, peer) + else: + return + gen.emit_encoder(node) + + +def generate_server_source(filename: str, root: Specification, language: str) -> None: + """Generate server-side source code""" + + gen = XdrSourceTopGenerator(language, "server") + gen.emit_source(filename, root) + + for definition in root.definitions: + emit_source_decoder(definition.value, language, "server") + for definition in root.definitions: + emit_source_encoder(definition.value, language, "server") + + +def generate_client_source(filename: str, root: Specification, language: str) -> None: + """Generate server-side source code""" + + gen = XdrSourceTopGenerator(language, "client") + gen.emit_source(filename, root) + + # cel: todo: client needs XDR size macros + + for definition in root.definitions: + emit_source_encoder(definition.value, language, "client") + for definition in root.definitions: + emit_source_decoder(definition.value, language, "client") + + # cel: todo: client needs PROC macros + + +def handle_parse_error(e: UnexpectedInput) -> bool: + """Simple parse error reporting, no recovery attempted""" + print(e) + return True + + +def subcmd(args: Namespace) -> int: + """Generate encoder and decoder functions""" + + set_xdr_annotate(args.annotate) + parser = xdr_parser() + with open(args.filename, encoding="utf-8") as f: + parse_tree = parser.parse(f.read(), on_error=handle_parse_error) + ast = transform_parse_tree(parse_tree) + match args.peer: + case "server": + generate_server_source(args.filename, ast, args.language) + case "client": + generate_client_source(args.filename, ast, args.language) + case _: + print("Code generation for", args.peer, "is not yet supported") + + return 0 |