mirror of
https://github.com/ARM-software/devlib.git
synced 2025-09-23 12:21:54 +01:00
subprocess: Fix quoting issues
Strings are passed to subprocess.Popen is used instead of sequences, so proper quoting of parameters in command lines is needed to avoid failures on filenames containing e.g. parenthesis. Use pipes.quote to quote a string to avoid any issue with special characters.
This commit is contained in:
committed by
Marc Bonnici
parent
d376bc10ee
commit
ed7f0e56a2
@@ -235,7 +235,7 @@ class AdbConnection(object):
|
||||
def push(self, source, dest, timeout=None):
|
||||
if timeout is None:
|
||||
timeout = self.timeout
|
||||
command = "push '{}' '{}'".format(source, dest)
|
||||
command = "push {} {}".format(quote(source), quote(dest))
|
||||
if not os.path.exists(source):
|
||||
raise HostError('No such file "{}"'.format(source))
|
||||
return adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
|
||||
@@ -249,10 +249,10 @@ class AdbConnection(object):
|
||||
command = 'shell {} {}'.format(self.ls_command, source)
|
||||
output = adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
|
||||
for line in output.splitlines():
|
||||
command = "pull '{}' '{}'".format(line.strip(), dest)
|
||||
command = "pull {} {}".format(quote(line.strip()), quote(dest))
|
||||
adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
|
||||
return
|
||||
command = "pull '{}' '{}'".format(source, dest)
|
||||
command = "pull {} {}".format(quote(source), quote(dest))
|
||||
return adb_command(self.device, command, timeout=timeout, adb_server=self.adb_server)
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@@ -285,7 +285,7 @@ class AdbConnection(object):
|
||||
|
||||
def fastboot_command(command, timeout=None, device=None):
|
||||
_check_env()
|
||||
target = '-s {}'.format(device) if device else ''
|
||||
target = '-s {}'.format(quote(device)) if device else ''
|
||||
full_command = 'fastboot {} {}'.format(target, command)
|
||||
logger.debug(full_command)
|
||||
output, _ = check_output(full_command, timeout, shell=True)
|
||||
@@ -293,7 +293,7 @@ def fastboot_command(command, timeout=None, device=None):
|
||||
|
||||
|
||||
def fastboot_flash_partition(partition, path_to_image):
|
||||
command = 'flash {} {}'.format(partition, path_to_image)
|
||||
command = 'flash {} {}'.format(quote(partition), quote(path_to_image))
|
||||
fastboot_command(command)
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@ def adb_connect(device, timeout=None, attempts=MAX_ATTEMPTS):
|
||||
tries += 1
|
||||
if device:
|
||||
if "." in device: # Connect is required only for ADB-over-IP
|
||||
command = 'adb connect {}'.format(device)
|
||||
command = 'adb connect {}'.format(quote(device))
|
||||
logger.debug(command)
|
||||
output, _ = check_output(command, shell=True, timeout=timeout)
|
||||
if _ping(device):
|
||||
@@ -368,7 +368,7 @@ def adb_disconnect(device):
|
||||
|
||||
def _ping(device):
|
||||
_check_env()
|
||||
device_string = ' -s {}'.format(device) if device else ''
|
||||
device_string = ' -s {}'.format(quote(device)) if device else ''
|
||||
command = "adb{} shell \"ls /data/local/tmp > /dev/null\"".format(device_string)
|
||||
logger.debug(command)
|
||||
result = subprocess.call(command, stderr=subprocess.PIPE, shell=True)
|
||||
@@ -610,9 +610,9 @@ class LogcatMonitor(object):
|
||||
# Logcat on older version of android do not support the -e argument
|
||||
# so fall back to using grep.
|
||||
if self.target.get_sdk_version() > 23:
|
||||
logcat_cmd = '{} -e "{}"'.format(logcat_cmd, regexp)
|
||||
logcat_cmd = '{} -e {}'.format(logcat_cmd, quote(regexp))
|
||||
else:
|
||||
logcat_cmd = '{} | grep "{}"'.format(logcat_cmd, regexp)
|
||||
logcat_cmd = '{} | grep {}'.format(logcat_cmd, quote(regexp))
|
||||
|
||||
logcat_cmd = get_adb_command(self.target.conn.device, logcat_cmd)
|
||||
|
||||
|
@@ -35,6 +35,7 @@ import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import wrapt
|
||||
import warnings
|
||||
|
||||
|
||||
from past.builtins import basestring
|
||||
@@ -416,6 +417,16 @@ def convert_new_lines(text):
|
||||
""" Convert new lines to a common format. """
|
||||
return text.replace('\r\n', '\n').replace('\r', '\n')
|
||||
|
||||
def sanitize_cmd_template(cmd):
|
||||
msg = (
|
||||
'''Quoted placeholder should not be used, as it will result in quoting the text twice. {} should be used instead of '{}' or "{}" in the template: '''
|
||||
)
|
||||
for unwanted in ('"{}"', "'{}'"):
|
||||
if unwanted in cmd:
|
||||
warnings.warn(msg + cmd, stacklevel=2)
|
||||
cmd = cmd.replace(unwanted, '{}')
|
||||
|
||||
return cmd
|
||||
|
||||
def escape_quotes(text):
|
||||
"""Escape quotes, and escaped quotes, in the specified text."""
|
||||
|
@@ -21,6 +21,7 @@ import tempfile
|
||||
import threading
|
||||
import time
|
||||
from collections import namedtuple
|
||||
from pipes import quote
|
||||
|
||||
# pylint: disable=redefined-builtin
|
||||
from devlib.exception import WorkerThreadError, TargetNotRespondingError, TimeoutError
|
||||
@@ -138,8 +139,8 @@ class SurfaceFlingerFrameCollector(FrameCollector):
|
||||
self.target.execute('dumpsys SurfaceFlinger --latency-clear ')
|
||||
|
||||
def get_latencies(self, activity):
|
||||
cmd = 'dumpsys SurfaceFlinger --latency "{}"'
|
||||
return self.target.execute(cmd.format(activity))
|
||||
cmd = 'dumpsys SurfaceFlinger --latency {}'
|
||||
return self.target.execute(cmd.format(quote(activity)))
|
||||
|
||||
def list(self):
|
||||
text = self.target.execute('dumpsys SurfaceFlinger --list')
|
||||
|
@@ -26,6 +26,7 @@ import socket
|
||||
import sys
|
||||
import time
|
||||
import atexit
|
||||
from pipes import quote
|
||||
|
||||
# pylint: disable=import-error,wrong-import-position,ungrouped-imports,wrong-import-order
|
||||
import pexpect
|
||||
@@ -39,7 +40,7 @@ from pexpect import EOF, TIMEOUT, spawn
|
||||
# pylint: disable=redefined-builtin,wrong-import-position
|
||||
from devlib.exception import (HostError, TargetStableError, TargetNotRespondingError,
|
||||
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
|
||||
from devlib.utils.misc import (escape_single_quotes, escape_double_quotes,
|
||||
escape_spaces)
|
||||
from devlib.utils.types import boolean
|
||||
@@ -169,7 +170,7 @@ class SshConnection(object):
|
||||
password_prompt=None,
|
||||
original_prompt=None,
|
||||
platform=None,
|
||||
sudo_cmd="sudo -- sh -c '{}'"
|
||||
sudo_cmd="sudo -- sh -c {}"
|
||||
):
|
||||
self.host = host
|
||||
self.username = username
|
||||
@@ -178,22 +179,18 @@ class SshConnection(object):
|
||||
self.port = port
|
||||
self.lock = threading.Lock()
|
||||
self.password_prompt = password_prompt if password_prompt is not None else self.default_password_prompt
|
||||
self.sudo_cmd = sudo_cmd
|
||||
self.sudo_cmd = sanitize_cmd_template(sudo_cmd)
|
||||
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, self.keyfile, port, timeout, False, None)
|
||||
atexit.register(self.close)
|
||||
|
||||
def push(self, source, dest, timeout=30):
|
||||
dest = '"{}"@"{}":"{}"'.format(escape_double_quotes(self.username),
|
||||
escape_spaces(escape_double_quotes(self.host)),
|
||||
escape_spaces(escape_double_quotes(dest)))
|
||||
dest = '{}@{}:{}'.format(self.username, self.host, dest)
|
||||
return self._scp(source, dest, timeout)
|
||||
|
||||
def pull(self, source, dest, timeout=30):
|
||||
source = '"{}"@"{}":"{}"'.format(escape_double_quotes(self.username),
|
||||
escape_spaces(escape_double_quotes(self.host)),
|
||||
escape_spaces(escape_double_quotes(source)))
|
||||
source = '{}@{}:{}'.format(self.username, self.host, source)
|
||||
return self._scp(source, dest, timeout)
|
||||
|
||||
def execute(self, command, timeout=None, check_exit_code=True,
|
||||
@@ -240,7 +237,7 @@ class SshConnection(object):
|
||||
command = '{} {} {} {}@{} {}'.format(ssh, keyfile_string, port_string, self.username, self.host, command)
|
||||
logger.debug(command)
|
||||
if self.password:
|
||||
command = _give_password(self.password, command)
|
||||
command, _ = _give_password(self.password, command)
|
||||
return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True)
|
||||
except EOF:
|
||||
raise TargetNotRespondingError('Connection lost.')
|
||||
@@ -310,14 +307,13 @@ class SshConnection(object):
|
||||
# fails to connect to a device if port is explicitly specified using -P
|
||||
# option, even if it is the default port, 22. To minimize this problem,
|
||||
# only specify -P for scp if the port is *not* the default.
|
||||
port_string = '-P {}'.format(self.port) if (self.port and self.port != 22) else ''
|
||||
keyfile_string = '-i {}'.format(self.keyfile) if self.keyfile else ''
|
||||
command = '{} -r {} {} {} {}'.format(scp, keyfile_string, port_string, source, dest)
|
||||
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 ''
|
||||
command = '{} -r {} {} {} {}'.format(scp, keyfile_string, port_string, quote(source), quote(dest))
|
||||
command_redacted = command
|
||||
logger.debug(command)
|
||||
if self.password:
|
||||
command = _give_password(self.password, command)
|
||||
command_redacted = command.replace(self.password, '<redacted>')
|
||||
command, command_redacted = _give_password(self.password, command)
|
||||
try:
|
||||
check_output(command, timeout=timeout, shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
@@ -456,13 +452,12 @@ class Gem5Connection(TelnetConnection):
|
||||
if os.path.basename(dest) != filename:
|
||||
dest = os.path.join(dest, filename)
|
||||
# Back to the gem5 world
|
||||
self._gem5_shell("ls -al {}{}".format(self.gem5_input_dir, filename))
|
||||
self._gem5_shell("cat '{}''{}' > '{}'".format(self.gem5_input_dir,
|
||||
filename,
|
||||
dest))
|
||||
filename = quote(self.gem5_input_dir + filename)
|
||||
self._gem5_shell("ls -al {}".format(filename))
|
||||
self._gem5_shell("cat {} > {}".format(filename, quote(dest)))
|
||||
self._gem5_shell("sync")
|
||||
self._gem5_shell("ls -al {}".format(dest))
|
||||
self._gem5_shell("ls -al {}".format(self.gem5_input_dir))
|
||||
self._gem5_shell("ls -al {}".format(quote(dest)))
|
||||
self._gem5_shell("ls -al {}".format(quote(self.gem5_input_dir)))
|
||||
logger.debug("Push complete.")
|
||||
|
||||
def pull(self, source, dest, timeout=0): #pylint: disable=unused-argument
|
||||
@@ -491,8 +486,8 @@ class Gem5Connection(TelnetConnection):
|
||||
if os.path.isabs(source):
|
||||
if os.path.dirname(source) != self.execute('pwd',
|
||||
check_exit_code=False):
|
||||
self._gem5_shell("cat '{}' > '{}'".format(filename,
|
||||
dest_file))
|
||||
self._gem5_shell("cat {} > {}".format(quote(filename),
|
||||
quote(dest_file)))
|
||||
self._gem5_shell("sync")
|
||||
self._gem5_shell("ls -la {}".format(dest_file))
|
||||
logger.debug('Finished the copy in the simulator')
|
||||
@@ -875,7 +870,8 @@ class Gem5Connection(TelnetConnection):
|
||||
"""
|
||||
Internal method to check if the target has a certain file
|
||||
"""
|
||||
command = 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi'
|
||||
filepath = quote(filepath)
|
||||
command = 'if [ -e {} ]; then echo 1; else echo 0; fi'
|
||||
output = self.execute(command.format(filepath), as_root=self.is_rooted)
|
||||
return boolean(output.strip())
|
||||
|
||||
@@ -931,8 +927,10 @@ class AndroidGem5Connection(Gem5Connection):
|
||||
def _give_password(password, command):
|
||||
if not sshpass:
|
||||
raise HostError('Must have sshpass installed on the host in order to use password-based auth.')
|
||||
pass_string = "sshpass -p '{}' ".format(password)
|
||||
return pass_string + command
|
||||
pass_template = "sshpass -p {} "
|
||||
pass_string = pass_template.format(quote(password))
|
||||
redacted_string = pass_template.format(quote('<redacted>'))
|
||||
return (pass_string + command, redacted_string + command)
|
||||
|
||||
|
||||
def _check_env():
|
||||
|
Reference in New Issue
Block a user