Packages
nerves
1.7.6
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/mix/nerves/shell.ex
defmodule Mix.Nerves.Shell do
def open(command, initial_input \\ []) do
# We need to get raw binary access to the stdout file descriptor
# so we can directly pass through control characters output by the command
stdout_port = Port.open({:fd, 0, 1}, [:binary, :eof, :stream, :out])
# We use the tty_sl driver for input because it handles tty geometry and
# streaming mode.
stdin_port = Port.open({:spawn, "tty_sl -c -e"}, [:binary, :eof, :stream, :in])
_ = Application.stop(:logger)
# We run the command through the script command to emulate a pty
cmd =
"script -q /dev/null " <>
case Nerves.Env.host_os() do
"linux" ->
"-c \"#{command}\""
_ ->
"#{command}"
end
cmd_port =
Port.open({:spawn, cmd}, [
:binary,
:eof,
:stream,
:stderr_to_stdout,
{:env, [{'PATH', Mix.Nerves.Utils.sanitize_path() |> to_charlist()}]}
])
# Tell the script command about the terminal dimensions
{w, h} = get_tty_geometry(stdin_port)
Port.command(cmd_port, """
stty sane rows #{h} cols #{w}; stty -echo
export PS1=""; export PS2=""
start() {
echo -e "\\e[25F\\e[0J\\e[1;7m\n Preparing Nerves Shell \\e[0m"
echo -e "\\e]0;Nerves Shell\\a"
export PS1="\\e[1;7m Nerves \\e[0;1m \\W > \\e[0m"
export PS2="\\e[1;7m Nerves \\e[0;1m \\W ..\\e[0m"
#{Enum.join(initial_input, "\n")}
stty echo
}; start
""")
shell_loop(stdin_port, stdout_port, cmd_port)
end
defp shell_loop(stdin_port, stdout_port, cmd_port) do
receive do
# Route input from stdin to the command port
{^stdin_port, {:data, data}} ->
Port.command(cmd_port, data)
shell_loop(stdin_port, stdout_port, cmd_port)
# Route output from the command port to stdout
{^cmd_port, {:data, data}} ->
Port.command(stdout_port, data)
shell_loop(stdin_port, stdout_port, cmd_port)
# If any of the ports get closed, break out of the loop
{_port, :eof} ->
:ok
# Ignore other messages
_message ->
shell_loop(stdin_port, stdout_port, cmd_port)
end
end
# Starting in OTP 21.3.0, the CTRL_OP_GET_WINSIZE changes to be
#
# define(ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER, 16#018b0900).
# define(CTRL_OP_GET_WINSIZE, (100 + ?ERTS_TTYSL_DRV_CONTROL_MAGIC_NUMBER)).
#
# See https://github.com/erlang/otp/commit/ad5822c6b1401111bbdbc5e77fe097a3f1b2b3cb
@erts_ttysl_drv_control_magic_number 0x018B0900
@ctrl_op_get_winsize 100
@ctrl_op_get_winsize_otp_21_3 @ctrl_op_get_winsize + @erts_ttysl_drv_control_magic_number
defp get_tty_geometry(tty_port) do
geometry =
try do
:erlang.port_control(tty_port, @ctrl_op_get_winsize, [])
rescue
_e in ArgumentError ->
:erlang.port_control(tty_port, @ctrl_op_get_winsize_otp_21_3, [])
end
|> :erlang.list_to_binary()
<<w::native-integer-size(32), h::native-integer-size(32)>> = geometry
{w, h}
end
end