diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt index 7e11c8e..3670451 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/AiGenerated.kt @@ -342,6 +342,24 @@ fun getAllAIGeneratedValidators(): Map { Validator("config_parse_unit_condition_string", "CONDITION_MEMORY_PRESSURE") to ConfigParseUnitConditionPressureOptionValue() as OptionValueInformation, Validator("config_parse_unit_condition_string", "CONDITION_CPU_PRESSURE") to ConfigParseUnitConditionPressureOptionValue() as OptionValueInformation, Validator("config_parse_unit_condition_string", "CONDITION_IO_PRESSURE") to ConfigParseUnitConditionPressureOptionValue() as OptionValueInformation, + // The .network/.netdev/.link [Match] conditions (config_parse_net_condition). Same runtime checks as + // the [Unit] conditions above (condition_test_net dispatches to the same functions), reusing the + // shared parameter grammars; only the marker prefix differs (netCondition = `!` only). + Validator("config_parse_net_condition", "CONDITION_HOST") to ConfigParseNetConditionHostOptionValue() as OptionValueInformation, + Validator("config_parse_net_condition", "CONDITION_VIRTUALIZATION") to ConfigParseNetConditionVirtualizationOptionValue() as OptionValueInformation, + Validator("config_parse_net_condition", "CONDITION_KERNEL_COMMAND_LINE") to ConfigParseNetConditionKernelCommandLineOptionValue() as OptionValueInformation, + Validator("config_parse_net_condition", "CONDITION_VERSION") to ConfigParseNetConditionVersionOptionValue() as OptionValueInformation, + Validator("config_parse_net_condition", "CONDITION_CREDENTIAL") to ConfigParseNetConditionCredentialOptionValue() as OptionValueInformation, + Validator("config_parse_net_condition", "CONDITION_ARCHITECTURE") to ConfigParseNetConditionArchitectureOptionValue() as OptionValueInformation, + Validator("config_parse_net_condition", "CONDITION_FIRMWARE") to ConfigParseNetConditionFirmwareOptionValue() as OptionValueInformation, + Validator("config_parse_net_condition", "CONDITION_MACHINE_TAG") to ConfigParseNetConditionMachineTagOptionValue() as OptionValueInformation, + // Traffic-control Handle=/Parent=/ClassId= (.network [QDisc]/[*Class]). The QDISC_KIND_*/TCLASS_KIND_* + // ltype only selects which struct is allocated, not how the value parses, so each registers once under + // the `*` wildcard and covers every kind. + Validator("config_parse_qdisc_handle", "*") to ConfigParseQdiscHandleOptionValue() as OptionValueInformation, + Validator("config_parse_qdisc_parent", "*") to ConfigParseQdiscParentOptionValue() as OptionValueInformation, + Validator("config_parse_tclass_parent", "*") to ConfigParseTclassParentOptionValue() as OptionValueInformation, + Validator("config_parse_tclass_classid", "*") to ConfigParseTclassClassidOptionValue() as OptionValueInformation, // Enumerations and address forms that had no validator (#509). Validator("config_parse_netdev_kind", "0") to ConfigParseNetdevKindOptionValue() as OptionValueInformation, diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionArchitectureOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionArchitectureOptionValue.kt index 7acdc15..e143aaa 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionArchitectureOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionArchitectureOptionValue.kt @@ -1,65 +1,27 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CONDITION_ARCHITECTURE import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString /* * [Unit] ConditionArchitecture= / AssertArchitecture=. * * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionArchitecture= - * parser https://github.com/systemd/systemd/blob/a8e93919c3/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_ARCHITECTURE) - * check https://github.com/systemd/systemd/blob/a8e93919c3/src/shared/condition.c condition_test_architecture_parameter - * values https://github.com/systemd/systemd/blob/a8e93919c3/src/basic/architecture.c architecture_table + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_ARCHITECTURE) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_architecture_parameter + * values https://github.com/systemd/systemd/blob/048970650c/src/basic/architecture.c architecture_table */ /** * Validator for `[Unit] ConditionArchitecture=` / `AssertArchitecture=`. * * C function: config_parse_unit_condition_string with ltype CONDITION_ARCHITECTURE; the parameter is - * checked by condition_test_architecture_parameter (src/shared/condition.c), which accepts the literal - * `native` or any name in architecture_table (src/basic/architecture.c). + * checked by condition_test_architecture_parameter, which accepts the literal `native` or any name in + * architecture_table. The value grammar is shared with the `.network`/`.netdev`/`.link` `[Match]` + * Architecture= key — see [CONDITION_ARCHITECTURE]. */ class ConfigParseUnitConditionArchitectureOptionValue : SimpleGrammarOptionValues( "config_parse_unit_condition_string", - conditionString(ARCHITECTURE) -) { - companion object { - private val ARCHITECTURE = FlexibleLiteralChoiceTerminal( - "native", - "alpha", - "arc", - "arc-be", - "arm", - "arm-be", - "arm64", - "arm64-be", - "cris", - "ia64", - "loongarch64", - "m68k", - "mips", - "mips-le", - "mips64", - "mips64-le", - "nios2", - "parisc", - "parisc64", - "ppc", - "ppc-le", - "ppc64", - "ppc64-le", - "riscv32", - "riscv64", - "s390", - "s390x", - "sh", - "sh64", - "sparc", - "sparc64", - "tilegx", - "x86", - "x86-64", - ) - } -} + conditionString(CONDITION_ARCHITECTURE) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCredentialOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCredentialOptionValue.kt index 4be02c3..b3b5218 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCredentialOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionCredentialOptionValue.kt @@ -1,7 +1,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CONDITION_CREDENTIAL import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString /* @@ -29,7 +29,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseUnitConditionCredentialOptionValue : SimpleGrammarOptionValues( "config_parse_unit_condition_string", - conditionString( - RegexTerminal(".+", "(?!\\.\\.?\$)[\\x20-\\x7E&&[^/:]]{1,255}") - ) + conditionString(CONDITION_CREDENTIAL) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFirmwareOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFirmwareOptionValue.kt index fda96a4..18ae6b5 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFirmwareOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionFirmwareOptionValue.kt @@ -1,11 +1,7 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.RegexTerminal -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CONDITION_FIRMWARE import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString /* @@ -35,19 +31,5 @@ import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gram */ class ConfigParseUnitConditionFirmwareOptionValue : SimpleGrammarOptionValues( "config_parse_unit_condition_string", - conditionString( - AlternativeCombinator( - SequenceCombinator( - LiteralChoiceTerminal("device-tree-compatible("), - RegexTerminal("[^)]+", "[^)]+"), - LiteralChoiceTerminal(")"), - ), - SequenceCombinator( - LiteralChoiceTerminal("smbios-field("), - RegexTerminal("[^)]+", "[^)]+"), - LiteralChoiceTerminal(")"), - ), - FlexibleLiteralChoiceTerminal("device-tree", "uefi"), - ) - ) + conditionString(CONDITION_FIRMWARE) ) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionVirtualizationOptionValue.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionVirtualizationOptionValue.kt index 7537540..7ed21f2 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionVirtualizationOptionValue.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/ConfigParseUnitConditionVirtualizationOptionValue.kt @@ -1,80 +1,27 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.BOOLEAN -import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CONDITION_VIRTUALIZATION import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.conditionString /* * [Unit] ConditionVirtualization= / AssertVirtualization=. * * man https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html#ConditionVirtualization= - * parser https://github.com/systemd/systemd/blob/a8e93919c3/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_VIRTUALIZATION) - * check https://github.com/systemd/systemd/blob/a8e93919c3/src/shared/condition.c condition_test_virtualization - * values https://github.com/systemd/systemd/blob/a8e93919c3/src/basic/virt.c virtualization_table - * - * The check tries, in order: the literal "private-users", parse_boolean(), the categories "vm" and - * "container", and finally an exact id from the table — so all four groups are one choice set here. + * parser https://github.com/systemd/systemd/blob/048970650c/src/core/load-fragment.c config_parse_unit_condition_string (CONDITION_VIRTUALIZATION) + * check https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_virtualization + * values https://github.com/systemd/systemd/blob/048970650c/src/basic/virt.c virtualization_table */ /** * Validator for `[Unit] ConditionVirtualization=` / `AssertVirtualization=`. * * C function: config_parse_unit_condition_string with ltype CONDITION_VIRTUALIZATION; the parameter is - * checked by condition_test_virtualization (src/shared/condition.c), which accepts, in order: - * `private-users`, any boolean parse_boolean() understands, the categories `vm` and `container`, and - * finally any id in virtualization_table (src/basic/virt.c). - * - * The boolean is kept as its own [BOOLEAN] alternative rather than folded into the name list: to - * systemd these really are two different branches, and a later formatting or completion pass can only - * tell "this span is a boolean" from "this span is a virtualization id" if the grammar says so. - * - * [BOOLEAN] has to come second. It matches a prefix of the value, so on `none` — a real entry in - * virtualization_table — it would otherwise match the leading `no` and strand `ne`, and under the - * classic engine AlternativeCombinator never backtracks out of a branch that matched. + * checked by condition_test_virtualization, which accepts `private-users`, a boolean, the categories + * `vm`/`container`, or any virtualization_table id. The value grammar is shared with the + * `.network`/`.netdev`/`.link` `[Match]` Virtualization= key — see [CONDITION_VIRTUALIZATION]. */ class ConfigParseUnitConditionVirtualizationOptionValue : SimpleGrammarOptionValues( "config_parse_unit_condition_string", - conditionString(AlternativeCombinator(VIRTUALIZATION, BOOLEAN)) -) { - companion object { - private val VIRTUALIZATION = FlexibleLiteralChoiceTerminal( - // categories, plus the userns special case - "vm", "container", "private-users", - // virtualization_table — VMs - "none", - "kvm", - "amazon", - "qemu", - "bochs", - "xen", - "uml", - "vmware", - "oracle", - "microsoft", - "zvm", - "parallels", - "bhyve", - "qnx", - "acrn", - "powervm", - "apple", - "sre", - "google", - "vm-other", - // virtualization_table — containers - "systemd-nspawn", - "lxc-libvirt", - "lxc", - "openvz", - "docker", - "podman", - "rkt", - "wsl", - "proot", - "pouch", - "container-other", - ) - } -} + conditionString(CONDITION_VIRTUALIZATION) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/NetConditionOptionValues.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/NetConditionOptionValues.kt new file mode 100644 index 0000000..89245e1 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/NetConditionOptionValues.kt @@ -0,0 +1,68 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ANY_CONDITION_ARGUMENT +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CONDITION_ARCHITECTURE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CONDITION_CREDENTIAL +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CONDITION_FIRMWARE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.CONDITION_VIRTUALIZATION +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.netCondition + +/* + * The [Match] Condition keys of .network / .netdev / .link files — config_parse_net_condition. + * + * parser https://github.com/systemd/systemd/blob/048970650c/src/shared/net-condition.c config_parse_net_condition + * checks https://github.com/systemd/systemd/blob/048970650c/src/shared/condition.c condition_test_net (table_net) + * keys systemd-build/build/{networkd-network,netdev,link-config}-gperf.gperf (the Match.* keys) + * + * config_parse_net_condition strips a single optional leading `!` (negate) and stores the rest, which is + * then checked at runtime by exactly the same condition_test_* functions as the matching [Unit] + * Condition*=/Assert*= settings — condition_test_net dispatches all eight shared types to the same leaf + * checks. So each value grammar below is the shared parameter grammar from grammar/Combinators.kt, + * wrapped by [netCondition] (which models the `!`-only prefix) instead of [conditionString] (which also + * handles the `|` trigger the [Unit] parser accepts). + * + * Keys: Match.Host, Match.Virtualization, Match.KernelCommandLine, Match.KernelVersion, Match.Version, + * Match.Credential, Match.Architecture, Match.Firmware, Match.MachineTag. + */ + +/** `[Match] Host=` — a hostname, machine/boot/product ID, or fnmatch glob (any non-empty string). */ +class ConfigParseNetConditionHostOptionValue : SimpleGrammarOptionValues( + "config_parse_net_condition", netCondition(ANY_CONDITION_ARGUMENT) +) + +/** `[Match] Virtualization=` — condition_test_virtualization. */ +class ConfigParseNetConditionVirtualizationOptionValue : SimpleGrammarOptionValues( + "config_parse_net_condition", netCondition(CONDITION_VIRTUALIZATION) +) + +/** `[Match] KernelCommandLine=` — a bare option or `name=value` (stored verbatim, any non-empty string). */ +class ConfigParseNetConditionKernelCommandLineOptionValue : SimpleGrammarOptionValues( + "config_parse_net_condition", netCondition(ANY_CONDITION_ARGUMENT) +) + +/** `[Match] KernelVersion=` / `[Match] Version=` — a version comparison list; a bare token is a glob, so + * any non-empty string is valid (condition_test_version). */ +class ConfigParseNetConditionVersionOptionValue : SimpleGrammarOptionValues( + "config_parse_net_condition", netCondition(ANY_CONDITION_ARGUMENT) +) + +/** `[Match] Credential=` — credential_name_valid. */ +class ConfigParseNetConditionCredentialOptionValue : SimpleGrammarOptionValues( + "config_parse_net_condition", netCondition(CONDITION_CREDENTIAL) +) + +/** `[Match] Architecture=` — `native` or an architecture_table name. */ +class ConfigParseNetConditionArchitectureOptionValue : SimpleGrammarOptionValues( + "config_parse_net_condition", netCondition(CONDITION_ARCHITECTURE) +) + +/** `[Match] Firmware=` — uefi / device-tree / device-tree-compatible(...) / smbios-field(...). */ +class ConfigParseNetConditionFirmwareOptionValue : SimpleGrammarOptionValues( + "config_parse_net_condition", netCondition(CONDITION_FIRMWARE) +) + +/** `[Match] MachineTag=` — an fnmatch glob against /etc/machine-info TAGS (any non-empty string). */ +class ConfigParseNetConditionMachineTagOptionValue : SimpleGrammarOptionValues( + "config_parse_net_condition", netCondition(ANY_CONDITION_ARGUMENT) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/TrafficControlHandleOptionValues.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/TrafficControlHandleOptionValues.kt new file mode 100644 index 0000000..8b1b1a0 --- /dev/null +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/ai/TrafficControlHandleOptionValues.kt @@ -0,0 +1,56 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.SimpleGrammarOptionValues +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.AlternativeCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.TC_HANDLE +import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.TC_HANDLE_NUMBER + +/* + * Traffic-control Handle= / Parent= / ClassId= for the [QDisc] and [*Class] sections of .network files. + * + * parsers https://github.com/systemd/systemd/blob/048970650c/src/network/tc/qdisc.c config_parse_qdisc_handle, config_parse_qdisc_parent + * https://github.com/systemd/systemd/blob/048970650c/src/network/tc/tclass.c config_parse_tclass_parent, config_parse_tclass_classid + * https://github.com/systemd/systemd/blob/048970650c/src/network/tc/tc-util.c parse_handle + * keys systemd-build/build/networkd-network-gperf.gperf ([QDisc]/[]/[] Handle=/Parent=/ClassId=) + * + * Each parser's ltype (QDISC_KIND_* / TCLASS_KIND_*) is passed only to qdisc_new_static / tclass_new_static + * to allocate the right struct — it has no effect on how the value is parsed. So each of these registers + * once under the `*` ltype wildcard and covers every kind. + * + * The man page is silent on Handle= and documents Parent= for one section only as "clsact or ingress", + * which is narrower than the parser: `Parent=root` (54 occurrences in the reference corpus) and the + * `major:minor` form are both accepted here, following the C source. + */ + +/** `Handle=` in a [QDisc]/qdisc section — config_parse_qdisc_handle: a single hex uint16. */ +class ConfigParseQdiscHandleOptionValue : SimpleGrammarOptionValues( + "config_parse_qdisc_handle", + SequenceCombinator(TC_HANDLE_NUMBER, EOF()) +) + +/** `Parent=` in a [QDisc]/qdisc section — config_parse_qdisc_parent: `root`, `clsact`, `ingress`, or a handle. */ +class ConfigParseQdiscParentOptionValue : SimpleGrammarOptionValues( + "config_parse_qdisc_parent", + SequenceCombinator( + AlternativeCombinator(FlexibleLiteralChoiceTerminal("root", "clsact", "ingress"), TC_HANDLE), + EOF() + ) +) + +/** `Parent=` in a [*Class] section — config_parse_tclass_parent: `root` or a handle (no clsact/ingress). */ +class ConfigParseTclassParentOptionValue : SimpleGrammarOptionValues( + "config_parse_tclass_parent", + SequenceCombinator( + AlternativeCombinator(FlexibleLiteralChoiceTerminal("root"), TC_HANDLE), + EOF() + ) +) + +/** `ClassId=` in a [*Class] section — config_parse_tclass_classid: a handle. */ +class ConfigParseTclassClassidOptionValue : SimpleGrammarOptionValues( + "config_parse_tclass_classid", + SequenceCombinator(TC_HANDLE, EOF()) +) diff --git a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt index 0d53f87..0bf5299 100644 --- a/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt +++ b/src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/grammar/Combinators.kt @@ -433,3 +433,92 @@ val FNMATCH_COMPARE_OPERATOR = LiteralChoiceTerminal("!$=", "$=", "<>", "<=", "> // An environment-variable name — env_name_is_valid (systemd src/basic/env-util.c): a letter or // underscore followed by letters, digits and underscores. Used by ConditionOSRelease= keys. val ENV_NAME = RegexTerminal("[A-Za-z_][A-Za-z0-9_]*", "[A-Za-z_][A-Za-z0-9_]*") + + +// --------------------------------------------------------------------------------------------------- +// Shared Condition/Assert/[Match] parameter grammars. +// +// The same condition_test_* leaf check (systemd src/shared/condition.c) validates a given condition +// type whether it was written as a [Unit] Condition*=/Assert*= (config_parse_unit_condition_string) or +// as a [Match] key in .network/.netdev/.link (config_parse_net_condition) — condition_test_net and +// condition_test dispatch the same eight shared types to identical functions. Only the marker prefix +// differs, which is why the value grammars live here and the two entry points ([conditionString], +// [netCondition]) wrap them differently. + +/** + * A `native`-or-architecture-name parameter — condition_test_architecture_parameter, backed by + * architecture_from_string (src/basic/architecture.c). + */ +val CONDITION_ARCHITECTURE = FlexibleLiteralChoiceTerminal( + "native", + "alpha", "arc", "arc-be", "arm", "arm-be", "arm64", "arm64-be", "cris", "ia64", "loongarch64", + "m68k", "mips", "mips-le", "mips64", "mips64-le", "nios2", "parisc", "parisc64", "ppc", "ppc-le", + "ppc64", "ppc64-le", "riscv32", "riscv64", "s390", "s390x", "sh", "sh64", "sparc", "sparc64", + "tilegx", "x86", "x86-64", +) + +/** + * A virtualization parameter — condition_test_virtualization. Accepts, in order: `private-users`, a + * boolean, the categories `vm`/`container`, or any id in virtualization_table (src/basic/virt.c). + * [BOOLEAN] must follow the names so it cannot strand the `no` inside `none`. + */ +val CONDITION_VIRTUALIZATION = AlternativeCombinator( + FlexibleLiteralChoiceTerminal( + "vm", "container", "private-users", + "none", "kvm", "amazon", "qemu", "bochs", "xen", "uml", "vmware", "oracle", "microsoft", "zvm", + "parallels", "bhyve", "qnx", "acrn", "powervm", "apple", "sre", "google", "vm-other", + "systemd-nspawn", "lxc-libvirt", "lxc", "openvz", "docker", "podman", "rkt", "wsl", "proot", + "pouch", "container-other", + ), + BOOLEAN, +) + +/** + * A credential name — credential_name_valid (src/shared/creds-util.c) = filename_is_valid && + * fdname_is_valid: one to 255 printable-ASCII characters, none of them `/` or `:`, and not `.`/`..`. + */ +val CONDITION_CREDENTIAL = RegexTerminal(".+", "(?!\\.\\.?\$)[\\x20-\\x7E&&[^/:]]{1,255}") + +/** + * A firmware parameter — condition_test_firmware. One of `uefi`, `device-tree`, or the parenthesised + * `device-tree-compatible(DTC)` / `smbios-field(FIELD OP VALUE)` forms (argument modelled loosely; the + * parser closes on the last `)`). + */ +val CONDITION_FIRMWARE = AlternativeCombinator( + SequenceCombinator(LiteralChoiceTerminal("device-tree-compatible("), RegexTerminal("[^)]+", "[^)]+"), LiteralChoiceTerminal(")")), + SequenceCombinator(LiteralChoiceTerminal("smbios-field("), RegexTerminal("[^)]+", "[^)]+"), LiteralChoiceTerminal(")")), + FlexibleLiteralChoiceTerminal("device-tree", "uefi"), +) + +/** + * A `[Match]` condition value for config_parse_net_condition (.network/.netdev/.link, src/shared/ + * net-condition.c). That parser strips a single optional leading `!` (negate) — no `|` trigger and no + * whitespace skip after it — then hands the rest to the same condition_test_* check as the [Unit] + * conditions. Spelled as alternatives (rather than a ZeroOrOne over `!`) for the same reason as + * [conditionPath]: it keeps the reported match offset equal to what was consumed. + */ +fun netCondition(parameter: Combinator): Combinator = SequenceCombinator( + AlternativeCombinator( + SequenceCombinator(BANG, parameter), + parameter, + ), + EOF() +) + + +// --------------------------------------------------------------------------------------------------- +// Traffic-control handles — the [QDisc]/[*Class] Handle=, Parent= and ClassId= values in .network files +// (systemd src/network/tc/qdisc.c, src/network/tc/tclass.c, src/network/tc/tc-util.c). +// +// Every number here is read with safe_atou16_full(..., base 16): always hexadecimal (so `Handle=20` is +// 0x20, not 20), fitting a uint16. strtoul(base 16) also tolerates an optional `0x` and leading zeros — +// real files write bare hex like `3a` or `0002`. The value is bounded to <= 0xffff by capping the +// significant hex digits at four. +val TC_HANDLE_NUMBER = RegexTerminal("(?:0[xX])?[0-9a-fA-F]+", "(?:0[xX])?0*[0-9a-fA-F]{1,4}") + +/** + * A tc handle `major:minor` — parse_handle (src/network/tc/tc-util.c), which splits once on `:` and + * reads each side as a [TC_HANDLE_NUMBER]. Both sides are mandatory. Used verbatim for `ClassId=` and as + * the numeric branch of `Parent=`. + */ +val TC_HANDLE = SequenceCombinator(TC_HANDLE_NUMBER, COLON, TC_HANDLE_NUMBER) diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/NetConditionOptionValuesTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/NetConditionOptionValuesTest.kt new file mode 100644 index 0000000..348e7e4 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/NetConditionOptionValuesTest.kt @@ -0,0 +1,54 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +/** The .network/.netdev/.link [Match] condition keys — config_parse_net_condition. */ +class NetConditionOptionValuesTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // language="unit file (systemd)" + val file = """ + [Match] + Host=myhost + Host=!myhost + Virtualization=kvm + Virtualization=!container + Virtualization=no + Architecture=x86-64 + Architecture=!arm64 + Firmware=uefi + Firmware=device-tree-compatible(some-board) + Credential=mycred + KernelVersion=>=5.4 + Version=6.1 + KernelCommandLine=quiet + MachineTag=production + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // language="unit file (systemd)" + val file = """ + [Match] + Virtualization=bogus + Architecture=nonsense + Firmware=bios + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(3, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/TrafficControlHandleOptionValuesTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/TrafficControlHandleOptionValuesTest.kt new file mode 100644 index 0000000..514a435 --- /dev/null +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/inspections/ai/TrafficControlHandleOptionValuesTest.kt @@ -0,0 +1,66 @@ +package net.sjrx.intellij.plugins.systemdunitfiles.inspections.ai + +import net.sjrx.intellij.plugins.systemdunitfiles.AbstractUnitFileTest +import net.sjrx.intellij.plugins.systemdunitfiles.inspections.InvalidValueInspection +import org.junit.Test + +/** Traffic-control Handle= / Parent= / ClassId= in .network [QDisc] and [*Class] sections. */ +class TrafficControlHandleOptionValuesTest : AbstractUnitFileTest() { + + @Test + fun testValidValues() { + // Handles are hexadecimal uint16 (so `10` is 0x10). Parent= takes root/clsact/ingress or a + // major:minor handle in a [QDisc], but only root or a handle in a [*Class]. + // language="unit file (systemd)" + val file = """ + [QDisc] + Handle=3a + Handle=0002 + Handle=ffff + Handle=10 + Parent=root + Parent=clsact + Parent=ingress + Parent=2:3c + + [NetworkEmulator] + Handle=0037 + Parent=35:0 + + [HierarchyTokenBucketClass] + Parent=root + Parent=1:1 + ClassId=1:10 + ClassId=abcd:1 + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(0, highlights) + } + + @Test + fun testInvalidValues() { + // Handle=gg: not hex. Handle=1:2: a handle number takes no colon. Parent=clsact in a [*Class]: + // clsact/ingress are qdisc-only, not accepted by config_parse_tclass_parent. + // language="unit file (systemd)" + val file = """ + [QDisc] + Handle=gg + Handle=1:2 + Parent=nonsense + + [HierarchyTokenBucketClass] + Parent=clsact + ClassId=root + """.trimIndent() + + setupFileInEditor("file.network", file) + enableInspection(InvalidValueInspection::class.java) + val highlights = myFixture.doHighlighting() + + assertSize(5, highlights) + } +} diff --git a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt index 6eda9c5..0ecc043 100644 --- a/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt +++ b/src/test/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/optionvalues/OptionValueTest.kt @@ -43,13 +43,25 @@ class OptionValueTest : AbstractUnitFileTest() { val reductionPerDay = 1 val allowed = maxOf(0, startingCount - (daysSinceStart * reductionPerDay)) + // The allowance shrinks by reductionPerDay each day; the test fails once it reaches the current + // missing count. Solving allowed <= missing for the date gives the day this count stops passing. + val failDate = startDate.plusDays(((startingCount - missingValidators.size) / reductionPerDay).toLong()) + println("Missing Keywords:$totalMissingValidators") println("Missing Validators:${missingValidators.size}") println("Allowed Missing Validation: $allowed") println("Found:$totalFoundValidators") + if (missingValidators.size >= allowed) { + println("Burndown: test started failing on $failDate (at the current missing count)") + } else { + println("Burndown: test will start to fail on $failDate unless the count keeps dropping") + } if (missingValidators.size >= allowed) { assertEquals("Number of missing validators is too high at ${missingValidators.size} > $allowed vs. found ${foundValidators.size} ${totalFoundValidators}", sortedList, "") + } else { + println("Missing Validators:") + println(sortedList) } if (totalFoundValidators == 0) {