blob: b601d9daa2ae529084dbc4c7fd1473762a8da931 [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.
Mike Frysingera95870e2023-03-27 15:45:34 -0400189 cmd += [f"DEPOT_TOOLS={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 = (
Brian Norris4f251e42023-03-09 15:53:26 -0800687 goma_lib.Goma(
688 options.goma_dir, chroot_dir=options.chroot, out_dir=options.out_dir
689 )
Brian Norriscf031912023-02-21 15:04:27 -0800690 if options.goma_dir
691 else None
692 )
Mike Frysinger9a5124e2023-01-26 17:16:44 -0500693
Alex Klein1699fab2022-09-08 08:46:06 -0600694 # Merge the outside PATH setting if we re-execed ourselves.
695 if "CHROMEOS_SUDO_PATH" in os.environ:
696 os.environ["PATH"] = "%s:%s" % (
697 os.environ.pop("CHROMEOS_SUDO_PATH"),
698 os.environ["PATH"],
699 )
Mike Frysinger2bda4d12020-07-14 11:15:49 -0400700
Alex Klein1699fab2022-09-08 08:46:06 -0600701 _ReportMissing(osutils.FindMissingBinaries(NEEDED_TOOLS))
702 if options.proxy_sim:
703 _ReportMissing(osutils.FindMissingBinaries(PROXY_NEEDED_TOOLS))
Brian Harringb938c782012-02-29 15:14:38 -0800704
Alex Klein1699fab2022-09-08 08:46:06 -0600705 if (
706 sdk_latest_version == "<unknown>"
707 or bootstrap_latest_version == "<unknown>"
708 ):
709 cros_build_lib.Die(
710 "No SDK version was found. "
711 "Are you in a Chromium source tree instead of Chromium OS?\n\n"
712 "Please change to a directory inside your Chromium OS source tree\n"
713 "and retry. If you need to setup a Chromium OS source tree, see\n"
714 " https://dev.chromium.org/chromium-os/developer-guide"
715 )
Benjamin Gordon040a1162017-06-29 13:44:47 -0600716
Alex Klein1699fab2022-09-08 08:46:06 -0600717 _ReExecuteIfNeeded([sys.argv[0]] + argv, options)
David James471532c2013-01-21 10:23:31 -0800718
Alex Klein1699fab2022-09-08 08:46:06 -0600719 lock_path = os.path.dirname(options.chroot)
720 lock_path = os.path.join(
721 lock_path, ".%s_lock" % os.path.basename(options.chroot).lstrip(".")
722 )
Benjamin Gordonabb3e372017-08-09 10:21:05 -0600723
Alex Klein1699fab2022-09-08 08:46:06 -0600724 # Expand out the aliases...
725 if options.replace:
726 options.delete = options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800727
Alex Klein1699fab2022-09-08 08:46:06 -0600728 if options.bootstrap:
729 options.create = True
Brian Harringb938c782012-02-29 15:14:38 -0800730
Alex Klein1699fab2022-09-08 08:46:06 -0600731 # If a command is not given, default to enter.
732 # pylint: disable=protected-access
733 # This _group_actions access sucks, but upstream decided to not include an
734 # alternative to optparse's option_list, and this is what they recommend.
735 options.enter |= not any(
736 getattr(options, x.dest) for x in commands._group_actions
737 )
738 # pylint: enable=protected-access
Mike Frysinger114a7b92023-01-26 19:08:57 -0500739 options.enter |= bool(options.commands)
Brian Harring218e13c2012-10-10 16:21:26 -0700740
Brian Norris35a7ed02023-02-23 12:50:14 -0800741 if options.delete and not options.create and options.enter:
Alex Klein1699fab2022-09-08 08:46:06 -0600742 parser.error(
Brian Norris35a7ed02023-02-23 12:50:14 -0800743 "Trying to enter the chroot when --delete "
Alex Klein1699fab2022-09-08 08:46:06 -0600744 "was specified makes no sense."
745 )
Brian Harring218e13c2012-10-10 16:21:26 -0700746
Alex Klein1699fab2022-09-08 08:46:06 -0600747 chroot_exists = cros_sdk_lib.IsChrootReady(options.chroot)
Alex Klein1699fab2022-09-08 08:46:06 -0600748 # Finally, flip create if necessary.
Brian Norris35a7ed02023-02-23 12:50:14 -0800749 if options.enter:
Alex Klein1699fab2022-09-08 08:46:06 -0600750 options.create |= not chroot_exists
751
752 # Make sure we will download if we plan to create.
753 options.download |= options.create
754
Mike Frysinger6bbd3e92023-01-27 00:05:02 -0500755 options.Freeze()
756
Mike Frysingerded13922023-02-01 15:31:59 -0500757 if options.reclient_dir and not options.reproxy_cfg_file:
758 cros_build_lib.Die("--reclient-dir requires --reproxy-cfg-file")
759 if not options.reclient_dir and options.reproxy_cfg_file:
760 cros_build_lib.Die(
761 "--reproxy-cfg-file only makes sense with --reclient-dir"
762 )
763
Mike Frysinger253c8392023-01-27 01:12:21 -0500764 remoteexec = (
765 remoteexec_util.Remoteexec(
766 options.reclient_dir, options.reproxy_cfg_file
767 )
768 if (options.reclient_dir and options.reproxy_cfg_file)
769 else None
770 )
771
Mike Frysingerec32bea2023-01-27 01:20:48 -0500772 chroot = chroot_lib.Chroot(
773 path=options.chroot,
Brian Norris2adb74c2023-03-29 12:20:44 -0700774 out_path=options.out_dir,
Mike Frysingerec32bea2023-01-27 01:20:48 -0500775 cache_dir=options.cache_dir,
776 chrome_root=options.chrome_root,
777 goma=goma,
778 remoteexec=remoteexec,
779 )
780
Alex Kleinef517832023-01-13 12:06:51 -0700781 # Anything that needs to manipulate the main chroot mount or communicate
782 # with LVM needs to be done here before we enter the new namespaces.
Alex Klein1699fab2022-09-08 08:46:06 -0600783
Alex Klein1699fab2022-09-08 08:46:06 -0600784 if options.delete:
785 # Set a timeout of 300 seconds when getting the lock.
786 with locking.FileLock(
787 lock_path, "chroot lock", blocking_timeout=300
788 ) as lock:
789 try:
790 lock.write_lock()
791 except timeout_util.TimeoutError as e:
792 logging.error(
793 "Acquiring write_lock on %s failed: %s", lock_path, e
794 )
795 if not options.force:
796 cros_build_lib.Die(
797 "Exiting; use --force to continue w/o lock."
798 )
799 else:
800 logging.warning(
801 "cros_sdk was invoked with force option, continuing."
802 )
803 logging.notice("Deleting chroot.")
Mike Frysingerec32bea2023-01-27 01:20:48 -0500804 cros_sdk_lib.CleanupChrootMount(chroot.path, delete=True)
Alex Klein1699fab2022-09-08 08:46:06 -0600805
Alex Kleinef517832023-01-13 12:06:51 -0700806 # Enter a new set of namespaces. Everything after here cannot directly
807 # affect the hosts's mounts or alter LVM volumes.
Alex Klein1699fab2022-09-08 08:46:06 -0600808 namespaces.SimpleUnshare(net=options.ns_net, pid=options.ns_pid)
Benjamin Gordon386b9eb2017-07-20 09:21:33 -0600809
Alex Klein1699fab2022-09-08 08:46:06 -0600810 if not options.sdk_version:
811 sdk_version = (
812 bootstrap_latest_version
813 if options.bootstrap
814 else sdk_latest_version
815 )
Yong Hong4e29b622018-02-05 14:31:10 +0800816 else:
Alex Klein1699fab2022-09-08 08:46:06 -0600817 sdk_version = options.sdk_version
818 if options.buildbot_log_version:
819 cbuildbot_alerts.PrintBuildbotStepText(sdk_version)
Brian Harring1790ac42012-09-23 08:53:33 -0700820
Alex Klein1699fab2022-09-08 08:46:06 -0600821 # Based on selections, determine the tarball to fetch.
Alex Klein22690a12022-11-17 10:56:09 -0700822 urls = []
Mike Frysingerbf47cce2021-01-20 13:46:30 -0500823 if options.download:
Alex Klein1699fab2022-09-08 08:46:06 -0600824 if options.sdk_url:
825 urls = [options.sdk_url]
826 else:
827 urls = GetArchStageTarballs(sdk_version)
Brian Harring218e13c2012-10-10 16:21:26 -0700828
Alex Klein1699fab2022-09-08 08:46:06 -0600829 with locking.FileLock(lock_path, "chroot lock") as lock:
830 if options.proxy_sim:
831 _ProxySimSetup(options)
Brian Harring1790ac42012-09-23 08:53:33 -0700832
Mike Frysingerec32bea2023-01-27 01:20:48 -0500833 sdk_cache = os.path.join(chroot.cache_dir, "sdks")
834 distfiles_cache = os.path.join(chroot.cache_dir, "distfiles")
835 osutils.SafeMakedirsNonRoot(chroot.cache_dir)
Dan Callaghanada9e032023-01-30 14:28:46 +1100836 osutils.SafeMakedirsNonRoot(options.out_dir)
Alex Klein1699fab2022-09-08 08:46:06 -0600837
838 for target in (sdk_cache, distfiles_cache):
839 src = os.path.join(constants.SOURCE_ROOT, os.path.basename(target))
840 if not os.path.exists(src):
841 osutils.SafeMakedirsNonRoot(target)
842 continue
843 lock.write_lock(
844 "Upgrade to %r needed but chroot is locked; please exit "
845 "all instances so this upgrade can finish." % src
846 )
847 if not os.path.exists(src):
Alex Kleinef517832023-01-13 12:06:51 -0700848 # Note that while waiting for the write lock, src may've
849 # vanished; it's a rare race during the upgrade process that's a
850 # byproduct of us avoiding taking a write lock to do the src
851 # check. If we took a write lock for that check, it would
852 # effectively limit all cros_sdk for a chroot to a single
853 # instance.
Alex Klein1699fab2022-09-08 08:46:06 -0600854 osutils.SafeMakedirsNonRoot(target)
855 elif not os.path.exists(target):
856 # Upgrade occurred, but a reversion, or something whacky
857 # occurred writing to the old location. Wipe and continue.
858 os.rename(src, target)
859 else:
860 # Upgrade occurred once already, but either a reversion or
861 # some before/after separate cros_sdk usage is at play.
862 # Wipe and continue.
863 osutils.RmDir(src)
864
Brian Norrisc2b71e42023-03-30 15:27:36 -0700865 cros_sdk_lib.MigrateStatePaths(chroot, lock)
866
Alex Klein1699fab2022-09-08 08:46:06 -0600867 mounted = False
868 if options.create:
869 lock.write_lock()
Alex Kleinef517832023-01-13 12:06:51 -0700870 # Recheck if the chroot is set up here before creating to make sure
Brian Norrisf624bf42023-03-02 12:57:49 -0800871 # we account for whatever the various delete/cleanup steps above
872 # have done.
Mike Frysingerec32bea2023-01-27 01:20:48 -0500873 if cros_sdk_lib.IsChrootReady(chroot.path):
Alex Klein1699fab2022-09-08 08:46:06 -0600874 logging.debug("Chroot already exists. Skipping creation.")
875 else:
Alex Klein22690a12022-11-17 10:56:09 -0700876 sdk_tarball = FetchRemoteTarballs(sdk_cache, urls)
Alex Klein1699fab2022-09-08 08:46:06 -0600877 cros_sdk_lib.CreateChroot(
Mike Frysingerec32bea2023-01-27 01:20:48 -0500878 Path(chroot.path),
Alex Klein1699fab2022-09-08 08:46:06 -0600879 Path(sdk_tarball),
Dan Callaghanada9e032023-01-30 14:28:46 +1100880 options.out_dir,
Mike Frysingerec32bea2023-01-27 01:20:48 -0500881 Path(chroot.cache_dir),
Alex Klein1699fab2022-09-08 08:46:06 -0600882 usepkg=not options.bootstrap and not options.nousepkg,
883 chroot_upgrade=options.chroot_upgrade,
884 )
885 mounted = True
Alex Klein22690a12022-11-17 10:56:09 -0700886 elif options.download:
887 # Allow downloading only.
888 lock.write_lock()
889 FetchRemoteTarballs(sdk_cache, urls)
Alex Klein1699fab2022-09-08 08:46:06 -0600890
891 if options.enter:
892 lock.read_lock()
893 if not mounted:
Dan Callaghanada9e032023-01-30 14:28:46 +1100894 cros_sdk_lib.MountChrootPaths(chroot.path, options.out_dir)
Mike Frysinger53baf882021-06-24 23:16:50 -0400895 ret = cros_sdk_lib.EnterChroot(
Mike Frysingerec32bea2023-01-27 01:20:48 -0500896 chroot,
Mike Frysinger53baf882021-06-24 23:16:50 -0400897 chrome_root_mount=options.chrome_root_mount,
898 cwd=options.working_dir,
899 cmd=options.commands,
Alex Klein1699fab2022-09-08 08:46:06 -0600900 )
901 sys.exit(ret.returncode)