Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,32 @@ fun getAllAIGeneratedValidators(): Map<Validator, OptionValueInformation> {
Validator("config_parse_socket_service", "0") to ConfigParseSocketServiceOptionValue() as OptionValueInformation,
Validator("config_parse_service_sockets", "0") to ConfigParseServiceSocketsOptionValue() as OptionValueInformation,

// Batch 2026-10: nspawn
// .nspawn settings (src/nspawn/nspawn-settings.c at 0f0682b773). None of these parsers is used by
// any other gperf table, and every (parser, ltype) below is shared only by keys of one value type.
Validator("config_parse_capability", "0") to ConfigParseCapabilityOptionValue() as OptionValueInformation,
Validator("config_parse_volatile_mode", "0") to ConfigParseVolatileModeOptionValue() as OptionValueInformation,
Validator("config_parse_veth_extra", "0") to ConfigParseVethExtraOptionValue() as OptionValueInformation,
Validator("config_parse_tmpfs", "0") to ConfigParseTmpfsOptionValue() as OptionValueInformation,
Validator("config_parse_restrict_address_families", "0") to ConfigParseRestrictAddressFamiliesOptionValue() as OptionValueInformation,
Validator("config_parse_resolv_conf", "0") to ConfigParseResolvConfOptionValue() as OptionValueInformation,
Validator("config_parse_pivot_root", "0") to ConfigParsePivotRootOptionValue() as OptionValueInformation,
// ltype only selects read-only for Overlay=/OverlayReadOnly= and Bind=/BindReadOnly=.
Validator("config_parse_overlay", "0") to ConfigParseOverlayOptionValue() as OptionValueInformation,
Validator("config_parse_overlay", "1") to ConfigParseOverlayOptionValue() as OptionValueInformation,
Validator("config_parse_oom_score_adjust", "0") to ConfigParseOomScoreAdjustOptionValue() as OptionValueInformation,
Validator("config_parse_network_zone", "0") to ConfigParseNetworkZoneOptionValue() as OptionValueInformation,
Validator("config_parse_network_iface_pair", "0") to ConfigParseNetworkIfacePairOptionValue() as OptionValueInformation,
Validator("config_parse_macvlan_iface_pair", "0") to ConfigParseMacvlanIfacePairOptionValue() as OptionValueInformation,
Validator("config_parse_ipvlan_iface_pair", "0") to ConfigParseIpvlanIfacePairOptionValue() as OptionValueInformation,
Validator("config_parse_link_journal", "0") to ConfigParseLinkJournalOptionValue() as OptionValueInformation,
Validator("config_parse_inaccessible", "0") to ConfigParseInaccessibleOptionValue() as OptionValueInformation,
Validator("config_parse_id128", "0") to ConfigParseId128OptionValue() as OptionValueInformation,
Validator("config_parse_expose_port", "0") to ConfigParseExposePortOptionValue() as OptionValueInformation,
Validator("config_parse_bind_user", "0") to ConfigParseBindUserOptionValue() as OptionValueInformation,
Validator("config_parse_bind", "0") to ConfigParseBindOptionValue() as OptionValueInformation,
Validator("config_parse_bind", "1") to ConfigParseBindOptionValue() as OptionValueInformation,

)

return allValidators
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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.COLON
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.COLON_FIELD_ABSOLUTE_PATH
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.COLON_FIELD_SOURCE_PATH
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.LiteralChoiceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.OneOrMore
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator
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.ZeroOrMore
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne

/*
* [Files] Bind= / BindReadOnly= (.nspawn).
*
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.nspawn.html#Bind=
* man https://www.freedesktop.org/software/systemd/man/latest/systemd-nspawn.html#--bind=
* parser https://github.com/systemd/systemd/blob/0f0682b773/src/nspawn/nspawn-settings.c#L351-L375 config_parse_bind (0 = Bind=, 1 = BindReadOnly=)
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/nspawn/nspawn-mount.c#L234-L275 bind_mount_parse
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/nspawn/nspawn-mount.c#L86-L118 source_path_parse / source_path_parse_nullable
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L240-L281 extract_many_words (":", EXTRACT_DONT_COALESCE_SEPARATORS)
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/nspawn/nspawn-mount.c#L759-L800 parse_mount_bind_options (only at container start, NOT at parse time)
* oracle steve-local/systemd-tools/bin/sd-check (fuzz-nspawn-settings at 0f0682b773): `Bind=/a:/b:bogus`, `Bind=/a:/b:rbind:x`,
* `BindReadOnly=/a:/b:ro`, `Bind=/a:/b:rbind,bogus`, `Bind=/a:/b::`, `Bind=/a:/b:has space` are all accepted
* (no output); `Bind=/a:relative:rbind` → "Invalid bind mount specification ... Invalid argument".
* tests https://github.com/systemd/systemd/blob/0f0682b773/test/units/TEST-13-NSPAWN.nspawn.sh#L264-L287
*/

/**
* The options parse_mount_bind_options() knows. Listed first only for completion and highlighting: anything
* else is accepted too (see BIND_OPTIONS_FIELD), because only the container start rejects it.
*/
private val BIND_OPTION = FlexibleLiteralChoiceTerminal("rbind", "norbind", "idmap", "noidmap", "rootidmap", "owneridmap")

private val COMMA = LiteralChoiceTerminal(",")

/**
* The option string: split with extract_first_word(",", 0), which coalesces repeated commas and ignores
* leading and trailing ones (so a string of nothing but commas means "no options").
*/
private val BIND_OPTIONS = SequenceCombinator(
ZeroOrMore(COMMA),
ZeroOrOne(SequenceCombinator(BIND_OPTION, ZeroOrMore(SequenceCombinator(OneOrMore(COMMA), BIND_OPTION)))),
ZeroOrMore(COMMA),
)

/**
* Everything after the second colon. bind_mount_parse() strdup()s it verbatim (no unescaping, no checks), so
* any text is accepted at parse time. The known-option list is tried first so that a valid list is coloured
* and completed as options; otherwise the whole rest is taken as one opaque option string.
* parse_mount_bind_options() rejects unknown options only when the container starts ("Invalid bind mount
* option"); GitHub issue #565 tracks a separate warning for those.
*/
private val BIND_OPTIONS_FIELD = PreferValidAlternativeCombinator(
SequenceCombinator(BIND_OPTIONS, EOF()),
SequenceCombinator(RegexTerminal("""[\s\S]+""", """[\s\S]+"""), EOF()),
)

/** A lone path is both source and destination, so it must be absolute (optionally `+`-prefixed). */
private val SINGLE_PATH = RegexTerminal("""(?:[^:\\]|\\[\s\S])+""", """\+?\\?/(?:[^:\\]|\\[\s\S])*""")

/**
* Validator for the .nspawn `[Files] Bind=` and `BindReadOnly=` settings (config_parse_bind; ltype 1
* only sets the read-only flag, so both share one grammar).
*
* bind_mount_parse() splits the value into at most two colon-separated fields with extract_many_words(":",
* EXTRACT_DONT_COALESCE_SEPARATORS) — backslash makes the next character literal, so `\:` embeds a colon;
* quotes and whitespace are ordinary characters — and keeps whatever follows the second colon verbatim as
* the option string:
*
* - `PATH` alone is used as both source and destination: it must be absolute, optionally prefixed with
* `+` (meaning "relative to the container image"), which is dropped for the destination.
* - `SOURCE:DEST[:OPTIONS]` — SOURCE may be empty (a temporary directory under /var/tmp), `+/abs`, or any
* other path (a relative one is resolved against the working directory). DEST must be absolute.
* `SOURCE:` (an empty DEST) is invalid; an empty OPTIONS after the second colon is ignored.
*
* OPTIONS is not inspected at config-parse time, so any option string is accepted here. Only when the
* container starts does parse_mount_bind_options() refuse anything other than a comma-separated list of
* `rbind`, `norbind`, `idmap`, `noidmap`, `rootidmap`, `owneridmap` (e.g. `bogus`, `rbind:x`, `ro`); this
* validator follows the parser and accepts them, and GitHub issue #565 tracks a separate warning for
* such runtime-only rejections. Parse errors are logged and the setting ignored.
*/
class ConfigParseBindOptionValue : SimpleGrammarOptionValues(
"config_parse_bind",
AlternativeCombinator(
SequenceCombinator(
ZeroOrOne(COLON_FIELD_SOURCE_PATH),
COLON,
COLON_FIELD_ABSOLUTE_PATH,
AlternativeCombinator(
SequenceCombinator(COLON, BIND_OPTIONS_FIELD),
EOF(),
),
),
SequenceCombinator(SINGLE_PATH, EOF()),
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.STRICT_USER_NAME
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore

/*
* [Files] BindUser= (.nspawn).
*
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.nspawn.html#BindUser=
* man https://www.freedesktop.org/software/systemd/man/latest/systemd-nspawn.html#--bind-user=
* parser https://github.com/systemd/systemd/blob/0f0682b773/src/nspawn/nspawn-settings.c#L979-L1024 config_parse_bind_user (0)
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (WHITESPACE, flags 0)
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/user-util.c#L718-L832 valid_user_group_name (flags 0 = strict)
* tests https://github.com/systemd/systemd/blob/0f0682b773/test/units/TEST-13-NSPAWN.nspawn.sh#L631-L632
*/

/**
* Validator for the .nspawn `[Files] BindUser=` setting (config_parse_bind_user, ltype 0).
*
* The value is a whitespace-separated list of user names (an empty value resets the list and never
* reaches the grammar). Each name must pass valid_user_group_name() in its STRICT form (no
* VALID_USER_RELAX): a letter or `_`, then letters, digits, `_` or `-`, at most 31 bytes (the utmp
* ut_user field). Dots, leading digits or dashes, and numeric UIDs are all refused. On the first invalid
* name the rest of the line is ignored with a warning.
*/
class ConfigParseBindUserOptionValue : SimpleGrammarOptionValues(
"config_parse_bind_user",
SequenceCombinator(
STRICT_USER_NAME,
ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), STRICT_USER_NAME)),
EOF(),
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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.CAPABILITY_NAME
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.WhitespaceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrMore

/*
* [Exec] Capability= / AmbientCapability= / DropCapability= (.nspawn).
*
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.nspawn.html#Capability=
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.nspawn.html#AmbientCapability=
* parser https://github.com/systemd/systemd/blob/0f0682b773/src/nspawn/nspawn-settings.c#L276-L323 config_parse_capability (0)
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/extract-word.c#L10-L188 extract_first_word (WHITESPACE, flags 0)
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/capability-list.c#L45-L66 capability_from_name
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L497-L520 safe_atoi (numeric capabilities)
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/capability-util.h#L17 CAP_LIMIT
* check https://github.com/systemd/systemd/blob/0f0682b773/src/nspawn/nspawn.c#L1559-L1561 verify_arguments (refuses AmbientCapability=all after parsing)
* tests https://github.com/systemd/systemd/blob/0f0682b773/test/units/TEST-13-NSPAWN.nspawn.sh#L518-L520
* linux https://man7.org/linux/man-pages/man7/capabilities.7.html
*/

/** One capability word: the literal `all`, a capability number, or a capability name. */
private val CAPABILITY_WORD = AlternativeCombinator(
LiteralChoiceTerminal("all"),
SafeAtoTerminal(
signed = true,
base = 0,
flags = emptySet(),
minInclusive = 0L,
maxInclusive = 62L,
acceptsWhitespace = false,
acceptsMinus = true,
),
CAPABILITY_NAME,
)

/**
* Validator for the .nspawn `[Exec] Capability=`, `AmbientCapability=` and `DropCapability=` settings,
* all of which use config_parse_capability with ltype 0.
*
* The value is a whitespace-separated list (extract_first_word with the default WHITESPACE separators,
* so runs of whitespace coalesce). Each word is either:
*
* - `all` — matched with a case-sensitive streq(), so `ALL` is not special and fails the name lookup;
* - a capability number — capability_from_name() first tries safe_atoi() (base 0, so `0x15` works)
* and accepts 0…CAP_LIMIT (62), even above the running kernel's last capability;
* - a capability name — looked up in a gperf table generated with `--ignore-case`, so `cap_net_admin`
* is as good as `CAP_NET_ADMIN`.
*
* An unknown word is logged and skipped rather than failing the whole line; it is still flagged here.
*
* Deliberately lenient: the man page and nspawn itself refuse `all` for AmbientCapability= — that check
* happens later, in verify_arguments() ("AmbientCapability= does not support the value all"), and the
* grammar is shared by all three keys via (parser, ltype), so it cannot single that key out.
*/
class ConfigParseCapabilityOptionValue : SimpleGrammarOptionValues(
"config_parse_capability",
SequenceCombinator(
CAPABILITY_WORD,
ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), CAPABILITY_WORD)),
EOF(),
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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.COLON
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.LiteralChoiceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.ZeroOrOne

/*
* [Network] Port= (.nspawn).
*
* man https://www.freedesktop.org/software/systemd/man/latest/systemd.nspawn.html#Port=
* man https://www.freedesktop.org/software/systemd/man/latest/systemd-nspawn.html#--port=
* parser https://github.com/systemd/systemd/blob/0f0682b773/src/nspawn/nspawn-settings.c#L248-L274 config_parse_expose_port (0)
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/nspawn/nspawn-expose-ports.c#L17-L73 expose_port_parse
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L718-L734 parse_ip_port
* leaf https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L429-L478 safe_atou_full (base 0, SAFE_ATO_REFUSE_LEADING_WHITESPACE)
* tests https://github.com/systemd/systemd/blob/0f0682b773/test/units/TEST-13-NSPAWN.nspawn.sh#L557-L560
* tests https://github.com/systemd/systemd/blob/0f0682b773/test/units/TEST-13-NSPAWN.nspawn.sh#L315-L326 (accepted and rejected --port= spellings)
*/

/**
* parse_ip_port(): safe_atou16_full(s, SAFE_ATO_REFUSE_LEADING_WHITESPACE) (base 0), then zero is refused.
* Whitespace between a `0b`/`0o` prefix and the digits is skipped by strtoul() (`Port=0o 120` is valid), but a
* leading space or tab is refused (`80: 90` is not). The flag only checks `" \t\n\r"`, so a leading `\v`
* reaches strtoul(), which skips it (`Port=\v80` is valid).
*/
private val PORT = SafeAtoTerminal(
signed = false,
base = 0,
flags = setOf(SafeAtoTerminal.Flag.REFUSE_LEADING_WHITESPACE),
minInclusive = 1L,
maxInclusive = 65535L,
acceptsWhitespace = true,
acceptsMinus = true,
)

/**
* Validator for the .nspawn `[Network] Port=` setting (config_parse_expose_port, ltype 0).
*
* expose_port_parse() strips an optional `tcp:` or `udp:` prefix (case-sensitive startswith(); the
* default is TCP), then splits the rest at the first `:` into a host port and a container port. With no
* colon the one number is used for both. Each port goes through parse_ip_port(): safe_atou16() with
* base 0 (so `0x50` is port 80) and no leading whitespace, then 1…65535. A malformed or duplicate
* specification is logged and ignored.
*/
class ConfigParseExposePortOptionValue : SimpleGrammarOptionValues(
"config_parse_expose_port",
SequenceCombinator(
ZeroOrOne(SequenceCombinator(LiteralChoiceTerminal("tcp", "udp"), COLON)),
PORT,
ZeroOrOne(SequenceCombinator(COLON, PORT)),
EOF(),
)
)
Loading
Loading