@@ -65,39 +65,88 @@ defmodule Mix.Tasks.Upload do
6565 Uploading to #{ ip } ...
6666 """ )
6767
68+ user = Process . whereis ( :user )
69+ Process . unregister ( :user )
70+
71+ # Take over STDIN in case SSH requires inputting password
72+ stdin_port = Port . open ( { :spawn , "tty_sl -c -e" } , [ :binary , :eof , :stream , :in ] )
73+ _ = Application . stop ( :logger )
74+
75+ shell = System . get_env ( "SHELL" )
76+
6877 # Options:
6978 #
7079 # ConnectTimeout - don't wait forever to connect
71- # PreferredAuthentications=publickey - since keyboard interactivity doesn't
72- # work, don't try password entry options.
73- # -T - No pseudoterminals since they're not needed for firmware updates
74- opts = [
75- :stream ,
76- :binary ,
77- :exit_status ,
78- :hide ,
79- :use_stdio ,
80- { :args ,
81- [
82- "-o" ,
83- "ConnectTimeout=3" ,
84- "-o" ,
85- "PreferredAuthentications=publickey" ,
86- "-T" ,
87- "-s" ,
88- ip ,
89- "fwup"
90- ] }
91- ]
92-
93- port = Port . open ( { :spawn_executable , ssh_path ( ) } , opts )
94-
95- fd = File . open! ( firmware_path , [ :read ] )
96-
80+ command = "cat #{ firmware_path } | #{ ssh_path ( ) } -o ConnectTimeout=3 -s #{ ip } fwup"
81+
82+ port =
83+ Port . open ( { :spawn , ~s( script -q /dev/null #{ shell } -c "#{ command } ") } , [
84+ :binary ,
85+ :exit_status ,
86+ :stream ,
87+ :stderr_to_stdout ,
88+ {
89+ :env ,
90+ # pass the whole user env
91+ for ( { k , v } <- System . get_env ( ) , do: { to_charlist ( k ) , to_charlist ( v ) } )
92+ }
93+ ] )
94+
95+ Process . register ( user , :user )
9796 Process . flag ( :trap_exit , true )
9897
99- sender_pid = spawn_link ( fn -> send_data ( port , fd ) end )
100- port_read ( port , sender_pid )
98+ shell_loop ( stdin_port , port )
99+
100+ # Close the ports if they are still around
101+ if Port . info ( stdin_port ) , do: Port . close ( stdin_port )
102+ if Port . info ( port ) , do: Port . close ( port )
103+
104+ :ok
105+ end
106+
107+ defp shell_loop ( stdin_port , ssh_port ) do
108+ receive do
109+ # Route input from stdin to the command port
110+ { ^ stdin_port , { :data , data } } ->
111+ Port . command ( ssh_port , data )
112+ shell_loop ( stdin_port , ssh_port )
113+
114+ # Route output from the command port to stdout
115+ { ^ ssh_port , { :data , data } } ->
116+ IO . write ( data )
117+ shell_loop ( stdin_port , ssh_port )
118+
119+ # If any of the ports get closed, break out of the loop
120+ { ^ ssh_port , :eof } ->
121+ :ok
122+
123+ { ^ ssh_port , { :exit_status , 0 } } ->
124+ :ok
125+
126+ { _port , { :exit_status , status } } ->
127+ Mix . raise ( "ssh failed with status #{ status } " )
128+
129+ { :EXIT , ^ ssh_port , reason } ->
130+ Mix . raise ( """
131+ Unexpected exit from ssh (#{ inspect ( reason ) } )
132+
133+ This is known to happen when ssh interactively prompts you for a
134+ passphrase. The following are workarounds:
135+
136+ 1. Load your private key identity into the ssh agent by running
137+ `ssh-add`
138+
139+ 2. Use the `upload.sh` script. Create one by running
140+ `mix firmware.gen.script`.
141+ """ )
142+
143+ other ->
144+ Mix . raise ( """
145+ Unexpected message received: #{ inspect ( other ) }
146+
147+ Please open an issue so that we can fix this.
148+ """ )
149+ end
101150 end
102151
103152 defp firmware ( opts ) do
@@ -142,59 +191,6 @@ defmodule Mix.Tasks.Upload do
142191 end
143192 end
144193
145- defp port_read ( port , sender_pid ) do
146- receive do
147- { ^ port , { :data , data } } ->
148- IO . write ( data )
149- port_read ( port , sender_pid )
150-
151- { ^ port , { :exit_status , 0 } } ->
152- :ok
153-
154- { ^ port , { :exit_status , status } } ->
155- Mix . raise ( "ssh failed with status #{ status } " )
156-
157- { :EXIT , ^ sender_pid , :normal } ->
158- # All data has been sent
159- port_read ( port , sender_pid )
160-
161- { :EXIT , ^ port , reason } ->
162- Mix . raise ( """
163- Unexpected exit from ssh (#{ inspect ( reason ) } )
164-
165- This is known to happen when ssh interactively prompts you for a
166- passphrase. The following are workarounds:
167-
168- 1. Load your private key identity into the ssh agent by running
169- `ssh-add`
170-
171- 2. Use the `upload.sh` script. Create one by running
172- `mix firmware.gen.script`.
173- """ )
174-
175- other ->
176- Mix . raise ( """
177- Unexpected message received: #{ inspect ( other ) }
178-
179- Please open an issue so that we can fix this.
180- """ )
181- end
182- end
183-
184- defp send_data ( port , fd ) do
185- case IO . binread ( fd , 16384 ) do
186- :eof ->
187- :ok
188-
189- { :error , _reason } ->
190- exit ( :read_failed )
191-
192- data ->
193- Port . command ( port , data )
194- send_data ( port , fd )
195- end
196- end
197-
198194 defp target_ip_address_or_name_msg ( ) do
199195 ~S"""
200196 mix upload expects a target IP address or hostname
0 commit comments