xdrgen: Implement pass-through lines in specifications

XDR specification files can contain lines prefixed with '%' that
pass through unchanged to generated output. Traditional rpcgen
removes the '%' and emits the remainder verbatim, allowing direct
insertion of C includes, pragma directives, or other language-
specific content into the generated code.

Until now, xdrgen silently discarded these lines during parsing.
This prevented specifications from including necessary headers or
preprocessor directives that might be required for the generated
code to compile correctly.

The grammar now captures pass-through lines instead of ignoring
them. A new AST node type represents pass-through content, and
the AST transformer strips the leading '%' character. Definition
and source generators emit pass-through content in document order,
preserving the original placement within the specification.

This brings xdrgen closer to feature parity with traditional
rpcgen while maintaining the existing document-order processing
model.

Existing generated xdrgen source code has been regenerated.

Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
Chuck Lever 2026-01-09 11:21:30 -05:00
parent 3daab3112f
commit 6bc85baba4
12 changed files with 116 additions and 20 deletions

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Generated by xdrgen. Manual edits will be lost.
// XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x
// XDR specification modification time: Thu Dec 25 13:44:43 2025
// XDR specification modification time: Thu Jan 8 23:11:48 2026
#include <linux/sunrpc/svc.h>
@ -178,6 +178,10 @@ xdrgen_decode_fattr4_open_arguments(struct xdr_stream *xdr, fattr4_open_argument
return xdrgen_decode_open_arguments4(xdr, ptr);
}
/*
* Determine what OPEN supports.
*/
bool
xdrgen_decode_fattr4_time_deleg_access(struct xdr_stream *xdr, fattr4_time_deleg_access *ptr)
{
@ -190,6 +194,11 @@ xdrgen_decode_fattr4_time_deleg_modify(struct xdr_stream *xdr, fattr4_time_deleg
return xdrgen_decode_nfstime4(xdr, ptr);
}
/*
* New RECOMMENDED Attribute for
* delegation caching of times
*/
static bool __maybe_unused
xdrgen_decode_open_delegation_type4(struct xdr_stream *xdr, open_delegation_type4 *ptr)
{

View file

@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x */
/* XDR specification modification time: Thu Dec 25 13:44:43 2025 */
/* XDR specification modification time: Thu Jan 8 23:11:48 2026 */
#ifndef _LINUX_XDRGEN_NFS4_1_DECL_H
#define _LINUX_XDRGEN_NFS4_1_DECL_H

View file

@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* Generated by xdrgen. Manual edits will be lost. */
/* XDR specification file: ../../Documentation/sunrpc/xdr/nfs4_1.x */
/* XDR specification modification time: Thu Dec 25 13:44:43 2025 */
/* XDR specification modification time: Thu Jan 8 23:11:48 2026 */
#ifndef _LINUX_XDRGEN_NFS4_1_DEF_H
#define _LINUX_XDRGEN_NFS4_1_DEF_H
@ -87,6 +87,10 @@ typedef enum open_args_createmode4 open_args_createmode4;
typedef struct open_arguments4 fattr4_open_arguments;
/*
* Determine what OPEN supports.
*/
enum { FATTR4_OPEN_ARGUMENTS = 86 };
enum { OPEN4_RESULT_NO_OPEN_STATEID = 0x00000010 };
@ -95,6 +99,11 @@ typedef struct nfstime4 fattr4_time_deleg_access;
typedef struct nfstime4 fattr4_time_deleg_modify;
/*
* New RECOMMENDED Attribute for
* delegation caching of times
*/
enum { FATTR4_TIME_DELEG_ACCESS = 84 };
enum { FATTR4_TIME_DELEG_MODIFY = 85 };

View file

@ -250,8 +250,6 @@ Add more pragma directives:
Enable something like a #include to dynamically insert the content
of other specification files
Properly support line-by-line pass-through via the "%" decorator
Build a unit test suite for verifying translation of XDR language
into compilable code

View file

@ -0,0 +1,26 @@
#!/usr/bin/env python3
# ex: set filetype=python:
"""Generate code for XDR pass-through lines"""
from generators import SourceGenerator, create_jinja2_environment
from xdr_ast import _XdrPassthru
class XdrPassthruGenerator(SourceGenerator):
"""Generate source code for XDR pass-through content"""
def __init__(self, language: str, peer: str):
"""Initialize an instance of this class"""
self.environment = create_jinja2_environment(language, "passthru")
self.peer = peer
def emit_definition(self, node: _XdrPassthru) -> None:
"""Emit one pass-through line"""
template = self.environment.get_template("definition.j2")
print(template.render(content=node.content))
def emit_decoder(self, node: _XdrPassthru) -> None:
"""Emit one pass-through line"""
template = self.environment.get_template("source.j2")
print(template.render(content=node.content))

View file

@ -78,6 +78,9 @@ definition : constant_def
| type_def
| program_def
| pragma_def
| passthru_def
passthru_def : PASSTHRU
//
// RPC program definitions not specified in RFC 4506
@ -115,8 +118,7 @@ decimal_constant : /[\+-]?(0|[1-9][0-9]*)/
hexadecimal_constant : /0x([a-f]|[A-F]|[0-9])+/
octal_constant : /0[0-7]+/
PASSTHRU : "%" | "%" /.+/
%ignore PASSTHRU
PASSTHRU : /%.*/
%import common.C_COMMENT
%ignore C_COMMENT

View file

@ -10,7 +10,6 @@ from argparse import Namespace
from lark import logger
from lark.exceptions import VisitError
from generators.constant import XdrConstantGenerator
from generators.enum import XdrEnumGenerator
from generators.header_bottom import XdrHeaderBottomGenerator
from generators.header_top import XdrHeaderTopGenerator
@ -21,8 +20,7 @@ from generators.struct import XdrStructGenerator
from generators.union import XdrUnionGenerator
from xdr_ast import transform_parse_tree, _RpcProgram, Specification
from xdr_ast import _XdrConstant, _XdrEnum, _XdrPointer
from xdr_ast import _XdrTypedef, _XdrStruct, _XdrUnion
from xdr_ast import _XdrEnum, _XdrPointer, _XdrTypedef, _XdrStruct, _XdrUnion
from xdr_parse import xdr_parser, set_xdr_annotate
from xdr_parse import make_error_handler, XdrParseError
from xdr_parse import handle_transform_error

View file

@ -14,6 +14,7 @@ from generators.constant import XdrConstantGenerator
from generators.enum import XdrEnumGenerator
from generators.header_bottom import XdrHeaderBottomGenerator
from generators.header_top import XdrHeaderTopGenerator
from generators.passthru import XdrPassthruGenerator
from generators.pointer import XdrPointerGenerator
from generators.program import XdrProgramGenerator
from generators.typedef import XdrTypedefGenerator
@ -21,7 +22,7 @@ from generators.struct import XdrStructGenerator
from generators.union import XdrUnionGenerator
from xdr_ast import transform_parse_tree, Specification
from xdr_ast import _RpcProgram, _XdrConstant, _XdrEnum, _XdrPointer
from xdr_ast import _RpcProgram, _XdrConstant, _XdrEnum, _XdrPassthru, _XdrPointer
from xdr_ast import _XdrTypedef, _XdrStruct, _XdrUnion
from xdr_parse import xdr_parser, set_xdr_annotate
from xdr_parse import make_error_handler, XdrParseError
@ -47,6 +48,8 @@ def emit_header_definitions(root: Specification, language: str, peer: str) -> No
gen = XdrStructGenerator(language, peer)
elif isinstance(definition.value, _XdrUnion):
gen = XdrUnionGenerator(language, peer)
elif isinstance(definition.value, _XdrPassthru):
gen = XdrPassthruGenerator(language, peer)
else:
continue
gen.emit_definition(definition.value)

View file

@ -12,6 +12,7 @@ from lark.exceptions import VisitError
from generators.source_top import XdrSourceTopGenerator
from generators.enum import XdrEnumGenerator
from generators.passthru import XdrPassthruGenerator
from generators.pointer import XdrPointerGenerator
from generators.program import XdrProgramGenerator
from generators.typedef import XdrTypedefGenerator
@ -19,7 +20,7 @@ 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 _XdrAst, _XdrEnum, _XdrPassthru, _XdrPointer
from xdr_ast import _XdrStruct, _XdrTypedef, _XdrUnion
from xdr_parse import xdr_parser, set_xdr_annotate, set_xdr_enum_validation
@ -74,22 +75,31 @@ def generate_server_source(filename: str, root: Specification, language: str) ->
gen.emit_source(filename, root)
for definition in root.definitions:
emit_source_decoder(definition.value, language, "server")
if isinstance(definition.value, _XdrPassthru):
passthru_gen = XdrPassthruGenerator(language, "server")
passthru_gen.emit_decoder(definition.value)
else:
emit_source_decoder(definition.value, language, "server")
for definition in root.definitions:
emit_source_encoder(definition.value, language, "server")
if not isinstance(definition.value, _XdrPassthru):
emit_source_encoder(definition.value, language, "server")
def generate_client_source(filename: str, root: Specification, language: str) -> None:
"""Generate server-side source code"""
"""Generate client-side source code"""
gen = XdrSourceTopGenerator(language, "client")
gen.emit_source(filename, root)
print("")
for definition in root.definitions:
emit_source_encoder(definition.value, language, "client")
if isinstance(definition.value, _XdrPassthru):
passthru_gen = XdrPassthruGenerator(language, "client")
passthru_gen.emit_decoder(definition.value)
else:
emit_source_encoder(definition.value, language, "client")
for definition in root.definitions:
emit_source_decoder(definition.value, language, "client")
if not isinstance(definition.value, _XdrPassthru):
emit_source_decoder(definition.value, language, "client")
# cel: todo: client needs PROC macros

View file

@ -0,0 +1,3 @@
{# SPDX-License-Identifier: GPL-2.0 #}
{{ content }}

View file

@ -0,0 +1,3 @@
{# SPDX-License-Identifier: GPL-2.0 #}
{{ content }}

View file

@ -516,6 +516,13 @@ class _Pragma(_XdrAst):
"""Empty class for pragma directives"""
@dataclass
class _XdrPassthru(_XdrAst):
"""Passthrough line to emit verbatim in output"""
content: str
@dataclass
class Definition(_XdrAst, ast_utils.WithMeta):
"""Corresponds to 'definition' in the grammar"""
@ -738,14 +745,42 @@ class ParseToAst(Transformer):
raise NotImplementedError("Directive not supported")
return _Pragma()
def passthru_def(self, children):
"""Instantiate one _XdrPassthru object"""
token = children[0]
content = token.value[1:]
return _XdrPassthru(content)
transformer = ast_utils.create_transformer(this_module, ParseToAst())
def _merge_consecutive_passthru(definitions: List[Definition]) -> List[Definition]:
"""Merge consecutive passthru definitions into single nodes"""
result = []
i = 0
while i < len(definitions):
if isinstance(definitions[i].value, _XdrPassthru):
lines = [definitions[i].value.content]
meta = definitions[i].meta
j = i + 1
while j < len(definitions) and isinstance(definitions[j].value, _XdrPassthru):
lines.append(definitions[j].value.content)
j += 1
merged = _XdrPassthru("\n".join(lines))
result.append(Definition(meta, merged))
i = j
else:
result.append(definitions[i])
i += 1
return result
def transform_parse_tree(parse_tree):
"""Transform productions into an abstract syntax tree"""
return transformer.transform(parse_tree)
ast = transformer.transform(parse_tree)
ast.definitions = _merge_consecutive_passthru(ast.definitions)
return ast
def get_header_name() -> str: