29
29
from pipes import quote
30
30
from future .utils import raise_from
31
31
32
+ from paramiko .client import SSHClient , AutoAddPolicy
33
+ from paramiko .ssh_exception import NoValidConnectionsError
34
+ # By default paramiko is very verbose, including at the INFO level
35
+ logging .getLogger ("paramiko" ).setLevel (logging .WARNING )
36
+
32
37
# pylint: disable=import-error,wrong-import-position,ungrouped-imports,wrong-import-order
33
38
import pexpect
34
39
from distutils .version import StrictVersion as V
54
59
logger = logging .getLogger ('ssh' )
55
60
gem5_logger = logging .getLogger ('gem5-connection' )
56
61
57
- def ssh_get_shell (host ,
58
- username ,
59
- password = None ,
60
- keyfile = None ,
61
- port = None ,
62
- timeout = 10 ,
63
- telnet = False ,
64
- original_prompt = None ,
65
- options = None ):
62
+ def ssh_get_shell (host , username , password = None , keyfile = None , port = None , timeout = 10 , telnet = False , original_prompt = None ):
66
63
_check_env ()
67
64
start_time = time .time ()
68
65
while True :
@@ -71,8 +68,7 @@ def ssh_get_shell(host,
71
68
raise ValueError ('keyfile may not be used with a telnet connection.' )
72
69
conn = TelnetPxssh (original_prompt = original_prompt )
73
70
else : # ssh
74
- conn = pxssh .pxssh (options = options ,
75
- echo = False )
71
+ conn = pxssh .pxssh (echo = False )
76
72
77
73
try :
78
74
if keyfile :
@@ -159,7 +155,6 @@ def check_keyfile(keyfile):
159
155
160
156
class SshConnection (object ):
161
157
162
- default_password_prompt = '[sudo] password'
163
158
max_cancel_attempts = 5
164
159
default_timeout = 10
165
160
@@ -171,7 +166,7 @@ def name(self):
171
166
def connected_as_root (self ):
172
167
if self ._connected_as_root is None :
173
168
# Execute directly to prevent deadlocking of connection
174
- result = self ._execute_and_wait_for_prompt ('id' , as_root = False )
169
+ exit_code , result = self ._execute ('id' , as_root = False )
175
170
self ._connected_as_root = 'uid=0(' in result
176
171
return self ._connected_as_root
177
172
@@ -187,12 +182,8 @@ def __init__(self,
187
182
keyfile = None ,
188
183
port = None ,
189
184
timeout = None ,
190
- telnet = False ,
191
- password_prompt = None ,
192
- original_prompt = None ,
193
185
platform = None ,
194
- sudo_cmd = "sudo -- sh -c {}" ,
195
- options = None
186
+ sudo_cmd = "sudo -S -- sh -c {}"
196
187
):
197
188
self ._connected_as_root = None
198
189
self .host = host
@@ -201,185 +192,120 @@ def __init__(self,
201
192
self .keyfile = check_keyfile (keyfile ) if keyfile else keyfile
202
193
self .port = port
203
194
self .lock = threading .Lock ()
204
- self .password_prompt = password_prompt if password_prompt is not None else self .default_password_prompt
205
195
self .sudo_cmd = sanitize_cmd_template (sudo_cmd )
206
196
logger .debug ('Logging in {}@{}' .format (username , host ))
207
197
timeout = timeout if timeout is not None else self .default_timeout
208
- self .options = options if options is not None else {}
209
- self .conn = ssh_get_shell (host ,
210
- username ,
211
- password ,
212
- self .keyfile ,
213
- port ,
214
- timeout ,
215
- False ,
216
- None ,
217
- self .options )
198
+
199
+ client = SSHClient ()
200
+ client .load_system_host_keys ()
201
+ client .set_missing_host_key_policy (AutoAddPolicy )
202
+ client .connect (
203
+ hostname = host ,
204
+ port = port ,
205
+ username = username ,
206
+ password = password ,
207
+ key_filename = keyfile ,
208
+ timeout = timeout ,
209
+ )
210
+ self .client = client
211
+ self .chan = self ._get_channel ()
218
212
atexit .register (self .close )
219
213
214
+ def _get_channel (self ):
215
+ transport = self .client .get_transport ()
216
+ channel = transport .open_session ()
217
+ return channel
218
+
219
+ def _get_sftp (self , timeout ):
220
+ sftp = self .client .open_sftp ()
221
+ sftp .get_channel ().settimeout (timeout )
222
+ return sftp
223
+
220
224
def push (self , source , dest , timeout = 30 ):
221
- dest = '{}@{}:{}' . format ( self .username , self . host , dest )
222
- return self . _scp (source , dest , timeout )
225
+ sftp = self ._get_sftp ( timeout )
226
+ sftp . put (source , dest )
223
227
224
228
def pull (self , source , dest , timeout = 30 ):
225
- source = '{}@{}:{}' . format ( self .username , self . host , source )
226
- return self . _scp (source , dest , timeout )
229
+ sftp = self ._get_sftp ( timeout )
230
+ sftp . get (source , dest )
227
231
228
232
def execute (self , command , timeout = None , check_exit_code = True ,
229
233
as_root = False , strip_colors = True , will_succeed = False ): #pylint: disable=unused-argument
230
234
if command == '' :
231
- # Empty command is valid but the __devlib_ec stuff below will
232
- # produce a syntax error with bash. Treat as a special case.
233
235
return ''
234
236
try :
237
+ _command = '({}) 2>&1' .format (command )
235
238
with self .lock :
236
- _command = '({}); __devlib_ec=$?; echo; echo $__devlib_ec' .format (command )
237
- full_output = self ._execute_and_wait_for_prompt (_command , timeout , as_root , strip_colors )
238
- split_output = full_output .rsplit ('\r \n ' , 2 )
239
- try :
240
- output , exit_code_text , _ = split_output
241
- except ValueError as e :
242
- raise TargetStableError (
243
- "cannot split reply (target misconfiguration?):\n '{}'" .format (full_output ))
244
- if check_exit_code :
245
- try :
246
- exit_code = int (exit_code_text )
247
- if exit_code :
248
- message = 'Got exit code {}\n from: {}\n OUTPUT: {}'
249
- raise TargetStableError (message .format (exit_code , command , output ))
250
- except (ValueError , IndexError ):
251
- logger .warning (
252
- 'Could not get exit code for "{}",\n got: "{}"' \
253
- .format (command , exit_code_text ))
254
- return output
255
- except EOF :
239
+ exit_code , output = self ._execute (_command , timeout , as_root , strip_colors )
240
+ except NoValidConnectionsError :
256
241
raise TargetNotRespondingError ('Connection lost.' )
257
242
except TargetStableError as e :
258
243
if will_succeed :
259
244
raise TargetTransientError (e )
260
245
else :
261
246
raise
247
+ else :
248
+ if check_exit_code and exit_code :
249
+ message = 'Got exit code {}\n from: {}\n OUTPUT: {}'
250
+ raise TargetStableError (message .format (exit_code , command , output ))
251
+ return output
262
252
263
253
def background (self , command , stdout = subprocess .PIPE , stderr = subprocess .PIPE , as_root = False ):
254
+ channel = self ._get_channel ()
264
255
try :
265
- port_string = '-p {}' .format (self .port ) if self .port else ''
266
- keyfile_string = '-i {}' .format (self .keyfile ) if self .keyfile else ''
267
- if as_root and not self .connected_as_root :
268
- command = self .sudo_cmd .format (command )
269
- options = " " .join ([ "-o {}={}" .format (key ,val )
270
- for key ,val in self .options .items ()])
271
- command = '{} {} {} {} {}@{} {}' .format (ssh ,
272
- options ,
273
- keyfile_string ,
274
- port_string ,
275
- self .username ,
276
- self .host ,
277
- command )
278
- logger .debug (command )
279
- if self .password :
280
- command , _ = _give_password (self .password , command )
281
- return subprocess .Popen (command , stdout = stdout , stderr = stderr , shell = True )
282
- except EOF :
256
+ return channel .exec_command (command )
257
+ except NoValidConnectionsError :
283
258
raise TargetNotRespondingError ('Connection lost.' )
284
259
285
260
def close (self ):
286
261
logger .debug ('Logging out {}@{}' .format (self .username , self .host ))
287
262
try :
288
- self .conn . logout ()
289
- except :
263
+ self .client . close ()
264
+ except NoValidConnectionsError :
290
265
logger .debug ('Connection lost.' )
291
- self .conn .close (force = True )
292
-
293
- def cancel_running_command (self ):
294
- # simulate impatiently hitting ^C until command prompt appears
295
- logger .debug ('Sending ^C' )
296
- for _ in range (self .max_cancel_attempts ):
297
- self ._sendline (chr (3 ))
298
- if self .conn .prompt (0.1 ):
299
- return True
300
- return False
301
-
302
- def _execute_and_wait_for_prompt (self , command , timeout = None , as_root = False , strip_colors = True , log = True ):
303
- self .conn .prompt (0.1 ) # clear an existing prompt if there is one.
304
- if as_root and self .connected_as_root :
305
- # As we're already root, there is no need to use sudo.
306
- as_root = False
307
- if as_root :
308
- command = self .sudo_cmd .format (quote (command ))
309
- if log :
310
- logger .debug (command )
311
- self ._sendline (command )
312
- if self .password :
313
- index = self .conn .expect_exact ([self .password_prompt , TIMEOUT ], timeout = 0.5 )
314
- if index == 0 :
315
- self ._sendline (self .password )
316
- else : # not as_root
317
- if log :
318
- logger .debug (command )
319
- self ._sendline (command )
320
- timed_out = self ._wait_for_prompt (timeout )
321
- if sys .version_info [0 ] == 3 :
322
- output = process_backspaces (self .conn .before .decode (sys .stdout .encoding or 'utf-8' , 'replace' ))
323
- else :
324
- output = process_backspaces (self .conn .before )
325
266
326
- if timed_out :
327
- self .cancel_running_command ()
328
- raise TimeoutError (command , output )
267
+ def _execute (self , command , timeout = None , as_root = False , strip_colors = True , log = True ):
268
+ # As we're already root, there is no need to use sudo.
269
+ use_sudo = not (as_root and self .connected_as_root )
270
+ log_debug = logger .debug if log else lambda msg : None
271
+
272
+ if use_sudo and not self .password :
273
+ raise TargetStableError ('Attempt to use sudo but no password was specified' )
274
+
275
+ try :
276
+ if use_sudo :
277
+ command = self .sudo_cmd .format (quote (command ))
278
+ log_debug (command )
279
+ stdin , stdout , stderr = self .client .exec_command (command , timeout = timeout )
280
+ stdin .write (self .password + '\n ' )
281
+ stdin .flush ()
282
+ else :
283
+ log_debug (command )
284
+ stdin , stdout , stderr = self .client .exec_command (command , timeout = timeout )
285
+ except socket .timeout :
286
+ raise TimeoutError (command )
287
+
288
+ # Empty the stdout buffer of the command, allowing it to carry on to
289
+ # completion
290
+ output_chunks = []
291
+ chunk = True
292
+ while chunk :
293
+ chunk = stdout .read ()
294
+ output_chunks .append (chunk )
295
+
296
+ # Wait until the command completes
297
+ exit_code = stdout .channel .recv_exit_status ()
298
+
299
+ # Join in one go to avoid O(N^2) concatenation
300
+ output = b'' .join (output_chunks )
301
+
302
+ if sys .version_info [0 ] == 3 :
303
+ output = output .decode (sys .stdout .encoding or 'utf-8' , 'replace' )
329
304
if strip_colors :
330
305
output = strip_bash_colors (output )
331
- return output
332
306
333
- def _wait_for_prompt (self , timeout = None ):
334
- if timeout :
335
- return not self .conn .prompt (timeout )
336
- else : # cannot timeout; wait forever
337
- while not self .conn .prompt (1 ):
338
- pass
339
- return False
340
-
341
- def _scp (self , source , dest , timeout = 30 ):
342
- # NOTE: the version of scp in Ubuntu 12.04 occasionally (and bizarrely)
343
- # fails to connect to a device if port is explicitly specified using -P
344
- # option, even if it is the default port, 22. To minimize this problem,
345
- # only specify -P for scp if the port is *not* the default.
346
- port_string = '-P {}' .format (quote (str (self .port ))) if (self .port and self .port != 22 ) else ''
347
- keyfile_string = '-i {}' .format (quote (self .keyfile )) if self .keyfile else ''
348
- options = " " .join (["-o {}={}" .format (key ,val )
349
- for key ,val in self .options .items ()])
350
- command = '{} {} -r {} {} {} {}' .format (scp ,
351
- options ,
352
- keyfile_string ,
353
- port_string ,
354
- quote (source ),
355
- quote (dest ))
356
- command_redacted = command
357
- logger .debug (command )
358
- if self .password :
359
- command , command_redacted = _give_password (self .password , command )
360
- try :
361
- check_output (command , timeout = timeout , shell = True )
362
- except subprocess .CalledProcessError as e :
363
- raise_from (HostError ("Failed to copy file with '{}'. Output:\n {}" .format (
364
- command_redacted , e .output )), None )
365
- except TimeoutError as e :
366
- raise TimeoutError (command_redacted , e .output )
367
-
368
- def _sendline (self , command ):
369
- # Workaround for https://github.com/pexpect/pexpect/issues/552
370
- if len (command ) == self ._get_window_size ()[1 ] - self ._get_prompt_length ():
371
- command += ' '
372
- self .conn .sendline (command )
373
-
374
- @memoized
375
- def _get_prompt_length (self ):
376
- self .conn .sendline ()
377
- self .conn .prompt ()
378
- return len (self .conn .after )
307
+ return (exit_code , output )
379
308
380
- @memoized
381
- def _get_window_size (self ):
382
- return self .conn .getwinsize ()
383
309
384
310
class TelnetConnection (SshConnection ):
385
311
0 commit comments