Packages
nerves
1.7.17
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.0
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.3
1.9.2
retired
1.9.1
1.9.0
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
retired
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
retired
1.7.1
retired
1.7.0
retired
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.11.0
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-rc.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
Craft and deploy bulletproof embedded software
Current section
Files
Jump to
Current section
Files
lib/nerves/erlinit.ex
defmodule Nerves.Erlinit do
@moduledoc """
Decode and encode erlinit.config files
This module is used to decode, merge, and encode multiple erlinit.config
files.
"""
@switches [
boot: :string,
ctty: :string,
uniqueid_exec: :string,
env: :keep,
gid: :integer,
graceful_shutdown_timeout: :integer,
hang_on_exit: :boolean,
reboot_on_exit: :boolean,
hang_on_fatal: :boolean,
limits: :string,
mount: :keep,
hostname_pattern: :string,
pre_run_exec: :string,
poweroff_on_exit: :boolean,
poweroff_on_fatal: :boolean,
reboot_on_fatal: :boolean,
release_path: :string,
run_on_exit: :string,
alternate_exec: :string,
print_timing: :boolean,
uid: :integer,
update_clock: :boolean,
verbose: :boolean,
warn_unused_tty: :boolean,
working_directory: :string,
shutdown_report: :string
]
@aliases [
b: :boot,
c: :ctty,
d: :uniqueid_exec,
e: :env,
h: :hang_on_exit,
l: :limits,
m: :mount,
n: :hostname_pattern,
r: :release_path,
s: :alternate_exec,
t: :print_timing,
v: :verbose
]
@type t :: [
boot: Path.t(),
ctty: String.t(),
uniqueid_exec: String.t(),
env: String.t(),
gid: non_neg_integer(),
graceful_shutdown_timeout: non_neg_integer(),
hang_on_exit: boolean(),
hang_on_fatal: boolean(),
limits: String.t(),
mount: String.t(),
hostname_pattern: String.t(),
pre_run_exec: String.t(),
poweroff_on_exit: boolean(),
poweroff_on_fatal: boolean(),
reboot_on_fatal: boolean(),
release_path: Path.t(),
run_on_exit: String.t(),
alternate_exec: String.t(),
print_timing: boolean(),
uid: non_neg_integer(),
update_clock: boolean(),
verbose: boolean(),
warn_unused_tty: boolean(),
working_directory: Path.t(),
shutdown_report: Path.t()
]
@doc """
Return the path to the erlinit.config file provided by the Nerves System
"""
@spec system_config_file(Nerves.Package.t()) :: {:ok, Path.t()} | {:error, :no_config}
def system_config_file(%Nerves.Package{path: path}) do
file = Path.join(path, "rootfs_overlay/etc/erlinit.config")
case File.exists?(file) do
true ->
{:ok, file}
false ->
{:error, :no_config}
end
end
@doc """
Decode the data from the config into a keyword list
"""
@spec decode_config(String.t()) :: t()
def decode_config(config) do
argv =
config
|> String.split("\n")
|> Enum.map(&String.trim_leading/1)
|> Enum.filter(&String.starts_with?(&1, "-"))
|> Enum.map(&trim_trailing_comments/1)
|> Enum.map(&String.split(&1, " ", parts: 2))
|> List.flatten()
|> Enum.map(&String.trim/1)
|> Enum.map(&trim_quoted_string/1)
# `allow_nonexistent_atoms: true` allows unknown erlinit options to pass through.
{opts, _, _} =
OptionParser.parse(argv,
switches: @switches,
aliases: @aliases,
allow_nonexistent_atoms: true
)
opts
end
defp trim_quoted_string(<<?", rest::binary>>) do
content_len = byte_size(rest) - 1
<<content::binary-size(content_len), _>> = rest
content
end
defp trim_quoted_string(s), do: s
defp trim_trailing_comments(s) do
# Trim everything after a #. This is flawed since quoted '#'s should work,
# but I don't think that that exists in anything that erlinit can do...
String.split(s, "#", parts: 2) |> hd()
end
@doc """
Merge keyword options
"""
@spec merge_opts(t(), t()) :: t()
def merge_opts(old, new) do
Enum.reduce(new, old, fn
{k, nil}, acc ->
Keyword.delete(acc, k)
{k, v}, acc ->
case Keyword.get(@switches, k) do
:keep ->
[{k, v} | acc]
_ ->
Keyword.put(acc, k, v)
end
end)
end
@doc """
Encode the keyword list options into an erlinit.config file format
"""
@spec encode_config(t()) :: String.t()
def encode_config(config) do
config
|> Enum.map(&encode_line/1)
|> IO.iodata_to_binary()
end
defp encode_line({k, v}) do
Keyword.get(@switches, k)
|> encode_kv(k, v)
end
defp encode_kv(:boolean, _k, false), do: []
defp encode_kv(:boolean, k, true), do: [encode_key(k), "\n"]
defp encode_kv(type, k, v) do
[encode_key(k), " ", encode_value(type, v), "\n"]
end
defp encode_value(:string, v) do
if String.contains?(v, " ") do
["\"", v, "\""]
else
v
end
end
defp encode_value(_, v), do: to_string(v)
defp encode_key(key), do: "--" <> String.replace(to_string(key), "_", "-")
end