diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-20 12:39:56 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-20 12:39:56 -0700 |
| commit | d141ec2825b4d3ec52f27c43bdd864090159273a (patch) | |
| tree | 1a51bb2c3692f0ab5f609c1a9e64374bcbb30490 /tools | |
| parent | ada9ccfb81eca6943c3cd42d23b9ddd446e75342 (diff) | |
| parent | 46db3c8a1be96a354758b44b1d4fbb4b70d09a20 (diff) | |
| download | linux-next-d141ec2825b4d3ec52f27c43bdd864090159273a.tar.gz linux-next-d141ec2825b4d3ec52f27c43bdd864090159273a.zip | |
Merge tag 'nfsd-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux
Pull nfsd updates from Chuck Lever:
- CB_NOTIFY support for NFSD's NFSv4.1 directory delegations
The server used to recall a delegation as soon as the directory
changed. NFSD now watches delegated directories through fsnotify and
reports adds, removes, renames, and directory attribute changes,
carrying the filehandle and attributes of the affected entry, so
clients can keep their caches. Some of the NOTIFY4 flags come from
RFC 8881bis (Jeff Layton)
- Continued netlink work
A new server-stats-get operation reports what /proc/net/rpc/nfsd
publishes, plus NFSv4 callback counts, and SUNRPC now keeps its
per-procedure call counts per network namespace, so a container sees
its own numbers. nfsstat reads all of this over netlink, with a
procfs fallback for older kernels (Jeff Layton)
- Remove SUNRPC service thread pool mode selection
Per node is the right choice on any host we run today, so the auto,
global, and percpu modes have been removed. A single-node host still
gets one pool. A multi-NUMA host now gets a pool per node.
sunrpc.pool_mode accepts the old names but no longer selects
anything.
- Bug fixes, clean-ups, and small optimizations:
- async COPY offload rework (Jeff Layton)
- more use-after-free fixes in the NFSv4 state revocation paths
- percpu counter contention removed from the reply cache and IO
accounting
- a long list of hardening fixes (Chris Mason)
Sincere thanks to all contributors, reviewers, testers, and bug
reporters who participated in the v7.3 NFSD development cycle.
* tag 'nfsd-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/cel/linux: (182 commits)
nfsd: export NFSv4 callback op stats via netlink
nfsd: count NFSv4 callback operations per netns
sunrpc: remove unused svc_version vs_count field
nfsd: implement server-stats-get netlink handler
sunrpc: use per-net counts in svc_seq_show()
sunrpc: add per-netns per-procedure call counts to svc_stat
NFSD: Document reply_cache_stats ABI
NFSD: Eliminate percpu counter contention in IO byte accounting
NFSD: Eliminate percpu counter contention in reply cache statistics
NFSD: Eliminate percpu counter contention in DRC memory accounting
NFSD: Fix off-by-one in DRC bucket pruning limit
NFSD: Relocate NFSv4 "supported attributes" to new header
NFSD: Relocate nfsd4_set_netaddr()
NFSD: Relocate nfsd_user_namespace()
NFSD: Move struct readdir_cd
NFSD: Move the export.h include from nfsd.h to auth.c
NFSD: Remove '#include "nfsd.h"' from fs/nfsd/cache.h
NFSD: include "netns.h"
NFSD: Explicitly include "stats.h"
NFSD: Make "stats.h" self-contained
...
Diffstat (limited to 'tools')
21 files changed, 445 insertions, 69 deletions
diff --git a/tools/net/sunrpc/xdrgen/generators/program.py b/tools/net/sunrpc/xdrgen/generators/program.py index c0cb3f6d3319..37f9655c83fe 100644 --- a/tools/net/sunrpc/xdrgen/generators/program.py +++ b/tools/net/sunrpc/xdrgen/generators/program.py @@ -38,6 +38,8 @@ def emit_version_declarations( arguments = dict.fromkeys([]) for procedure in version.procedures: if procedure.name not in excluded_apis: + if procedure.argument.type_name == "void": + continue arguments[procedure.argument.type_name] = None if len(arguments) > 0: print("") @@ -48,6 +50,8 @@ def emit_version_declarations( results = dict.fromkeys([]) for procedure in version.procedures: if procedure.name not in excluded_apis: + if procedure.result.type_name == "void": + continue results[procedure.result.type_name] = None if len(results) > 0: print("") @@ -63,6 +67,8 @@ def emit_version_argument_decoders( arguments = dict.fromkeys([]) for procedure in version.procedures: if procedure.name not in excluded_apis: + if procedure.argument.type_name == "void": + continue arguments[procedure.argument.type_name] = None template = environment.get_template("decoder/argument.j2") @@ -105,6 +111,8 @@ def emit_version_result_encoders( results = dict.fromkeys([]) for procedure in version.procedures: if procedure.name not in excluded_apis: + if procedure.result.type_name == "void": + continue results[procedure.result.type_name] = None template = environment.get_template("encoder/result.j2") diff --git a/tools/net/sunrpc/xdrgen/subcmds/declarations.py b/tools/net/sunrpc/xdrgen/subcmds/declarations.py index ed83d48d1f68..f187611466d7 100644 --- a/tools/net/sunrpc/xdrgen/subcmds/declarations.py +++ b/tools/net/sunrpc/xdrgen/subcmds/declarations.py @@ -21,16 +21,15 @@ from generators.union import XdrUnionGenerator from xdr_ast import transform_parse_tree, _RpcProgram, Specification from xdr_ast import _XdrEnum, _XdrPointer, _XdrTypedef, _XdrStruct, _XdrUnion +from xdr_ast import XdrSemanticError from xdr_parse import xdr_parser, set_xdr_annotate from xdr_parse import make_error_handler, XdrParseError -from xdr_parse import handle_transform_error +from xdr_parse import handle_transform_error, handle_semantic_error logger.setLevel(logging.INFO) -def emit_header_declarations( - root: Specification, language: str, peer: str -) -> None: +def emit_header_declarations(root: Specification, language: str, peer: str) -> None: """Emit header declarations""" for definition in root.definitions: if isinstance(definition.value, _XdrEnum): @@ -68,6 +67,9 @@ def subcmd(args: Namespace) -> int: except VisitError as e: handle_transform_error(e, source, args.filename) return 1 + except XdrSemanticError as e: + handle_semantic_error(e, source, args.filename) + return 1 gen = XdrHeaderTopGenerator(args.language, args.peer) gen.emit_declaration(args.filename, ast) diff --git a/tools/net/sunrpc/xdrgen/subcmds/definitions.py b/tools/net/sunrpc/xdrgen/subcmds/definitions.py index a48ca0549382..77b666943a11 100644 --- a/tools/net/sunrpc/xdrgen/subcmds/definitions.py +++ b/tools/net/sunrpc/xdrgen/subcmds/definitions.py @@ -21,12 +21,12 @@ from generators.typedef import XdrTypedefGenerator from generators.struct import XdrStructGenerator from generators.union import XdrUnionGenerator -from xdr_ast import transform_parse_tree, Specification +from xdr_ast import transform_parse_tree, Specification, XdrSemanticError 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 -from xdr_parse import handle_transform_error +from xdr_parse import handle_transform_error, handle_semantic_error logger.setLevel(logging.INFO) @@ -94,6 +94,9 @@ def subcmd(args: Namespace) -> int: except VisitError as e: handle_transform_error(e, source, args.filename) return 1 + except XdrSemanticError as e: + handle_semantic_error(e, source, args.filename) + return 1 gen = XdrHeaderTopGenerator(args.language, args.peer) gen.emit_definition(args.filename, ast) diff --git a/tools/net/sunrpc/xdrgen/subcmds/lint.py b/tools/net/sunrpc/xdrgen/subcmds/lint.py index e1da49632e62..b4ea0f55f079 100644 --- a/tools/net/sunrpc/xdrgen/subcmds/lint.py +++ b/tools/net/sunrpc/xdrgen/subcmds/lint.py @@ -11,8 +11,8 @@ from lark import logger from lark.exceptions import VisitError from xdr_parse import xdr_parser, make_error_handler, XdrParseError -from xdr_parse import handle_transform_error -from xdr_ast import transform_parse_tree +from xdr_parse import handle_transform_error, handle_semantic_error +from xdr_ast import transform_parse_tree, XdrSemanticError logger.setLevel(logging.DEBUG) @@ -34,5 +34,8 @@ def subcmd(args: Namespace) -> int: except VisitError as e: handle_transform_error(e, source, args.filename) return 1 + except XdrSemanticError as e: + handle_semantic_error(e, source, args.filename) + return 1 return 0 diff --git a/tools/net/sunrpc/xdrgen/subcmds/source.py b/tools/net/sunrpc/xdrgen/subcmds/source.py index 27e8767b1b58..56eba34d8eb3 100644 --- a/tools/net/sunrpc/xdrgen/subcmds/source.py +++ b/tools/net/sunrpc/xdrgen/subcmds/source.py @@ -21,11 +21,11 @@ from generators.union import XdrUnionGenerator from xdr_ast import transform_parse_tree, _RpcProgram, Specification from xdr_ast import _XdrAst, _XdrEnum, _XdrPassthru, _XdrPointer -from xdr_ast import _XdrStruct, _XdrTypedef, _XdrUnion +from xdr_ast import _XdrStruct, _XdrTypedef, _XdrUnion, XdrSemanticError from xdr_parse import xdr_parser, set_xdr_annotate, set_xdr_enum_validation from xdr_parse import make_error_handler, XdrParseError -from xdr_parse import handle_transform_error +from xdr_parse import handle_transform_error, handle_semantic_error logger.setLevel(logging.INFO) @@ -123,6 +123,9 @@ def subcmd(args: Namespace) -> int: except VisitError as e: handle_transform_error(e, source, args.filename) return 1 + except XdrSemanticError as e: + handle_semantic_error(e, source, args.filename) + return 1 match args.peer: case "server": generate_server_source(args.filename, ast, args.language) diff --git a/tools/net/sunrpc/xdrgen/templates/C/enum/declaration/enum.j2 b/tools/net/sunrpc/xdrgen/templates/C/enum/declaration/enum.j2 index c7ae506076bb..d1405c7c5354 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/enum/declaration/enum.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/enum/declaration/enum.j2 @@ -1,3 +1,4 @@ {# SPDX-License-Identifier: GPL-2.0 #} + bool xdrgen_decode_{{ name }}(struct xdr_stream *xdr, {{ name }} *ptr); bool xdrgen_encode_{{ name }}(struct xdr_stream *xdr, {{ name }} value); diff --git a/tools/net/sunrpc/xdrgen/templates/C/pointer/encoder/string.j2 b/tools/net/sunrpc/xdrgen/templates/C/pointer/encoder/string.j2 index cf65b71eaef3..7ddc2bf3edac 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/pointer/encoder/string.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/pointer/encoder/string.j2 @@ -2,7 +2,9 @@ {% if annotate %} /* member {{ name }} (variable-length string) */ {% endif %} +{% if maxsize != "0" %} if (value->{{ name }}.len > {{ maxsize }}) return false; +{% endif %} if (xdr_stream_encode_opaque(xdr, value->{{ name }}.data, value->{{ name }}.len) < 0) return false; diff --git a/tools/net/sunrpc/xdrgen/templates/C/pointer/encoder/variable_length_opaque.j2 b/tools/net/sunrpc/xdrgen/templates/C/pointer/encoder/variable_length_opaque.j2 index 1d477c2d197a..5bf00070ae95 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/pointer/encoder/variable_length_opaque.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/pointer/encoder/variable_length_opaque.j2 @@ -2,7 +2,9 @@ {% if annotate %} /* member {{ name }} (variable-length opaque) */ {% endif %} +{% if maxsize != "0" %} if (value->{{ name }}.len > {{ maxsize }}) return false; +{% endif %} if (xdr_stream_encode_opaque(xdr, value->{{ name }}.data, value->{{ name }}.len) < 0) return false; diff --git a/tools/net/sunrpc/xdrgen/templates/C/program/decoder/argument.j2 b/tools/net/sunrpc/xdrgen/templates/C/program/decoder/argument.j2 index 19b219dd276d..096d553b2a1e 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/program/decoder/argument.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/program/decoder/argument.j2 @@ -11,9 +11,6 @@ */ bool {{ program }}_svc_decode_{{ argument }}(struct svc_rqst *rqstp, struct xdr_stream *xdr) { -{% if argument == 'void' %} - return xdrgen_decode_void(xdr); -{% else %} {% if argument in structs %} struct {{ argument }} *argp = rqstp->rq_argp; {% else %} @@ -21,5 +18,4 @@ bool {{ program }}_svc_decode_{{ argument }}(struct svc_rqst *rqstp, struct xdr_ {% endif %} return xdrgen_decode_{{ argument }}(xdr, argp); -{% endif %} } diff --git a/tools/net/sunrpc/xdrgen/templates/C/program/encoder/result.j2 b/tools/net/sunrpc/xdrgen/templates/C/program/encoder/result.j2 index 746592cfda56..4243d91966fd 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/program/encoder/result.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/program/encoder/result.j2 @@ -11,9 +11,6 @@ */ bool {{ program }}_svc_encode_{{ result }}(struct svc_rqst *rqstp, struct xdr_stream *xdr) { -{% if result == 'void' %} - return xdrgen_encode_void(xdr); -{% else %} {% if result in structs %} struct {{ result }} *resp = rqstp->rq_resp; @@ -23,5 +20,4 @@ bool {{ program }}_svc_encode_{{ result }}(struct svc_rqst *rqstp, struct xdr_st return xdrgen_encode_{{ result }}(xdr, *resp); {% endif %} -{% endif %} } diff --git a/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/string.j2 b/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/string.j2 index cf65b71eaef3..7ddc2bf3edac 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/string.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/string.j2 @@ -2,7 +2,9 @@ {% if annotate %} /* member {{ name }} (variable-length string) */ {% endif %} +{% if maxsize != "0" %} if (value->{{ name }}.len > {{ maxsize }}) return false; +{% endif %} if (xdr_stream_encode_opaque(xdr, value->{{ name }}.data, value->{{ name }}.len) < 0) return false; diff --git a/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/variable_length_opaque.j2 b/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/variable_length_opaque.j2 index 1d477c2d197a..5bf00070ae95 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/variable_length_opaque.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/struct/encoder/variable_length_opaque.j2 @@ -2,7 +2,9 @@ {% if annotate %} /* member {{ name }} (variable-length opaque) */ {% endif %} +{% if maxsize != "0" %} if (value->{{ name }}.len > {{ maxsize }}) return false; +{% endif %} if (xdr_stream_encode_opaque(xdr, value->{{ name }}.data, value->{{ name }}.len) < 0) return false; diff --git a/tools/net/sunrpc/xdrgen/templates/C/union/definition/close.j2 b/tools/net/sunrpc/xdrgen/templates/C/union/definition/close.j2 index 5fc1937ba774..19ee759d70c6 100644 --- a/tools/net/sunrpc/xdrgen/templates/C/union/definition/close.j2 +++ b/tools/net/sunrpc/xdrgen/templates/C/union/definition/close.j2 @@ -1,9 +1,3 @@ {# SPDX-License-Identifier: GPL-2.0 #} } u; }; -{%- if name in public_apis %} - - -bool xdrgen_decode_{{ name }}(struct xdr_stream *xdr, struct {{ name }} *ptr); -bool xdrgen_encode_{{ name }}(struct xdr_stream *xdr, const struct {{ name }} *ptr); -{%- endif -%} diff --git a/tools/net/sunrpc/xdrgen/tests/bad-procedure-number-negative.x b/tools/net/sunrpc/xdrgen/tests/bad-procedure-number-negative.x new file mode 100644 index 000000000000..33ed272c3ce2 --- /dev/null +++ b/tools/net/sunrpc/xdrgen/tests/bad-procedure-number-negative.x @@ -0,0 +1,20 @@ +/* + * NEGATIVE TEST CASE -- xdrgen must REJECT this specification. + * + * RFC 5531 assigns only unsigned constants to program, version, and + * procedure numbers (Section 12.3). This spec gives a procedure a + * negative number, which the front end must reject. + * + * Expected diagnostic: + * negative procedure number -5 in version 'BADVERS' + * + * The tests directory has no automated runner; exercise by hand: + * ./xdrgen definitions tests/bad-procedure-number-negative.x (must fail) + */ + +program BADPROG { + version BADVERS { + void BADPROC_NULL(void) = 0; + void BADPROC_FOO(void) = -5; + } = 1; +} = 100000; diff --git a/tools/net/sunrpc/xdrgen/tests/bad-procedure-number-too-large.x b/tools/net/sunrpc/xdrgen/tests/bad-procedure-number-too-large.x new file mode 100644 index 000000000000..521581c57358 --- /dev/null +++ b/tools/net/sunrpc/xdrgen/tests/bad-procedure-number-too-large.x @@ -0,0 +1,20 @@ +/* + * NEGATIVE TEST CASE -- xdrgen must REJECT this specification. + * + * RFC 5531 encodes program, version, and procedure numbers as unsigned + * 32-bit integers (Section 9). This spec gives a procedure a number one + * past the 32-bit maximum, which the front end must reject. + * + * Expected diagnostic: + * procedure number 4294967296 in version 'BADVERS' exceeds 4294967295 + * + * The tests directory has no automated runner; exercise by hand: + * ./xdrgen definitions tests/bad-procedure-number-too-large.x (must fail) + */ + +program BADPROG { + version BADVERS { + void BADPROC_NULL(void) = 0; + void BADPROC_FOO(void) = 4294967296; + } = 1; +} = 100000; diff --git a/tools/net/sunrpc/xdrgen/tests/bad-program-number-negative.x b/tools/net/sunrpc/xdrgen/tests/bad-program-number-negative.x new file mode 100644 index 000000000000..f7b71ee07f6c --- /dev/null +++ b/tools/net/sunrpc/xdrgen/tests/bad-program-number-negative.x @@ -0,0 +1,19 @@ +/* + * NEGATIVE TEST CASE -- xdrgen must REJECT this specification. + * + * RFC 5531 assigns only unsigned constants to program, version, and + * procedure numbers (Section 12.3). This spec gives the program a + * negative number, which the front end must reject. + * + * Expected diagnostic: + * negative program number -100000 in program 'BADPROG' + * + * The tests directory has no automated runner; exercise by hand: + * ./xdrgen definitions tests/bad-program-number-negative.x (must fail) + */ + +program BADPROG { + version BADVERS { + void BADPROC_NULL(void) = 0; + } = 1; +} = -100000; diff --git a/tools/net/sunrpc/xdrgen/tests/bad-program-number-too-large.x b/tools/net/sunrpc/xdrgen/tests/bad-program-number-too-large.x new file mode 100644 index 000000000000..c761584e712f --- /dev/null +++ b/tools/net/sunrpc/xdrgen/tests/bad-program-number-too-large.x @@ -0,0 +1,19 @@ +/* + * NEGATIVE TEST CASE -- xdrgen must REJECT this specification. + * + * RFC 5531 encodes program, version, and procedure numbers as unsigned + * 32-bit integers (Section 9). This spec gives the program a number one + * past the 32-bit maximum, which the front end must reject. + * + * Expected diagnostic: + * program number 4294967296 in program 'BADPROG' exceeds 4294967295 + * + * The tests directory has no automated runner; exercise by hand: + * ./xdrgen definitions tests/bad-program-number-too-large.x (must fail) + */ + +program BADPROG { + version BADVERS { + void BADPROC_NULL(void) = 0; + } = 1; +} = 4294967296; diff --git a/tools/net/sunrpc/xdrgen/tests/bad-version-number-negative.x b/tools/net/sunrpc/xdrgen/tests/bad-version-number-negative.x new file mode 100644 index 000000000000..dd9c773435c0 --- /dev/null +++ b/tools/net/sunrpc/xdrgen/tests/bad-version-number-negative.x @@ -0,0 +1,19 @@ +/* + * NEGATIVE TEST CASE -- xdrgen must REJECT this specification. + * + * RFC 5531 assigns only unsigned constants to program, version, and + * procedure numbers (Section 12.3). This spec gives the version a + * negative number, which the front end must reject. + * + * Expected diagnostic: + * negative version number -1 in program 'BADPROG' + * + * The tests directory has no automated runner; exercise by hand: + * ./xdrgen definitions tests/bad-version-number-negative.x (must fail) + */ + +program BADPROG { + version BADVERS { + void BADPROC_NULL(void) = 0; + } = -1; +} = 100000; diff --git a/tools/net/sunrpc/xdrgen/tests/bad-version-number-too-large.x b/tools/net/sunrpc/xdrgen/tests/bad-version-number-too-large.x new file mode 100644 index 000000000000..dd44f6eed564 --- /dev/null +++ b/tools/net/sunrpc/xdrgen/tests/bad-version-number-too-large.x @@ -0,0 +1,19 @@ +/* + * NEGATIVE TEST CASE -- xdrgen must REJECT this specification. + * + * RFC 5531 encodes program, version, and procedure numbers as unsigned + * 32-bit integers (Section 9). This spec gives the version a number one + * past the 32-bit maximum, which the front end must reject. + * + * Expected diagnostic: + * version number 4294967296 in program 'BADPROG' exceeds 4294967295 + * + * The tests directory has no automated runner; exercise by hand: + * ./xdrgen definitions tests/bad-version-number-too-large.x (must fail) + */ + +program BADPROG { + version BADVERS { + void BADPROC_NULL(void) = 0; + } = 4294967296; +} = 100000; diff --git a/tools/net/sunrpc/xdrgen/xdr_ast.py b/tools/net/sunrpc/xdrgen/xdr_ast.py index 14bff9477473..9dab8bc545b0 100644 --- a/tools/net/sunrpc/xdrgen/xdr_ast.py +++ b/tools/net/sunrpc/xdrgen/xdr_ast.py @@ -5,7 +5,7 @@ import sys from typing import List -from dataclasses import dataclass +from dataclasses import dataclass, KW_ONLY from lark import ast_utils, Transformer from lark.tree import Meta @@ -65,6 +65,16 @@ max_widths = { class _XdrAst(ast_utils.Ast): """Base class for the XDR abstract syntax tree""" + # Source position of the construct's declared identifier, when + # the transformer records one, so semantic diagnostics can point + # at the exact declaration. The KW_ONLY marker makes the fields + # keyword-only, so they never disturb the positional child + # ordering lark uses to build each node; 0 means the position was + # not recorded. + _: KW_ONLY + line: int = 0 + column: int = 0 + @dataclass class _XdrIdentifier(_XdrAst): @@ -488,7 +498,7 @@ class _RpcProcedure(_XdrAst): """RPC procedure definition""" name: str - number: str + number: int argument: _XdrTypeSpecifier result: _XdrTypeSpecifier @@ -498,7 +508,7 @@ class _RpcVersion(_XdrAst): """RPC version definition""" name: str - number: str + number: int procedures: List[_RpcProcedure] @@ -507,7 +517,7 @@ class _RpcProgram(_XdrAst): """RPC program definition""" name: str - number: str + number: int versions: List[_RpcVersion] @@ -543,7 +553,8 @@ class ParseToAst(Transformer): def identifier(self, children): """Instantiate one _XdrIdentifier object""" - return _XdrIdentifier(children[0].value) + token = children[0] + return _XdrIdentifier(token.value, line=token.line, column=token.column) def value(self, children): """Instantiate one _XdrValue object""" @@ -573,84 +584,103 @@ class ParseToAst(Transformer): def constant_def(self, children): """Instantiate one _XdrConstant object""" - name = children[0].symbol + ident = children[0] value = children[1].value - return _XdrConstant(name, value) + return _XdrConstant(ident.symbol, value, line=ident.line, column=ident.column) def enum(self, children): """Instantiate one _XdrEnum object""" - enum_name = children[0].symbol + name_ident = children[0] i = 0 enumerators = [] body = children[1] while i < len(body.children): - name = body.children[i].symbol + ident = body.children[i] value = body.children[i + 1].value - enumerators.append(_XdrEnumerator(name, value)) + enumerators.append( + _XdrEnumerator( + ident.symbol, value, line=ident.line, column=ident.column + ) + ) i = i + 2 - return _XdrEnum(enum_name, enumerators) + return _XdrEnum( + name_ident.symbol, + enumerators, + line=name_ident.line, + column=name_ident.column, + ) def fixed_length_opaque(self, children): """Instantiate one _XdrFixedLengthOpaque declaration object""" - name = children[0].symbol + ident = children[0] size = children[1].value - return _XdrFixedLengthOpaque(name, size) + return _XdrFixedLengthOpaque( + ident.symbol, size, line=ident.line, column=ident.column + ) def variable_length_opaque(self, children): """Instantiate one _XdrVariableLengthOpaque declaration object""" - name = children[0].symbol + ident = children[0] if children[1] is not None: maxsize = children[1].value else: maxsize = "0" - return _XdrVariableLengthOpaque(name, maxsize) + return _XdrVariableLengthOpaque( + ident.symbol, maxsize, line=ident.line, column=ident.column + ) def string(self, children): """Instantiate one _XdrString declaration object""" - name = children[0].symbol + ident = children[0] if children[1] is not None: maxsize = children[1].value else: maxsize = "0" - return _XdrString(name, maxsize) + return _XdrString(ident.symbol, maxsize, line=ident.line, column=ident.column) def fixed_length_array(self, children): """Instantiate one _XdrFixedLengthArray declaration object""" spec = children[0] - name = children[1].symbol + ident = children[1] size = children[2].value - return _XdrFixedLengthArray(name, spec, size) + return _XdrFixedLengthArray( + ident.symbol, spec, size, line=ident.line, column=ident.column + ) def variable_length_array(self, children): """Instantiate one _XdrVariableLengthArray declaration object""" spec = children[0] - name = children[1].symbol + ident = children[1] if children[2] is not None: maxsize = children[2].value else: maxsize = "0" - return _XdrVariableLengthArray(name, spec, maxsize) + return _XdrVariableLengthArray( + ident.symbol, spec, maxsize, line=ident.line, column=ident.column + ) def optional_data(self, children): """Instantiate one _XdrOptionalData declaration object""" spec = children[0] - name = children[1].symbol + ident = children[1] - return _XdrOptionalData(name, spec) + return _XdrOptionalData( + ident.symbol, spec, line=ident.line, column=ident.column + ) def basic(self, children): """Instantiate one _XdrBasic object""" spec = children[0] - name = children[1].symbol + ident = children[1] - return _XdrBasic(name, spec) + return _XdrBasic(ident.symbol, spec, line=ident.line, column=ident.column) def void(self, children): """Instantiate one _XdrVoid declaration object""" @@ -659,17 +689,19 @@ class ParseToAst(Transformer): def struct(self, children): """Instantiate one _XdrStruct object""" - name = children[0].symbol + ident = children[0] + name = ident.symbol fields = children[1].children + pos = {"line": ident.line, "column": ident.column} last_field = fields[-1] if ( isinstance(last_field, _XdrOptionalData) and name == last_field.spec.type_name ): - return _XdrPointer(name, fields) + return _XdrPointer(name, fields, **pos) - return _XdrStruct(name, fields) + return _XdrStruct(name, fields, **pos) def typedef(self, children): """Instantiate one _XdrTypedef object""" @@ -694,39 +726,57 @@ class ParseToAst(Transformer): def union(self, children): """Instantiate one _XdrUnion object""" - name = children[0].symbol + ident = children[0] body = children[1] discriminant = body.children[0].children[0] cases = body.children[1:-1] default = body.children[-1] - return _XdrUnion(name, discriminant, cases, default) + return _XdrUnion( + ident.symbol, + discriminant, + cases, + default, + line=ident.line, + column=ident.column, + ) def procedure_def(self, children): """Instantiate one _RpcProcedure object""" result = children[0] - name = children[1].symbol + ident = children[1] argument = children[2] number = children[3].value - return _RpcProcedure(name, number, argument, result) + return _RpcProcedure( + ident.symbol, + number, + argument, + result, + line=ident.line, + column=ident.column, + ) def version_def(self, children): """Instantiate one _RpcVersion object""" - name = children[0].symbol + ident = children[0] number = children[-1].value procedures = children[1:-1] - return _RpcVersion(name, number, procedures) + return _RpcVersion( + ident.symbol, number, procedures, line=ident.line, column=ident.column + ) def program_def(self, children): """Instantiate one _RpcProgram object""" - name = children[0].symbol + ident = children[0] number = children[-1].value versions = children[1:-1] - return _RpcProgram(name, number, versions) + return _RpcProgram( + ident.symbol, number, versions, line=ident.line, column=ident.column + ) def pragma_def(self, children): """Instantiate one _Pragma object""" @@ -764,7 +814,9 @@ def _merge_consecutive_passthru(definitions: List[Definition]) -> List[Definitio lines = [definitions[i].value.content] meta = definitions[i].meta j = i + 1 - while j < len(definitions) and isinstance(definitions[j].value, _XdrPassthru): + while j < len(definitions) and isinstance( + definitions[j].value, _XdrPassthru + ): lines.append(definitions[j].value.content) j += 1 merged = _XdrPassthru("\n".join(lines)) @@ -776,10 +828,174 @@ def _merge_consecutive_passthru(definitions: List[Definition]) -> List[Definitio return result +def _meta_line(meta) -> int: + """Return the 1-based source line for a node's meta, or 0 if unknown""" + try: + return meta.line + except AttributeError: + return 0 + + +class XdrSemanticError(Exception): + """A specification that parses but violates an XDR semantic rule. + + Detection lives in the language-independent front end because a + duplicate name is malformed XDR regardless of the output language. + """ + + def __init__(self, message: str, meta): + super().__init__(message) + self.message = message + self.line = _meta_line(meta) + self.column = getattr(meta, "column", 0) + + +def _introduced_names(value): + """Yield (name, node) for each identifier a definition introduces.""" + if isinstance(value, (_XdrStruct, _XdrUnion, _XdrPointer)): + yield value.name, value + elif isinstance(value, _XdrEnum): + yield value.name, value + for enumerator in value.enumerators: + yield enumerator.name, enumerator + elif isinstance(value, _XdrTypedef): + yield value.declaration.name, value.declaration + elif isinstance(value, _XdrConstant): + yield value.name, value + elif isinstance(value, _RpcProgram): + yield value.name, value + + +def _check_rpc_scope_names(program: "_RpcProgram") -> None: + """Enforce RFC 5531 Section 12.3 scoping within an RPC program. + + A version name and number are unique within the program and a + procedure name and number are unique within its version. + """ + version_names = set() + version_numbers = set() + for version in program.versions: + if version.name in version_names: + raise XdrSemanticError( + f"duplicate version name '{version.name}'" + f" in program '{program.name}'", + version, + ) + version_names.add(version.name) + if version.number in version_numbers: + raise XdrSemanticError( + f"duplicate version number {version.number}" + f" in program '{program.name}'", + version, + ) + version_numbers.add(version.number) + procedure_names = set() + procedure_numbers = set() + for procedure in version.procedures: + if procedure.name in procedure_names: + raise XdrSemanticError( + f"duplicate procedure name '{procedure.name}'" + f" in version '{version.name}'", + procedure, + ) + procedure_names.add(procedure.name) + if procedure.number in procedure_numbers: + raise XdrSemanticError( + f"duplicate procedure number {procedure.number}" + f" in version '{version.name}'", + procedure, + ) + procedure_numbers.add(procedure.number) + + +def check_duplicate_definitions(root: "Specification") -> None: + """Reject a spec that declares an identifier more than once. + + RFC 4506 Section 6.4 places constant and type identifiers in a + single name space that must be unique within a specification. + RFC 5531 Section 12.3 adds RPC program names to that name space + and scopes version names and numbers to their program and + procedure names and numbers to their version. + """ + seen = {} + for definition in root.definitions: + for name, node in _introduced_names(definition.value): + where = node if node.line else definition.meta + first = seen.get(name) + if first is not None: + raise XdrSemanticError( + f"duplicate identifier '{name}'" + f" (first declared at line {_meta_line(first)})", + where, + ) + seen[name] = where + if isinstance(definition.value, _RpcProgram): + _check_rpc_scope_names(definition.value) + + +# RFC 5531 (Section 9) encodes program, version, and procedure numbers +# as unsigned 32-bit integers, so each must fall within [0, 2**32 - 1]. +_RPC_NUMBER_MAX = 2**32 - 1 + + +def _check_rpc_number(kind: str, number: int, scope: str, meta) -> None: + """Reject one RPC number that is negative or wider than 32 bits.""" + if number < 0: + raise XdrSemanticError( + f"negative {kind} number {number} {scope}", + meta, + ) + if number > _RPC_NUMBER_MAX: + raise XdrSemanticError( + f"{kind} number {number} {scope} exceeds {_RPC_NUMBER_MAX}", + meta, + ) + + +def check_rpc_number_range(root: "Specification") -> None: + """Reject an out-of-range program, version, or procedure number. + + RFC 5531 assigns only unsigned constants to program, version, and + procedure numbers (Section 12.3) and encodes each as an unsigned + 32-bit integer (Section 9). RFC 4506 Section 6.2 permits a signed + decimal constant for XDR constants in general and sets no ceiling on + magnitude, so the grammar accepts an out-of-range value; the range + is enforced here instead. The parser retains no per-version or + per-procedure source location, so a violation is reported against the + program definition. + """ + for definition in root.definitions: + program = definition.value + if not isinstance(program, _RpcProgram): + continue + _check_rpc_number( + "program", + program.number, + f"in program '{program.name}'", + definition.meta, + ) + for version in program.versions: + _check_rpc_number( + "version", + version.number, + f"in program '{program.name}'", + definition.meta, + ) + for procedure in version.procedures: + _check_rpc_number( + "procedure", + procedure.number, + f"in version '{version.name}'", + definition.meta, + ) + + def transform_parse_tree(parse_tree): """Transform productions into an abstract syntax tree""" ast = transformer.transform(parse_tree) ast.definitions = _merge_consecutive_passthru(ast.definitions) + check_duplicate_definitions(ast) + check_rpc_number_range(ast) return ast diff --git a/tools/net/sunrpc/xdrgen/xdr_parse.py b/tools/net/sunrpc/xdrgen/xdr_parse.py index 241e96c1fdd9..78298553ee78 100644 --- a/tools/net/sunrpc/xdrgen/xdr_parse.py +++ b/tools/net/sunrpc/xdrgen/xdr_parse.py @@ -63,6 +63,22 @@ def get_xdr_enum_validation() -> bool: return enum_validation +def format_source_caret(line_text: str, column: int) -> list[str]: + """Render an offending source line with a caret beneath a column. + + Args: + line_text: The raw source line containing the error + column: 1-based column of the offending token within line_text + + Returns: + Output lines for the diagnostic: a blank separator, the source + line with tabs expanded, and a caret aligned under the column. + """ + expanded = line_text.expandtabs() + caret = len(line_text[: column - 1].expandtabs()) + return ["", f" {expanded}", f" {' ' * caret}^"] + + def make_error_handler(source: str, filename: str) -> Callable[[UnexpectedInput], bool]: """Create an error handler that reports the first parse error and aborts. @@ -110,10 +126,7 @@ def make_error_handler(source: str, filename: str) -> Callable[[UnexpectedInput] msg_parts.append(str(e).split("\n")[0]) # Show the offending line with a caret pointing to the error - msg_parts.append("") - msg_parts.append(f" {line_text}") - prefix = line_text[: column - 1].expandtabs() - msg_parts.append(f" {' ' * len(prefix)}^") + msg_parts.extend(format_source_caret(line_text, column)) sys.stderr.write("\n".join(msg_parts) + "\n") raise XdrParseError() @@ -151,10 +164,27 @@ def handle_transform_error(e: VisitError, source: str, filename: str) -> None: # Show the offending line with a caret pointing to the error if line_text: - msg_parts.append("") - msg_parts.append(f" {line_text}") - prefix = line_text[: column - 1].expandtabs() - msg_parts.append(f" {' ' * len(prefix)}^") + msg_parts.extend(format_source_caret(line_text, column)) + + sys.stderr.write("\n".join(msg_parts) + "\n") + + +def handle_semantic_error(e, source: str, filename: str) -> None: + """Report a semantic error (e.g., a duplicate name) with context. + + Args: + e: The XdrSemanticError carrying message and source position + source: The XDR source text being parsed + filename: The name of the file being parsed + """ + lines = source.splitlines() + line_num = getattr(e, "line", 0) + column = getattr(e, "column", 0) + line_text = lines[line_num - 1] if 0 < line_num <= len(lines) else "" + + msg_parts = [f"{filename}:{line_num}:{column}: semantic error", e.message] + if line_text: + msg_parts.extend(format_source_caret(line_text, column)) sys.stderr.write("\n".join(msg_parts) + "\n") |
