mirror of
https://github.com/torvalds/linux.git
synced 2025-11-02 17:49:03 +02:00
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>
168 lines
6.2 KiB
Python
168 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
# ex: set filetype=python:
|
|
|
|
"""Generate code for an RPC program's procedures"""
|
|
|
|
from jinja2 import Environment
|
|
|
|
from generators import SourceGenerator, create_jinja2_environment
|
|
from xdr_ast import _RpcProgram, _RpcVersion, excluded_apis
|
|
|
|
|
|
def emit_version_definitions(
|
|
environment: Environment, program: str, version: _RpcVersion
|
|
) -> None:
|
|
"""Emit procedure numbers for each RPC version's procedures"""
|
|
template = environment.get_template("definition/open.j2")
|
|
print(template.render(program=program.upper()))
|
|
|
|
template = environment.get_template("definition/procedure.j2")
|
|
for procedure in version.procedures:
|
|
if procedure.name not in excluded_apis:
|
|
print(
|
|
template.render(
|
|
name=procedure.name,
|
|
value=procedure.number,
|
|
)
|
|
)
|
|
|
|
template = environment.get_template("definition/close.j2")
|
|
print(template.render())
|
|
|
|
|
|
def emit_version_declarations(
|
|
environment: Environment, program: str, version: _RpcVersion
|
|
) -> None:
|
|
"""Emit declarations for each RPC version's procedures"""
|
|
arguments = set()
|
|
for procedure in version.procedures:
|
|
if procedure.name not in excluded_apis:
|
|
arguments.add(procedure.argument.type_name)
|
|
if len(arguments) > 0:
|
|
print("")
|
|
template = environment.get_template("declaration/argument.j2")
|
|
for argument in arguments:
|
|
print(template.render(program=program, argument=argument))
|
|
|
|
results = set()
|
|
for procedure in version.procedures:
|
|
if procedure.name not in excluded_apis:
|
|
results.add(procedure.result.type_name)
|
|
if len(results) > 0:
|
|
print("")
|
|
template = environment.get_template("declaration/result.j2")
|
|
for result in results:
|
|
print(template.render(program=program, result=result))
|
|
|
|
|
|
def emit_version_argument_decoders(
|
|
environment: Environment, program: str, version: _RpcVersion
|
|
) -> None:
|
|
"""Emit server argument decoders for each RPC version's procedures"""
|
|
arguments = set()
|
|
for procedure in version.procedures:
|
|
if procedure.name not in excluded_apis:
|
|
arguments.add(procedure.argument.type_name)
|
|
|
|
template = environment.get_template("decoder/argument.j2")
|
|
for argument in arguments:
|
|
print(template.render(program=program, argument=argument))
|
|
|
|
|
|
def emit_version_result_decoders(
|
|
environment: Environment, program: str, version: _RpcVersion
|
|
) -> None:
|
|
"""Emit client result decoders for each RPC version's procedures"""
|
|
results = set()
|
|
for procedure in version.procedures:
|
|
if procedure.name not in excluded_apis:
|
|
results.add(procedure.result.type_name)
|
|
|
|
template = environment.get_template("decoder/result.j2")
|
|
for result in results:
|
|
print(template.render(program=program, result=result))
|
|
|
|
|
|
def emit_version_argument_encoders(
|
|
environment: Environment, program: str, version: _RpcVersion
|
|
) -> None:
|
|
"""Emit client argument encoders for each RPC version's procedures"""
|
|
arguments = set()
|
|
for procedure in version.procedures:
|
|
if procedure.name not in excluded_apis:
|
|
arguments.add(procedure.argument.type_name)
|
|
|
|
template = environment.get_template("encoder/argument.j2")
|
|
for argument in arguments:
|
|
print(template.render(program=program, argument=argument))
|
|
|
|
|
|
def emit_version_result_encoders(
|
|
environment: Environment, program: str, version: _RpcVersion
|
|
) -> None:
|
|
"""Emit server result encoders for each RPC version's procedures"""
|
|
results = set()
|
|
for procedure in version.procedures:
|
|
if procedure.name not in excluded_apis:
|
|
results.add(procedure.result.type_name)
|
|
|
|
template = environment.get_template("encoder/result.j2")
|
|
for result in results:
|
|
print(template.render(program=program, result=result))
|
|
|
|
|
|
class XdrProgramGenerator(SourceGenerator):
|
|
"""Generate source code for an RPC program's procedures"""
|
|
|
|
def __init__(self, language: str, peer: str):
|
|
"""Initialize an instance of this class"""
|
|
self.environment = create_jinja2_environment(language, "program")
|
|
self.peer = peer
|
|
|
|
def emit_definition(self, node: _RpcProgram) -> None:
|
|
"""Emit procedure numbers for each of an RPC programs's procedures"""
|
|
raw_name = node.name
|
|
program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
|
|
|
|
for version in node.versions:
|
|
emit_version_definitions(self.environment, program, version)
|
|
|
|
def emit_declaration(self, node: _RpcProgram) -> None:
|
|
"""Emit a declaration pair for each of an RPC programs's procedures"""
|
|
raw_name = node.name
|
|
program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
|
|
|
|
for version in node.versions:
|
|
emit_version_declarations(self.environment, program, version)
|
|
|
|
def emit_decoder(self, node: _RpcProgram) -> None:
|
|
"""Emit all decoder functions for an RPC program's procedures"""
|
|
raw_name = node.name
|
|
program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
|
|
match self.peer:
|
|
case "server":
|
|
for version in node.versions:
|
|
emit_version_argument_decoders(
|
|
self.environment, program, version,
|
|
)
|
|
case "client":
|
|
for version in node.versions:
|
|
emit_version_result_decoders(
|
|
self.environment, program, version,
|
|
)
|
|
|
|
def emit_encoder(self, node: _RpcProgram) -> None:
|
|
"""Emit all encoder functions for an RPC program's procedures"""
|
|
raw_name = node.name
|
|
program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
|
|
match self.peer:
|
|
case "server":
|
|
for version in node.versions:
|
|
emit_version_result_encoders(
|
|
self.environment, program, version,
|
|
)
|
|
case "client":
|
|
for version in node.versions:
|
|
emit_version_argument_encoders(
|
|
self.environment, program, version,
|
|
)
|