Packages

A custom shell for debugging and running commands on Nerves devices in a bash-like environment.

Current section

Files

Jump to
nerves_runtime_shell lib evaluator.ex
Raw

lib/evaluator.ex

defmodule Nerves.Runtime.Shell.Evaluator do
@moduledoc """
The evaluator is responsible for managing the shell port and executing
commands against it.
"""
def init(command, server, leader, _opts) do
old_leader = Process.group_leader
Process.group_leader(self(), leader)
command == :ack && :proc_lib.init_ack(self())
path = System.find_executable("sh")
port = Port.open({:spawn_executable, path}, [:binary, :stderr_to_stdout, :eof, :exit_status])
state = %{port: port, path: path}
try do
loop(server, state)
after
Process.group_leader(self(), old_leader)
end
end
defp loop(server, state) do
port = state.port
receive do
{^port, {:data, data}} ->
IO.puts("#{data}")
loop(server, state)
{^port, {:exit_status, status}} ->
IO.puts("Interactive shell port exited with status #{status}")
:ok
{:eval, ^server, command, shell_state} ->
send(state.port, {self(), {:command, command}})
# If the command changes the shell's directory, there's
# a chance that this checks too early. In practice, it
# seems to work for "cd".
new_shell_state = %{shell_state | counter: shell_state.counter + 1,
cwd: get_cwd(port) }
send(server, {:evaled, self(), new_shell_state})
loop(server, state)
{:done, ^server} ->
send(port, {self(), :close})
:ok
other ->
IO.inspect(other, label: "Unknown message received by Nerves host command evaluator")
loop(server, state)
end
end
defp get_cwd(port) do
# Get the current working directory of the port via the Linux /proc mechanism
with {:os_pid, os_pid} <- Port.info(port, :os_pid),
{:ok, cwd} <- :file.read_link("/proc/#{os_pid}/cwd"),
do: cwd
end
end