Skip to content
Merged
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
@@ -1,39 +1,137 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues

import kotlinx.html.ADDRESS
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFilePropertyType
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*

/**
* SocketBindAllow= / SocketBindDeny=.
*
* [config_parse_cgroup_socket_bind](https://github.com/systemd/systemd/blob/0f0682b773/src/core/load-fragment.c#L5940-L5983)
* hands the value to
* [parse_socket_bind_item](https://github.com/systemd/systemd/blob/0f0682b773/src/shared/parse-helpers.c#L222-L290),
* which splits it on `:` (an empty field is an error) and tries, in this order and each at most once,
* [parse_af_token](https://github.com/systemd/systemd/blob/0f0682b773/src/shared/parse-helpers.c#L147-L164)
* (`ipv4`/`ipv6`, case-sensitive), parse_ip_protocol_token (`tcp`/`udp` in any case) and
* [parse_ip_ports_token](https://github.com/systemd/systemd/blob/0f0682b773/src/shared/parse-helpers.c#L187-L213):
* `any`, or a port or port range read by
* [parse_ip_port_range](https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L736-L760)
* with port 0 allowed. Each bound goes through safe_atou
* ([parse_range](https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L305-L340)), so leading
* whitespace, `0x50` and `+80` are all fine, and `ipv4:tcp: 12345` is accepted. Anything left over is an error.
*
* parse_ip_port_range also refuses a range whose upper bound is below the lower one (`5-1`); that is checked on
* the parsed numbers once the grammar has accepted the value. Not modelled: backslash escapes inside a field.
*/
class CgroupSocketBindOptionValue() : GrammarOptionValue("config_parse_cgroup_socket_bind", GRAMMAR) {

override fun generateProblemDescriptors(property: UnitFilePropertyType, holder: ProblemsHolder) {
val before = holder.resultCount
super.generateProblemDescriptors(property, holder)
if (holder.resultCount != before) {
return
}

class CgroupSocketBindOptionValue() : GrammarOptionValue("config_parse_cgroup_socket_bind", GRAMMAR) {
// The grammar accepted the value, so the ports (if any) are the last `:` field, and a `-` in it splits the
// range: https://github.com/systemd/systemd/blob/0f0682b773/src/basic/parse-util.c#L753-L754
val ports = (property.valueText ?: return).substringAfterLast(':')
if (!ports.contains('-')) {
return
}
val low = SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = false)
.valueOf(ports.substringBefore('-'))
val high = SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = true)
.valueOf(ports.substringAfter('-'))
if (low != null && high != null && high < low) {
holder.registerProblem(
property.valueNode.psi,
"${property.key}'s port range ends below where it starts ($low-$high)",
ProblemHighlightType.GENERIC_ERROR_OR_WARNING
)
}
}

companion object {
// https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html
val ADDRESS_FAMILY = LiteralChoiceTerminal("ipv4", "ipv6")
val TRANSPORT_PROTOCOL = LiteralChoiceTerminal("tcp", "udp")
val IP_PORT = IntegerTerminal(1, 65536)
val DASH = LiteralChoiceTerminal("-")
val COLON = LiteralChoiceTerminal(":")
val IP_PORT_RANGE = SequenceCombinator(IP_PORT, DASH, IP_PORT)
val IP_PORTS = AlternativeCombinator(IP_PORT_RANGE, IP_PORT)

// Order matters for the classic engine only, which falls back to the first branch that matches leniently:
// ports first (the lenient match of `ipv4` would take the first four digits of a port), then the address
// family (its lenient match takes a whole bad word such as `icmp`, which the 3-letter `tcp` would cut short).
val GRAMMAR = SequenceCombinator(
AlternativeCombinator(
PreferValidAlternativeCombinator(
// ports only
SequenceCombinator(
SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = false),
ZeroOrOne(
SequenceCombinator(
LiteralChoiceTerminal("-"),
SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = true)
)
)
),
LiteralChoiceTerminal("any"),
// The grammar has three blocks address-family, transport-protocol, and ip-ports, if there is more than one, there is a colon.
// We break this up into a few cases
// All specified
SequenceCombinator(ADDRESS_FAMILY, COLON, TRANSPORT_PROTOCOL,COLON, IP_PORTS),
// No ports
SequenceCombinator(ADDRESS_FAMILY, COLON, TRANSPORT_PROTOCOL),
// No transport
SequenceCombinator(ADDRESS_FAMILY, COLON, IP_PORTS),
// No Address family
SequenceCombinator(TRANSPORT_PROTOCOL,COLON, IP_PORTS),
ADDRESS_FAMILY,
TRANSPORT_PROTOCOL,
IP_PORTS
// address family, then optionally protocol and/or ports
SequenceCombinator(
FlexibleLiteralChoiceTerminal("ipv4", "ipv6"),
ZeroOrOne(
SequenceCombinator(
LiteralChoiceTerminal(":"),
PreferValidAlternativeCombinator(
SequenceCombinator(
SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = false),
ZeroOrOne(
SequenceCombinator(
LiteralChoiceTerminal("-"),
SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = true)
)
)
),
LiteralChoiceTerminal("any"),
SequenceCombinator(
FlexibleLiteralChoiceTerminal("tcp", "udp", ignoreCase = true),
ZeroOrOne(
SequenceCombinator(
LiteralChoiceTerminal(":"),
PreferValidAlternativeCombinator(
SequenceCombinator(
SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = false),
ZeroOrOne(
SequenceCombinator(
LiteralChoiceTerminal("-"),
SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = true)
)
)
),
LiteralChoiceTerminal("any")
)
)
)
)
)
)
)
),
// protocol, then optionally ports
SequenceCombinator(
FlexibleLiteralChoiceTerminal("tcp", "udp", ignoreCase = true),
ZeroOrOne(
SequenceCombinator(
LiteralChoiceTerminal(":"),
PreferValidAlternativeCombinator(
SequenceCombinator(
SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = false),
ZeroOrOne(
SequenceCombinator(
LiteralChoiceTerminal("-"),
SafeAtoTerminal(signed = false, base = 0, flags = emptySet(), minInclusive = 0L, maxInclusive = 65535L, acceptsWhitespace = true, acceptsMinus = true)
)
)
),
LiteralChoiceTerminal("any")
)
)
)
)
),
EOF())

Expand All @@ -42,4 +140,3 @@ class CgroupSocketBindOptionValue() : GrammarOptionValue("config_parse_cgroup_so
)
}
}

Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues

import com.google.common.collect.ImmutableSet
import com.intellij.openapi.project.Project
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator



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.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.FlexibleLiteralChoiceTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.PreferValidAlternativeCombinator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SafeAtoTerminal
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator



/**
* FailureAction=, SuccessAction=, JobTimeoutAction=, StartLimitAction= and friends.
*
* Every name in systemd's
* [emergency_action_table](https://github.com/systemd/systemd/blob/0f0682b773/src/core/emergency-action.c#L15-L32),
* looked up case-sensitively by
* [parse_emergency_action](https://github.com/systemd/systemd/blob/0f0682b773/src/core/emergency-action.c#L245-L263).
* In the user manager everything after `exit-force` is refused with EOPNOTSUPP, but the plugin cannot tell the
* scope of a unit file, so it accepts the full system-manager table.
*/
class EmergencyActionOptionValue : AbstractGrammarEnumOptionValue(validOptions, VALIDATOR_NAME){

companion object {
private val validOptions : Set<String> = ImmutableSet.of("none", "reboot", "reboot-force", "reboot-immediate", "poweroff", "poweroff-force", "poweroff-immediate", "exit", "exit-force")
private val validOptions : Set<String> = ImmutableSet.of(
"none", "reboot", "reboot-force", "reboot-immediate", "poweroff", "poweroff-force", "poweroff-immediate", "exit", "exit-force",
"soft-reboot", "soft-reboot-force", "kexec", "kexec-force", "halt", "halt-force", "halt-immediate",
)
const val VALIDATOR_NAME = "config_parse_emergency_action"
}
}
Expand Down Expand Up @@ -74,19 +95,77 @@ class PersonalityOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("a
}
}

class CpuSchedulingPolicyOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("other", "batch", "idle", "fifo", "rr"), VALIDATOR_NAME) {
/**
* sched_policy_from_string(): a name from sched_policy_table, or (string-table fallback) any safe_atou() number up
* to INT_MAX.
* https://github.com/systemd/systemd/blob/0f0682b773/src/basic/process-util.c#L2317-L2326
* https://github.com/systemd/systemd/blob/0f0682b773/src/basic/string-table.c#L65-L80
*/
class CpuSchedulingPolicyOptionValue : SimpleGrammarOptionValues(
VALIDATOR_NAME,
SequenceCombinator(
AlternativeCombinator(
FlexibleLiteralChoiceTerminal("other", "batch", "idle", "fifo", "ext", "rr", ignoreCase = false),
SafeAtoTerminal(
signed = false,
base = 0,
flags = emptySet(),
minInclusive = 0L,
maxInclusive = Int.MAX_VALUE.toLong(),
acceptsWhitespace = true,
acceptsMinus = true,
),
),
EOF(),
),
) {
override fun getAutoCompleteOptions(project: Project): Set<String> = ImmutableSet.of("other", "batch", "idle", "fifo", "ext", "rr")

companion object {
const val VALIDATOR_NAME = "config_parse_exec_cpu_sched_policy"
}
}

class NumaPolicyOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("default", "bind", "interleave", "local"), VALIDATOR_NAME) {
/**
* NUMAPolicy=: every name in
* [mpol_table](https://github.com/systemd/systemd/blob/0f0682b773/src/shared/numa-util.c#L253-L261), parsed by
* [DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT](https://github.com/systemd/systemd/blob/0f0682b773/src/core/load-fragment.c#L174).
*/
class NumaPolicyOptionValue : AbstractGrammarEnumOptionValue(
ImmutableSet.of("default", "preferred", "bind", "interleave", "local", "preferred-many", "weighted-interleave"),
VALIDATOR_NAME
) {
companion object {
const val VALIDATOR_NAME = "config_parse_numa_policy"
}
}

class IOSchedulingClassOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("realtime", "best-effort", "idle"), VALIDATOR_NAME) {
/**
* ioprio_class_from_string(): a name from ioprio_class_table, or (string-table fallback) any safe_atou() number up
* to IOPRIO_NR_CLASSES-1 (7).
* https://github.com/systemd/systemd/blob/0f0682b773/src/shared/ioprio-util.c#L24-L31
* https://github.com/systemd/systemd/blob/0f0682b773/src/basic/string-table.c#L65-L80
*/
class IOSchedulingClassOptionValue : SimpleGrammarOptionValues(
VALIDATOR_NAME,
SequenceCombinator(
AlternativeCombinator(
FlexibleLiteralChoiceTerminal("none", "realtime", "best-effort", "idle", ignoreCase = false),
SafeAtoTerminal(
signed = false,
base = 0,
flags = emptySet(),
minInclusive = 0L,
maxInclusive = 7L,
acceptsWhitespace = true,
acceptsMinus = true,
),
),
EOF(),
),
) {
override fun getAutoCompleteOptions(project: Project): Set<String> = ImmutableSet.of("none", "realtime", "best-effort", "idle")

companion object {
const val VALIDATOR_NAME = "config_parse_exec_io_class"
}
Expand Down Expand Up @@ -152,13 +231,51 @@ class SocketTimestampingOptionValue : AbstractGrammarEnumOptionValue(ImmutableSe
}
}

class SocketProtocolOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("udplite", "sctp"), VALIDATOR_NAME) {
/**
* [Socket] SocketProtocol=.
*
* [parse_socket_protocol](https://github.com/systemd/systemd/blob/0f0682b773/src/core/load-fragment.c#L87-L97) runs
* [parse_ip_protocol_full](https://github.com/systemd/systemd/blob/0f0682b773/src/shared/ip-protocol-list.c#L36-L70)
* (a protocol name in any case, or a safe_atoi number that has a name) and then keeps only IPPROTO_UDPLITE (136),
* IPPROTO_SCTP (132) and IPPROTO_MPTCP (262). So `mptcp`, `SCTP`, `136` and `0x84` are all accepted.
*/
class SocketProtocolOptionValue : SimpleGrammarOptionValues(
VALIDATOR_NAME,
SequenceCombinator(
PreferValidAlternativeCombinator(
FlexibleLiteralChoiceTerminal("udplite", "sctp", "mptcp", ignoreCase = true),
SafeAtoTerminal(signed = true, base = 0, flags = emptySet(), minInclusive = 136L, maxInclusive = 136L, acceptsWhitespace = true, acceptsMinus = true),
SafeAtoTerminal(signed = true, base = 0, flags = emptySet(), minInclusive = 132L, maxInclusive = 132L, acceptsWhitespace = true, acceptsMinus = true),
SafeAtoTerminal(signed = true, base = 0, flags = emptySet(), minInclusive = 262L, maxInclusive = 262L, acceptsWhitespace = true, acceptsMinus = true),
),
EOF()
)
) {
override fun getAutoCompleteOptions(project: Project): Set<String> = setOf("udplite", "sctp", "mptcp")

companion object {
const val VALIDATOR_NAME = "config_parse_socket_protocol"
}
}

class SocketBindOptionValue : AbstractGrammarEnumOptionValue(ImmutableSet.of("default", "both", "ipv6-only"), VALIDATOR_NAME) {
/**
* [Socket] BindIPv6Only=.
*
* [socket_address_bind_ipv6_only_or_bool_from_string](https://github.com/systemd/systemd/blob/0f0682b773/src/shared/socket-label.c#L21-L39)
* first tries parse_boolean (true means `ipv6-only`, false means `both`) and then the case-sensitive table
* `default`, `both`, `ipv6-only`.
*/
class SocketBindOptionValue : SimpleGrammarOptionValues(
VALIDATOR_NAME,
SequenceCombinator(
PreferValidAlternativeCombinator(
FlexibleLiteralChoiceTerminal("default", "both", "ipv6-only"),
BOOLEAN,
),
EOF()
)
) {
override fun getAutoCompleteOptions(project: Project): Set<String> = setOf("default", "both", "ipv6-only")

companion object {
const val VALIDATOR_NAME = "config_parse_socket_bind"
Expand Down
Loading
Loading