Packages
nerves
1.7.12
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/port.ex
defmodule Nerves.Port do
@moduledoc """
The code from this file was copied in from MuonTrap
https://github.com/fhunleth/muontrap
"""
@spec exec_path() :: String.t()
def exec_path() do
Application.app_dir(:nerves, ["priv", "port"])
end
@doc """
Run a command in a similar way to System.cmd/3.
"""
@spec cmd(binary(), [binary()], keyword()) ::
{Collectable.t(), exit_status :: non_neg_integer()}
def cmd(command, args, opts \\ []) when is_binary(command) and is_list(args) do
exec_path()
|> File.exists?()
|> do_cmd(command, args, opts)
end
# Port process was compiled
defp do_cmd(true, command, args, opts) do
options = validate(command, args, opts)
opts = port_options(options)
{initial, fun} = Collectable.into(options.into)
try do
do_port_cmd(Port.open({:spawn_executable, to_charlist(exec_path())}, opts), initial, fun)
catch
kind, reason ->
fun.(initial, :halt)
:erlang.raise(kind, reason, __STACKTRACE__)
else
{acc, status} -> {fun.(acc, :done), status}
end
end
# Port process was not compiled, fail back to System.cmd/3
defp do_cmd(false, command, args, opts) do
System.cmd(command, args, opts)
end
defp do_port_cmd(port, acc, fun) do
receive do
{^port, {:data, data}} ->
do_port_cmd(port, fun.(acc, {:cont, data}), fun)
{^port, {:exit_status, status}} ->
{acc, status}
end
end
def port_options(options) do
[
:use_stdio,
:exit_status,
:binary,
:hide,
{:args, nerves_args(options)} | Enum.flat_map(options, &port_option/1)
]
end
defp nerves_args(options) do
Enum.flat_map(options, &nerves_arg/1) ++ ["--", options.cmd] ++ options.args
end
defp nerves_arg({:delay_to_sigkill, delay}), do: ["--delay-to-sigkill", to_string(delay)]
defp nerves_arg({:arg0, arg0}), do: ["--arg0", arg0]
defp nerves_arg(_other), do: []
defp port_option({:stderr_to_stdout, true}), do: [:stderr_to_stdout]
defp port_option({:env, env}), do: [{:env, env}]
defp port_option({:cd, bin}), do: [{:cd, bin}]
defp port_option({:arg0, bin}), do: [{:arg0, bin}]
defp port_option({:parallelism, bool}), do: [{:parallelism, bool}]
defp port_option(_other), do: []
defp validate(cmd, args, opts) do
assert_no_null_byte!(cmd)
unless Enum.all?(args, &is_binary/1) do
raise ArgumentError, "all arguments for Nerves.Port.cmd/3 must be binaries"
end
abs_command = System.find_executable(cmd) || :erlang.error(:enoent, [cmd, args, opts])
validate_options(abs_command, args, opts)
end
defp validate_options(cmd, args, opts) do
Enum.reduce(
opts,
%{cmd: cmd, args: args, into: ""},
&validate_option/2
)
end
# System.cmd/3 options
defp validate_option({:into, what}, opts), do: Map.put(opts, :into, what)
defp validate_option({:cd, bin}, opts) when is_binary(bin), do: Map.put(opts, :cd, bin)
defp validate_option({:arg0, bin}, opts) when is_binary(bin),
do: Map.put(opts, :arg0, bin)
defp validate_option({:stderr_to_stdout, bool}, opts) when is_boolean(bool),
do: Map.put(opts, :stderr_to_stdout, bool)
defp validate_option({:parallelism, bool}, opts) when is_boolean(bool),
do: Map.put(opts, :parallelism, bool)
defp validate_option({:env, enum}, opts),
do: Map.put(opts, :env, validate_env(enum))
defp validate_option({:delay_to_sigkill, delay}, opts) when is_integer(delay),
do: Map.put(opts, :delay_to_sigkill, delay)
defp validate_option({key, val}, _opts),
do: raise(ArgumentError, "invalid option #{inspect(key)} with value #{inspect(val)}")
defp validate_env(enum) do
Enum.map(enum, fn
{k, nil} ->
{String.to_charlist(k), false}
{k, v} ->
{String.to_charlist(k), String.to_charlist(v)}
other ->
raise ArgumentError, "invalid environment key-value #{inspect(other)}"
end)
end
# Copied from Elixir's system.ex to make Nerves.Port.cmd pass System.cmd's tests
defp assert_no_null_byte!(binary) do
case :binary.match(binary, "\0") do
{_, _} ->
raise ArgumentError,
"cannot execute Nerves.Port.cmd/3 for program with null byte, got: #{inspect(binary)}"
:nomatch ->
:ok
end
end
end