mirror of
				https://github.com/ARM-software/devlib.git
				synced 2025-11-03 23:41:21 +00:00 
			
		
		
		
	connections: Unify BackgroundCommand API and use paramiko for SSH
* Unify the behavior of background commands in connections.BackgroundCommand(). This implements a subset of subprocess.Popen class, with a unified behavior across all connection types * Implement the SSH connection using paramiko rather than pxssh.
This commit is contained in:
		
				
					committed by
					
						
						Marc Bonnici
					
				
			
			
				
	
			
			
			
						parent
						
							eb6fa93845
						
					
				
				
					commit
					62e24c5764
				
			
							
								
								
									
										351
									
								
								devlib/connection.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										351
									
								
								devlib/connection.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,351 @@
 | 
				
			|||||||
 | 
					#    Copyright 2019 ARM Limited
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					# you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					# You may obtain a copy of the License at
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					# distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					# See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					# limitations under the License.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					import signal
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					from weakref import WeakSet
 | 
				
			||||||
 | 
					from abc import ABC, abstractmethod
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from devlib.utils.misc import InitCheckpoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_KILL_TIMEOUT = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _kill_pgid_cmd(pgid, sig):
 | 
				
			||||||
 | 
					    return 'kill -{} -{}'.format(sig.name, pgid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConnectionBase(InitCheckpoint):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Base class for all connections.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self._current_bg_cmds = WeakSet()
 | 
				
			||||||
 | 
					        self._closed = False
 | 
				
			||||||
 | 
					        self._close_lock = threading.Lock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def cancel_running_command(self):
 | 
				
			||||||
 | 
					        bg_cmds = set(self._current_bg_cmds)
 | 
				
			||||||
 | 
					        for bg_cmd in bg_cmds:
 | 
				
			||||||
 | 
					            bg_cmd.cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def _close(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Close the connection.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        The public :meth:`close` method makes sure that :meth:`_close` will
 | 
				
			||||||
 | 
					        only be called once, and will serialize accesses to it if it happens to
 | 
				
			||||||
 | 
					        be called from multiple threads at once.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def close(self):
 | 
				
			||||||
 | 
					        # Locking the closing allows any thread to safely call close() as long
 | 
				
			||||||
 | 
					        # as the connection can be closed from a thread that is not the one it
 | 
				
			||||||
 | 
					        # started its life in.
 | 
				
			||||||
 | 
					        with self._close_lock:
 | 
				
			||||||
 | 
					            if not self._closed:
 | 
				
			||||||
 | 
					                self._close()
 | 
				
			||||||
 | 
					                self._closed = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Ideally, that should not be relied upon but that will improve the chances
 | 
				
			||||||
 | 
					    # of the connection being properly cleaned up when it's not in use anymore.
 | 
				
			||||||
 | 
					    def __del__(self):
 | 
				
			||||||
 | 
					        # Since __del__ will be called if an exception is raised in __init__
 | 
				
			||||||
 | 
					        # (e.g. we cannot connect), we only run close() when we are sure
 | 
				
			||||||
 | 
					        # __init__ has completed successfully.
 | 
				
			||||||
 | 
					        if self.initialized:
 | 
				
			||||||
 | 
					            self.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BackgroundCommand(ABC):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Allows managing a running background command using a subset of the
 | 
				
			||||||
 | 
					    :class:`subprocess.Popen` API.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Instances of this class can be used as context managers, with the same
 | 
				
			||||||
 | 
					    semantic as :class:`subprocess.Popen`.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def send_signal(self, sig):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Send a POSIX signal to the background command's process group ID
 | 
				
			||||||
 | 
					        (PGID).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :param signal: Signal to send.
 | 
				
			||||||
 | 
					        :type signal: signal.Signals
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def kill(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Send SIGKILL to the background command.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        self.send_signal(signal.SIGKILL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def cancel(self, kill_timeout=_KILL_TIMEOUT):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Try to gracefully terminate the process by sending ``SIGTERM``, then
 | 
				
			||||||
 | 
					        waiting for ``kill_timeout`` to send ``SIGKILL``.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def wait(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Block until the background command completes, and return its exit code.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def poll(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Return exit code if the command has exited, None otherwise.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def stdin(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        File-like object connected to the background's command stdin.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def stdout(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        File-like object connected to the background's command stdout.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def stderr(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        File-like object connected to the background's command stderr.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def pid(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Process Group ID (PGID) of the background command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Since the command is usually wrapped in shell processes for IO
 | 
				
			||||||
 | 
					        redirections, sudo etc, the PID cannot be assumed to be the actual PID
 | 
				
			||||||
 | 
					        of the command passed by the user. It's is guaranteed to be a PGID
 | 
				
			||||||
 | 
					        instead, which means signals sent to it as such will target all
 | 
				
			||||||
 | 
					        subprocesses involved in executing that command.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @abstractmethod
 | 
				
			||||||
 | 
					    def close(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Close all opened streams and then wait for command completion.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        :returns: Exit code of the command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .. note:: If the command is writing to its stdout/stderr, it might be
 | 
				
			||||||
 | 
					            blocked on that and die when the streams are closed.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __enter__(self):
 | 
				
			||||||
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __exit__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PopenBackgroundCommand(BackgroundCommand):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    :class:`subprocess.Popen`-based background command.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, popen):
 | 
				
			||||||
 | 
					        self.popen = popen
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def send_signal(self, sig):
 | 
				
			||||||
 | 
					        return os.killpg(self.popen.pid, sig)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def stdin(self):
 | 
				
			||||||
 | 
					        return self.popen.stdin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def stdout(self):
 | 
				
			||||||
 | 
					        return self.popen.stdout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def stderr(self):
 | 
				
			||||||
 | 
					        return self.popen.stderr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def pid(self):
 | 
				
			||||||
 | 
					        return self.popen.pid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def wait(self):
 | 
				
			||||||
 | 
					        return self.popen.wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def poll(self):
 | 
				
			||||||
 | 
					        return self.popen.poll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def cancel(self, kill_timeout=_KILL_TIMEOUT):
 | 
				
			||||||
 | 
					        popen = self.popen
 | 
				
			||||||
 | 
					        popen.send_signal(signal.SIGTERM)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            popen.wait(timeout=_KILL_TIMEOUT)
 | 
				
			||||||
 | 
					        except subprocess.TimeoutExpired:
 | 
				
			||||||
 | 
					            popen.kill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def close(self):
 | 
				
			||||||
 | 
					        self.popen.__exit__(None, None, None)
 | 
				
			||||||
 | 
					        return self.popen.returncode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __enter__(self):
 | 
				
			||||||
 | 
					        self.popen.__enter__()
 | 
				
			||||||
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __exit__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.popen.__exit__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ParamikoBackgroundCommand(BackgroundCommand):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    :mod:`paramiko`-based background command.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __init__(self, conn, chan, pid, as_root, stdin, stdout, stderr, redirect_thread):
 | 
				
			||||||
 | 
					        self.chan = chan
 | 
				
			||||||
 | 
					        self.as_root = as_root
 | 
				
			||||||
 | 
					        self.conn = conn
 | 
				
			||||||
 | 
					        self._pid = pid
 | 
				
			||||||
 | 
					        self._stdin = stdin
 | 
				
			||||||
 | 
					        self._stdout = stdout
 | 
				
			||||||
 | 
					        self._stderr = stderr
 | 
				
			||||||
 | 
					        self.redirect_thread = redirect_thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def send_signal(self, sig):
 | 
				
			||||||
 | 
					        # If the command has already completed, we don't want to send a signal
 | 
				
			||||||
 | 
					        # to another process that might have gotten that PID in the meantime.
 | 
				
			||||||
 | 
					        if self.poll() is not None:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        # Use -PGID to target a process group rather than just the process
 | 
				
			||||||
 | 
					        # itself
 | 
				
			||||||
 | 
					        cmd = _kill_pgid_cmd(self.pid, sig)
 | 
				
			||||||
 | 
					        self.conn.execute(cmd, as_root=self.as_root)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def pid(self):
 | 
				
			||||||
 | 
					        return self._pid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def wait(self):
 | 
				
			||||||
 | 
					        return self.chan.recv_exit_status()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def poll(self):
 | 
				
			||||||
 | 
					        if self.chan.exit_status_ready():
 | 
				
			||||||
 | 
					            return self.wait()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def cancel(self, kill_timeout=_KILL_TIMEOUT):
 | 
				
			||||||
 | 
					        self.send_signal(signal.SIGTERM)
 | 
				
			||||||
 | 
					        # Check if the command terminated quickly
 | 
				
			||||||
 | 
					        time.sleep(10e-3)
 | 
				
			||||||
 | 
					        # Otherwise wait for the full timeout and kill it
 | 
				
			||||||
 | 
					        if self.poll() is None:
 | 
				
			||||||
 | 
					            time.sleep(kill_timeout)
 | 
				
			||||||
 | 
					            self.send_signal(signal.SIGKILL)
 | 
				
			||||||
 | 
					            self.wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def stdin(self):
 | 
				
			||||||
 | 
					        return self._stdin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def stdout(self):
 | 
				
			||||||
 | 
					        return self._stdout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def stderr(self):
 | 
				
			||||||
 | 
					        return self._stderr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def close(self):
 | 
				
			||||||
 | 
					        for x in (self.stdin, self.stdout, self.stderr):
 | 
				
			||||||
 | 
					            if x is not None:
 | 
				
			||||||
 | 
					                x.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        exit_code = self.wait()
 | 
				
			||||||
 | 
					        thread = self.redirect_thread
 | 
				
			||||||
 | 
					        if thread:
 | 
				
			||||||
 | 
					            thread.join()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return exit_code
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AdbBackgroundCommand(BackgroundCommand):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    ``adb``-based background command.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, conn, adb_popen, pid, as_root):
 | 
				
			||||||
 | 
					        self.conn = conn
 | 
				
			||||||
 | 
					        self.as_root = as_root
 | 
				
			||||||
 | 
					        self.adb_popen = adb_popen
 | 
				
			||||||
 | 
					        self._pid = pid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def send_signal(self, sig):
 | 
				
			||||||
 | 
					        self.conn.execute(
 | 
				
			||||||
 | 
					            _kill_pgid_cmd(self.pid, sig),
 | 
				
			||||||
 | 
					            as_root=self.as_root,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def stdin(self):
 | 
				
			||||||
 | 
					        return self.adb_popen.stdin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def stdout(self):
 | 
				
			||||||
 | 
					        return self.adb_popen.stdout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def stderr(self):
 | 
				
			||||||
 | 
					        return self.adb_popen.stderr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @property
 | 
				
			||||||
 | 
					    def pid(self):
 | 
				
			||||||
 | 
					        return self._pid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def wait(self):
 | 
				
			||||||
 | 
					        return self.adb_popen.wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def poll(self):
 | 
				
			||||||
 | 
					        return self.adb_popen.poll()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def cancel(self, kill_timeout=_KILL_TIMEOUT):
 | 
				
			||||||
 | 
					        self.send_signal(signal.SIGTERM)
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.adb_popen.wait(timeout=_KILL_TIMEOUT)
 | 
				
			||||||
 | 
					        except subprocess.TimeoutExpired:
 | 
				
			||||||
 | 
					            self.send_signal(signal.SIGKILL)
 | 
				
			||||||
 | 
					            self.adb_popen.kill()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def close(self):
 | 
				
			||||||
 | 
					        self.adb_popen.__exit__(None, None, None)
 | 
				
			||||||
 | 
					        return self.adb_popen.returncode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __enter__(self):
 | 
				
			||||||
 | 
					        self.adb_popen.__enter__()
 | 
				
			||||||
 | 
					        return self
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __exit__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        self.adb_popen.__exit__(*args, **kwargs)
 | 
				
			||||||
@@ -24,6 +24,7 @@ from pipes import quote
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from devlib.exception import TargetTransientError, TargetStableError
 | 
					from devlib.exception import TargetTransientError, TargetStableError
 | 
				
			||||||
from devlib.utils.misc import check_output
 | 
					from devlib.utils.misc import check_output
 | 
				
			||||||
 | 
					from devlib.connection import ConnectionBase, PopenBackgroundCommand
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PACKAGE_BIN_DIRECTORY = os.path.join(os.path.dirname(__file__), 'bin')
 | 
					PACKAGE_BIN_DIRECTORY = os.path.join(os.path.dirname(__file__), 'bin')
 | 
				
			||||||
@@ -37,7 +38,7 @@ def kill_children(pid, signal=signal.SIGKILL):
 | 
				
			|||||||
            os.kill(cpid, signal)
 | 
					            os.kill(cpid, signal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LocalConnection(object):
 | 
					class LocalConnection(ConnectionBase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    name = 'local'
 | 
					    name = 'local'
 | 
				
			||||||
    host = 'localhost'
 | 
					    host = 'localhost'
 | 
				
			||||||
@@ -56,6 +57,7 @@ class LocalConnection(object):
 | 
				
			|||||||
    # pylint: disable=unused-argument
 | 
					    # pylint: disable=unused-argument
 | 
				
			||||||
    def __init__(self, platform=None, keep_password=True, unrooted=False,
 | 
					    def __init__(self, platform=None, keep_password=True, unrooted=False,
 | 
				
			||||||
                 password=None, timeout=None):
 | 
					                 password=None, timeout=None):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
        self._connected_as_root = None
 | 
					        self._connected_as_root = None
 | 
				
			||||||
        self.logger = logging.getLogger('local_connection')
 | 
					        self.logger = logging.getLogger('local_connection')
 | 
				
			||||||
        self.keep_password = keep_password
 | 
					        self.keep_password = keep_password
 | 
				
			||||||
@@ -105,9 +107,24 @@ class LocalConnection(object):
 | 
				
			|||||||
                raise TargetStableError('unrooted')
 | 
					                raise TargetStableError('unrooted')
 | 
				
			||||||
            password = self._get_password()
 | 
					            password = self._get_password()
 | 
				
			||||||
            command = 'echo {} | sudo -S '.format(quote(password)) + command
 | 
					            command = 'echo {} | sudo -S '.format(quote(password)) + command
 | 
				
			||||||
        return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def close(self):
 | 
					        # Make sure to get a new PGID so PopenBackgroundCommand() can kill
 | 
				
			||||||
 | 
					        # all sub processes that could be started without troubles.
 | 
				
			||||||
 | 
					        def preexec_fn():
 | 
				
			||||||
 | 
					            os.setpgrp()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        popen = subprocess.Popen(
 | 
				
			||||||
 | 
					            command,
 | 
				
			||||||
 | 
					            stdout=stdout,
 | 
				
			||||||
 | 
					            stderr=stderr,
 | 
				
			||||||
 | 
					            shell=True,
 | 
				
			||||||
 | 
					            preexec_fn=preexec_fn,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        bg_cmd = PopenBackgroundCommand(popen)
 | 
				
			||||||
 | 
					        self._current_bg_cmds.add(bg_cmd)
 | 
				
			||||||
 | 
					        return bg_cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _close(self):
 | 
				
			||||||
        pass
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def cancel_running_command(self):
 | 
					    def cancel_running_command(self):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ from collections import defaultdict
 | 
				
			|||||||
import pexpect
 | 
					import pexpect
 | 
				
			||||||
import xml.etree.ElementTree
 | 
					import xml.etree.ElementTree
 | 
				
			||||||
import zipfile
 | 
					import zipfile
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    from shlex import quote
 | 
					    from shlex import quote
 | 
				
			||||||
@@ -37,7 +38,8 @@ except ImportError:
 | 
				
			|||||||
    from pipes import quote
 | 
					    from pipes import quote
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from devlib.exception import TargetTransientError, TargetStableError, HostError
 | 
					from devlib.exception import TargetTransientError, TargetStableError, HostError
 | 
				
			||||||
from devlib.utils.misc import check_output, which, ABI_MAP
 | 
					from devlib.utils.misc import check_output, which, ABI_MAP, redirect_streams
 | 
				
			||||||
 | 
					from devlib.connection import ConnectionBase, AdbBackgroundCommand
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger('android')
 | 
					logger = logging.getLogger('android')
 | 
				
			||||||
@@ -233,7 +235,7 @@ class ApkInfo(object):
 | 
				
			|||||||
        return output
 | 
					        return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AdbConnection(object):
 | 
					class AdbConnection(ConnectionBase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # maintains the count of parallel active connections to a device, so that
 | 
					    # maintains the count of parallel active connections to a device, so that
 | 
				
			||||||
    # adb disconnect is not invoked untill all connections are closed
 | 
					    # adb disconnect is not invoked untill all connections are closed
 | 
				
			||||||
@@ -263,6 +265,7 @@ class AdbConnection(object):
 | 
				
			|||||||
    # pylint: disable=unused-argument
 | 
					    # pylint: disable=unused-argument
 | 
				
			||||||
    def __init__(self, device=None, timeout=None, platform=None, adb_server=None,
 | 
					    def __init__(self, device=None, timeout=None, platform=None, adb_server=None,
 | 
				
			||||||
                 adb_as_root=False):
 | 
					                 adb_as_root=False):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
        self.timeout = timeout if timeout is not None else self.default_timeout
 | 
					        self.timeout = timeout if timeout is not None else self.default_timeout
 | 
				
			||||||
        if device is None:
 | 
					        if device is None:
 | 
				
			||||||
            device = adb_get_device(timeout=timeout, adb_server=adb_server)
 | 
					            device = adb_get_device(timeout=timeout, adb_server=adb_server)
 | 
				
			||||||
@@ -312,13 +315,21 @@ class AdbConnection(object):
 | 
				
			|||||||
                raise
 | 
					                raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
 | 
					    def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
 | 
				
			||||||
        return adb_background_shell(self.device, command, stdout, stderr, as_root, adb_server=self.adb_server)
 | 
					        adb_shell, pid = adb_background_shell(self, command, stdout, stderr, as_root)
 | 
				
			||||||
 | 
					        bg_cmd = AdbBackgroundCommand(
 | 
				
			||||||
 | 
					            conn=self,
 | 
				
			||||||
 | 
					            adb_popen=adb_shell,
 | 
				
			||||||
 | 
					            pid=pid,
 | 
				
			||||||
 | 
					            as_root=as_root
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self._current_bg_cmds.add(bg_cmd)
 | 
				
			||||||
 | 
					        return bg_cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def close(self):
 | 
					    def _close(self):
 | 
				
			||||||
        AdbConnection.active_connections[self.device] -= 1
 | 
					        AdbConnection.active_connections[self.device] -= 1
 | 
				
			||||||
        if AdbConnection.active_connections[self.device] <= 0:
 | 
					        if AdbConnection.active_connections[self.device] <= 0:
 | 
				
			||||||
            if self.adb_as_root:
 | 
					            if self.adb_as_root:
 | 
				
			||||||
                self.adb_root(self.device, enable=False)
 | 
					                self.adb_root(enable=False)
 | 
				
			||||||
            adb_disconnect(self.device, self.adb_server)
 | 
					            adb_disconnect(self.device, self.adb_server)
 | 
				
			||||||
            del AdbConnection.active_connections[self.device]
 | 
					            del AdbConnection.active_connections[self.device]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -536,20 +547,41 @@ def adb_shell(device, command, timeout=None, check_exit_code=False,
 | 
				
			|||||||
    return output
 | 
					    return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def adb_background_shell(device, command,
 | 
					def adb_background_shell(conn, command,
 | 
				
			||||||
                         stdout=subprocess.PIPE,
 | 
					                         stdout=subprocess.PIPE,
 | 
				
			||||||
                         stderr=subprocess.PIPE,
 | 
					                         stderr=subprocess.PIPE,
 | 
				
			||||||
                         as_root=False,
 | 
					                         as_root=False):
 | 
				
			||||||
                         adb_server=None):
 | 
					 | 
				
			||||||
    """Runs the sepcified command in a subprocess, returning the the Popen object."""
 | 
					    """Runs the sepcified command in a subprocess, returning the the Popen object."""
 | 
				
			||||||
 | 
					    device = conn.device
 | 
				
			||||||
 | 
					    adb_server = conn.adb_server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _check_env()
 | 
					    _check_env()
 | 
				
			||||||
 | 
					    stdout, stderr, command = redirect_streams(stdout, stderr, command)
 | 
				
			||||||
    if as_root:
 | 
					    if as_root:
 | 
				
			||||||
        command = 'echo {} | su'.format(quote(command))
 | 
					        command = 'echo {} | su'.format(quote(command))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Attach a unique UUID to the command line so it can be looked for without
 | 
				
			||||||
 | 
					    # any ambiguity with ps
 | 
				
			||||||
 | 
					    uuid_ = uuid.uuid4().hex
 | 
				
			||||||
 | 
					    uuid_var = 'BACKGROUND_COMMAND_UUID={}'.format(uuid_)
 | 
				
			||||||
 | 
					    command = "{} sh -c {}".format(uuid_var, quote(command))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    adb_cmd = get_adb_command(None, 'shell', adb_server)
 | 
					    adb_cmd = get_adb_command(None, 'shell', adb_server)
 | 
				
			||||||
    full_command = '{} {}'.format(adb_cmd, quote(command))
 | 
					    full_command = '{} {}'.format(adb_cmd, quote(command))
 | 
				
			||||||
    logger.debug(full_command)
 | 
					    logger.debug(full_command)
 | 
				
			||||||
    return subprocess.Popen(full_command, stdout=stdout, stderr=stderr, shell=True)
 | 
					    p = subprocess.Popen(full_command, stdout=stdout, stderr=stderr, shell=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Out of band PID lookup, to avoid conflicting needs with stdout redirection
 | 
				
			||||||
 | 
					    find_pid = 'ps -A -o pid,args | grep {}'.format(quote(uuid_var))
 | 
				
			||||||
 | 
					    ps_out = conn.execute(find_pid)
 | 
				
			||||||
 | 
					    pids = [
 | 
				
			||||||
 | 
					        int(line.strip().split(' ', 1)[0])
 | 
				
			||||||
 | 
					        for line in ps_out.splitlines()
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    # The line we are looking for is the first one, since it was started before
 | 
				
			||||||
 | 
					    # any look up command
 | 
				
			||||||
 | 
					    pid = sorted(pids)[0]
 | 
				
			||||||
 | 
					    return (p, pid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def adb_kill_server(timeout=30, adb_server=None):
 | 
					def adb_kill_server(timeout=30, adb_server=None):
 | 
				
			||||||
    adb_command(None, 'kill-server', timeout, adb_server)
 | 
					    adb_command(None, 'kill-server', timeout, adb_server)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,7 @@ Miscellaneous functions that don't fit anywhere else.
 | 
				
			|||||||
"""
 | 
					"""
 | 
				
			||||||
from __future__ import division
 | 
					from __future__ import division
 | 
				
			||||||
from contextlib import contextmanager
 | 
					from contextlib import contextmanager
 | 
				
			||||||
from functools import partial, reduce
 | 
					from functools import partial, reduce, wraps
 | 
				
			||||||
from itertools import groupby
 | 
					from itertools import groupby
 | 
				
			||||||
from operator import itemgetter
 | 
					from operator import itemgetter
 | 
				
			||||||
from weakref import WeakKeyDictionary, WeakSet
 | 
					from weakref import WeakKeyDictionary, WeakSet
 | 
				
			||||||
@@ -854,3 +854,50 @@ class _BoundTLSProperty:
 | 
				
			|||||||
            instance=self.instance,
 | 
					            instance=self.instance,
 | 
				
			||||||
            owner=self.owner,
 | 
					            owner=self.owner,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InitCheckpointMeta(type):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Metaclass providing an ``initialized`` boolean attributes on instances.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ``initialized`` is set to ``True`` once the ``__init__`` constructor has
 | 
				
			||||||
 | 
					    returned. It will deal cleanly with nested calls to ``super().__init__``.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    def __new__(metacls, name, bases, dct, **kwargs):
 | 
				
			||||||
 | 
					        cls = super().__new__(metacls, name, bases, dct, **kwargs)
 | 
				
			||||||
 | 
					        init_f = cls.__init__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @wraps(init_f)
 | 
				
			||||||
 | 
					        def init_wrapper(self, *args, **kwargs):
 | 
				
			||||||
 | 
					            self.initialized = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Track the nesting of super()__init__ to set initialized=True only
 | 
				
			||||||
 | 
					            # when the outer level is finished
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                stack = self._init_stack
 | 
				
			||||||
 | 
					            except AttributeError:
 | 
				
			||||||
 | 
					                stack = []
 | 
				
			||||||
 | 
					                self._init_stack = stack
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            stack.append(init_f)
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                x = init_f(self, *args, **kwargs)
 | 
				
			||||||
 | 
					            finally:
 | 
				
			||||||
 | 
					                stack.pop()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not stack:
 | 
				
			||||||
 | 
					                self.initialized = True
 | 
				
			||||||
 | 
					                del self._init_stack
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return x
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cls.__init__ = init_wrapper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return cls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InitCheckpoint(metaclass=InitCheckpointMeta):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Inherit from this class to set the :class:`InitCheckpointMeta` metaclass.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    pass
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,9 +26,18 @@ import socket
 | 
				
			|||||||
import sys
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
import atexit
 | 
					import atexit
 | 
				
			||||||
 | 
					import contextlib
 | 
				
			||||||
 | 
					import weakref
 | 
				
			||||||
 | 
					import select
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
from pipes import quote
 | 
					from pipes import quote
 | 
				
			||||||
from future.utils import raise_from
 | 
					from future.utils import raise_from
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from paramiko.client import SSHClient, AutoAddPolicy, RejectPolicy
 | 
				
			||||||
 | 
					import paramiko.ssh_exception
 | 
				
			||||||
 | 
					# By default paramiko is very verbose, including at the INFO level
 | 
				
			||||||
 | 
					logging.getLogger("paramiko").setLevel(logging.WARNING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# pylint: disable=import-error,wrong-import-position,ungrouped-imports,wrong-import-order
 | 
					# pylint: disable=import-error,wrong-import-position,ungrouped-imports,wrong-import-order
 | 
				
			||||||
import pexpect
 | 
					import pexpect
 | 
				
			||||||
from distutils.version import StrictVersion as V
 | 
					from distutils.version import StrictVersion as V
 | 
				
			||||||
@@ -42,8 +51,9 @@ from pexpect import EOF, TIMEOUT, spawn
 | 
				
			|||||||
from devlib.exception import (HostError, TargetStableError, TargetNotRespondingError,
 | 
					from devlib.exception import (HostError, TargetStableError, TargetNotRespondingError,
 | 
				
			||||||
                              TimeoutError, TargetTransientError)
 | 
					                              TimeoutError, TargetTransientError)
 | 
				
			||||||
from devlib.utils.misc import (which, strip_bash_colors, check_output,
 | 
					from devlib.utils.misc import (which, strip_bash_colors, check_output,
 | 
				
			||||||
                               sanitize_cmd_template, memoized)
 | 
					                               sanitize_cmd_template, memoized, redirect_streams)
 | 
				
			||||||
from devlib.utils.types import boolean
 | 
					from devlib.utils.types import boolean
 | 
				
			||||||
 | 
					from devlib.connection import ConnectionBase, ParamikoBackgroundCommand, PopenBackgroundCommand
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ssh = None
 | 
					ssh = None
 | 
				
			||||||
@@ -54,30 +64,112 @@ sshpass = None
 | 
				
			|||||||
logger = logging.getLogger('ssh')
 | 
					logger = logging.getLogger('ssh')
 | 
				
			||||||
gem5_logger = logging.getLogger('gem5-connection')
 | 
					gem5_logger = logging.getLogger('gem5-connection')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def ssh_get_shell(host,
 | 
					@contextlib.contextmanager
 | 
				
			||||||
 | 
					def _handle_paramiko_exceptions(command=None):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        yield
 | 
				
			||||||
 | 
					    except paramiko.ssh_exception.NoValidConnectionsError as e:
 | 
				
			||||||
 | 
					        raise TargetNotRespondingError('Connection lost: {}'.format(e))
 | 
				
			||||||
 | 
					    except paramiko.ssh_exception.AuthenticationException as e:
 | 
				
			||||||
 | 
					        raise TargetStableError('Could not authenticate: {}'.format(e))
 | 
				
			||||||
 | 
					    except paramiko.ssh_exception.BadAuthenticationType as e:
 | 
				
			||||||
 | 
					        raise TargetStableError('Bad authentication type: {}'.format(e))
 | 
				
			||||||
 | 
					    except paramiko.ssh_exception.BadHostKeyException as e:
 | 
				
			||||||
 | 
					        raise TargetStableError('Bad host key: {}'.format(e))
 | 
				
			||||||
 | 
					    except paramiko.ssh_exception.ChannelException as e:
 | 
				
			||||||
 | 
					        raise TargetStableError('Could not open an SSH channel: {}'.format(e))
 | 
				
			||||||
 | 
					    except paramiko.ssh_exception.PasswordRequiredException as e:
 | 
				
			||||||
 | 
					        raise TargetStableError('Please unlock the private key file: {}'.format(e))
 | 
				
			||||||
 | 
					    except paramiko.ssh_exception.ProxyCommandFailure as e:
 | 
				
			||||||
 | 
					        raise TargetStableError('Proxy command failure: {}'.format(e))
 | 
				
			||||||
 | 
					    except paramiko.ssh_exception.SSHException as e:
 | 
				
			||||||
 | 
					        raise TargetTransientError('SSH logic error: {}'.format(e))
 | 
				
			||||||
 | 
					    except socket.timeout:
 | 
				
			||||||
 | 
					        raise TimeoutError(command, output=None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _read_paramiko_streams(stdout, stderr, select_timeout, callback, init, chunk_size=int(1e42)):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return _read_paramiko_streams_internal(stdout, stderr, select_timeout, callback, init, chunk_size)
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        # Close the channel to make sure the remove process will receive
 | 
				
			||||||
 | 
					        # SIGPIPE when writing on its streams. That could happen if the
 | 
				
			||||||
 | 
					        # user closed the out_streams but the remote process has not
 | 
				
			||||||
 | 
					        # finished yet.
 | 
				
			||||||
 | 
					        assert stdout.channel is stderr.channel
 | 
				
			||||||
 | 
					        stdout.channel.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _read_paramiko_streams_internal(stdout, stderr, select_timeout, callback, init, chunk_size):
 | 
				
			||||||
 | 
					    channel = stdout.channel
 | 
				
			||||||
 | 
					    assert stdout.channel is stderr.channel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read_channel(callback_state):
 | 
				
			||||||
 | 
					        read_list, _, _ = select.select([channel], [], [], select_timeout)
 | 
				
			||||||
 | 
					        for desc in read_list:
 | 
				
			||||||
 | 
					            for ready, recv, name in (
 | 
				
			||||||
 | 
					                (desc.recv_ready(), desc.recv, 'stdout'),
 | 
				
			||||||
 | 
					                (desc.recv_stderr_ready(), desc.recv_stderr, 'stderr')
 | 
				
			||||||
 | 
					            ):
 | 
				
			||||||
 | 
					                if ready:
 | 
				
			||||||
 | 
					                    chunk = recv(chunk_size)
 | 
				
			||||||
 | 
					                    if chunk:
 | 
				
			||||||
 | 
					                        try:
 | 
				
			||||||
 | 
					                            callback_state = callback(callback_state, name, chunk)
 | 
				
			||||||
 | 
					                        except Exception as e:
 | 
				
			||||||
 | 
					                            return (e, callback_state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (None, callback_state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def read_all_channel(callback=None, callback_state=None):
 | 
				
			||||||
 | 
					        for stream, name in ((stdout, 'stdout'), (stderr, 'stderr')):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                chunk = stream.read()
 | 
				
			||||||
 | 
					            except Exception:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if callback is not None and chunk:
 | 
				
			||||||
 | 
					                callback_state = callback(callback_state, name, chunk)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return callback_state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    callback_excep = None
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        callback_state = init
 | 
				
			||||||
 | 
					        while not channel.exit_status_ready():
 | 
				
			||||||
 | 
					            callback_excep, callback_state = read_channel(callback_state)
 | 
				
			||||||
 | 
					            if callback_excep is not None:
 | 
				
			||||||
 | 
					                raise callback_excep
 | 
				
			||||||
 | 
					    # Make sure to always empty the streams to unblock the remote process on
 | 
				
			||||||
 | 
					    # the way to exit, in case something bad happened. For example, the
 | 
				
			||||||
 | 
					    # callback could raise an exception to signal it does not want to do
 | 
				
			||||||
 | 
					    # anything anymore, or only reading from one of the stream might have
 | 
				
			||||||
 | 
					    # raised an exception, leaving the other one non-empty.
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        if callback_excep is None:
 | 
				
			||||||
 | 
					            # Only call the callback if there was no exception originally, as
 | 
				
			||||||
 | 
					            # we don't want to reenter it if it raised an exception
 | 
				
			||||||
 | 
					            read_all_channel(callback, callback_state)
 | 
				
			||||||
 | 
					        raise e
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        # Finish emptying the buffers
 | 
				
			||||||
 | 
					        callback_state = read_all_channel(callback, callback_state)
 | 
				
			||||||
 | 
					        exit_code = channel.recv_exit_status()
 | 
				
			||||||
 | 
					        return (callback_state, exit_code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def telnet_get_shell(host,
 | 
				
			||||||
                  username,
 | 
					                  username,
 | 
				
			||||||
                  password=None,
 | 
					                  password=None,
 | 
				
			||||||
                  keyfile=None,
 | 
					 | 
				
			||||||
                  port=None,
 | 
					                  port=None,
 | 
				
			||||||
                  timeout=10,
 | 
					                  timeout=10,
 | 
				
			||||||
                  telnet=False,
 | 
					                  original_prompt=None):
 | 
				
			||||||
                  original_prompt=None,
 | 
					 | 
				
			||||||
                  options=None):
 | 
					 | 
				
			||||||
    _check_env()
 | 
					    _check_env()
 | 
				
			||||||
    start_time = time.time()
 | 
					    start_time = time.time()
 | 
				
			||||||
    while True:
 | 
					    while True:
 | 
				
			||||||
        if telnet:
 | 
					 | 
				
			||||||
            if keyfile:
 | 
					 | 
				
			||||||
                raise ValueError('keyfile may not be used with a telnet connection.')
 | 
					 | 
				
			||||||
        conn = TelnetPxssh(original_prompt=original_prompt)
 | 
					        conn = TelnetPxssh(original_prompt=original_prompt)
 | 
				
			||||||
        else:  # ssh
 | 
					 | 
				
			||||||
            conn = pxssh.pxssh(options=options,
 | 
					 | 
				
			||||||
                               echo=False)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            if keyfile:
 | 
					 | 
				
			||||||
                conn.login(host, username, ssh_key=keyfile, port=port, login_timeout=timeout)
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
            conn.login(host, username, password, port=port, login_timeout=timeout)
 | 
					            conn.login(host, username, password, port=port, login_timeout=timeout)
 | 
				
			||||||
            break
 | 
					            break
 | 
				
			||||||
        except EOF:
 | 
					        except EOF:
 | 
				
			||||||
@@ -157,10 +249,11 @@ def check_keyfile(keyfile):
 | 
				
			|||||||
        return keyfile
 | 
					        return keyfile
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SshConnection(object):
 | 
					class SshConnectionBase(ConnectionBase):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Base class for SSH connections.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    default_password_prompt = '[sudo] password'
 | 
					 | 
				
			||||||
    max_cancel_attempts = 5
 | 
					 | 
				
			||||||
    default_timeout = 10
 | 
					    default_timeout = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @property
 | 
					    @property
 | 
				
			||||||
@@ -170,51 +263,473 @@ class SshConnection(object):
 | 
				
			|||||||
    @property
 | 
					    @property
 | 
				
			||||||
    def connected_as_root(self):
 | 
					    def connected_as_root(self):
 | 
				
			||||||
        if self._connected_as_root is None:
 | 
					        if self._connected_as_root is None:
 | 
				
			||||||
            # Execute directly to prevent deadlocking of connection
 | 
					            try:
 | 
				
			||||||
            result = self._execute_and_wait_for_prompt('id', as_root=False)
 | 
					                result = self.execute('id', as_root=False)
 | 
				
			||||||
            self._connected_as_root = 'uid=0(' in result
 | 
					            except TargetStableError:
 | 
				
			||||||
 | 
					                is_root = False
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                is_root = 'uid=0(' in result
 | 
				
			||||||
 | 
					            self._connected_as_root = is_root
 | 
				
			||||||
        return self._connected_as_root
 | 
					        return self._connected_as_root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @connected_as_root.setter
 | 
					    @connected_as_root.setter
 | 
				
			||||||
    def connected_as_root(self, state):
 | 
					    def connected_as_root(self, state):
 | 
				
			||||||
        self._connected_as_root = state
 | 
					        self._connected_as_root = state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # pylint: disable=unused-argument,super-init-not-called
 | 
					 | 
				
			||||||
    def __init__(self,
 | 
					    def __init__(self,
 | 
				
			||||||
                 host,
 | 
					                 host,
 | 
				
			||||||
                 username,
 | 
					                 username,
 | 
				
			||||||
                 password=None,
 | 
					                 password=None,
 | 
				
			||||||
                 keyfile=None,
 | 
					                 keyfile=None,
 | 
				
			||||||
                 port=None,
 | 
					                 port=None,
 | 
				
			||||||
                 timeout=None,
 | 
					 | 
				
			||||||
                 telnet=False,
 | 
					 | 
				
			||||||
                 password_prompt=None,
 | 
					 | 
				
			||||||
                 original_prompt=None,
 | 
					 | 
				
			||||||
                 platform=None,
 | 
					                 platform=None,
 | 
				
			||||||
                 sudo_cmd="sudo -- sh -c {}",
 | 
					                 sudo_cmd="sudo -S -- sh -c {}",
 | 
				
			||||||
                 options=None
 | 
					                 strict_host_check=True,
 | 
				
			||||||
                 ):
 | 
					                 ):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
        self._connected_as_root = None
 | 
					        self._connected_as_root = None
 | 
				
			||||||
        self.host = host
 | 
					        self.host = host
 | 
				
			||||||
        self.username = username
 | 
					        self.username = username
 | 
				
			||||||
        self.password = password
 | 
					        self.password = password
 | 
				
			||||||
        self.keyfile = check_keyfile(keyfile) if keyfile else keyfile
 | 
					        self.keyfile = check_keyfile(keyfile) if keyfile else keyfile
 | 
				
			||||||
        self.port = port
 | 
					        self.port = port
 | 
				
			||||||
 | 
					        self.sudo_cmd = sanitize_cmd_template(sudo_cmd)
 | 
				
			||||||
 | 
					        self.platform = platform
 | 
				
			||||||
 | 
					        self.strict_host_check = strict_host_check
 | 
				
			||||||
 | 
					        logger.debug('Logging in {}@{}'.format(username, host))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SshConnection(SshConnectionBase):
 | 
				
			||||||
 | 
					    # pylint: disable=unused-argument,super-init-not-called
 | 
				
			||||||
 | 
					    def __init__(self,
 | 
				
			||||||
 | 
					                 host,
 | 
				
			||||||
 | 
					                 username,
 | 
				
			||||||
 | 
					                 password=None,
 | 
				
			||||||
 | 
					                 keyfile=None,
 | 
				
			||||||
 | 
					                 port=22,
 | 
				
			||||||
 | 
					                 timeout=None,
 | 
				
			||||||
 | 
					                 platform=None,
 | 
				
			||||||
 | 
					                 sudo_cmd="sudo -S -- sh -c {}",
 | 
				
			||||||
 | 
					                 strict_host_check=True,
 | 
				
			||||||
 | 
					                 ):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super().__init__(
 | 
				
			||||||
 | 
					            host=host,
 | 
				
			||||||
 | 
					            username=username,
 | 
				
			||||||
 | 
					            password=password,
 | 
				
			||||||
 | 
					            keyfile=keyfile,
 | 
				
			||||||
 | 
					            port=port,
 | 
				
			||||||
 | 
					            platform=platform,
 | 
				
			||||||
 | 
					            sudo_cmd=sudo_cmd,
 | 
				
			||||||
 | 
					            strict_host_check=strict_host_check,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.timeout = timeout if timeout is not None else self.default_timeout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.client = self._make_client()
 | 
				
			||||||
 | 
					        atexit.register(self.close)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Use a marker in the output so that we will be able to differentiate
 | 
				
			||||||
 | 
					        # target connection issues with "password needed".
 | 
				
			||||||
 | 
					        # Also, sudo might not be installed at all on the target (but
 | 
				
			||||||
 | 
					        # everything will work as long as we login as root). If sudo is still
 | 
				
			||||||
 | 
					        # needed, it will explode when someone tries to use it. After all, the
 | 
				
			||||||
 | 
					        # user might not be interested in being root at all.
 | 
				
			||||||
 | 
					        self._sudo_needs_password = (
 | 
				
			||||||
 | 
					            'NEED_PASSOWRD' in
 | 
				
			||||||
 | 
					            self.execute(
 | 
				
			||||||
 | 
					                # sudo -n is broken on some versions on MacOSX, revisit that if
 | 
				
			||||||
 | 
					                # someone ever cares
 | 
				
			||||||
 | 
					                'sudo -n true || echo NEED_PASSWORD',
 | 
				
			||||||
 | 
					                as_root=False,
 | 
				
			||||||
 | 
					                check_exit_code=False,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _make_client(self):
 | 
				
			||||||
 | 
					        if self.strict_host_check:
 | 
				
			||||||
 | 
					            policy = RejectPolicy
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            policy = AutoAddPolicy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with _handle_paramiko_exceptions():
 | 
				
			||||||
 | 
					            client = SSHClient()
 | 
				
			||||||
 | 
					            client.load_system_host_keys()
 | 
				
			||||||
 | 
					            client.set_missing_host_key_policy(policy)
 | 
				
			||||||
 | 
					            client.connect(
 | 
				
			||||||
 | 
					                hostname=self.host,
 | 
				
			||||||
 | 
					                port=self.port,
 | 
				
			||||||
 | 
					                username=self.username,
 | 
				
			||||||
 | 
					                password=self.password,
 | 
				
			||||||
 | 
					                key_filename=self.keyfile,
 | 
				
			||||||
 | 
					                timeout=self.timeout,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _make_channel(self):
 | 
				
			||||||
 | 
					        with _handle_paramiko_exceptions():
 | 
				
			||||||
 | 
					            transport = self.client.get_transport()
 | 
				
			||||||
 | 
					            channel = transport.open_session()
 | 
				
			||||||
 | 
					            return channel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _get_sftp(self, timeout):
 | 
				
			||||||
 | 
					        sftp = self.client.open_sftp()
 | 
				
			||||||
 | 
					        sftp.get_channel().settimeout(timeout)
 | 
				
			||||||
 | 
					        return sftp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def _push_file(cls, sftp, src, dst):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            sftp.put(src, dst)
 | 
				
			||||||
 | 
					        # Maybe the dst was a folder
 | 
				
			||||||
 | 
					        except OSError:
 | 
				
			||||||
 | 
					            # This might fail if the folder already exists
 | 
				
			||||||
 | 
					            with contextlib.suppress(IOError):
 | 
				
			||||||
 | 
					                sftp.mkdir(dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            new_dst = os.path.join(
 | 
				
			||||||
 | 
					                dst,
 | 
				
			||||||
 | 
					                os.path.basename(src),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return cls._push_file(sftp, src, new_dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def _push_folder(cls, sftp, src, dst):
 | 
				
			||||||
 | 
					        # Behave like the "mv" command or adb push: a new folder is created
 | 
				
			||||||
 | 
					        # inside the destination folder, rather than merging the trees.
 | 
				
			||||||
 | 
					        dst = os.path.join(
 | 
				
			||||||
 | 
					            dst,
 | 
				
			||||||
 | 
					            os.path.basename(src),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return cls._push_folder_internal(sftp, src, dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def _push_folder_internal(cls, sftp, src, dst):
 | 
				
			||||||
 | 
					        # This might fail if the folder already exists
 | 
				
			||||||
 | 
					        with contextlib.suppress(IOError):
 | 
				
			||||||
 | 
					            sftp.mkdir(dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for entry in os.scandir(src):
 | 
				
			||||||
 | 
					            name = entry.name
 | 
				
			||||||
 | 
					            src_path = os.path.join(src, name)
 | 
				
			||||||
 | 
					            dst_path = os.path.join(dst, name)
 | 
				
			||||||
 | 
					            if entry.is_dir():
 | 
				
			||||||
 | 
					                push = cls._push_folder_internal
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                push = cls._push_file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            push(sftp, src_path, dst_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def _push_path(cls, sftp, src, dst):
 | 
				
			||||||
 | 
					        push = cls._push_folder if os.path.isdir(src) else cls._push_file
 | 
				
			||||||
 | 
					        push(sftp, src, dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def _pull_file(cls, sftp, src, dst):
 | 
				
			||||||
 | 
					        # Pulling a file into a folder will use the source basename
 | 
				
			||||||
 | 
					        if os.path.isdir(dst):
 | 
				
			||||||
 | 
					            dst = os.path.join(
 | 
				
			||||||
 | 
					                dst,
 | 
				
			||||||
 | 
					                os.path.basename(src),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        with contextlib.suppress(FileNotFoundError):
 | 
				
			||||||
 | 
					            os.remove(dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sftp.get(src, dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def _pull_folder(cls, sftp, src, dst):
 | 
				
			||||||
 | 
					        with contextlib.suppress(FileNotFoundError):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                shutil.rmtree(dst)
 | 
				
			||||||
 | 
					            except OSError:
 | 
				
			||||||
 | 
					                os.remove(dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        os.makedirs(dst)
 | 
				
			||||||
 | 
					        for fileattr in sftp.listdir_attr(src):
 | 
				
			||||||
 | 
					            filename = fileattr.filename
 | 
				
			||||||
 | 
					            src_path = os.path.join(src, filename)
 | 
				
			||||||
 | 
					            dst_path = os.path.join(dst, filename)
 | 
				
			||||||
 | 
					            if stat.S_ISDIR(fileattr.st_mode):
 | 
				
			||||||
 | 
					                pull = cls._pull_folder
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                pull = cls._pull_file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            pull(sftp, src_path, dst_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def _pull_path(cls, sftp, src, dst):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            cls._pull_file(sftp, src, dst)
 | 
				
			||||||
 | 
					        except IOError:
 | 
				
			||||||
 | 
					            # Maybe that was a directory, so retry as such
 | 
				
			||||||
 | 
					            cls._pull_folder(sftp, src, dst)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def push(self, source, dest, timeout=30):
 | 
				
			||||||
 | 
					        with _handle_paramiko_exceptions(), self._get_sftp(timeout) as sftp:
 | 
				
			||||||
 | 
					            self._push_path(sftp, source, dest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def pull(self, source, dest, timeout=30):
 | 
				
			||||||
 | 
					        with _handle_paramiko_exceptions(), self._get_sftp(timeout) as sftp:
 | 
				
			||||||
 | 
					            self._pull_path(sftp, source, dest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def execute(self, command, timeout=None, check_exit_code=True,
 | 
				
			||||||
 | 
					                as_root=False, strip_colors=True, will_succeed=False): #pylint: disable=unused-argument
 | 
				
			||||||
 | 
					        if command == '':
 | 
				
			||||||
 | 
					            return ''
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            with _handle_paramiko_exceptions(command):
 | 
				
			||||||
 | 
					                exit_code, output = self._execute(command, timeout, as_root, strip_colors)
 | 
				
			||||||
 | 
					        except TargetStableError as e:
 | 
				
			||||||
 | 
					            if will_succeed:
 | 
				
			||||||
 | 
					                raise TargetTransientError(e)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if check_exit_code and exit_code:
 | 
				
			||||||
 | 
					                message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}'
 | 
				
			||||||
 | 
					                raise TargetStableError(message.format(exit_code, command, output))
 | 
				
			||||||
 | 
					            return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
 | 
				
			||||||
 | 
					        with _handle_paramiko_exceptions(command):
 | 
				
			||||||
 | 
					            bg_cmd = self._background(command, stdout, stderr, as_root)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self._current_bg_cmds.add(bg_cmd)
 | 
				
			||||||
 | 
					        return bg_cmd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _background(self, command, stdout, stderr, as_root):
 | 
				
			||||||
 | 
					        stdout, stderr, command = redirect_streams(stdout, stderr, command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command = "printf '%s\n' $$; exec sh -c {}".format(quote(command))
 | 
				
			||||||
 | 
					        channel = self._make_channel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def executor(cmd, timeout):
 | 
				
			||||||
 | 
					            channel.exec_command(cmd)
 | 
				
			||||||
 | 
					            # Read are not buffered so we will always get the data as soon as
 | 
				
			||||||
 | 
					            # they arrive
 | 
				
			||||||
 | 
					            return (
 | 
				
			||||||
 | 
					                channel.makefile_stdin(),
 | 
				
			||||||
 | 
					                channel.makefile(),
 | 
				
			||||||
 | 
					                channel.makefile_stderr(),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stdin, stdout_in, stderr_in = self._execute_command(
 | 
				
			||||||
 | 
					            command,
 | 
				
			||||||
 | 
					            as_root=as_root,
 | 
				
			||||||
 | 
					            log=False,
 | 
				
			||||||
 | 
					            timeout=None,
 | 
				
			||||||
 | 
					            executor=executor,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        pid = int(stdout_in.readline())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def create_out_stream(stream_in, stream_out):
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
 | 
					            Create a pair of file-like objects. The first one is used to read
 | 
				
			||||||
 | 
					            data and the second one to write.
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if stream_out == subprocess.DEVNULL:
 | 
				
			||||||
 | 
					                r, w = None, None
 | 
				
			||||||
 | 
					            # When asked for a pipe, we just give the file-like object as the
 | 
				
			||||||
 | 
					            # reading end and no writing end, since paramiko already writes to
 | 
				
			||||||
 | 
					            # it
 | 
				
			||||||
 | 
					            elif stream_out == subprocess.PIPE:
 | 
				
			||||||
 | 
					                r, w = os.pipe()
 | 
				
			||||||
 | 
					                r = os.fdopen(r, 'rb')
 | 
				
			||||||
 | 
					                w = os.fdopen(w, 'wb')
 | 
				
			||||||
 | 
					            # Turn a file descriptor into a file-like object
 | 
				
			||||||
 | 
					            elif isinstance(stream_out, int) and stream_out >= 0:
 | 
				
			||||||
 | 
					                r = os.fdopen(stream_out, 'rb')
 | 
				
			||||||
 | 
					                w = os.fdopen(stream_out, 'wb')
 | 
				
			||||||
 | 
					            # file-like object
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                r = stream_out
 | 
				
			||||||
 | 
					                w = stream_out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return (r, w)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        out_streams = {
 | 
				
			||||||
 | 
					            name: create_out_stream(stream_in, stream_out)
 | 
				
			||||||
 | 
					            for stream_in, stream_out, name in (
 | 
				
			||||||
 | 
					                (stdout_in, stdout, 'stdout'),
 | 
				
			||||||
 | 
					                (stderr_in, stderr, 'stderr'),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def redirect_thread_f(stdout_in, stderr_in, out_streams, select_timeout):
 | 
				
			||||||
 | 
					            def callback(out_streams, name, chunk):
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    r, w = out_streams[name]
 | 
				
			||||||
 | 
					                except KeyError:
 | 
				
			||||||
 | 
					                    return out_streams
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    w.write(chunk)
 | 
				
			||||||
 | 
					                # Write failed
 | 
				
			||||||
 | 
					                except ValueError:
 | 
				
			||||||
 | 
					                    # Since that stream is now closed, stop trying to write to it
 | 
				
			||||||
 | 
					                    del out_streams[name]
 | 
				
			||||||
 | 
					                    # If that was the last open stream, we raise an
 | 
				
			||||||
 | 
					                    # exception so the thread can terminate.
 | 
				
			||||||
 | 
					                    if not out_streams:
 | 
				
			||||||
 | 
					                        raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return out_streams
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                _read_paramiko_streams(stdout_in, stderr_in, select_timeout, callback, copy.copy(out_streams))
 | 
				
			||||||
 | 
					            # The streams closed while we were writing to it, the job is done here
 | 
				
			||||||
 | 
					            except ValueError:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Make sure the writing end are closed proper since we are not
 | 
				
			||||||
 | 
					            # going to write anything anymore
 | 
				
			||||||
 | 
					            for r, w in out_streams.values():
 | 
				
			||||||
 | 
					                if r is not w and w is not None:
 | 
				
			||||||
 | 
					                    w.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # If there is anything we need to redirect to, spawn a thread taking
 | 
				
			||||||
 | 
					        # care of that
 | 
				
			||||||
 | 
					        select_timeout = 1
 | 
				
			||||||
 | 
					        thread_out_streams = {
 | 
				
			||||||
 | 
					            name: (r, w)
 | 
				
			||||||
 | 
					            for name, (r, w) in out_streams.items()
 | 
				
			||||||
 | 
					            if w is not None
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        redirect_thread = threading.Thread(
 | 
				
			||||||
 | 
					            target=redirect_thread_f,
 | 
				
			||||||
 | 
					            args=(stdout_in, stderr_in, thread_out_streams, select_timeout),
 | 
				
			||||||
 | 
					            # The thread will die when the main thread dies
 | 
				
			||||||
 | 
					            daemon=True,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        redirect_thread.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return ParamikoBackgroundCommand(
 | 
				
			||||||
 | 
					            conn=self,
 | 
				
			||||||
 | 
					            as_root=as_root,
 | 
				
			||||||
 | 
					            chan=channel,
 | 
				
			||||||
 | 
					            pid=pid,
 | 
				
			||||||
 | 
					            stdin=stdin,
 | 
				
			||||||
 | 
					            # We give the reading end to the consumer of the data
 | 
				
			||||||
 | 
					            stdout=out_streams['stdout'][0],
 | 
				
			||||||
 | 
					            stderr=out_streams['stderr'][0],
 | 
				
			||||||
 | 
					            redirect_thread=redirect_thread,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _close(self):
 | 
				
			||||||
 | 
					        logger.debug('Logging out {}@{}'.format(self.username, self.host))
 | 
				
			||||||
 | 
					        with _handle_paramiko_exceptions():
 | 
				
			||||||
 | 
					            bg_cmds = set(self._current_bg_cmds)
 | 
				
			||||||
 | 
					            for bg_cmd in bg_cmds:
 | 
				
			||||||
 | 
					                bg_cmd.close()
 | 
				
			||||||
 | 
					            self.client.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _execute_command(self, command, as_root, log, timeout, executor):
 | 
				
			||||||
 | 
					        # As we're already root, there is no need to use sudo.
 | 
				
			||||||
 | 
					        log_debug = logger.debug if log else lambda msg: None
 | 
				
			||||||
 | 
					        use_sudo = as_root and not self.connected_as_root
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if use_sudo:
 | 
				
			||||||
 | 
					            if self._sudo_needs_password and not self.password:
 | 
				
			||||||
 | 
					                raise TargetStableError('Attempt to use sudo but no password was specified')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            command = self.sudo_cmd.format(quote(command))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            log_debug(command)
 | 
				
			||||||
 | 
					            streams = executor(command, timeout=timeout)
 | 
				
			||||||
 | 
					            if self._sudo_needs_password:
 | 
				
			||||||
 | 
					                stdin = streams[0]
 | 
				
			||||||
 | 
					                stdin.write(self.password + '\n')
 | 
				
			||||||
 | 
					                stdin.flush()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            log_debug(command)
 | 
				
			||||||
 | 
					            streams = executor(command, timeout=timeout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return streams
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def _execute(self, command, timeout=None, as_root=False, strip_colors=True, log=True):
 | 
				
			||||||
 | 
					        # Merge stderr into stdout since we are going without a TTY
 | 
				
			||||||
 | 
					        command = '({}) 2>&1'.format(command)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stdin, stdout, stderr = self._execute_command(
 | 
				
			||||||
 | 
					            command,
 | 
				
			||||||
 | 
					            as_root=as_root,
 | 
				
			||||||
 | 
					            log=log,
 | 
				
			||||||
 | 
					            timeout=timeout,
 | 
				
			||||||
 | 
					            executor=self.client.exec_command,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        stdin.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Empty the stdout buffer of the command, allowing it to carry on to
 | 
				
			||||||
 | 
					        # completion
 | 
				
			||||||
 | 
					        def callback(output_chunks, name, chunk):
 | 
				
			||||||
 | 
					            output_chunks.append(chunk)
 | 
				
			||||||
 | 
					            return output_chunks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        select_timeout = 1
 | 
				
			||||||
 | 
					        output_chunks, exit_code = _read_paramiko_streams(stdout, stderr, select_timeout, callback, [])
 | 
				
			||||||
 | 
					        # Join in one go to avoid O(N^2) concatenation
 | 
				
			||||||
 | 
					        output = b''.join(output_chunks)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if sys.version_info[0] == 3:
 | 
				
			||||||
 | 
					            output = output.decode(sys.stdout.encoding or 'utf-8', 'replace')
 | 
				
			||||||
 | 
					        if strip_colors:
 | 
				
			||||||
 | 
					            output = strip_bash_colors(output)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (exit_code, output)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TelnetConnection(SshConnectionBase):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default_password_prompt = '[sudo] password'
 | 
				
			||||||
 | 
					    max_cancel_attempts = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # pylint: disable=unused-argument,super-init-not-called
 | 
				
			||||||
 | 
					    def __init__(self,
 | 
				
			||||||
 | 
					                 host,
 | 
				
			||||||
 | 
					                 username,
 | 
				
			||||||
 | 
					                 password=None,
 | 
				
			||||||
 | 
					                 port=None,
 | 
				
			||||||
 | 
					                 timeout=None,
 | 
				
			||||||
 | 
					                 password_prompt=None,
 | 
				
			||||||
 | 
					                 original_prompt=None,
 | 
				
			||||||
 | 
					                 sudo_cmd="sudo -- sh -c {}",
 | 
				
			||||||
 | 
					                 strict_host_check=True,
 | 
				
			||||||
 | 
					                 platform=None):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        super().__init__(
 | 
				
			||||||
 | 
					            host=host,
 | 
				
			||||||
 | 
					            username=username,
 | 
				
			||||||
 | 
					            password=password,
 | 
				
			||||||
 | 
					            keyfile=None,
 | 
				
			||||||
 | 
					            port=port,
 | 
				
			||||||
 | 
					            platform=platform,
 | 
				
			||||||
 | 
					            sudo_cmd=sudo_cmd,
 | 
				
			||||||
 | 
					            strict_host_check=strict_host_check,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.strict_host_check:
 | 
				
			||||||
 | 
					            options = {
 | 
				
			||||||
 | 
					                'StrictHostKeyChecking': 'yes',
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            options = {
 | 
				
			||||||
 | 
					                'StrictHostKeyChecking': 'no',
 | 
				
			||||||
 | 
					                'UserKnownHostsFile': '/dev/null',
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        self.options = options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.lock = threading.Lock()
 | 
					        self.lock = threading.Lock()
 | 
				
			||||||
        self.password_prompt = password_prompt if password_prompt is not None else self.default_password_prompt
 | 
					        self.password_prompt = password_prompt if password_prompt is not None else self.default_password_prompt
 | 
				
			||||||
        self.sudo_cmd = sanitize_cmd_template(sudo_cmd)
 | 
					 | 
				
			||||||
        logger.debug('Logging in {}@{}'.format(username, host))
 | 
					        logger.debug('Logging in {}@{}'.format(username, host))
 | 
				
			||||||
        timeout = timeout if timeout is not None else self.default_timeout
 | 
					        timeout = timeout if timeout is not None else self.default_timeout
 | 
				
			||||||
        self.options = options if options is not None else {}
 | 
					
 | 
				
			||||||
        self.conn = ssh_get_shell(host,
 | 
					        self.conn = telnet_get_shell(host, username, password, port, timeout, original_prompt)
 | 
				
			||||||
                                  username,
 | 
					 | 
				
			||||||
                                  password,
 | 
					 | 
				
			||||||
                                  self.keyfile,
 | 
					 | 
				
			||||||
                                  port,
 | 
					 | 
				
			||||||
                                  timeout,
 | 
					 | 
				
			||||||
                                  False,
 | 
					 | 
				
			||||||
                                  None,
 | 
					 | 
				
			||||||
                                  self.options)
 | 
					 | 
				
			||||||
        atexit.register(self.close)
 | 
					        atexit.register(self.close)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def push(self, source, dest, timeout=30):
 | 
					    def push(self, source, dest, timeout=30):
 | 
				
			||||||
@@ -282,7 +797,7 @@ class SshConnection(object):
 | 
				
			|||||||
        except EOF:
 | 
					        except EOF:
 | 
				
			||||||
            raise TargetNotRespondingError('Connection lost.')
 | 
					            raise TargetNotRespondingError('Connection lost.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def close(self):
 | 
					    def _close(self):
 | 
				
			||||||
        logger.debug('Logging out {}@{}'.format(self.username, self.host))
 | 
					        logger.debug('Logging out {}@{}'.format(self.username, self.host))
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            self.conn.logout()
 | 
					            self.conn.logout()
 | 
				
			||||||
@@ -351,8 +866,8 @@ class SshConnection(object):
 | 
				
			|||||||
        # only specify -P for scp if the port is *not* the default.
 | 
					        # only specify -P for scp if the port is *not* the default.
 | 
				
			||||||
        port_string = '-P {}'.format(quote(str(self.port))) if (self.port and self.port != 22) else ''
 | 
					        port_string = '-P {}'.format(quote(str(self.port))) if (self.port and self.port != 22) else ''
 | 
				
			||||||
        keyfile_string = '-i {}'.format(quote(self.keyfile)) if self.keyfile else ''
 | 
					        keyfile_string = '-i {}'.format(quote(self.keyfile)) if self.keyfile else ''
 | 
				
			||||||
        options = " ".join(["-o {}={}".format(key,val)
 | 
					        options = " ".join(["-o {}={}".format(key, val)
 | 
				
			||||||
                            for key,val in self.options.items()])
 | 
					                            for key, val in self.options.items()])
 | 
				
			||||||
        command = '{} {} -r {} {} {} {}'.format(scp,
 | 
					        command = '{} {} -r {} {} {} {}'.format(scp,
 | 
				
			||||||
                                                options,
 | 
					                                                options,
 | 
				
			||||||
                                                keyfile_string,
 | 
					                                                keyfile_string,
 | 
				
			||||||
@@ -387,29 +902,6 @@ class SshConnection(object):
 | 
				
			|||||||
    def _get_window_size(self):
 | 
					    def _get_window_size(self):
 | 
				
			||||||
        return self.conn.getwinsize()
 | 
					        return self.conn.getwinsize()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TelnetConnection(SshConnection):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # pylint: disable=super-init-not-called
 | 
					 | 
				
			||||||
    def __init__(self,
 | 
					 | 
				
			||||||
                 host,
 | 
					 | 
				
			||||||
                 username,
 | 
					 | 
				
			||||||
                 password=None,
 | 
					 | 
				
			||||||
                 port=None,
 | 
					 | 
				
			||||||
                 timeout=None,
 | 
					 | 
				
			||||||
                 password_prompt=None,
 | 
					 | 
				
			||||||
                 original_prompt=None,
 | 
					 | 
				
			||||||
                 platform=None):
 | 
					 | 
				
			||||||
        self.host = host
 | 
					 | 
				
			||||||
        self.username = username
 | 
					 | 
				
			||||||
        self.password = password
 | 
					 | 
				
			||||||
        self.port = port
 | 
					 | 
				
			||||||
        self.keyfile = None
 | 
					 | 
				
			||||||
        self.lock = threading.Lock()
 | 
					 | 
				
			||||||
        self.password_prompt = password_prompt if password_prompt is not None else self.default_password_prompt
 | 
					 | 
				
			||||||
        logger.debug('Logging in {}@{}'.format(username, host))
 | 
					 | 
				
			||||||
        timeout = timeout if timeout is not None else self.default_timeout
 | 
					 | 
				
			||||||
        self.conn = ssh_get_shell(host, username, password, None, port, timeout, True, original_prompt)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Gem5Connection(TelnetConnection):
 | 
					class Gem5Connection(TelnetConnection):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -616,7 +1108,7 @@ class Gem5Connection(TelnetConnection):
 | 
				
			|||||||
                         'get this file'.format(redirection_file))
 | 
					                         'get this file'.format(redirection_file))
 | 
				
			||||||
        return output
 | 
					        return output
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def close(self):
 | 
					    def _close(self):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        Close and disconnect from the gem5 simulation. Additionally, we remove
 | 
					        Close and disconnect from the gem5 simulation. Additionally, we remove
 | 
				
			||||||
        the temporary directory used to pass files into the simulation.
 | 
					        the temporary directory used to pass files into the simulation.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
									
									
									
									
								
							@@ -82,6 +82,7 @@ params = dict(
 | 
				
			|||||||
        'python-dateutil',  # converting between UTC and local time.
 | 
					        'python-dateutil',  # converting between UTC and local time.
 | 
				
			||||||
        'pexpect>=3.3',  # Send/recieve to/from device
 | 
					        'pexpect>=3.3',  # Send/recieve to/from device
 | 
				
			||||||
        'pyserial',  # Serial port interface
 | 
					        'pyserial',  # Serial port interface
 | 
				
			||||||
 | 
					        'paramiko', # SSH connection
 | 
				
			||||||
        'wrapt',  # Basic for construction of decorator functions
 | 
					        'wrapt',  # Basic for construction of decorator functions
 | 
				
			||||||
        'future', # Python 2-3 compatibility
 | 
					        'future', # Python 2-3 compatibility
 | 
				
			||||||
        'enum34;python_version<"3.4"', # Enums for Python < 3.4
 | 
					        'enum34;python_version<"3.4"', # Enums for Python < 3.4
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user