blob: 421d6f81b1bf65ca3ed23008639dd3bfb67e3b65 [file] [log] [blame]
# Copyright 2012 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Manage SDK chroots.
This script is used for manipulating local chroot environments; creating,
deleting, downloading, etc. If given --enter (or no args), it defaults
to an interactive bash shell within the chroot.
If given args those are passed to the chroot environment, and executed.
"""
import argparse
import functools
import glob
import logging
import multiprocessing
import os
from pathlib import Path
import pwd
import shlex
import sys
from typing import Iterable, List, Optional, Tuple
from chromite.cbuildbot import cbuildbot_alerts
from chromite.lib import chromite_config
from chromite.lib import chroot_lib
from chromite.lib import commandline
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import cros_sdk_lib
from chromite.lib import goma_lib
from chromite.lib import locking
from chromite.lib import namespaces
from chromite.lib import osutils
from chromite.lib import path_util
from chromite.lib import process_util
from chromite.utils import xdg_util
# Which compression algos the SDK tarball uses. We've used xz since 2012.
COMPRESSION_PREFERENCE = ("xz",)
# Proxy simulator configuration.
PROXY_HOST_IP = "192.168.240.1"
PROXY_PORT = 8080
PROXY_GUEST_IP = "192.168.240.2"
PROXY_NETMASK = 30
PROXY_VETH_PREFIX = "veth"
PROXY_CONNECT_PORTS = (80, 443, 9418)
PROXY_APACHE_FALLBACK_USERS = ("www-data", "apache", "nobody")
PROXY_APACHE_MPMS = ("event", "worker", "prefork")
PROXY_APACHE_FALLBACK_PATH = ":".join(
"/usr/lib/apache2/mpm-%s" % mpm for mpm in PROXY_APACHE_MPMS
)
PROXY_APACHE_MODULE_GLOBS = ("/usr/lib*/apache2/modules", "/usr/lib*/apache2")
# We need these tools to run. Very common tools (tar,..) are omitted.
NEEDED_TOOLS = ("curl", "xz")
# Tools needed for --proxy-sim only.
PROXY_NEEDED_TOOLS = ("ip",)
def get_sdk_tarball_urls(
version: str,
bucket: Optional[str] = None,
) -> str:
"""Return URL candidates to download an SDK tarball.
Args:
version: The SDK version to download, such as '1970.01.01.314159'.
bucket: The Google Storage bucket containing the SDK tarball, if not the
standard SDK bucket.
"""
extension = {"xz": "tar.xz"}
return [
cros_sdk_lib.get_sdk_tarball_url(
version,
file_extension=extension[compressor],
override_bucket=bucket,
)
for compressor in COMPRESSION_PREFERENCE
]
def log_path_holders(path: Path, ignore_pids: Iterable[int] = ()) -> None:
"""Log details about processes holding references to `path`."""
result = cros_build_lib.dbg_run(
["lsof", "-t", "-n", "-f", "--", path],
capture_output=True,
check=False,
encoding="utf-8",
)
if result.returncode:
return
pids = [x for x in result.stdout.split() if x not in ignore_pids]
if not pids:
return
result = cros_build_lib.dbg_run(
["ps"] + pids,
capture_output=True,
check=False,
encoding="utf-8",
)
if not result.returncode:
logging.warning("Active processes:\n%s", result.stdout.rstrip())
def _SudoCommand():
"""Get the 'sudo' command, along with all needed environment variables."""
# Pass in the ENVIRONMENT_ALLOWLIST and ENV_PASSTHRU variables so that
# scripts in the chroot know what variables to pass through.
cmd = ["sudo"]
for key in constants.CHROOT_ENVIRONMENT_ALLOWLIST + constants.ENV_PASSTHRU:
value = os.environ.get(key)
if value is not None:
cmd += ["%s=%s" % (key, value)]
# We keep PATH not for the chroot but for the re-exec & for programs we
# might run before we chroot into the SDK. The process that enters the SDK
# itself will take care of initializing PATH to the right value then. But
# we can't override the system's default PATH for root as that will hide
# /sbin.
cmd += ["CHROMEOS_SUDO_PATH=%s" % os.environ.get("PATH", "")]
# Pass along current rlimit settings so we can restore them.
cmd += [f"CHROMEOS_SUDO_RLIMITS={cros_sdk_lib.ChrootEnteror.get_rlimits()}"]
return cmd
def _ReportMissing(missing) -> None:
"""Report missing utilities, then exit.
Args:
missing: List of missing utilities, as returned by
osutils.FindMissingBinaries. If non-empty, will not return.
"""
if missing:
raise SystemExit(
"The tool(s) %s were not found.\n"
"Please install the appropriate package in your host.\n"
"Example(ubuntu):\n"
" sudo apt-get install <packagename>" % ", ".join(missing)
)
def _ProxySimSetup(options) -> None:
"""Set up proxy simulator, and return only in the child environment.
TODO: Ideally, this should support multiple concurrent invocations of
cros_sdk --proxy-sim; currently, such invocations will conflict with each
other due to the veth device names and IP addresses. Either this code would
need to generate fresh, unused names for all of these before forking, or it
would need to support multiple concurrent cros_sdk invocations sharing one
proxy and allowing it to exit when unused (without counting on any local
service-management infrastructure on the host).
"""
may_need_mpm = False
apache_bin = osutils.Which("apache2")
if apache_bin is None:
apache_bin = osutils.Which("apache2", PROXY_APACHE_FALLBACK_PATH)
if apache_bin is None:
_ReportMissing(("apache2",))
else:
may_need_mpm = True
# Module names and .so names included for ease of grepping.
apache_modules = [
("proxy_module", "mod_proxy.so"),
("proxy_connect_module", "mod_proxy_connect.so"),
("proxy_http_module", "mod_proxy_http.so"),
("proxy_ftp_module", "mod_proxy_ftp.so"),
]
# Find the apache module directory and make sure it has the modules we need.
module_dirs = {}
for g in PROXY_APACHE_MODULE_GLOBS:
for _, so in apache_modules:
for f in glob.glob(os.path.join(g, so)):
module_dirs.setdefault(os.path.dirname(f), []).append(so)
for apache_module_path, modules_found in module_dirs.items():
if len(modules_found) == len(apache_modules):
break
else:
# Appease cros lint, which doesn't understand that this else block will
# not fall through to the subsequent code which relies on
# apache_module_path.
apache_module_path = None
raise SystemExit(
"Could not find apache module path containing all required "
"modules: %s" % ", ".join(so for mod, so in apache_modules)
)
def check_add_module(name):
so = "mod_%s.so" % name
if os.access(os.path.join(apache_module_path, so), os.F_OK):
mod = "%s_module" % name
apache_modules.append((mod, so))
return True
return False
check_add_module("authz_core")
if may_need_mpm:
for mpm in PROXY_APACHE_MPMS:
if check_add_module("mpm_%s" % mpm):
break
veth_host = "%s-host" % PROXY_VETH_PREFIX
veth_guest = "%s-guest" % PROXY_VETH_PREFIX
# Set up locks to sync the net namespace setup. We need the child to create
# the net ns first, and then have the parent assign the guest end of the
# veth interface to the child's new network namespace & bring up the proxy.
# Only then can the child move forward and rely on the network being up.
ns_create_lock = locking.PipeLock()
ns_setup_lock = locking.PipeLock()
pid = os.fork()
if not pid:
# Create our new isolated net namespace.
namespaces.Unshare(namespaces.CLONE_NEWNET)
# Signal the parent the ns is ready to be configured.
ns_create_lock.Post()
del ns_create_lock
# Wait for the parent to finish setting up the ns/proxy.
ns_setup_lock.Wait()
del ns_setup_lock
# Set up child side of the network.
commands = (
("ip", "link", "set", "up", "lo"),
(
"ip",
"address",
"add",
"%s/%u" % (PROXY_GUEST_IP, PROXY_NETMASK),
"dev",
veth_guest,
),
("ip", "link", "set", veth_guest, "up"),
)
try:
for cmd in commands:
cros_build_lib.dbg_run(cmd)
except cros_build_lib.RunCommandError as e:
cros_build_lib.Die("Proxy setup failed!\n%s", e)
proxy_url = "http://%s:%u" % (PROXY_HOST_IP, PROXY_PORT)
for proto in ("http", "https", "ftp"):
os.environ[proto + "_proxy"] = proxy_url
for v in ("all_proxy", "RSYNC_PROXY", "no_proxy"):
os.environ.pop(v, None)
return
# Set up parent side of the network.
uid = int(os.environ.get("SUDO_UID", "0"))
gid = int(os.environ.get("SUDO_GID", "0"))
if uid == 0 or gid == 0:
for username in PROXY_APACHE_FALLBACK_USERS:
try:
pwnam = pwd.getpwnam(username)
uid, gid = pwnam.pw_uid, pwnam.pw_gid
break
except KeyError:
continue
if uid == 0 or gid == 0:
raise SystemExit("Could not find a non-root user to run Apache as")
chroot_parent, chroot_base = os.path.split(options.chroot)
pid_file = os.path.join(chroot_parent, ".%s-apache-proxy.pid" % chroot_base)
log_file = os.path.join(chroot_parent, ".%s-apache-proxy.log" % chroot_base)
# Wait for the child to create the net ns.
ns_create_lock.Wait()
del ns_create_lock
apache_directives = [
"User #%u" % uid,
"Group #%u" % gid,
"PidFile %s" % pid_file,
"ErrorLog %s" % log_file,
"Listen %s:%u" % (PROXY_HOST_IP, PROXY_PORT),
"ServerName %s" % PROXY_HOST_IP,
"ProxyRequests On",
"AllowCONNECT %s" % " ".join(str(x) for x in PROXY_CONNECT_PORTS),
] + [
"LoadModule %s %s" % (mod, os.path.join(apache_module_path, so))
for (mod, so) in apache_modules
]
commands = (
(
"ip",
"link",
"add",
"name",
veth_host,
"type",
"veth",
"peer",
"name",
veth_guest,
),
(
"ip",
"address",
"add",
"%s/%u" % (PROXY_HOST_IP, PROXY_NETMASK),
"dev",
veth_host,
),
("ip", "link", "set", veth_host, "up"),
(
[apache_bin, "-f", "/dev/null"]
+ [arg for d in apache_directives for arg in ("-C", d)]
),
("ip", "link", "set", veth_guest, "netns", str(pid)),
)
cmd = None # Make cros lint happy.
try:
for cmd in commands:
cros_build_lib.dbg_run(cmd)
except cros_build_lib.RunCommandError as e:
# Clean up existing interfaces, if any.
cmd_cleanup = ("ip", "link", "del", veth_host)
try:
cros_build_lib.run(cmd_cleanup, print_cmd=False)
except cros_build_lib.RunCommandError:
logging.error("running %r failed", cmd_cleanup)
cros_build_lib.Die("Proxy network setup failed!\n%s", e)
# Signal the child that the net ns/proxy is fully configured now.
ns_setup_lock.Post()
del ns_setup_lock
process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
def _BuildReExecCommand(argv, opts) -> List[str]:
"""Generate new command for self-reexec."""
# Make sure to preserve the active Python executable in case the version
# we're running as is not the default one found via the (new) $PATH.
cmd = _SudoCommand() + ["--"]
if opts.strace:
cmd += ["strace"] + shlex.split(opts.strace_arguments) + ["--"]
return cmd + [sys.executable] + argv
def _ReExecuteIfNeeded(argv, opts) -> None:
"""Re-execute cros_sdk as root.
Also unshare the mount namespace so as to ensure that processes outside
the chroot can't mess with our mounts.
"""
if osutils.IsNonRootUser():
cmd = _BuildReExecCommand(argv, opts)
logging.debug(
"Reexecing self via sudo:\n%s", cros_build_lib.CmdToStr(cmd)
)
os.execvp(cmd[0], cmd)
def CreateParser(
version_conf: cros_sdk_lib.SdkVersionConfig,
) -> Tuple[argparse.ArgumentParser, argparse._ArgumentGroup]:
"""Generate and return the parser with all the options."""
usage = (
"usage: %(prog)s [options] "
"[VAR1=val1 ... VAR2=val2] [--] [command [args]]"
)
parser = commandline.ArgumentParser(
usage=usage, description=__doc__, caching=True
)
# Global options.
parser.add_argument(
"--chroot",
dest="chroot",
default=None,
type=Path,
help=f"SDK chroot dir name [.../{constants.DEFAULT_CHROOT_DIR}]",
)
parser.add_argument(
"--out-dir",
metavar="DIR",
default=None,
type=Path,
help=(
"Use DIR for build state and output files "
f"[.../{constants.DEFAULT_OUT_DIR}]"
),
)
parser.add_argument(
"--nouse-image",
dest="use_image",
action="store_false",
default=False,
deprecated="--[no]use-image is no longer supported (b/266878468).",
help=argparse.SUPPRESS,
)
parser.add_argument(
"--use-image",
dest="use_image",
action="store_true",
default=False,
deprecated="--[no]use-image is no longer supported (b/266878468).",
help=argparse.SUPPRESS,
)
parser.add_argument(
"--chrome-root",
"--chrome_root",
type="str_path",
help="Mount this chrome root into the SDK chroot",
)
parser.add_argument(
"--chrome_root_mount",
type="str_path",
help="Mount chrome into this path inside SDK chroot",
)
parser.add_argument(
"-u",
"--url",
dest="sdk_url",
help="Use sdk tarball located at this url. Use file:// "
"for local files.",
)
parser.add_argument(
"--sdk-version",
default=version_conf.get_default_version(),
help="Use this sdk version.",
)
parser.add_argument(
"--goma-dir",
"--goma_dir",
type="dir_exists",
help="Goma installed directory to mount into the chroot.",
)
parser.add_bool_argument(
"--delete-out-dir",
default=None,
enabled_desc="Delete the SDK build state along with the chroot. "
"Applies to --delete, --replace, or --update.",
disabled_desc="Don't delete the SDK build state along with the chroot. "
"Applies to --delete, --replace, or --update. Default for --update.",
)
parser.add_argument(
"--force",
action="store_true",
default=False,
help="Force delete of the current SDK chroot even if "
"obtaining the write lock fails. Applies only to --delete or "
"--replace.",
)
parser.add_bool_argument(
"--read-only",
default=None,
enabled_desc="Mount the SDK read-only.",
disabled_desc="Do not mount the SDK read-only. "
"This is default, but see also --read-only-sticky.",
)
parser.add_bool_argument(
"--read-only-sticky",
default=False,
enabled_desc="Remember the --[no-]read-only setting for future runs.",
disabled_desc="Leave --[no-]read-only stickiness alone.",
)
# Use type=str instead of type='path' to prevent the given path from being
# transferred to absolute path automatically.
parser.add_argument(
"--working-dir",
type=Path,
help="Run the command in specific working directory in "
"chroot. If the given directory is a relative "
"path, this program will transfer the path to "
"the corresponding one inside chroot.",
)
parser.add_argument("commands", nargs=argparse.REMAINDER)
# Commands.
group = parser.add_argument_group("Commands")
group.add_argument(
"--enter",
action="store_true",
default=False,
help="Enter the SDK chroot. Implies --create.",
)
group.add_argument(
"--create",
action="store_true",
default=False,
help="Create the chroot only if it does not already exist. Downloads "
"the SDK only if needed, even if --download explicitly passed.",
)
group.add_argument(
"-r",
"--replace",
action="store_true",
default=False,
help="Replace an existing SDK chroot. Basically an alias "
"for --delete --create.",
)
group.add_argument(
"--delete",
action="store_true",
default=False,
help="Delete the current SDK chroot and build state if they exist.",
)
group.add_argument(
"--unmount",
action="store_true",
default=False,
deprecated="loopback-image (--use-image) is no longer supported "
"(b/266878468). If needed, consider `cros unmount /path/to/chroot`.",
help=argparse.SUPPRESS,
)
group.add_bool_argument(
"--update",
default=None,
enabled_desc="Update the SDK upon entry",
disabled_desc="Do not update the SDK upon entry",
)
group.add_argument(
"--download",
action="store_true",
default=False,
help="Download the sdk.",
)
commands = group
# Namespace options.
group = parser.add_argument_group("Namespaces")
group.add_argument(
"--proxy-sim",
action="store_true",
default=False,
help="Simulate a restrictive network requiring an outbound" " proxy.",
)
for ns, default in (("pid", True), ("net", None)):
group.add_argument(
f"--ns-{ns}",
default=default,
action="store_true",
help=f"Create a new {ns} namespace.",
)
group.add_argument(
f"--no-ns-{ns}",
dest=f"ns_{ns}",
action="store_false",
help=f"Do not create a new {ns} namespace.",
)
# Debug options.
group = parser.debug_group
group.add_argument(
"--strace",
action="store_true",
help="Run cros_sdk through strace after re-exec via sudo",
)
group.add_argument(
"--strace-arguments",
default="",
help="Extra strace options (shell quoting permitted)",
)
# Internal options.
group = parser.add_argument_group(
"Internal Chromium OS Build Team Options",
"Caution: these are for meant for the Chromium OS build team only",
)
group.add_argument(
"--buildbot-log-version",
default=False,
action="store_true",
help="Log SDK version for buildbot consumption",
)
return parser, commands
def _FinalizeOptions(
parser: argparse.ArgumentParser,
options: argparse.Namespace,
commands: argparse._ArgumentGroup,
) -> None:
"""Perform any options tweaking, and prevent further modification."""
# Expand out the aliases...
if options.replace:
options.delete = options.create = True
# If a command is not given, default to enter.
# pylint: disable=protected-access
# This _group_actions access sucks, but upstream decided to not include an
# alternative to optparse's option_list, and this is what they recommend.
options.enter |= not any(
getattr(options, x.dest) for x in commands._group_actions
)
# pylint: enable=protected-access
options.enter |= bool(options.commands)
if options.delete and not options.create and options.enter:
parser.error(
"Trying to enter the chroot when --delete "
"was specified makes no sense."
)
# --update behavior should be disabled when --delete is specified. Further
# options like --create might also be in the command, but the --update flag
# won't actually need be enabled in this case, as we'll build off the latest
# tarball anyway.
if options.delete:
options.update = False
# We resolve the default for --update after considering --enter default, as
# "cros_sdk" should enter the SDK, whereas "cros_sdk --update" should update
# if required, but not actually enter.
if options.update is None:
options.update = True
if options.force and not options.delete:
parser.error("Specifying --force without --delete does not make sense.")
# Resolve default output directories.
chroot_path = (
constants.DEFAULT_CHROOT_PATH
if options.chroot is None
else options.chroot
)
out_path = (
constants.DEFAULT_OUT_PATH
if options.out_dir is None
else options.out_dir
)
checkout = path_util.DetermineCheckout()
if checkout.type == path_util.CheckoutType.CITC:
# If running in a citc client, set default output paths to ~/.
workspace_id_path = (
Path(checkout.root).parent / ".citc" / "workspace_id"
)
workspace_id = workspace_id_path.read_text(encoding="utf-8")
workspace_path = (
path_util.get_global_cog_base_dir() / "workspaces" / workspace_id
)
workspace_path.mkdir(parents=True, exist_ok=True)
chroot_path = workspace_path / constants.DEFAULT_CHROOT_DIR
out_path = workspace_path / constants.DEFAULT_OUT_DIR
options.chroot = osutils.ExpandPath(chroot_path)
options.out_dir = osutils.ExpandPath(out_path)
logging.debug("Configuring chroot to %s", options.chroot)
logging.debug("Configuring output dir to %s", options.out_dir)
chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
# Finally, flip create if necessary.
if options.enter:
options.create |= not chroot_exists
# Make sure we will download if we plan to create.
options.download |= options.create
if options.read_only is None and options.read_only_sticky:
parser.error(
"Specifying --read-only-sticky without --read-only or "
"--no-read-only does not make sense."
)
chromite_config.initialize()
osutils.SafeMakedirsNonRoot(xdg_util.CACHE_HOME)
ro_cfg = chromite_config.SDK_READONLY_STICKY_CONFIG
if options.read_only is None:
# Defer to sticky configuration file only if --read-only/--no-read-only
# were not provided.
options.read_only = ro_cfg.exists()
# Resolve tri-state --delete-out-dir to a boolean. This argument is
# default-on for --delete/--replace, but default-off for --update.
if options.delete_out_dir is None:
options.delete_out_dir = not options.update
options.Freeze()
if options.read_only_sticky:
# Notify the user when toggling stickiness.
if options.read_only:
if not ro_cfg.exists():
logging.warning("Making cros_sdk --read-only sticky")
ro_cfg.touch()
else:
if ro_cfg.exists():
logging.warning("Making cros_sdk --no-read-only sticky")
ro_cfg.unlink()
def main(argv) -> None:
# Turn on strict sudo checks.
cros_build_lib.STRICT_SUDO = True
try:
version_conf = cros_sdk_lib.SdkVersionConfig.load()
except FileNotFoundError:
cros_build_lib.Die(
"No SDK version was found. "
"Are you in a Chromium source tree instead of ChromiumOS?\n\n"
"Please change to a directory inside your ChromiumOS source tree\n"
"and retry. If you need to setup a ChromiumOS source tree, see\n"
" https://dev.chromium.org/chromium-os/developer-guide"
)
parser, commands = CreateParser(version_conf)
options = parser.parse_args(argv)
# Some basic checks first, before we ask for sudo credentials.
cros_build_lib.AssertOutsideChroot()
host = os.uname()[4]
if host != "x86_64":
cros_build_lib.Die(
"cros_sdk is currently only supported on x86_64; you're running"
" %s. Please find a x86_64 machine." % (host,)
)
# Merge the outside PATH setting if we re-execed ourselves.
if "CHROMEOS_SUDO_PATH" in os.environ:
os.environ["PATH"] = "%s:%s" % (
os.environ.pop("CHROMEOS_SUDO_PATH"),
os.environ["PATH"],
)
_ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
if options.proxy_sim:
_ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
_ReExecuteIfNeeded([sys.argv[0]] + argv, options)
# |options| cannot be modified after this.
_FinalizeOptions(parser, options, commands)
goma = (
goma_lib.Goma(
options.goma_dir, chroot_dir=options.chroot, out_dir=options.out_dir
)
if options.goma_dir
else None
)
chroot = chroot_lib.Chroot(
path=options.chroot,
out_path=options.out_dir,
cache_dir=options.cache_dir,
chrome_root=options.chrome_root,
goma=goma,
)
if not chroot.path_is_valid():
if options.force:
logging.warning("Proceeding with an invalid chroot due to --force.")
else:
cros_build_lib.Die(
"Your chroot directory (%s) doesn't look like a chroot, nor a "
"safe place to make one. If you really want to trash this "
"directory, pass --force.",
chroot.path,
)
# Most important chroot state bits migrated to the out directory via logic
# landed on 2023-05-08. If the user has not entered recently, don't let
# them unexpectedly loose state bits (since the migration logic is gone).
# TODO(2025-01-01): Delete this check.
if not options.delete:
chroot_version = cros_sdk_lib.GetChrootVersion(chroot.path)
if chroot_version and chroot_version <= 223:
cros_build_lib.Die(
"Your SDK is too old to be entered! Please copy any state you "
"need out of your chroot, and run `cros_sdk --replace`. "
"(chroot_version=%s)",
chroot_version,
)
if options.buildbot_log_version:
cbuildbot_alerts.PrintBuildbotStepText(options.sdk_version)
replace_for_update = False
if options.update:
replace_for_update = options.sdk_version != chroot.tarball_version
if replace_for_update:
logging.notice(
"Replacing the chroot for version update %s -> %s",
chroot.tarball_version,
options.sdk_version,
)
else:
logging.debug("--update: Replace not required")
# Anything that needs to manipulate the main chroot mount or communicate
# with LVM needs to be done here before we enter the new namespaces.
# Delete is handled in a background process so we can download the
# SDK tarball in parallel. Eventually, we may be able to fully
# background-off deletion and not block on it anywhere.
delete_proc: Optional[multiprocessing.Process] = None
if replace_for_update or options.delete:
delete_proc = multiprocessing.Process(
target=functools.partial(
chroot.delete,
delete_out_dir=options.delete_out_dir,
force=options.force,
),
)
delete_proc.start()
# Based on selections, determine the tarball to fetch.
urls = []
if replace_for_update or options.download:
if options.sdk_url:
urls = [options.sdk_url]
else:
urls = get_sdk_tarball_urls(
options.sdk_version, bucket=version_conf.bucket
)
sdk_cache = Path(chroot.cache_dir) / "sdks"
if options.download or options.create or replace_for_update:
sdk_tarball = cros_sdk_lib.fetch_remote_tarballs(sdk_cache, urls)
if delete_proc:
delete_proc.join(timeout=15)
if delete_proc.is_alive():
logging.warning(
"Waiting for SDK deletion. If you have SDK shells open, "
"please close them."
)
log_path_holders(chroot.lock_path, {str(delete_proc.pid)})
delete_proc.join()
if delete_proc.exitcode != 0:
cros_build_lib.Die(
"SDK deletion failed (exit code=%s)", delete_proc.exitcode
)
# Enter a new set of namespaces. Everything after here cannot directly
# affect the hosts's mounts or alter LVM volumes.
namespaces.SimpleUnshare(net=options.ns_net, pid=options.ns_pid)
with chroot.lock() as lock:
if options.proxy_sim:
_ProxySimSetup(options)
distfiles_cache = os.path.join(chroot.cache_dir, "distfiles")
osutils.SafeMakedirsNonRoot(chroot.cache_dir)
osutils.SafeMakedirsNonRoot(distfiles_cache)
osutils.SafeMakedirsNonRoot(options.out_dir)
# Create here (in addition to cros_sdk_lib.MountChrootPaths()) because
# some usages want to create tmp files here even before we've fully
# mounted the SDK.
osutils.SafeMakedirsNonRoot(options.out_dir / "tmp", mode=0o1777)
mounted = False
if replace_for_update or options.create:
lock.write_lock()
# Recheck if the chroot is set up here before creating to make sure
# we account for whatever the various delete/cleanup steps above
# have done.
if cros_sdk_lib.IsChrootReady(chroot.path):
logging.debug("Chroot already exists. Skipping creation.")
else:
cros_sdk_lib.CreateChroot(
chroot,
Path(sdk_tarball),
)
mounted = True
if options.enter:
lock.read_lock()
if not mounted:
cros_sdk_lib.MountChrootPaths(chroot)
ret = cros_sdk_lib.EnterChroot(
chroot,
chrome_root_mount=options.chrome_root_mount,
cwd=options.working_dir,
cmd=options.commands,
read_only=options.read_only,
)
sys.exit(ret.returncode)