1
0
mirror of https://github.com/ARM-software/devlib.git synced 2025-01-31 02:00:45 +00:00

android: Fix background command PID detection

Close the race between the background command and the detection of its
PID by freezing it while we detect the PID, then resuming it.
This commit is contained in:
Douglas Raillard 2022-04-05 15:44:18 +01:00 committed by Marc Bonnici
parent 14b4e2069b
commit 900531b417

View File

@ -654,33 +654,49 @@ def adb_background_shell(conn, command,
"""Runs the specified command in a subprocess, returning the the Popen object."""
device = conn.device
adb_server = conn.adb_server
busybox = conn.busybox
_check_env()
stdout, stderr, command = redirect_streams(stdout, stderr, command)
if as_root:
command = 'echo {} | su'.format(quote(command))
command = f'{busybox} printf "%s" {quote(command)} | su'
# 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))
def with_uuid(cmd):
# Attach a unique UUID to the command line so it can be looked for
# without any ambiguity with ps
uuid_ = uuid.uuid4().hex
# Unset the var, since not all connection types set it. This will avoid
# anyone depending on that value.
cmd = f'DEVLIB_CMD_UUID={uuid_}; unset DEVLIB_CMD_UUID; {cmd}'
# Ensure we have an sh -c layer so that the UUID will appear on the
# command line parameters of at least one command.
cmd = f'exec {busybox} sh -c {quote(cmd)}'
return (uuid_, cmd)
# Freeze the command with SIGSTOP to avoid racing with PID detection.
command = f"{busybox} kill -STOP $$ && exec {busybox} sh -c {quote(command)}"
command_uuid, command = with_uuid(command)
adb_cmd = get_adb_command(device, 'shell', adb_server)
full_command = '{} {}'.format(adb_cmd, quote(command))
full_command = f'{adb_cmd} {quote(command)}'
logger.debug(full_command)
p = subprocess.Popen(full_command, stdout=stdout, stderr=stderr, stdin=subprocess.PIPE, shell=True)
# Out of band PID lookup, to avoid conflicting needs with stdout redirection
find_pid = '{} ps -A -o pid,args | grep {}'.format(conn.busybox, 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]
grep_cmd = f'{busybox} grep {quote(command_uuid)}'
# Find the PID and release the blocked background command with SIGCONT.
# We get multiple PIDs:
# * One from the grep command itself, but we remove it with another grep command.
# * One for each sh -c layer in the command itself.
#
# For each of the parent layer, we issue SIGCONT as it is harmless and
# avoids having to rely on PID ordering (which could be misleading if PIDs
# got recycled).
find_pid = f'''pids=$({busybox} ps -A -o pid,args | {grep_cmd} | {busybox} grep -v {quote(grep_cmd)} | {busybox} awk '{{print $1}}') && {busybox} printf "%s" "$pids" && {busybox} kill -CONT $pids'''
pids = conn.execute(find_pid, as_root=as_root)
# We choose the highest PID as the "control" PID. It actually does not
# really matter which one we pick, as they are all equivalent sh -c layers.
pid = max(map(int, pids.split()))
return (p, pid)
def adb_kill_server(timeout=30, adb_server=None):