chroot: run: support running multiple commands with one call (MR 2252)

Building the command strings and entering the chroot is a
not-insubstantial amount of overhead. Implement support for running
multiple commands with a new pmb.chroot.rootm() function.

TODO: add alternative for chroot.user and run.root/user.

Signed-off-by: Caleb Connolly <caleb@postmarketos.org>
This commit is contained in:
Caleb Connolly 2024-06-08 05:55:02 +02:00 committed by Oliver Smith
parent 3a74018f89
commit 8a61d67053
No known key found for this signature in database
GPG key ID: 5AE7F5513E0885CB
5 changed files with 26 additions and 17 deletions

View file

@ -29,11 +29,11 @@ def executables_absolute_path():
return ret
def root(cmd: Sequence[PathString], chroot: Chroot=Chroot.native(), working_dir: PurePath=PurePath("/"), output="log",
def rootm(cmds: Sequence[Sequence[PathString]], chroot: Chroot=Chroot.native(), working_dir: PurePath=PurePath("/"), output="log",
output_return=False, check=None, env={},
disable_timeout=False, add_proxy_env_vars=True):
"""
Run a command inside a chroot as root.
Run a list of commands inside a chroot as root.
:param env: dict of environment variables to be passed to the command, e.g.
{"JOBS": "5"}
@ -48,7 +48,7 @@ def root(cmd: Sequence[PathString], chroot: Chroot=Chroot.native(), working_dir:
"""
# Convert any Path objects to their string representation
cmd_str = [os.fspath(x) for x in cmd]
cmd_strs = [[os.fspath(x) for x in cmd] for cmd in cmds]
# Readable log message (without all the escaping)
msg = f"({chroot}) % "
@ -56,7 +56,7 @@ def root(cmd: Sequence[PathString], chroot: Chroot=Chroot.native(), working_dir:
msg += f"{key}={value} "
if working_dir != PurePath("/"):
msg += f"cd {working_dir}; "
msg += " ".join(cmd_str)
msg += "; ".join([" ".join(cmd_str) for cmd_str in cmd_strs])
# Merge env with defaults into env_all
env_all: Env = {"CHARSET": "UTF-8",
@ -78,16 +78,23 @@ def root(cmd: Sequence[PathString], chroot: Chroot=Chroot.native(), working_dir:
# cmd_sudo: ["sudo", "env", "-i", "sh", "-c", "PATH=... /sbin/chroot ..."]
executables = executables_absolute_path()
cmd_chroot = [executables["chroot"], chroot.path, "/bin/sh", "-c",
pmb.helpers.run_core.flat_cmd(cmd_str, Path(working_dir))]
pmb.helpers.run_core.flat_cmd(cmd_strs, Path(working_dir))]
cmd_sudo = pmb.config.sudo([
"env", "-i", executables["sh"], "-c",
pmb.helpers.run_core.flat_cmd(cmd_chroot, env=env_all)]
pmb.helpers.run_core.flat_cmd([cmd_chroot], env=env_all)]
)
return pmb.helpers.run_core.core(msg, cmd_sudo, None, output,
output_return, check, True,
disable_timeout)
def root(cmds: Sequence[PathString], chroot: Chroot=Chroot.native(), working_dir: PurePath=PurePath("/"), output="log",
output_return=False, check=None, env={},
disable_timeout=False, add_proxy_env_vars=True):
return rootm([cmds], chroot, working_dir, output, output_return, check, env,
disable_timeout, add_proxy_env_vars)
def user(cmd, chroot: Chroot=Chroot.native(), working_dir: Path = Path("/"), output="log",
output_return=False, check=None, env={}):
"""
@ -107,7 +114,7 @@ def user(cmd, chroot: Chroot=Chroot.native(), working_dir: Path = Path("/"), out
if "HOME" not in env:
env["HOME"] = "/home/pmos"
flat_cmd = pmb.helpers.run_core.flat_cmd(cmd, env=env)
flat_cmd = pmb.helpers.run_core.flat_cmd([cmd], env=env)
cmd = ["busybox", "su", "pmos", "-c", flat_cmd]
return pmb.chroot.root(cmd, chroot, working_dir, output,
output_return, check, {},

View file

@ -41,7 +41,7 @@ def _create_command_with_progress(command, fifo):
"""
flags = ["--no-progress", "--progress-fd", "3"]
command_full = [command[0]] + flags + command[1:]
command_flat = pmb.helpers.run_core.flat_cmd(command_full)
command_flat = pmb.helpers.run_core.flat_cmd([command_full])
command_flat = f"exec 3>{fifo}; {command_flat}"
return ["sh", "-c", command_flat]

View file

@ -32,7 +32,7 @@ def user(cmd: Sequence[PathString], working_dir: Optional[Path] = None, output:
env = env.copy()
pmb.helpers.run_core.add_proxy_env_vars(env)
if env:
cmd_parts = ["sh", "-c", pmb.helpers.run_core.flat_cmd(cmd_parts, env=env)]
cmd_parts = ["sh", "-c", pmb.helpers.run_core.flat_cmd([cmd_parts], env=env)]
return pmb.helpers.run_core.core(msg, cmd_parts, working_dir, output,
output_return, check, sudo)
@ -60,7 +60,7 @@ def root(cmd: Sequence[PathString], working_dir=None, output="log", output_retur
pmb.helpers.run_core.add_proxy_env_vars(env)
if env:
cmd = ["sh", "-c", pmb.helpers.run_core.flat_cmd(cmd, env=env)]
cmd = ["sh", "-c", pmb.helpers.run_core.flat_cmd([cmd], env=env)]
cmd = pmb.config.sudo(cmd)
return user(cmd, working_dir, output, output_return, check, env,

View file

@ -20,10 +20,10 @@ import pmb.helpers.run
called by core(). """
def flat_cmd(cmd: Sequence[PathString], working_dir: Optional[Path]=None, env: Env={}):
def flat_cmd(cmds: Sequence[Sequence[PathString]], working_dir: Optional[Path]=None, env: Env={}):
"""Convert a shell command passed as list into a flat shell string with proper escaping.
:param cmd: command as list, e.g. ["echo", "string with spaces"]
:param cmds: list of commands as list, e.g. ["echo", "string with spaces"]
:param working_dir: when set, prepend "cd ...;" to execute the command
in the given working directory
:param env: dict of environment variables to be passed to the command, e.g.
@ -36,8 +36,10 @@ def flat_cmd(cmd: Sequence[PathString], working_dir: Optional[Path]=None, env: E
escaped = []
for key, value in env.items():
escaped.append(key + "=" + shlex.quote(os.fspath(value)))
for cmd in cmds:
for i in range(len(cmd)):
escaped.append(shlex.quote(os.fspath(cmd[i])))
escaped.append(";")
# Prepend working dir
ret = " ".join(escaped)

View file

@ -34,7 +34,7 @@ def scp_abuild_key(args: PmbArgs, user: str, host: str, port: str):
keyname = os.path.join("/tmp", os.path.basename(key))
remote_cmd_l: List[PathString] = ['sudo', '-p', pmb.config.sideload_sudo_prompt,
'-S', 'mv', '-n', keyname, "/etc/apk/keys/"]
remote_cmd = pmb.helpers.run_core.flat_cmd(remote_cmd_l)
remote_cmd = pmb.helpers.run_core.flat_cmd([remote_cmd_l])
command = ['ssh', '-t', '-p', port, f'{user}@{host}', remote_cmd]
pmb.helpers.run.user(command, output="tui")
@ -72,8 +72,8 @@ def ssh_install_apks(args: PmbArgs, user, host, port, paths):
logging.info(f"Installing packages at {user}@{host}")
add_cmd = ['sudo', '-p', pmb.config.sideload_sudo_prompt,
'-S', 'apk', '--wait', '30', 'add'] + remote_paths
add_cmd = pmb.helpers.run_core.flat_cmd(add_cmd)
clean_cmd = pmb.helpers.run_core.flat_cmd(['rm'] + remote_paths)
add_cmd = pmb.helpers.run_core.flat_cmd([add_cmd])
clean_cmd = pmb.helpers.run_core.flat_cmd([['rm'] + remote_paths])
add_cmd_complete = shlex.quote(f"{add_cmd}; rc=$?; {clean_cmd}; exit $rc")
# Run apk command in a subshell in case the foreign device has a non-POSIX shell.
command = ['ssh', '-t', '-p', port, f'{user}@{host}',