blob: 056c4f945e608f0e98443b81ff648fb582a2ebaa [file] [log] [blame]
Mike Frysingerf1ba7ad2022-09-12 05:42:57 -04001# Copyright 2012 The ChromiumOS Authors
Brian Harringb938c782012-02-29 15:14:38 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Manoj Gupta7fad04d2019-06-14 20:12:25 -07004
Mike Frysinger2f95cfc2015-06-04 04:00:26 -04005"""Manage SDK chroots.
6
7This script is used for manipulating local chroot environments; creating,
8deleting, downloading, etc. If given --enter (or no args), it defaults
9to an interactive bash shell within the chroot.
10
11If given args those are passed to the chroot environment, and executed.
12"""
Brian Harringb938c782012-02-29 15:14:38 -080013
Mike Frysinger2f95cfc2015-06-04 04:00:26 -040014import argparse
Josh Triplett472a4182013-03-08 11:48:57 -080015import glob
Chris McDonaldb55b7032021-06-17 16:41:32 -060016import logging
Brian Harringb938c782012-02-29 15:14:38 -080017import os
Mike Frysinger23b5cf52021-06-16 23:18:00 -040018from pathlib import Path
Josh Triplett472a4182013-03-08 11:48:57 -080019import pwd
Brian Norrisd37e2f72016-08-22 16:09:24 -070020import re
Mike Frysinger5f5c70b2022-07-19 12:55:21 -040021import shlex
David James56e6c2c2012-10-24 23:54:41 -070022import sys
Mike Frysingerec32bea2023-01-27 01:20:48 -050023from typing import List
Mike Frysingere852b072021-05-21 12:39:03 -040024import urllib.parse
Brian Harringb938c782012-02-29 15:14:38 -080025
Chris McDonaldb55b7032021-06-17 16:41:32 -060026from chromite.cbuildbot import cbuildbot_alerts
Mike Frysingerec32bea2023-01-27 01:20:48 -050027from chromite.lib import chroot_lib
Brian Harringb6cf9142012-09-01 20:43:17 -070028from chromite.lib import commandline
Chris McDonaldb55b7032021-06-17 16:41:32 -060029from chromite.lib import constants
Brian Harringb938c782012-02-29 15:14:38 -080030from chromite.lib import cros_build_lib
Benjamin Gordon74645232018-05-04 17:40:42 -060031from chromite.lib import cros_sdk_lib
Mike Frysinger9a5124e2023-01-26 17:16:44 -050032from chromite.lib import goma_lib
Brian Harringb938c782012-02-29 15:14:38 -080033from chromite.lib import locking
Josh Triplette759b232013-03-08 13:03:43 -080034from chromite.lib import namespaces
Brian Harringae0a5322012-09-15 01:46:51 -070035from chromite.lib import osutils
Mike Frysingere2d8f0d2014-11-01 13:09:26 -040036from chromite.lib import process_util
Mike Frysinger253c8392023-01-27 01:12:21 -050037from chromite.lib import remoteexec_util
David Jamesc93e6a4d2014-01-13 11:37:36 -080038from chromite.lib import retry_util
Michael Mortensenbf296fb2020-06-18 18:21:54 -060039from chromite.lib import timeout_util
Mike Frysinger8e727a32013-01-16 16:57:53 -050040from chromite.lib import toolchain
Mike Frysingere652ba12019-09-08 00:57:43 -040041from chromite.utils import key_value_store
42
Brian Harringb938c782012-02-29 15:14:38 -080043
Mike Frysingerf744d032022-05-07 20:38:39 -040044# Which compression algos the SDK tarball uses. We've used xz since 2012.
Alex Klein1699fab2022-09-08 08:46:06 -060045COMPRESSION_PREFERENCE = ("xz",)
Zdenek Behanfd0efe42012-04-13 04:36:40 +020046
Josh Triplett472a4182013-03-08 11:48:57 -080047# Proxy simulator configuration.
Alex Klein1699fab2022-09-08 08:46:06 -060048PROXY_HOST_IP = "192.168.240.1"
Josh Triplett472a4182013-03-08 11:48:57 -080049PROXY_PORT = 8080
Alex Klein1699fab2022-09-08 08:46:06 -060050PROXY_GUEST_IP = "192.168.240.2"
Josh Triplett472a4182013-03-08 11:48:57 -080051PROXY_NETMASK = 30
Alex Klein1699fab2022-09-08 08:46:06 -060052PROXY_VETH_PREFIX = "veth"
Josh Triplett472a4182013-03-08 11:48:57 -080053PROXY_CONNECT_PORTS = (80, 443, 9418)
Alex Klein1699fab2022-09-08 08:46:06 -060054PROXY_APACHE_FALLBACK_USERS = ("www-data", "apache", "nobody")
55PROXY_APACHE_MPMS = ("event", "worker", "prefork")
56PROXY_APACHE_FALLBACK_PATH = ":".join(
57 "/usr/lib/apache2/mpm-%s" % mpm for mpm in PROXY_APACHE_MPMS
58)
59PROXY_APACHE_MODULE_GLOBS = ("/usr/lib*/apache2/modules", "/usr/lib*/apache2")
Josh Triplett472a4182013-03-08 11:48:57 -080060
Josh Triplett9a495f62013-03-15 18:06:55 -070061# We need these tools to run. Very common tools (tar,..) are omitted.
Alex Klein1699fab2022-09-08 08:46:06 -060062NEEDED_TOOLS = ("curl", "xz")
Brian Harringb938c782012-02-29 15:14:38 -080063
Josh Triplett472a4182013-03-08 11:48:57 -080064# Tools needed for --proxy-sim only.
Alex Klein1699fab2022-09-08 08:46:06 -060065PROXY_NEEDED_TOOLS = ("ip",)
Brian Harringb938c782012-02-29 15:14:38 -080066
Mike Frysingercc838832014-05-24 13:10:30 -040067
Brian Harring1790ac42012-09-23 08:53:33 -070068def GetArchStageTarballs(version):
Alex Klein1699fab2022-09-08 08:46:06 -060069 """Returns the URL for a given arch/version"""
70 extension = {"xz": "tar.xz"}
71 return [
72 toolchain.GetSdkURL(
73 suburl="cros-sdk-%s.%s" % (version, extension[compressor])
74 )
75 for compressor in COMPRESSION_PREFERENCE
76 ]
Brian Harring1790ac42012-09-23 08:53:33 -070077
78
Mike Frysingerf744d032022-05-07 20:38:39 -040079def FetchRemoteTarballs(storage_dir, urls):
Alex Klein1699fab2022-09-08 08:46:06 -060080 """Fetches a tarball given by url, and place it in |storage_dir|.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020081
Alex Klein1699fab2022-09-08 08:46:06 -060082 Args:
Alex Kleinef517832023-01-13 12:06:51 -070083 storage_dir: Path where to save the tarball.
84 urls: List of URLs to try to download. Download will stop on first
85 success.
Zdenek Behanfd0efe42012-04-13 04:36:40 +020086
Alex Klein1699fab2022-09-08 08:46:06 -060087 Returns:
Alex Kleinef517832023-01-13 12:06:51 -070088 Full path to the downloaded file.
Gilad Arnoldecc86fa2015-05-22 12:06:04 -070089
Alex Klein1699fab2022-09-08 08:46:06 -060090 Raises:
Alex Kleinef517832023-01-13 12:06:51 -070091 ValueError: None of the URLs worked.
Alex Klein1699fab2022-09-08 08:46:06 -060092 """
93 # Note we track content length ourselves since certain versions of curl
94 # fail if asked to resume a complete file.
95 # https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3482927&group_id=976
96 status_re = re.compile(rb"^HTTP/[0-9]+(\.[0-9]+)? 200")
97 # pylint: disable=undefined-loop-variable
98 for url in urls:
99 logging.notice("Downloading tarball %s ...", urls[0].rsplit("/", 1)[-1])
100 parsed = urllib.parse.urlparse(url)
101 tarball_name = os.path.basename(parsed.path)
102 if parsed.scheme in ("", "file"):
103 if os.path.exists(parsed.path):
104 return parsed.path
105 continue
106 content_length = 0
107 logging.debug("Attempting download from %s", url)
108 result = retry_util.RunCurl(
109 ["-I", url],
110 print_cmd=False,
111 debug_level=logging.NOTICE,
112 capture_output=True,
113 )
114 successful = False
115 for header in result.stdout.splitlines():
116 # We must walk the output to find the 200 code for use cases where
117 # a proxy is involved and may have pushed down the actual header.
118 if status_re.match(header):
119 successful = True
120 elif header.lower().startswith(b"content-length:"):
121 content_length = int(header.split(b":", 1)[-1].strip())
122 if successful:
123 break
Brian Harring1790ac42012-09-23 08:53:33 -0700124 if successful:
Alex Klein1699fab2022-09-08 08:46:06 -0600125 break
126 else:
127 raise ValueError("No valid URLs found!")
Zdenek Behanfd0efe42012-04-13 04:36:40 +0200128
Alex Klein1699fab2022-09-08 08:46:06 -0600129 tarball_dest = os.path.join(storage_dir, tarball_name)
130 current_size = 0
131 if os.path.exists(tarball_dest):
132 current_size = os.path.getsize(tarball_dest)
133 if current_size > content_length:
134 osutils.SafeUnlink(tarball_dest)
135 current_size = 0
Zdenek Behanb2fa72e2012-03-16 04:49:30 +0100136
Alex Klein1699fab2022-09-08 08:46:06 -0600137 if current_size < content_length:
138 retry_util.RunCurl(
139 [
140 "--fail",
141 "-L",
142 "-y",
143 "30",
144 "-C",
145 "-",
146 "--output",
147 tarball_dest,
148 url,
149 ],
150 print_cmd=False,
151 debug_level=logging.NOTICE,
152 )
Brian Harringb938c782012-02-29 15:14:38 -0800153
Alex Klein1699fab2022-09-08 08:46:06 -0600154 # Cleanup old tarballs now since we've successfull fetched; only cleanup
155 # the tarballs for our prefix, or unknown ones. This gets a bit tricky
156 # because we might have partial overlap between known prefixes.
157 for p in Path(storage_dir).glob("cros-sdk-*"):
158 if p.name == tarball_name:
159 continue
160 logging.info("Cleaning up old tarball: %s", p)
161 osutils.SafeUnlink(p)
Zdenek Behan9c644dd2012-04-05 06:24:02 +0200162
Alex Klein1699fab2022-09-08 08:46:06 -0600163 return tarball_dest
Brian Harringb938c782012-02-29 15:14:38 -0800164
165
David James56e6c2c2012-10-24 23:54:41 -0700166def _SudoCommand():
Alex Klein1699fab2022-09-08 08:46:06 -0600167 """Get the 'sudo' command, along with all needed environment variables."""
David James56e6c2c2012-10-24 23:54:41 -0700168
Alex Klein1699fab2022-09-08 08:46:06 -0600169 # Pass in the ENVIRONMENT_ALLOWLIST and ENV_PASSTHRU variables so that
170 # scripts in the chroot know what variables to pass through.
171 cmd = ["sudo"]
172 for key in constants.CHROOT_ENVIRONMENT_ALLOWLIST + constants.ENV_PASSTHRU:
173 value = os.environ.get(key)
174 if value is not None:
175 cmd += ["%s=%s" % (key, value)]
David James56e6c2c2012-10-24 23:54:41 -0700176
Alex Kleinef517832023-01-13 12:06:51 -0700177 # We keep PATH not for the chroot but for the re-exec & for programs we
178 # might run before we chroot into the SDK. The process that enters the SDK
179 # itself will take care of initializing PATH to the right value then. But
180 # we can't override the system's default PATH for root as that will hide
181 # /sbin.
Alex Klein1699fab2022-09-08 08:46:06 -0600182 cmd += ["CHROMEOS_SUDO_PATH=%s" % os.environ.get("PATH", "")]
Mike Frysinger2bda4d12020-07-14 11:15:49 -0400183
Alex Klein1699fab2022-09-08 08:46:06 -0600184 # Pass along current rlimit settings so we can restore them.
Mike Frysinger53baf882021-06-24 23:16:50 -0400185 cmd += [f"CHROMEOS_SUDO_RLIMITS={cros_sdk_lib.ChrootEnteror.get_rlimits()}"]
Mike Frysinger12d055b2022-06-23 12:26:47 -0400186
Alex Klein1699fab2022-09-08 08:46:06 -0600187 # Pass in the path to the depot_tools so that users can access them from
188 # within the chroot.
189 cmd += ["DEPOT_TOOLS=%s" % constants.DEPOT_TOOLS_DIR]
Mike Frysinger749251e2014-01-29 05:04:27 -0500190
Alex Klein1699fab2022-09-08 08:46:06 -0600191 return cmd
David James56e6c2c2012-10-24 23:54:41 -0700192
193
Josh Triplett472a4182013-03-08 11:48:57 -0800194def _ReportMissing(missing):
Alex Klein1699fab2022-09-08 08:46:06 -0600195 """Report missing utilities, then exit.
Josh Triplett472a4182013-03-08 11:48:57 -0800196
Alex Klein1699fab2022-09-08 08:46:06 -0600197 Args:
Alex Kleinef517832023-01-13 12:06:51 -0700198 missing: List of missing utilities, as returned by
199 osutils.FindMissingBinaries. If non-empty, will not return.
Alex Klein1699fab2022-09-08 08:46:06 -0600200 """
Josh Triplett472a4182013-03-08 11:48:57 -0800201
Alex Klein1699fab2022-09-08 08:46:06 -0600202 if missing:
203 raise SystemExit(
204 "The tool(s) %s were not found.\n"
205 "Please install the appropriate package in your host.\n"
206 "Example(ubuntu):\n"
207 " sudo apt-get install <packagename>" % ", ".join(missing)
208 )
Josh Triplett472a4182013-03-08 11:48:57 -0800209
210
211def _ProxySimSetup(options):
Alex Klein1699fab2022-09-08 08:46:06 -0600212 """Set up proxy simulator, and return only in the child environment.
Josh Triplett472a4182013-03-08 11:48:57 -0800213
Alex Klein1699fab2022-09-08 08:46:06 -0600214 TODO: Ideally, this should support multiple concurrent invocations of
215 cros_sdk --proxy-sim; currently, such invocations will conflict with each
216 other due to the veth device names and IP addresses. Either this code would
217 need to generate fresh, unused names for all of these before forking, or it
218 would need to support multiple concurrent cros_sdk invocations sharing one
219 proxy and allowing it to exit when unused (without counting on any local
220 service-management infrastructure on the host).
221 """
Josh Triplett472a4182013-03-08 11:48:57 -0800222
Alex Klein1699fab2022-09-08 08:46:06 -0600223 may_need_mpm = False
224 apache_bin = osutils.Which("apache2")
Josh Triplett472a4182013-03-08 11:48:57 -0800225 if apache_bin is None:
Alex Klein1699fab2022-09-08 08:46:06 -0600226 apache_bin = osutils.Which("apache2", PROXY_APACHE_FALLBACK_PATH)
227 if apache_bin is None:
228 _ReportMissing(("apache2",))
229 else:
230 may_need_mpm = True
Josh Triplett472a4182013-03-08 11:48:57 -0800231
Alex Klein1699fab2022-09-08 08:46:06 -0600232 # Module names and .so names included for ease of grepping.
233 apache_modules = [
234 ("proxy_module", "mod_proxy.so"),
235 ("proxy_connect_module", "mod_proxy_connect.so"),
236 ("proxy_http_module", "mod_proxy_http.so"),
237 ("proxy_ftp_module", "mod_proxy_ftp.so"),
238 ]
Josh Triplett472a4182013-03-08 11:48:57 -0800239
Alex Kleinef517832023-01-13 12:06:51 -0700240 # Find the apache module directory and make sure it has the modules we need.
Alex Klein1699fab2022-09-08 08:46:06 -0600241 module_dirs = {}
242 for g in PROXY_APACHE_MODULE_GLOBS:
243 for _, so in apache_modules:
244 for f in glob.glob(os.path.join(g, so)):
245 module_dirs.setdefault(os.path.dirname(f), []).append(so)
246 for apache_module_path, modules_found in module_dirs.items():
247 if len(modules_found) == len(apache_modules):
248 break
249 else:
Alex Kleinef517832023-01-13 12:06:51 -0700250 # Appease cros lint, which doesn't understand that this else block will
251 # not fall through to the subsequent code which relies on
252 # apache_module_path.
Alex Klein1699fab2022-09-08 08:46:06 -0600253 apache_module_path = None
254 raise SystemExit(
Alex Kleinef517832023-01-13 12:06:51 -0700255 "Could not find apache module path containing all required "
256 "modules: %s" % ", ".join(so for mod, so in apache_modules)
Alex Klein1699fab2022-09-08 08:46:06 -0600257 )
Josh Triplett472a4182013-03-08 11:48:57 -0800258
Alex Klein1699fab2022-09-08 08:46:06 -0600259 def check_add_module(name):
260 so = "mod_%s.so" % name
261 if os.access(os.path.join(apache_module_path, so), os.F_OK):
262 mod = "%s_module" % name
263 apache_modules.append((mod, so))
264 return True
265 return False
Josh Triplett472a4182013-03-08 11:48:57 -0800266
Alex Klein1699fab2022-09-08 08:46:06 -0600267 check_add_module("authz_core")
268 if may_need_mpm:
269 for mpm in PROXY_APACHE_MPMS:
270 if check_add_module("mpm_%s" % mpm):
271 break
Josh Triplett472a4182013-03-08 11:48:57 -0800272
Alex Klein1699fab2022-09-08 08:46:06 -0600273 veth_host = "%s-host" % PROXY_VETH_PREFIX
274 veth_guest = "%s-guest" % PROXY_VETH_PREFIX
Josh Triplett472a4182013-03-08 11:48:57 -0800275
Alex Klein1699fab2022-09-08 08:46:06 -0600276 # Set up locks to sync the net namespace setup. We need the child to create
Alex Kleinef517832023-01-13 12:06:51 -0700277 # the net ns first, and then have the parent assign the guest end of the
278 # veth interface to the child's new network namespace & bring up the proxy.
279 # Only then can the child move forward and rely on the network being up.
Alex Klein1699fab2022-09-08 08:46:06 -0600280 ns_create_lock = locking.PipeLock()
281 ns_setup_lock = locking.PipeLock()
Josh Triplett472a4182013-03-08 11:48:57 -0800282
Alex Klein1699fab2022-09-08 08:46:06 -0600283 pid = os.fork()
284 if not pid:
285 # Create our new isolated net namespace.
286 namespaces.Unshare(namespaces.CLONE_NEWNET)
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500287
Alex Klein1699fab2022-09-08 08:46:06 -0600288 # Signal the parent the ns is ready to be configured.
289 ns_create_lock.Post()
290 del ns_create_lock
291
292 # Wait for the parent to finish setting up the ns/proxy.
293 ns_setup_lock.Wait()
294 del ns_setup_lock
295
296 # Set up child side of the network.
297 commands = (
298 ("ip", "link", "set", "up", "lo"),
299 (
300 "ip",
301 "address",
302 "add",
303 "%s/%u" % (PROXY_GUEST_IP, PROXY_NETMASK),
304 "dev",
305 veth_guest,
306 ),
307 ("ip", "link", "set", veth_guest, "up"),
308 )
309 try:
310 for cmd in commands:
311 cros_build_lib.dbg_run(cmd)
312 except cros_build_lib.RunCommandError as e:
313 cros_build_lib.Die("Proxy setup failed!\n%s", e)
314
315 proxy_url = "http://%s:%u" % (PROXY_HOST_IP, PROXY_PORT)
316 for proto in ("http", "https", "ftp"):
317 os.environ[proto + "_proxy"] = proxy_url
318 for v in ("all_proxy", "RSYNC_PROXY", "no_proxy"):
319 os.environ.pop(v, None)
320 return
321
322 # Set up parent side of the network.
323 uid = int(os.environ.get("SUDO_UID", "0"))
324 gid = int(os.environ.get("SUDO_GID", "0"))
325 if uid == 0 or gid == 0:
326 for username in PROXY_APACHE_FALLBACK_USERS:
327 try:
328 pwnam = pwd.getpwnam(username)
329 uid, gid = pwnam.pw_uid, pwnam.pw_gid
330 break
331 except KeyError:
332 continue
333 if uid == 0 or gid == 0:
334 raise SystemExit("Could not find a non-root user to run Apache as")
335
336 chroot_parent, chroot_base = os.path.split(options.chroot)
337 pid_file = os.path.join(chroot_parent, ".%s-apache-proxy.pid" % chroot_base)
338 log_file = os.path.join(chroot_parent, ".%s-apache-proxy.log" % chroot_base)
339
340 # Wait for the child to create the net ns.
341 ns_create_lock.Wait()
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500342 del ns_create_lock
343
Alex Klein1699fab2022-09-08 08:46:06 -0600344 apache_directives = [
345 "User #%u" % uid,
346 "Group #%u" % gid,
347 "PidFile %s" % pid_file,
348 "ErrorLog %s" % log_file,
349 "Listen %s:%u" % (PROXY_HOST_IP, PROXY_PORT),
350 "ServerName %s" % PROXY_HOST_IP,
351 "ProxyRequests On",
352 "AllowCONNECT %s" % " ".join(str(x) for x in PROXY_CONNECT_PORTS),
353 ] + [
354 "LoadModule %s %s" % (mod, os.path.join(apache_module_path, so))
355 for (mod, so) in apache_modules
356 ]
357 commands = (
358 (
359 "ip",
360 "link",
361 "add",
362 "name",
363 veth_host,
364 "type",
365 "veth",
366 "peer",
367 "name",
368 veth_guest,
369 ),
370 (
371 "ip",
372 "address",
373 "add",
374 "%s/%u" % (PROXY_HOST_IP, PROXY_NETMASK),
375 "dev",
376 veth_host,
377 ),
378 ("ip", "link", "set", veth_host, "up"),
379 (
380 [apache_bin, "-f", "/dev/null"]
381 + [arg for d in apache_directives for arg in ("-C", d)]
382 ),
383 ("ip", "link", "set", veth_guest, "netns", str(pid)),
384 )
385 cmd = None # Make cros lint happy.
386 try:
387 for cmd in commands:
388 cros_build_lib.dbg_run(cmd)
389 except cros_build_lib.RunCommandError as e:
390 # Clean up existing interfaces, if any.
391 cmd_cleanup = ("ip", "link", "del", veth_host)
392 try:
393 cros_build_lib.run(cmd_cleanup, print_cmd=False)
394 except cros_build_lib.RunCommandError:
395 logging.error("running %r failed", cmd_cleanup)
396 cros_build_lib.Die("Proxy network setup failed!\n%s", e)
397
398 # Signal the child that the net ns/proxy is fully configured now.
399 ns_setup_lock.Post()
Mike Frysinger77bf4af2016-02-26 17:13:15 -0500400 del ns_setup_lock
Josh Triplett472a4182013-03-08 11:48:57 -0800401
Alex Klein1699fab2022-09-08 08:46:06 -0600402 process_util.ExitAsStatus(os.waitpid(pid, 0)[1])
Josh Triplett472a4182013-03-08 11:48:57 -0800403
404
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400405def _BuildReExecCommand(argv, opts) -> List[str]:
Alex Klein1699fab2022-09-08 08:46:06 -0600406 """Generate new command for self-reexec."""
407 # Make sure to preserve the active Python executable in case the version
408 # we're running as is not the default one found via the (new) $PATH.
409 cmd = _SudoCommand() + ["--"]
410 if opts.strace:
411 cmd += ["strace"] + shlex.split(opts.strace_arguments) + ["--"]
412 return cmd + [sys.executable] + argv
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400413
414
415def _ReExecuteIfNeeded(argv, opts):
Alex Klein1699fab2022-09-08 08:46:06 -0600416 """Re-execute cros_sdk as root.
David James56e6c2c2012-10-24 23:54:41 -0700417
Alex Klein1699fab2022-09-08 08:46:06 -0600418 Also unshare the mount namespace so as to ensure that processes outside
419 the chroot can't mess with our mounts.
420 """
421 if osutils.IsNonRootUser():
422 cmd = _BuildReExecCommand(argv, opts)
423 logging.debug(
424 "Reexecing self via sudo:\n%s", cros_build_lib.CmdToStr(cmd)
425 )
426 os.execvp(cmd[0], cmd)
David James56e6c2c2012-10-24 23:54:41 -0700427
428
Mike Frysinger34db8692013-11-11 14:54:08 -0500429def _CreateParser(sdk_latest_version, bootstrap_latest_version):
Alex Klein1699fab2022-09-08 08:46:06 -0600430 """Generate and return the parser with all the options."""
431 usage = (
432 "usage: %(prog)s [options] "
433 "[VAR1=val1 ... VAR2=val2] [--] [command [args]]"
434 )
435 parser = commandline.ArgumentParser(
436 usage=usage, description=__doc__, caching=True
437 )
Brian Harring218e13c2012-10-10 16:21:26 -0700438
Alex Klein1699fab2022-09-08 08:46:06 -0600439 # Global options.
Alex Klein1699fab2022-09-08 08:46:06 -0600440 parser.add_argument(
441 "--chroot",
442 dest="chroot",
Brian Norrise1760452023-02-23 15:54:40 -0800443 default=constants.DEFAULT_CHROOT_PATH,
Alex Klein1699fab2022-09-08 08:46:06 -0600444 type="path",
445 help=("SDK chroot dir name [%s]" % constants.DEFAULT_CHROOT_DIR),
446 )
447 parser.add_argument(
Dan Callaghanada9e032023-01-30 14:28:46 +1100448 "--out-dir",
449 metavar="DIR",
Brian Norrise1760452023-02-23 15:54:40 -0800450 default=constants.DEFAULT_OUT_PATH,
Dan Callaghanada9e032023-01-30 14:28:46 +1100451 type=Path,
452 help="Use DIR for build state and output files",
453 )
454 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600455 "--nouse-image",
456 dest="use_image",
457 action="store_false",
458 default=False,
Brian Norris35a7ed02023-02-23 12:50:14 -0800459 deprecated="--[no]use-image is no longer supported (b/266878468).",
460 help=argparse.SUPPRESS,
Alex Klein1699fab2022-09-08 08:46:06 -0600461 )
462 parser.add_argument(
463 "--use-image",
464 dest="use_image",
465 action="store_true",
466 default=False,
Brian Norris35a7ed02023-02-23 12:50:14 -0800467 deprecated="--[no]use-image is no longer supported (b/266878468).",
468 help=argparse.SUPPRESS,
Alex Klein1699fab2022-09-08 08:46:06 -0600469 )
Brian Harringb938c782012-02-29 15:14:38 -0800470
Alex Klein1699fab2022-09-08 08:46:06 -0600471 parser.add_argument(
472 "--chrome-root",
473 "--chrome_root",
474 type="path",
475 help="Mount this chrome root into the SDK chroot",
476 )
477 parser.add_argument(
478 "--chrome_root_mount",
479 type="path",
480 help="Mount chrome into this path inside SDK chroot",
481 )
482 parser.add_argument(
483 "--nousepkg",
484 action="store_true",
485 default=False,
486 help="Do not use binary packages when creating a chroot.",
487 )
488 parser.add_argument(
489 "-u",
490 "--url",
491 dest="sdk_url",
492 help="Use sdk tarball located at this url. Use file:// "
493 "for local files.",
494 )
495 parser.add_argument(
496 "--sdk-version",
497 help=(
498 "Use this sdk version. For prebuilt, current is %r"
499 ", for bootstrapping it is %r."
500 % (sdk_latest_version, bootstrap_latest_version)
501 ),
502 )
503 parser.add_argument(
Mike Frysinger85824562023-01-31 02:40:43 -0500504 "--goma-dir",
Alex Klein1699fab2022-09-08 08:46:06 -0600505 "--goma_dir",
Mike Frysinger8fc83772023-02-01 15:30:47 -0500506 type="dir_exists",
Alex Klein1699fab2022-09-08 08:46:06 -0600507 help="Goma installed directory to mount into the chroot.",
508 )
509 parser.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600510 "--reclient-dir",
Mike Frysinger8fc83772023-02-01 15:30:47 -0500511 type="dir_exists",
Alex Klein1699fab2022-09-08 08:46:06 -0600512 help="Reclient installed directory to mount into the chroot.",
513 )
514 parser.add_argument(
515 "--reproxy-cfg-file",
Mike Frysinger8fc83772023-02-01 15:30:47 -0500516 type="file_exists",
Alex Klein1699fab2022-09-08 08:46:06 -0600517 help="Config file for re-client's reproxy used for remoteexec.",
518 )
519 parser.add_argument(
520 "--skip-chroot-upgrade",
521 dest="chroot_upgrade",
522 action="store_false",
523 default=True,
Alex Kleinef517832023-01-13 12:06:51 -0700524 help="Skip automatic SDK and toolchain upgrade when entering the "
525 "chroot. Never guaranteed to work, especially as ToT moves forward.",
Alex Klein1699fab2022-09-08 08:46:06 -0600526 )
Yong Hong84ba9172018-02-07 01:37:42 +0800527
Alex Klein1699fab2022-09-08 08:46:06 -0600528 # Use type=str instead of type='path' to prevent the given path from being
Alex Kleinef517832023-01-13 12:06:51 -0700529 # transferred to absolute path automatically.
Alex Klein1699fab2022-09-08 08:46:06 -0600530 parser.add_argument(
531 "--working-dir",
Mike Frysinger695f2fd2023-02-03 20:45:14 -0500532 type=Path,
Alex Klein1699fab2022-09-08 08:46:06 -0600533 help="Run the command in specific working directory in "
534 "chroot. If the given directory is a relative "
535 "path, this program will transfer the path to "
536 "the corresponding one inside chroot.",
537 )
Yong Hong84ba9172018-02-07 01:37:42 +0800538
Alex Klein1699fab2022-09-08 08:46:06 -0600539 parser.add_argument("commands", nargs=argparse.REMAINDER)
Mike Frysinger34db8692013-11-11 14:54:08 -0500540
Alex Klein1699fab2022-09-08 08:46:06 -0600541 # Commands.
542 group = parser.add_argument_group("Commands")
Mike Frysinger79024a32021-04-05 03:28:35 -0400543 group.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600544 "--enter",
545 action="store_true",
546 default=False,
547 help="Enter the SDK chroot. Implies --create.",
548 )
Mike Frysinger79024a32021-04-05 03:28:35 -0400549 group.add_argument(
Alex Klein1699fab2022-09-08 08:46:06 -0600550 "--create",
551 action="store_true",
552 default=False,
Alex Klein22690a12022-11-17 10:56:09 -0700553 help="Create the chroot only if it does not already exist. Downloads "
554 "the SDK only if needed, even if --download explicitly passed.",
Alex Klein1699fab2022-09-08 08:46:06 -0600555 )
556 group.add_argument(
557 "--bootstrap",
558 action="store_true",
559 default=False,
560 help="Build everything from scratch, including the sdk. "
561 "Use this only if you need to validate a change "
562 "that affects SDK creation itself (toolchain and "
563 "build are typically the only folk who need this). "
564 "Note this will quite heavily slow down the build. "
565 "This option implies --create --nousepkg.",
566 )
567 group.add_argument(
568 "-r",
569 "--replace",
570 action="store_true",
571 default=False,
572 help="Replace an existing SDK chroot. Basically an alias "
573 "for --delete --create.",
574 )
575 group.add_argument(
576 "--delete",
577 action="store_true",
578 default=False,
579 help="Delete the current SDK chroot if it exists.",
580 )
581 group.add_argument(
582 "--force",
583 action="store_true",
584 default=False,
Brian Norrisf624bf42023-03-02 12:57:49 -0800585 help="Force delete of the current SDK chroot even if "
Alex Klein1699fab2022-09-08 08:46:06 -0600586 "obtaining the write lock fails.",
587 )
588 group.add_argument(
589 "--unmount",
590 action="store_true",
591 default=False,
Brian Norrisf624bf42023-03-02 12:57:49 -0800592 deprecated="loopback-image (--use-image) is no longer supported "
593 "(b/266878468). If needed, consider `cros unmount /path/to/chroot`.",
594 help=argparse.SUPPRESS,
Alex Klein1699fab2022-09-08 08:46:06 -0600595 )
596 group.add_argument(
597 "--download",
598 action="store_true",
599 default=False,
600 help="Download the sdk.",
601 )
Alex Klein1699fab2022-09-08 08:46:06 -0600602 commands = group
Mike Frysinger80dfce92014-04-21 10:58:53 -0400603
Alex Klein1699fab2022-09-08 08:46:06 -0600604 # Namespace options.
605 group = parser.add_argument_group("Namespaces")
606 group.add_argument(
607 "--proxy-sim",
608 action="store_true",
609 default=False,
610 help="Simulate a restrictive network requiring an outbound" " proxy.",
611 )
612 for ns, default in (("pid", True), ("net", None)):
613 group.add_argument(
614 f"--ns-{ns}",
615 default=default,
616 action="store_true",
617 help=f"Create a new {ns} namespace.",
618 )
619 group.add_argument(
620 f"--no-ns-{ns}",
621 dest=f"ns_{ns}",
622 action="store_false",
623 help=f"Do not create a new {ns} namespace.",
624 )
Mike Frysinger5f5c70b2022-07-19 12:55:21 -0400625
Alex Klein1699fab2022-09-08 08:46:06 -0600626 # Debug options.
627 group = parser.debug_group
628 group.add_argument(
629 "--strace",
630 action="store_true",
631 help="Run cros_sdk through strace after re-exec via sudo",
632 )
633 group.add_argument(
634 "--strace-arguments",
635 default="",
636 help="Extra strace options (shell quoting permitted)",
637 )
Mike Frysinger34db8692013-11-11 14:54:08 -0500638
Alex Klein1699fab2022-09-08 08:46:06 -0600639 # Internal options.
640 group = parser.add_argument_group(
641 "Internal Chromium OS Build Team Options",
642 "Caution: these are for meant for the Chromium OS build team only",
643 )
644 group.add_argument(
645 "--buildbot-log-version",
646 default=False,
647 action="store_true",
648 help="Log SDK version for buildbot consumption",
649 )
650
651 return parser, commands
Mike Frysinger34db8692013-11-11 14:54:08 -0500652
653
654def main(argv):
Alex Klein1699fab2022-09-08 08:46:06 -0600655 # Turn on strict sudo checks.
656 cros_build_lib.STRICT_SUDO = True
657 conf = key_value_store.LoadFile(
658 os.path.join(constants.SOURCE_ROOT, constants.SDK_VERSION_FILE),
659 ignore_missing=True,
660 )
661 sdk_latest_version = conf.get("SDK_LATEST_VERSION", "<unknown>")
662 bootstrap_frozen_version = conf.get("BOOTSTRAP_FROZEN_VERSION", "<unknown>")
Manoj Gupta55a63092019-06-13 11:47:13 -0700663
Alex Klein1699fab2022-09-08 08:46:06 -0600664 # Use latest SDK for bootstrapping if requested. Use a frozen version of SDK
665 # for bootstrapping if BOOTSTRAP_FROZEN_VERSION is set.
666 bootstrap_latest_version = (
667 sdk_latest_version
668 if bootstrap_frozen_version == "<unknown>"
669 else bootstrap_frozen_version
670 )
671 parser, commands = _CreateParser(
672 sdk_latest_version, bootstrap_latest_version
673 )
674 options = parser.parse_args(argv)
Brian Harringb938c782012-02-29 15:14:38 -0800675
Alex Klein1699fab2022-09-08 08:46:06 -0600676 # Some basic checks first, before we ask for sudo credentials.
677 cros_build_lib.AssertOutsideChroot()
Brian Harringb938c782012-02-29 15:14:38 -0800678
Alex Klein1699fab2022-09-08 08:46:06 -0600679 host = os.uname()[4]
680 if host != "x86_64":
681 cros_build_lib.Die(
682 "cros_sdk is currently only supported on x86_64; you're running"
683 " %s. Please find a x86_64 machine." % (host,)
684 )
Brian Harring1790ac42012-09-23 08:53:33 -0700685
Brian Norriscf031912023-02-21 15:04:27 -0800686 goma = (
687 goma_lib.Goma(options.goma_dir, chroot_dir=options.chroot)
688 if options.goma_dir
689 else None
690 )
Mike Frysinger9a5124e2023-01-26 17:16:44 -0500691
Alex Klein1699fab2022-09-08 08:46:06 -0600692 # Merge the outside PATH setting if we re-execed ourselves.
693 if "CHROMEOS_SUDO_PATH" in os.environ:
694 os.environ["PATH"] = "%s:%s" % (
695 os.environ.pop("CHROMEOS_SUDO_PATH"),
696 os.environ["PATH"],
697 )
Mike Frysinger2bda4d12020-07-14 11:15:49 -0400698
Alex Klein1699fab2022-09-08 08:46:06 -0600699 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
700 if options.proxy_sim:
701 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800702
Alex Klein1699fab2022-09-08 08:46:06 -0600703 if (
704 sdk_latest_version == "<unknown>"
705 or bootstrap_latest_version == "<unknown>"
706 ):
707 cros_build_lib.Die(
708 "No SDK version was found. "
709 "Are you in a Chromium source tree instead of Chromium OS?\n\n"
710 "Please change to a directory inside your Chromium OS source tree\n"
711 "and retry. If you need to setup a Chromium OS source tree, see\n"
712 " https://dev.chromium.org/chromium-os/developer-guide"
713 )
Benjamin Gordon040a1162017-06-29 13:44:47 -0600714
Alex Klein1699fab2022-09-08 08:46:06 -0600715 _ReExecuteIfNeeded([sys.argv[0]] + argv, options)
David James471532c2013-01-21 10:23:31 -0800716
Alex Klein1699fab2022-09-08 08:46:06 -0600717 lock_path = os.path.dirname(options.chroot)
718 lock_path = os.path.join(
719 lock_path, ".%s_lock" % os.path.basename(options.chroot).lstrip(".")
720 )
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600721
Alex Klein1699fab2022-09-08 08:46:06 -0600722 # Expand out the aliases...
723 if options.replace:
724 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800725
Alex Klein1699fab2022-09-08 08:46:06 -0600726 if options.bootstrap:
727 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800728
Alex Klein1699fab2022-09-08 08:46:06 -0600729 # If a command is not given, default to enter.
730 # pylint: disable=protected-access
731 # This _group_actions access sucks, but upstream decided to not include an
732 # alternative to optparse's option_list, and this is what they recommend.
733 options.enter |= not any(
734 getattr(options, x.dest) for x in commands._group_actions
735 )
736 # pylint: enable=protected-access
Mike Frysinger114a7b92023-01-26 19:08:57 -0500737 options.enter |= bool(options.commands)
Brian Harring218e13c2012-10-10 16:21:26 -0700738
Brian Norris35a7ed02023-02-23 12:50:14 -0800739 if options.delete and not options.create and options.enter:
Alex Klein1699fab2022-09-08 08:46:06 -0600740 parser.error(
Brian Norris35a7ed02023-02-23 12:50:14 -0800741 "Trying to enter the chroot when --delete "
Alex Klein1699fab2022-09-08 08:46:06 -0600742 "was specified makes no sense."
743 )
Brian Harring218e13c2012-10-10 16:21:26 -0700744
Alex Klein1699fab2022-09-08 08:46:06 -0600745 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Alex Klein1699fab2022-09-08 08:46:06 -0600746 # Finally, flip create if necessary.
Brian Norris35a7ed02023-02-23 12:50:14 -0800747 if options.enter:
Alex Klein1699fab2022-09-08 08:46:06 -0600748 options.create |= not chroot_exists
749
750 # Make sure we will download if we plan to create.
751 options.download |= options.create
752
Mike Frysinger6bbd3e92023-01-27 00:05:02 -0500753 options.Freeze()
754
Mike Frysingerded13922023-02-01 15:31:59 -0500755 if options.reclient_dir and not options.reproxy_cfg_file:
756 cros_build_lib.Die("--reclient-dir requires --reproxy-cfg-file")
757 if not options.reclient_dir and options.reproxy_cfg_file:
758 cros_build_lib.Die(
759 "--reproxy-cfg-file only makes sense with --reclient-dir"
760 )
761
Mike Frysinger253c8392023-01-27 01:12:21 -0500762 remoteexec = (
763 remoteexec_util.Remoteexec(
764 options.reclient_dir, options.reproxy_cfg_file
765 )
766 if (options.reclient_dir and options.reproxy_cfg_file)
767 else None
768 )
769
Mike Frysingerec32bea2023-01-27 01:20:48 -0500770 chroot = chroot_lib.Chroot(
771 path=options.chroot,
772 cache_dir=options.cache_dir,
773 chrome_root=options.chrome_root,
774 goma=goma,
775 remoteexec=remoteexec,
776 )
777
Alex Kleinef517832023-01-13 12:06:51 -0700778 # Anything that needs to manipulate the main chroot mount or communicate
779 # with LVM needs to be done here before we enter the new namespaces.
Alex Klein1699fab2022-09-08 08:46:06 -0600780
Alex Klein1699fab2022-09-08 08:46:06 -0600781 if options.delete:
782 # Set a timeout of 300 seconds when getting the lock.
783 with locking.FileLock(
784 lock_path, "chroot lock", blocking_timeout=300
785 ) as lock:
786 try:
787 lock.write_lock()
788 except timeout_util.TimeoutError as e:
789 logging.error(
790 "Acquiring write_lock on %s failed: %s", lock_path, e
791 )
792 if not options.force:
793 cros_build_lib.Die(
794 "Exiting; use --force to continue w/o lock."
795 )
796 else:
797 logging.warning(
798 "cros_sdk was invoked with force option, continuing."
799 )
800 logging.notice("Deleting chroot.")
Mike Frysingerec32bea2023-01-27 01:20:48 -0500801 cros_sdk_lib.CleanupChrootMount(chroot.path, delete=True)
Alex Klein1699fab2022-09-08 08:46:06 -0600802
Alex Kleinef517832023-01-13 12:06:51 -0700803 # Enter a new set of namespaces. Everything after here cannot directly
804 # affect the hosts's mounts or alter LVM volumes.
Alex Klein1699fab2022-09-08 08:46:06 -0600805 namespaces.SimpleUnshare(net=options.ns_net, pid=options.ns_pid)
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600806
Alex Klein1699fab2022-09-08 08:46:06 -0600807 if not options.sdk_version:
808 sdk_version = (
809 bootstrap_latest_version
810 if options.bootstrap
811 else sdk_latest_version
812 )
Yong Hong4e29b622018-02-05 14:31:10 +0800813 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600814 sdk_version = options.sdk_version
815 if options.buildbot_log_version:
816 cbuildbot_alerts.PrintBuildbotStepText(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -0700817
Alex Klein1699fab2022-09-08 08:46:06 -0600818 # Based on selections, determine the tarball to fetch.
Alex Klein22690a12022-11-17 10:56:09 -0700819 urls = []
Mike Frysingerbf47cce2021-01-20 13:46:30 -0500820 if options.download:
Alex Klein1699fab2022-09-08 08:46:06 -0600821 if options.sdk_url:
822 urls = [options.sdk_url]
823 else:
824 urls = GetArchStageTarballs(sdk_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700825
Alex Klein1699fab2022-09-08 08:46:06 -0600826 with locking.FileLock(lock_path, "chroot lock") as lock:
827 if options.proxy_sim:
828 _ProxySimSetup(options)
Brian Harring1790ac42012-09-23 08:53:33 -0700829
Mike Frysingerec32bea2023-01-27 01:20:48 -0500830 sdk_cache = os.path.join(chroot.cache_dir, "sdks")
831 distfiles_cache = os.path.join(chroot.cache_dir, "distfiles")
832 osutils.SafeMakedirsNonRoot(chroot.cache_dir)
Dan Callaghanada9e032023-01-30 14:28:46 +1100833 osutils.SafeMakedirsNonRoot(options.out_dir)
Alex Klein1699fab2022-09-08 08:46:06 -0600834
835 for target in (sdk_cache, distfiles_cache):
836 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
837 if not os.path.exists(src):
838 osutils.SafeMakedirsNonRoot(target)
839 continue
840 lock.write_lock(
841 "Upgrade to %r needed but chroot is locked; please exit "
842 "all instances so this upgrade can finish." % src
843 )
844 if not os.path.exists(src):
Alex Kleinef517832023-01-13 12:06:51 -0700845 # Note that while waiting for the write lock, src may've
846 # vanished; it's a rare race during the upgrade process that's a
847 # byproduct of us avoiding taking a write lock to do the src
848 # check. If we took a write lock for that check, it would
849 # effectively limit all cros_sdk for a chroot to a single
850 # instance.
Alex Klein1699fab2022-09-08 08:46:06 -0600851 osutils.SafeMakedirsNonRoot(target)
852 elif not os.path.exists(target):
853 # Upgrade occurred, but a reversion, or something whacky
854 # occurred writing to the old location. Wipe and continue.
855 os.rename(src, target)
856 else:
857 # Upgrade occurred once already, but either a reversion or
858 # some before/after separate cros_sdk usage is at play.
859 # Wipe and continue.
860 osutils.RmDir(src)
861
Alex Klein1699fab2022-09-08 08:46:06 -0600862 mounted = False
863 if options.create:
864 lock.write_lock()
Alex Kleinef517832023-01-13 12:06:51 -0700865 # Recheck if the chroot is set up here before creating to make sure
Brian Norrisf624bf42023-03-02 12:57:49 -0800866 # we account for whatever the various delete/cleanup steps above
867 # have done.
Mike Frysingerec32bea2023-01-27 01:20:48 -0500868 if cros_sdk_lib.IsChrootReady(chroot.path):
Alex Klein1699fab2022-09-08 08:46:06 -0600869 logging.debug("Chroot already exists. Skipping creation.")
870 else:
Alex Klein22690a12022-11-17 10:56:09 -0700871 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Alex Klein1699fab2022-09-08 08:46:06 -0600872 cros_sdk_lib.CreateChroot(
Mike Frysingerec32bea2023-01-27 01:20:48 -0500873 Path(chroot.path),
Alex Klein1699fab2022-09-08 08:46:06 -0600874 Path(sdk_tarball),
Dan Callaghanada9e032023-01-30 14:28:46 +1100875 options.out_dir,
Mike Frysingerec32bea2023-01-27 01:20:48 -0500876 Path(chroot.cache_dir),
Alex Klein1699fab2022-09-08 08:46:06 -0600877 usepkg=not options.bootstrap and not options.nousepkg,
878 chroot_upgrade=options.chroot_upgrade,
879 )
880 mounted = True
Alex Klein22690a12022-11-17 10:56:09 -0700881 elif options.download:
882 # Allow downloading only.
883 lock.write_lock()
884 FetchRemoteTarballs(sdk_cache, urls)
Alex Klein1699fab2022-09-08 08:46:06 -0600885
886 if options.enter:
887 lock.read_lock()
888 if not mounted:
Dan Callaghanada9e032023-01-30 14:28:46 +1100889 cros_sdk_lib.MountChrootPaths(chroot.path, options.out_dir)
Mike Frysinger53baf882021-06-24 23:16:50 -0400890 ret = cros_sdk_lib.EnterChroot(
Mike Frysingerec32bea2023-01-27 01:20:48 -0500891 chroot,
Mike Frysinger53baf882021-06-24 23:16:50 -0400892 chrome_root_mount=options.chrome_root_mount,
893 cwd=options.working_dir,
894 cmd=options.commands,
Alex Klein1699fab2022-09-08 08:46:06 -0600895 )
896 sys.exit(ret.returncode)