Skip to content

Commit 7fdd544

Browse files
committed
Use parser-based unit value validation
1 parent b86e1e4 commit 7fdd544

7 files changed

Lines changed: 75 additions & 13 deletions

File tree

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,9 @@ Integration tests are excluded by default. Enable them only inside a Linux syste
2727
```sh
2828
SYSTEMD_INTEGRATION=1 mix test
2929
```
30+
31+
From macOS, prefer the checked-in wrapper for a full VM integration run:
32+
33+
```sh
34+
scripts/integration_test.sh
35+
```
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Release Checklist
1+
# Contributing
22

3-
Before publishing a pre-release or Hex package:
3+
Before opening release-oriented changes or publishing a pre-release/Hex package:
44

55
- Run `mix ci` locally.
66
- Run `mix docs` and inspect generated module examples.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,4 @@ Quick VM checks:
7979
~/.local/bin/limactl shell systemd-test -- busctl --system list --no-pager
8080
```
8181

82-
See `docs/RELEASE_CHECKLIST.md` before publishing a release.
82+
See `CONTRIBUTING.md` before publishing a release.

lib/systemd/dbus/signature.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ defmodule Systemd.DBus.Signature do
1818
defp primitive?(""), do: true
1919

2020
defp primitive?(signature) do
21-
String.match?(signature, ~r/^[syobintqxuagdvh]+$/)
21+
signature
22+
|> String.to_charlist()
23+
|> Enum.all?(&(&1 in ~c"syobintqxuagdvh"))
2224
end
2325
end

lib/systemd/unit_file/parser.ex

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,13 @@ defmodule Systemd.UnitFile.Parser do
155155
end
156156

157157
defp first_non_whitespace_column(line) do
158-
case Regex.run(~r/\S/, line, return: :index) do
159-
[{index, _length}] -> index + 1
160-
_no_match -> 1
161-
end
158+
line
159+
|> :binary.bin_to_list()
160+
|> Enum.with_index(1)
161+
|> Enum.find_value(1, fn
162+
{char, column} when char not in [?\s, ?\t] -> column
163+
_whitespace -> false
164+
end)
162165
end
163166

164167
defp ensure_final_newline(text) do

lib/systemd/unit_file/validator.ex

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Systemd.UnitFile.Validator do
22
@moduledoc false
33

44
alias Systemd.UnitFile
5-
alias Systemd.UnitFile.{Directive, Section, ValidationError, Value}
5+
alias Systemd.UnitFile.{Directive, Section, ValidationError, Value, ValueParser}
66

77
@known_sections %{
88
"service" => MapSet.new(["Unit", "Service", "Install"]),
@@ -273,10 +273,8 @@ defmodule Systemd.UnitFile.Validator do
273273
one_of(section, directive, String.downcase(value), ~w(1 yes true on 0 no false off), span)
274274
end
275275

276-
defp duration(_section, _directive, "infinity", _span), do: []
277-
278276
defp duration(section, directive, value, span) do
279-
if String.match?(value, ~r/^\d+(\.\d+)?\s*(us|µs|ms|s|sec|m|min|h|hr|d|day|w|week)?$/) do
277+
if ValueParser.duration?(value) do
280278
[]
281279
else
282280
[
@@ -292,7 +290,7 @@ defmodule Systemd.UnitFile.Validator do
292290
end
293291

294292
defp octal_mode(section, directive, value, span) do
295-
if String.match?(value, ~r/^[0-7]{3,4}$/) do
293+
if ValueParser.octal_mode?(value) do
296294
[]
297295
else
298296
[value_error(section, directive, value, "expected an octal mode such as 0660", span)]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
defmodule Systemd.UnitFile.ValueParser do
2+
@moduledoc false
3+
4+
import NimbleParsec
5+
6+
digits = ascii_string([?0..?9], min: 1)
7+
decimal = digits |> optional(ignore(string(".")) |> concat(digits))
8+
spacing = repeat(choice([string(" "), string("\t")]))
9+
10+
duration_unit =
11+
choice([
12+
string("week"),
13+
string("day"),
14+
string("min"),
15+
string("sec"),
16+
string("hr"),
17+
string("us"),
18+
string("µs"),
19+
string("ms"),
20+
string("s"),
21+
string("m"),
22+
string("h"),
23+
string("d"),
24+
string("w")
25+
])
26+
27+
duration =
28+
choice([
29+
string("infinity"),
30+
decimal |> ignore(spacing) |> optional(duration_unit)
31+
])
32+
|> eos()
33+
34+
octal_mode = ascii_string([?0..?7], min: 3, max: 4) |> eos()
35+
36+
defparsecp(:parse_duration_value, duration)
37+
defparsecp(:parse_octal_mode_value, octal_mode)
38+
39+
@doc false
40+
@spec duration?(String.t()) :: boolean()
41+
def duration?(value) when is_binary(value) do
42+
parse_duration_value(value) |> parse_ok?()
43+
end
44+
45+
@doc false
46+
@spec octal_mode?(String.t()) :: boolean()
47+
def octal_mode?(value) when is_binary(value) do
48+
parse_octal_mode_value(value) |> parse_ok?()
49+
end
50+
51+
defp parse_ok?({:ok, _tokens, "", _context, _line, _offset}), do: true
52+
defp parse_ok?(_other), do: false
53+
end

0 commit comments

Comments
 (0)